Nothing
test_that("project settings save preserves all extra_directories", {
skip_on_cran()
# Setup: Create a test project with initial extra directories
test_dir <- tempdir()
project_path <- file.path(test_dir, "test-project-settings")
# Clean up from previous runs
if (dir.exists(project_path)) {
unlink(project_path, recursive = TRUE)
}
dir.create(project_path, recursive = TRUE)
# Create initial settings.yml with existing extra directories
initial_settings <- list(
default = list(
project_name = "Test Project",
project_type = "project",
extra_directories = list(
list(
key = "inputs_archive",
label = "Archive",
path = "inputs/archive",
category = "input",
`_id` = 111111
),
list(
key = "inputs_raw_data",
label = "Raw Data",
path = "inputs/raw/data",
category = "input",
`_id` = 222222
)
),
enabled = list(
inputs_raw = TRUE,
inputs_intermediate = TRUE,
inputs_final = TRUE,
inputs_archive = TRUE,
inputs_raw_data = TRUE
)
)
)
yaml::write_yaml(initial_settings, file.path(project_path, "settings.yml"))
# Simulate adding a NEW workspace directory (scripts)
updated_settings <- initial_settings
updated_settings$default$extra_directories <- c(
updated_settings$default$extra_directories,
list(
list(
key = "workspace_scripts",
label = "Scripts",
path = "workspace/scripts",
category = "workspace",
`_id` = 333333
)
)
)
updated_settings$default$enabled$workspace_scripts <- TRUE
# Test: Save the updated settings
# This simulates what the POST /api/project/<id>/settings endpoint does
setwd(project_path)
# Simulate the save logic from plumber.R
settings_file <- "settings.yml"
current_settings <- yaml::read_yaml(settings_file)
# Update with new data (as the endpoint does)
if (!is.null(updated_settings$default$extra_directories)) {
extra_dirs <- updated_settings$default$extra_directories
if (is.list(extra_dirs)) {
names(extra_dirs) <- NULL # Remove names to ensure array serialization
}
current_settings$default$extra_directories <- extra_dirs
}
if (!is.null(updated_settings$default$enabled)) {
current_settings$default$enabled <- updated_settings$default$enabled
}
# Write back
yaml::write_yaml(current_settings, settings_file)
# Verify: Read back the settings
saved_settings <- yaml::read_yaml(settings_file)
# Test 1: All extra_directories should be present (2 original + 1 new = 3)
expect_equal(length(saved_settings$default$extra_directories), 3)
# Test 2: Original directories should still exist
keys <- sapply(saved_settings$default$extra_directories, function(d) d$key)
expect_true("inputs_archive" %in% keys)
expect_true("inputs_raw_data" %in% keys)
expect_true("workspace_scripts" %in% keys)
# Test 3: All enabled states should be preserved
expect_true(saved_settings$default$enabled$inputs_archive)
expect_true(saved_settings$default$enabled$inputs_raw_data)
expect_true(saved_settings$default$enabled$workspace_scripts)
# Test 4: extra_directories should be an array (unnamed list)
expect_null(names(saved_settings$default$extra_directories))
# Cleanup
unlink(project_path, recursive = TRUE)
})
test_that("connections API save writes new schema (databases + storage + defaults)", {
skip_on_cran()
skip_if_not_installed("yaml")
# Setup project with split connections file
test_dir <- tempdir()
project_path <- file.path(test_dir, "test-connections-api")
if (dir.exists(project_path)) unlink(project_path, recursive = TRUE)
dir.create(file.path(project_path, "settings"), recursive = TRUE)
# Seed config.yml referencing split connections file
config <- list(
default = list(
project_name = "Test",
project_type = "project",
connections = "settings/connections.yml"
)
)
yaml::write_yaml(config, file.path(project_path, "config.yml"))
# Prepare body from UI (new schema)
body <- list(
default_database = "warehouse",
default_storage_bucket = "s3_bucket",
databases = list(
warehouse = list(
driver = "postgres",
host = "localhost",
port = "5432",
database = "analytics",
schema = "public",
user = "analyst",
password = "secret"
)
),
storage_buckets = list(
s3_bucket = list(
bucket = "my-bucket",
region = "us-east-1",
endpoint = "https://s3.amazonaws.com",
access_key = "abc",
secret_key = "xyz"
)
)
)
split_info <- framework:::.uses_split_file(project_path, "connections")
# Simulate POST /api/project/<id>/connections logic
connections_data <- list(
default_database = body$default_database,
default_storage_bucket = body$default_storage_bucket,
databases = body$databases %||% list(),
storage_buckets = body$storage_buckets %||% list()
)
dir.create(dirname(split_info$split_file), recursive = TRUE, showWarnings = FALSE)
yaml::write_yaml(list(connections = connections_data), split_info$split_file)
# Validate saved connections.yml
saved <- yaml::read_yaml(split_info$split_file)$connections
expect_equal(saved$default_database, "warehouse")
expect_equal(saved$default_storage_bucket, "s3_bucket")
expect_true("warehouse" %in% names(saved$databases))
expect_true("s3_bucket" %in% names(saved$storage_buckets))
expect_equal(saved$databases$warehouse$driver, "postgres")
expect_equal(saved$storage_buckets$s3_bucket$bucket, "my-bucket")
})
test_that("project settings save with data.frame conversion", {
skip_on_cran()
# Setup
test_dir <- tempdir()
project_path <- file.path(test_dir, "test-dataframe-conversion")
if (dir.exists(project_path)) {
unlink(project_path, recursive = TRUE)
}
dir.create(project_path, recursive = TRUE)
initial_settings <- list(
default = list(
project_name = "Test Project",
project_type = "project",
extra_directories = list(),
enabled = list(inputs_raw = TRUE)
)
)
yaml::write_yaml(initial_settings, file.path(project_path, "settings.yml"))
# Simulate jsonlite converting array to data.frame (as happens in plumber)
json_str <- '[{"key":"inputs_new","label":"New","path":"inputs/new","category":"input","_id":123456}]'
parsed <- jsonlite::fromJSON(json_str)
# This will be a data.frame - simulate the conversion logic
directories <- parsed
if (is.data.frame(directories)) {
directories <- lapply(1:nrow(directories), function(i) {
as.list(directories[i, , drop = FALSE][1, ])
})
}
# Remove names to ensure array
if (is.list(directories)) {
names(directories) <- NULL
}
# Test: Verify it's now an unnamed list (array)
expect_true(is.list(directories))
expect_null(names(directories))
expect_equal(length(directories), 1)
expect_equal(directories[[1]]$key, "inputs_new")
# Cleanup
unlink(project_path, recursive = TRUE)
})
test_that("project settings GET returns extra_directories as array", {
skip_on_cran()
# Setup: Create project with single extra directory (prone to becoming object)
test_dir <- tempdir()
project_path <- file.path(test_dir, "test-get-array")
if (dir.exists(project_path)) {
unlink(project_path, recursive = TRUE)
}
dir.create(project_path, recursive = TRUE)
settings <- list(
default = list(
project_name = "Test",
extra_directories = list(
list(
key = "inputs_test",
label = "Test",
path = "inputs/test",
`_id` = 999
)
)
)
)
yaml::write_yaml(settings, file.path(project_path, "settings.yml"))
# Read back (simulating GET endpoint)
loaded_settings <- yaml::read_yaml(file.path(project_path, "settings.yml"))
settings_resolved <- loaded_settings$default
# Apply the fix from plumber.R GET endpoint
if (!is.null(settings_resolved$extra_directories)) {
if (!is.null(names(settings_resolved$extra_directories)) &&
length(names(settings_resolved$extra_directories)) > 0) {
# It's a named list (single object) - wrap in unnamed list
settings_resolved$extra_directories <- list(settings_resolved$extra_directories)
names(settings_resolved$extra_directories) <- NULL
} else {
# Already unnamed list, just ensure no names
names(settings_resolved$extra_directories) <- NULL
}
}
# Test: Should be unnamed list (array)
expect_null(names(settings_resolved$extra_directories))
expect_equal(length(settings_resolved$extra_directories), 1)
expect_equal(settings_resolved$extra_directories[[1]]$key, "inputs_test")
# Cleanup
unlink(project_path, recursive = TRUE)
})
test_that("enabled state persists for custom directories", {
skip_on_cran()
# Setup
test_dir <- tempdir()
project_path <- file.path(test_dir, "test-enabled-persist")
if (dir.exists(project_path)) {
unlink(project_path, recursive = TRUE)
}
dir.create(project_path, recursive = TRUE)
# Initial state: no custom directories
initial_settings <- list(
default = list(
project_name = "Test",
extra_directories = list(),
enabled = list(inputs_raw = TRUE, inputs_final = TRUE)
)
)
yaml::write_yaml(initial_settings, file.path(project_path, "settings.yml"))
# Add custom directory and enable it
updated_enabled <- initial_settings$default$enabled
updated_enabled$inputs_custom <- TRUE
updated_extra <- list(
list(key = "inputs_custom", label = "Custom", path = "inputs/custom", `_id` = 123)
)
# Save
current <- yaml::read_yaml(file.path(project_path, "settings.yml"))
current$default$extra_directories <- updated_extra
current$default$enabled <- updated_enabled
names(current$default$extra_directories) <- NULL
yaml::write_yaml(current, file.path(project_path, "settings.yml"))
# Reload
reloaded <- yaml::read_yaml(file.path(project_path, "settings.yml"))
# Test: enabled state should persist
expect_true(reloaded$default$enabled$inputs_custom)
expect_equal(length(reloaded$default$extra_directories), 1)
expect_equal(reloaded$default$extra_directories[[1]]$key, "inputs_custom")
# Cleanup
unlink(project_path, recursive = TRUE)
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.