tests/testthat/test-data-config-resolve.R

box::use(
  testthat[
    expect_equal,
    expect_false,
    expect_null,
    expect_true,
    expect_length,
    test_that
  ],
  withr[local_options]
)

# ── merge_config ──────────────────────────────────────────────────────────────

test_that("merge_config: returns base when overrides are empty", {
  box::use(artma / data_config / resolve[merge_config])

  base <- list(
    x = list(var_name = "x", bma = NA, gltl = NA),
    y = list(var_name = "y", bma = NA, gltl = NA)
  )

  result <- merge_config(base, list())
  expect_equal(result, base)
})

test_that("merge_config: returns base when overrides are NULL", {
  box::use(artma / data_config / resolve[merge_config])

  base <- list(x = list(bma = NA))
  result <- merge_config(base, NULL)
  expect_equal(result, base)
})

test_that("merge_config: overlays specific fields on existing variable", {
  box::use(artma / data_config / resolve[merge_config])

  base <- list(
    my_var = list(
      var_name = "my_var",
      bma = NA,
      gltl = NA,
      equal = NA,
      variable_summary = TRUE
    )
  )

  overrides <- list(
    my_var = list(
      bma = TRUE,
      gltl = "median"
    )
  )

  result <- merge_config(base, overrides)

  expect_equal(result$my_var$bma, TRUE)
  expect_equal(result$my_var$gltl, "median")
  expect_true(is.na(result$my_var$equal))
  expect_equal(result$my_var$var_name, "my_var")
  expect_equal(result$my_var$variable_summary, TRUE)
})

test_that("merge_config: adds new variable not in base", {
  box::use(artma / data_config / resolve[merge_config])

  base <- list(
    x = list(var_name = "x", bma = NA)
  )

  overrides <- list(
    new_col = list(bma = TRUE, effect_sum_stats = TRUE)
  )

  result <- merge_config(base, overrides)

  expect_length(result, 2)
  expect_true("new_col" %in% names(result))
  expect_equal(result$new_col$bma, TRUE)
  expect_equal(result$new_col$effect_sum_stats, TRUE)
  expect_equal(result$x$var_name, "x")
})

test_that("merge_config: overrides multiple variables independently", {
  box::use(artma / data_config / resolve[merge_config])

  base <- list(
    a = list(var_name = "a", bma = NA, gltl = NA),
    b = list(var_name = "b", bma = NA, gltl = NA)
  )

  overrides <- list(
    a = list(bma = TRUE),
    b = list(gltl = "median")
  )

  result <- merge_config(base, overrides)

  expect_equal(result$a$bma, TRUE)
  expect_true(is.na(result$a$gltl))
  expect_true(is.na(result$b$bma))
  expect_equal(result$b$gltl, "median")
})

test_that("merge_config: skips non-list override entries", {
  box::use(artma / data_config / resolve[merge_config])

  base <- list(
    x = list(var_name = "x", bma = NA)
  )

  overrides <- list(
    x = "not a list"
  )

  result <- merge_config(base, overrides)

  # Non-list override is skipped, base is unchanged
  expect_equal(result$x$var_name, "x")
  expect_true(is.na(result$x$bma))
})

# ── read_df_for_config caching ────────────────────────────────────────────────

test_that("read_df_for_config uses a primed dataframe cache without re-reading data", {
  box::use(
    artma / data / read[read_data],
    artma / data_config / resolve[
      prime_df_for_config_cache,
      read_df_for_config
    ],
    testing / mocks / index[MOCKS]
  )

  df <- MOCKS$create_mock_df(seed = 42)
  tmp_file <- tempfile(fileext = ".csv")
  utils::write.csv(df, tmp_file, row.names = FALSE)
  withr::defer(unlink(tmp_file))

  withr::local_options(list(
    "artma.data.source_path" = tmp_file,
    "artma.verbose" = 3
  ))

  loaded_df <- read_data(tmp_file)
  prime_df_for_config_cache(loaded_df, tmp_file)

  replay_msgs <- testthat::capture_messages(read_df_for_config())
  read_msgs <- grep("Reading data from", replay_msgs, value = TRUE)

  expect_equal(length(read_msgs), 0L)
  expect_equal(nrow(read_df_for_config()), nrow(loaded_df))
})

test_that("read_df_for_config re-reads when the source file changes at the same path", {
  box::use(
    artma / data_config / resolve[read_df_for_config],
    testing / mocks / index[MOCKS]
  )

  df <- MOCKS$create_mock_df(seed = 42)
  tmp_file <- tempfile(fileext = ".csv")
  utils::write.csv(df, tmp_file, row.names = FALSE)
  withr::defer(unlink(tmp_file))

  withr::local_options(list(
    "artma.data.source_path" = tmp_file,
    "artma.verbose" = 1
  ))

  first <- read_df_for_config()
  expect_false("new_moderator" %in% colnames(first))

  # Rewrite the file at the same path with an extra column and bump the
  # modification time so the change is unambiguous even on coarse filesystems
  df$new_moderator <- seq_len(nrow(df))
  utils::write.csv(df, tmp_file, row.names = FALSE)
  Sys.setFileTime(tmp_file, Sys.time() + 5)

  second <- read_df_for_config()
  expect_true("new_moderator" %in% colnames(second))
})

