# Skip all global config tests on CRAN - they write to user config directory # and require proper isolation via FW_CONFIG_HOME test_that("configure_global reads current config when no settings provided", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Call without settings - should return defaults result <- configure_global() expect_true(is.list(result)) expect_true(!is.null(result$author)) expect_true(!is.null(result$defaults)) }) test_that("configure_global updates author information", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Update author result <- configure_global(settings = list( author = list( name = "Test User", email = "test@example.com", affiliation = "Test Org" ) )) expect_equal(result$author$name, "Test User") expect_equal(result$author$email, "test@example.com") expect_equal(result$author$affiliation, "Test Org") # Verify file was written settings_path <- file.path(fw_config_dir(), "settings.yml") expect_true(file.exists(settings_path)) # Read back and verify saved <- read_frameworkrc() expect_equal(saved$author$name, "Test User") }) test_that("configure_global updates defaults", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Update defaults result <- configure_global(settings = list( defaults = list( project_type = "presentation", notebook_format = "rmarkdown", ide = "rstudio", use_git = FALSE, seed = 12345 ) )) expect_equal(result$defaults$project_type, "presentation") expect_equal(result$defaults$notebook_format, "rmarkdown") expect_equal(result$defaults$ide, "rstudio") expect_false(result$defaults$use_git) expect_equal(result$defaults$seed, 12345) }) test_that("configure_global validates project_type choices", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Invalid project type should error expect_error( configure_global(settings = list( defaults = list(project_type = "invalid") )), "project_type" ) }) test_that("configure_global validates notebook_format choices", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Invalid format should error expect_error( configure_global(settings = list( defaults = list(notebook_format = "invalid") )), "notebook_format" ) }) test_that("configure_global validates IDE choices", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Invalid IDE should error expect_error( configure_global(settings = list( defaults = list(ide = "invalid") )), "ide" ) }) test_that("configure_global validates boolean flags", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Invalid boolean should error expect_error( configure_global(settings = list( defaults = list(use_git = "yes") # should be logical )), "flag" ) }) test_that("configure_global accepts numeric or character seed", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Numeric seed result1 <- configure_global(settings = list( defaults = list(seed = 12345) )) expect_equal(result1$defaults$seed, 12345) # Character seed result2 <- configure_global(settings = list( defaults = list(seed = "20250102") )) expect_equal(result2$defaults$seed, "20250102") }) test_that("configure_global performs deep merge", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Set author first configure_global(settings = list( author = list( name = "First User", email = "first@example.com" ) )) # Update just email - name should remain result <- configure_global(settings = list( author = list( email = "updated@example.com" ) )) expect_equal(result$author$name, "First User") # unchanged expect_equal(result$author$email, "updated@example.com") # updated }) test_that("configure_global can skip validation", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Should not error even with invalid data when validate = FALSE expect_no_error( suppressMessages( configure_global( settings = list( defaults = list(project_type = "invalid") ), validate = FALSE ) ) ) }) test_that("configure_global writes YAML format", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) configure_global(settings = list( author = list(name = "Test User") )) settings_path <- file.path(fw_config_dir(), "settings.yml") expect_true(file.exists(settings_path)) # Should be valid YAML yaml_content <- yaml::read_yaml(settings_path) expect_true(is.list(yaml_content)) expect_equal(yaml_content$author$name, "Test User") }) # Tests for extra_directories validation test_that("configure_global accepts valid extra_directories", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Valid extra_directories result <- configure_global(settings = list( project_types = list( project = list( extra_directories = list( list(key = "inputs_archive", label = "Archive", path = "inputs/archive", type = "input"), list(key = "outputs_animations", label = "Animations", path = "outputs/animations", type = "output") ) ) ) )) expect_equal(length(result$project_types$project$extra_directories), 2) expect_equal(result$project_types$project$extra_directories[[1]]$key, "inputs_archive") expect_equal(result$project_types$project$extra_directories[[2]]$type, "output") }) test_that("configure_global rejects extra_directories with missing fields", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Missing 'key' field expect_error( configure_global(settings = list( project_types = list( project = list( extra_directories = list( list(label = "Archive", path = "inputs/archive", type = "input") ) ) ) )), "missing required field 'key'" ) # Missing 'type' field expect_error( configure_global(settings = list( project_types = list( project = list( extra_directories = list( list(key = "test", label = "Test", path = "test") ) ) ) )), "missing required field 'type'" ) }) test_that("configure_global rejects invalid extra_directories key format", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Key with hyphen (invalid) expect_error( configure_global(settings = list( project_types = list( project = list( extra_directories = list( list(key = "my-dir", label = "My Dir", path = "my-dir", type = "input") ) ) ) )), "must contain only letters, numbers, and underscores" ) # Key with space (invalid) expect_error( configure_global(settings = list( project_types = list( project = list( extra_directories = list( list(key = "my dir", label = "My Dir", path = "mydir", type = "input") ) ) ) )), "must contain only letters, numbers, and underscores" ) }) test_that("configure_global rejects duplicate extra_directories keys", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Duplicate keys expect_error( configure_global(settings = list( project_types = list( project = list( extra_directories = list( list(key = "test_dir", label = "Test 1", path = "test1", type = "input"), list(key = "test_dir", label = "Test 2", path = "test2", type = "input") ) ) ) )), "duplicate extra_directories key 'test_dir'" ) }) test_that("configure_global rejects invalid extra_directories type", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Invalid type expect_error( configure_global(settings = list( project_types = list( project = list( extra_directories = list( list(key = "test", label = "Test", path = "test", type = "invalid") ) ) ) )), "type 'invalid' must be one of: input, workspace, output" ) }) test_that("configure_global rejects absolute paths in extra_directories", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Absolute path expect_error( configure_global(settings = list( project_types = list( project = list( extra_directories = list( list(key = "test", label = "Test", path = "/absolute/path", type = "input") ) ) ) )), "must be relative \\(no leading slash\\)" ) }) test_that("configure_global rejects path traversal in extra_directories", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Path traversal expect_error( configure_global(settings = list( project_types = list( project = list( extra_directories = list( list(key = "test", label = "Test", path = "../parent", type = "input") ) ) ) )), "cannot contain '\\.\\.' \\(path traversal\\)" ) }) test_that("configure_global preserves extra_directories through modifyList", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Set initial extra_directories configure_global(settings = list( project_types = list( project = list( extra_directories = list( list(key = "test1", label = "Test 1", path = "test1", type = "input") ) ) ) )) # Update author (different field) - extra_directories should persist result <- configure_global(settings = list( author = list(name = "Updated User") )) # extra_directories should still exist expect_equal(length(result$project_types$project$extra_directories), 1) expect_equal(result$project_types$project$extra_directories[[1]]$key, "test1") # Now update extra_directories result2 <- configure_global(settings = list( project_types = list( project = list( extra_directories = list( list(key = "test2", label = "Test 2", path = "test2", type = "workspace") ) ) ) )) # Should be replaced expect_equal(length(result2$project_types$project$extra_directories), 1) expect_equal(result2$project_types$project$extra_directories[[1]]$key, "test2") expect_equal(result2$project_types$project$extra_directories[[1]]$type, "workspace") }) test_that("configure_global saves global.projects_root", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Set global.projects_root test_path <- "/Users/test/my-projects" result <- configure_global(settings = list( global = list( projects_root = test_path ) )) # Verify return value has the path expect_equal(result$global$projects_root, test_path) # Verify file was written settings_path <- file.path(fw_config_dir(), "settings.yml") expect_true(file.exists(settings_path)) # Read the raw YAML to verify it was actually saved saved_yaml <- yaml::read_yaml(settings_path) expect_equal(saved_yaml$global$projects_root, test_path) # Also verify through read_frameworkrc saved <- read_frameworkrc() expect_equal(saved$global$projects_root, test_path) }) test_that("configure_global persists global.projects_root through multiple saves", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # First save: set projects_root configure_global(settings = list( global = list( projects_root = "/first/path" ) )) # Second save: change projects_root result <- configure_global(settings = list( global = list( projects_root = "/second/path" ) )) expect_equal(result$global$projects_root, "/second/path") # Verify in file saved <- read_frameworkrc() expect_equal(saved$global$projects_root, "/second/path") }) test_that("configure_global preserves global.projects_root when saving other settings", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # First: set projects_root configure_global(settings = list( global = list( projects_root = "/my/projects" ) )) # Second: save author (without sending global) configure_global(settings = list( author = list( name = "New Author" ) )) # projects_root should still be there saved <- read_frameworkrc() expect_equal(saved$global$projects_root, "/my/projects") expect_equal(saved$author$name, "New Author") }) # ============================================================================ # Cache Directory Configuration Tests # ============================================================================ test_that("configure_global can set cache directory per project type", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Set cache directory for standard project type result <- configure_global(settings = list( project_types = list( project = list( directories = list( cache = "custom/cache/path" ) ) ) )) expect_equal(result$project_types$project$directories$cache, "custom/cache/path") }) test_that("configure_global supports different cache paths per project type", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Set different cache directories for different project types result <- configure_global(settings = list( project_types = list( project = list( directories = list( cache = "outputs/cache" ) ), project_sensitive = list( directories = list( cache = "outputs/private/cache" ) ), presentation = list( directories = list( cache = "cache" ) ), course = list( directories = list( cache = "cache" ) ) ) )) expect_equal(result$project_types$project$directories$cache, "outputs/cache") expect_equal(result$project_types$project_sensitive$directories$cache, "outputs/private/cache") expect_equal(result$project_types$presentation$directories$cache, "cache") expect_equal(result$project_types$course$directories$cache, "cache") }) test_that("configure_global preserves cache directory when updating other settings", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # First: set cache directory configure_global(settings = list( project_types = list( project = list( directories = list( cache = "my-custom-cache" ) ) ) )) # Second: update author (without sending project_types) configure_global(settings = list( author = list( name = "Updated Author" ) )) # cache directory should still be there saved <- read_frameworkrc() expect_equal(saved$project_types$project$directories$cache, "my-custom-cache") expect_equal(saved$author$name, "Updated Author") }) test_that("configure_global saves and loads directories_enabled", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # Save directories_enabled for project type result <- configure_global(settings = list( project_types = list( project = list( directories_enabled = list( scratch = TRUE, outputs_tables = FALSE, outputs_figures = TRUE ) ) ) )) # Verify it was saved expect_equal(result$project_types$project$directories_enabled$scratch, TRUE) expect_equal(result$project_types$project$directories_enabled$outputs_tables, FALSE) expect_equal(result$project_types$project$directories_enabled$outputs_figures, TRUE) # Verify it persists in file saved <- read_frameworkrc() expect_equal(saved$project_types$project$directories_enabled$scratch, TRUE) expect_equal(saved$project_types$project$directories_enabled$outputs_tables, FALSE) expect_equal(saved$project_types$project$directories_enabled$outputs_figures, TRUE) }) test_that("configure_global preserves directories_enabled when updating other fields", { skip_on_cran() cleanup <- setup_isolated_config() on.exit(cleanup()) # First: save directories_enabled configure_global(settings = list( project_types = list( project = list( directories_enabled = list( scratch = TRUE, outputs_tables = FALSE ) ) ) )) # Second: update author (without sending directories_enabled) configure_global(settings = list( author = list(name = "New Author") )) # directories_enabled should still be there saved <- read_frameworkrc() expect_equal(saved$project_types$project$directories_enabled$scratch, TRUE) expect_equal(saved$project_types$project$directories_enabled$outputs_tables, FALSE) expect_equal(saved$author$name, "New Author") })