Nothing
# ==============================================================================
# Config System V2 - TDD Test Suite
# ==============================================================================
#
# This test suite defines the contract for Framework's custom config loader.
# Tests are written BEFORE implementation (TDD approach).
#
# Architecture:
# - Custom YAML loader (no config package dependency)
# - Environment-aware merging (default + active environment)
# - Recursive split file resolution
# - Conflict detection (main file wins)
# - !expr evaluation support
#
# ==============================================================================
# ==============================================================================
# CATEGORY 1: Environment Handling
# ==============================================================================
test_that("flat config without environment sections works", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
# Flat config (no default: wrapper)
config_content <- "database: localhost
port: 5432
debug: true
"
writeLines(config_content, "config.yml")
cfg <- settings_read("config.yml")
# Should treat entire file as default environment
expect_equal(cfg$database, "localhost")
expect_equal(cfg$port, 5432)
expect_true(cfg$debug)
unlink("config.yml")
})
test_that("environment sections merge correctly (production inherits default)", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
config_content <- "default:
database: localhost
port: 5432
debug: true
timeout: 30
production:
database: prod.example.com
debug: false
"
writeLines(config_content, "config.yml")
# Test default environment
cfg_default <- settings_read("config.yml", environment = "default")
expect_equal(cfg_default$database, "localhost")
expect_equal(cfg_default$port, 5432)
expect_true(cfg_default$debug)
expect_equal(cfg_default$timeout, 30)
# Test production environment (inherits port & timeout from default)
cfg_prod <- settings_read("config.yml", environment = "production")
expect_equal(cfg_prod$database, "prod.example.com")
expect_equal(cfg_prod$port, 5432) # Inherited from default
expect_false(cfg_prod$debug)
expect_equal(cfg_prod$timeout, 30) # Inherited from default
unlink("config.yml")
})
test_that("deep nested environment inheritance works", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
config_content <- "default:
connections:
db:
host: localhost
port: 5432
options:
timeout: 30
retry: 3
production:
connections:
db:
host: prod.example.com
"
writeLines(config_content, "config.yml")
cfg <- settings_read("config.yml", environment = "production")
# Production should inherit nested structure
expect_equal(cfg$connections$db$host, "prod.example.com")
expect_equal(cfg$connections$db$port, 5432) # Inherited
expect_equal(cfg$connections$db$options$timeout, 30) # Nested inherited
expect_equal(cfg$connections$db$options$retry, 3) # Nested inherited
unlink("config.yml")
})
test_that("config with only production environment errors without default", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
# Missing default environment
config_content <- "production:
database: prod.example.com
"
writeLines(config_content, "config.yml")
expect_error(
settings_read("config.yml"),
"no 'default' environment"
)
unlink("config.yml")
})
test_that("unknown environment falls back to default with warning", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
config_content <- "default:
database: localhost
production:
database: prod.example.com
"
writeLines(config_content, "config.yml")
expect_warning(
cfg <- settings_read("config.yml", environment = "staging"),
"Environment 'staging' not found.*using 'default'"
)
# Should use default
expect_equal(cfg$database, "localhost")
unlink("config.yml")
})
test_that("R_CONFIG_ACTIVE environment variable sets active environment", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit({
setwd(old_wd)
Sys.unsetenv("R_CONFIG_ACTIVE")
})
config_content <- "default:
database: localhost
production:
database: prod.example.com
"
writeLines(config_content, "config.yml")
# Set environment variable
Sys.setenv(R_CONFIG_ACTIVE = "production")
# Should use production (no explicit environment arg)
cfg <- settings_read("config.yml")
expect_equal(cfg$database, "prod.example.com")
unlink("config.yml")
})
# ==============================================================================
# CATEGORY 2: Split File Resolution
# ==============================================================================
test_that("split file reference loads and merges correctly", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
dir.create("settings", showWarnings = FALSE)
# Main config
config_content <- "default:
project_type: project
connections: settings/connections.yml
"
writeLines(config_content, "config.yml")
# Split file
connections_content <- "default:
connections:
db:
host: localhost
port: 5432
"
writeLines(connections_content, "settings/connections.yml")
cfg <- settings_read("config.yml")
expect_equal(cfg$project_type, "project")
expect_equal(cfg$connections$db$host, "localhost")
expect_equal(cfg$connections$db$port, 5432)
unlink("config.yml")
unlink("settings", recursive = TRUE)
})
test_that("split file with flat structure (no environment sections) works", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
dir.create("settings", showWarnings = FALSE)
# Main config
config_content <- "default:
data: settings/data.yml
"
writeLines(config_content, "config.yml")
# Split file (flat, no default:)
data_content <- "data:
example:
path: data/example.csv
type: csv
"
writeLines(data_content, "settings/data.yml")
cfg <- settings_read("config.yml")
expect_equal(cfg$data$example$path, "data/example.csv")
expect_equal(cfg$data$example$type, "csv")
unlink("config.yml")
unlink("settings", recursive = TRUE)
})
test_that("split file with environment sections merges correctly", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
dir.create("settings", showWarnings = FALSE)
# Main config
config_content <- "default:
connections: settings/connections.yml
api_key: default-key
production:
api_key: prod-key
"
writeLines(config_content, "config.yml")
# Split file with environments
connections_content <- "default:
connections:
db:
host: localhost
production:
connections:
db:
host: prod-db.example.com
"
writeLines(connections_content, "settings/connections.yml")
# Test production environment
cfg <- settings_read("config.yml", environment = "production")
expect_equal(cfg$api_key, "prod-key")
expect_equal(cfg$connections$db$host, "prod-db.example.com")
unlink("config.yml")
unlink("settings", recursive = TRUE)
})
test_that("missing split file errors with clear message", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
config_content <- "default:
connections: settings/missing.yml
"
writeLines(config_content, "config.yml")
expect_error(
settings_read("config.yml"),
"settings/missing.yml.*not found"
)
unlink("config.yml")
})
test_that("split file with invalid YAML errors clearly", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
dir.create("settings", showWarnings = FALSE)
config_content <- "default:
connections: settings/bad.yml
"
writeLines(config_content, "config.yml")
# Invalid YAML
bad_content <- "connections:
db:
host: [unclosed
"
writeLines(bad_content, "settings/bad.yml")
expect_error(
settings_read("config.yml"),
"Failed to parse.*settings/bad.yml"
)
unlink("config.yml")
unlink("settings", recursive = TRUE)
})
test_that("circular split file reference errors gracefully", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
dir.create("settings", showWarnings = FALSE)
# Main references A
config_content <- "default:
data: settings/a.yml
"
writeLines(config_content, "config.yml")
# A references B
a_content <- "default:
connections: settings/b.yml
"
writeLines(a_content, "settings/a.yml")
# B references A (circular!)
b_content <- "default:
data: settings/a.yml
"
writeLines(b_content, "settings/b.yml")
expect_error(
settings_read("config.yml"),
"Circular reference"
)
unlink("config.yml")
unlink("settings", recursive = TRUE)
})
test_that("nested split file references work (A -> B -> C)", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
dir.create("settings", showWarnings = FALSE)
# Main references A
config_content <- "default:
level: main
config_a: settings/a.yml
"
writeLines(config_content, "config.yml")
# A references B
a_content <- "default:
level_a: from_a
config_b: settings/b.yml
"
writeLines(a_content, "settings/a.yml")
# B has data
b_content <- "default:
level_b: from_b
data:
example: value
"
writeLines(b_content, "settings/b.yml")
cfg <- settings_read("config.yml")
expect_equal(cfg$level, "main")
expect_equal(cfg$level_a, "from_a")
expect_equal(cfg$level_b, "from_b")
expect_equal(cfg$data$example, "value")
unlink("config.yml")
unlink("settings", recursive = TRUE)
})
# ==============================================================================
# CATEGORY 3: Conflict Detection
# ==============================================================================
test_that("main file wins conflict with split file (top-level key)", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
dir.create("settings", showWarnings = FALSE)
# Main config defines default_connection
config_content <- "default:
connections: settings/connections.yml
default_connection: from_main
"
writeLines(config_content, "config.yml")
# Split file also defines default_connection (conflict!)
connections_content <- "default:
connections:
db:
host: localhost
default_connection: from_split
"
writeLines(connections_content, "settings/connections.yml")
expect_warning(
cfg <- settings_read("config.yml"),
"default_connection.*defined in both.*main config"
)
# Main file should win
expect_equal(cfg$default_connection, "from_main")
unlink("config.yml")
unlink("settings", recursive = TRUE)
})
test_that("multiple split files with conflicting keys (first wins)", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
dir.create("settings", showWarnings = FALSE)
# Main references two split files
config_content <- "default:
connections: settings/connections.yml
data: settings/data.yml
"
writeLines(config_content, "config.yml")
# Both define cache_enabled
connections_content <- "default:
cache_enabled: true
"
writeLines(connections_content, "settings/connections.yml")
data_content <- "default:
cache_enabled: false
"
writeLines(data_content, "settings/data.yml")
expect_warning(
cfg <- settings_read("config.yml"),
"cache_enabled.*already defined"
)
# First split file (connections) should win
expect_true(cfg$cache_enabled)
unlink("config.yml")
unlink("settings", recursive = TRUE)
})
# ==============================================================================
# CATEGORY 4: !expr Evaluation
# ==============================================================================
test_that("!expr evaluates environment variables", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit({
setwd(old_wd)
Sys.unsetenv("TEST_DB_HOST")
})
Sys.setenv(TEST_DB_HOST = "test.example.com")
config_content <- "default:
database_host: !expr Sys.getenv('TEST_DB_HOST')
"
writeLines(config_content, "config.yml")
cfg <- settings_read("config.yml")
expect_equal(cfg$database_host, "test.example.com")
unlink("config.yml")
})
test_that("!expr with default value works", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit({
setwd(old_wd)
Sys.unsetenv("MISSING_VAR")
})
config_content <- "default:
database_host: !expr Sys.getenv('MISSING_VAR', 'localhost')
"
writeLines(config_content, "config.yml")
cfg <- settings_read("config.yml")
expect_equal(cfg$database_host, "localhost")
unlink("config.yml")
})
test_that("!expr works in split files", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit({
setwd(old_wd)
Sys.unsetenv("SPLIT_VAR")
})
Sys.setenv(SPLIT_VAR = "from_env")
dir.create("settings", showWarnings = FALSE)
config_content <- "default:
connections: settings/connections.yml
"
writeLines(config_content, "config.yml")
connections_content <- "default:
connections:
db:
host: !expr Sys.getenv('SPLIT_VAR')
"
writeLines(connections_content, "settings/connections.yml")
cfg <- settings_read("config.yml")
expect_equal(cfg$connections$db$host, "from_env")
unlink("config.yml")
unlink("settings", recursive = TRUE)
})
test_that("!expr with invalid expression errors clearly", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
config_content <- "default:
bad_expr: !expr this_function_does_not_exist()
"
writeLines(config_content, "config.yml")
expect_error(
settings_read("config.yml"),
"Failed to evaluate expression"
)
unlink("config.yml")
})
test_that("env() syntax evaluates environment variables", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit({
setwd(old_wd)
Sys.unsetenv("TEST_ENV_VAR")
})
Sys.setenv(TEST_ENV_VAR = "value_from_env")
config_content <- "default:
database_host: env(\"TEST_ENV_VAR\")
"
writeLines(config_content, "config.yml")
cfg <- settings_read("config.yml")
expect_equal(cfg$database_host, "value_from_env")
unlink("config.yml")
})
test_that("env() syntax with default value works", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit({
setwd(old_wd)
Sys.unsetenv("MISSING_ENV_VAR")
})
config_content <- "default:
database_host: env(\"MISSING_ENV_VAR\", \"localhost\")
"
writeLines(config_content, "config.yml")
cfg <- settings_read("config.yml")
expect_equal(cfg$database_host, "localhost")
unlink("config.yml")
})
test_that("env() syntax works in split files", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit({
setwd(old_wd)
Sys.unsetenv("SPLIT_ENV_VAR")
})
Sys.setenv(SPLIT_ENV_VAR = "from_split_env")
dir.create("settings", showWarnings = FALSE)
config_content <- "default:
connections: settings/connections.yml
"
writeLines(config_content, "config.yml")
connections_content <- "default:
connections:
db:
host: env(\"SPLIT_ENV_VAR\")
"
writeLines(connections_content, "settings/connections.yml")
cfg <- settings_read("config.yml")
expect_equal(cfg$connections$db$host, "from_split_env")
unlink("config.yml")
unlink("settings", recursive = TRUE)
})
# ==============================================================================
# CATEGORY 5: Type Handling
# ==============================================================================
test_that("NULL values handled correctly", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
config_content <- "default:
optional_value: null
required_value: present
"
writeLines(config_content, "config.yml")
cfg <- settings_read("config.yml")
expect_null(cfg$optional_value)
expect_equal(cfg$required_value, "present")
unlink("config.yml")
})
test_that("empty sections don't error", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
config_content <- "default:
connections:
data:
"
writeLines(config_content, "config.yml")
cfg <- settings_read("config.yml")
expect_true(is.null(cfg$connections) || (is.list(cfg$connections) && length(cfg$connections) == 0))
expect_true(is.null(cfg$data) || (is.list(cfg$data) && length(cfg$data) == 0))
unlink("config.yml")
})
test_that("arrays replace not merge in environment inheritance", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
config_content <- "default:
packages:
- dplyr
- ggplot2
- tidyr
production:
packages:
- dplyr
"
writeLines(config_content, "config.yml")
cfg <- settings_read("config.yml", environment = "production")
# Production should REPLACE array, not merge
expect_equal(length(cfg$packages), 1)
expect_equal(cfg$packages[[1]], "dplyr")
unlink("config.yml")
})
# ==============================================================================
# CATEGORY 6: Error Handling
# ==============================================================================
test_that("missing config file errors clearly", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
expect_error(
settings_read("nonexistent.yml"),
"not found|does not exist"
)
})
test_that("invalid YAML in main config errors clearly", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
bad_content <- "default:
key: [unclosed
"
writeLines(bad_content, "config.yml")
expect_error(
settings_read("config.yml"),
"Failed to parse"
)
unlink("config.yml")
})
# ==============================================================================
# CATEGORY 7: Backward Compatibility
# ==============================================================================
test_that("existing config.yml files still work", {
tmp <- tempdir()
old_wd <- getwd()
setwd(tmp)
on.exit(setwd(old_wd))
# Current Framework config structure
config_content <- "default:
project_type: project
directories:
notebooks: notebooks
scripts: scripts
packages:
- dplyr
- ggplot2
"
writeLines(config_content, "config.yml")
cfg <- settings_read("config.yml")
expect_equal(cfg$project_type, "project")
expect_equal(cfg$directories$notebooks, "notebooks")
expect_equal(length(cfg$packages), 2)
unlink("config.yml")
})
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.