test_that("get_data_config picks up file changes at the same source path", {
  box::use(
    artma / data_config / read[get_data_config],
    testing / mocks / index[MOCKS]
  )

  df <- MOCKS$create_mock_df(seed = 42)
  tmp_file <- tempfile(fileext = ".csv")
  utils::write.csv(df, tmp_file, row.names = FALSE)
  withr::defer(unlink(tmp_file))

  withr::local_options(list(
    "artma.data.source_path" = tmp_file,
    "artma.data.columns" = list(),
    "artma.verbose" = 1
  ))

  config_before <- get_data_config()
  expect_false("new_moderator" %in% names(config_before))

  df$new_moderator <- seq_len(nrow(df))
  utils::write.csv(df, tmp_file, row.names = FALSE)
  Sys.setFileTime(tmp_file, Sys.time() + 5)

  config_after <- get_data_config()
  expect_true("new_moderator" %in% names(config_after))
})

# ── canonical config keys ─────────────────────────────────────────────────────

test_that("config keys are standardized regardless of which entry point resolves first", {
  box::use(
    artma / data / read[read_data],
    artma / data / utils[standardize_column_names],
    artma / data_config / read[get_data_config],
    artma / data_config / resolve[prime_df_for_config_cache],
    testing / mocks / index[MOCKS]
  )

  raw_map <- list(
    study_id = "study",
    effect = "coef",
    se = "stderr",
    n_obs = "nobs"
  )
  df <- MOCKS$create_mock_df(seed = 42, colnames_map = raw_map)
  tmp_file <- tempfile(fileext = ".csv")
  utils::write.csv(df, tmp_file, row.names = FALSE)
  withr::defer(unlink(tmp_file))

  withr::local_options(list(
    "artma.data.source_path" = tmp_file,
    "artma.data.columns" = list(
      study_id = list(source_name = "study"),
      effect = list(source_name = "coef"),
      se = list(source_name = "stderr"),
      n_obs = list(source_name = "nobs")
    ),
    "artma.verbose" = 1
  ))

  # Cold cache: this is what public entry points (config_get) hit first
  cold_config <- get_data_config()

  expect_true(all(c("study_id", "effect", "se", "n_obs") %in% names(cold_config)))
  expect_false(any(c("coef", "stderr", "nobs") %in% names(cold_config)))

  # Pipeline path: prepare_data standardizes the df and primes the cache
  df_std <- standardize_column_names(read_data(tmp_file))
  prime_df_for_config_cache(df_std, tmp_file)
  primed_config <- get_data_config()

  expect_equal(sort(names(cold_config)), sort(names(primed_config)))
})

# ── get_data_config (integration with resolve) ───────────────────────────────

test_that("get_data_config: returns overrides when df not available", {
  box::use(artma / data_config / read[get_data_config])

  config <- list(
    my_var = list(
      var_name = "my_var",
      bma = TRUE,
      gltl = "median"
    )
  )

  withr::local_options(list(
    "artma.data.columns" = config,
    "artma.data.source_path" = NA,
    "artma.verbose" = 1
  ))

  result <- get_data_config()
  expect_equal(result$my_var$bma, TRUE)
  expect_equal(result$my_var$gltl, "median")
})

test_that("get_data_config: returns empty list when no config and no df", {
  box::use(artma / data_config / read[get_data_config])

  withr::local_options(list(
    "artma.data.columns" = list(),
    "artma.data.source_path" = NA,
    "artma.verbose" = 1
  ))

  result <- get_data_config(create_if_missing = TRUE)
  expect_equal(result, list())
})

test_that("get_data_config: merges overrides onto base when df is available", {
  box::use(
    artma / data_config / read[get_data_config],
    testing / mocks / index[MOCKS]
  )

  # Create a real temp CSV file
  df <- MOCKS$create_mock_df(seed = 42)
  tmp_file <- tempfile(fileext = ".csv")
  utils::write.csv(df, tmp_file, row.names = FALSE)
  withr::defer(unlink(tmp_file))

  withr::local_options(list(
    "artma.data.columns" = list(
      effect = list(bma = TRUE)
    ),
    "artma.data.source_path" = tmp_file,
    "artma.verbose" = 1,
    "artma.data.na_handling" = "remove"
  ))

  result <- get_data_config()

  # Should have all columns from the df
  expect_true("effect" %in% names(result))
  expect_true("se" %in% names(result))

  # The override should be applied
  expect_equal(result$effect$bma, TRUE)

  # Non-overridden fields should have defaults
  expect_true(is.na(result$se$bma))
})

Try the artma package in your browser

Any scripts or data that you put into this service are public.

artma documentation built on Oct. 27, 2026, 9:06 a.m.