# ────────────────────────────────────────────────────────────────────────────── # test-input-validation.R — error condition and boundary case tests # # Follows the bgms pattern: every user-facing function should give # informative errors on invalid input, tested with expect_error(regexp = ...). # ────────────────────────────────────────────────────────────────────────────── # ═══════════════════════════════════════════════════════════════════════════════ # §1 sim_var() validation # ═══════════════════════════════════════════════════════════════════════════════ test_that("sim_var rejects invalid family", { expect_error( sim_var(N = 3, T_obs = 20, p = 2, family = "poisson"), "arg" ) }) test_that("sim_var rejects N < 1", { expect_error( sim_var(N = 0, T_obs = 20, p = 2, family = "bernoulli", seed = 1), "N >= 1" ) }) test_that("sim_var rejects T_obs < K + 1", { expect_error( sim_var(N = 3, T_obs = 1, p = 2, K = 1, family = "bernoulli", seed = 1), "T_obs >= K" ) }) test_that("sim_var rejects p < 1", { expect_error( sim_var(N = 3, T_obs = 20, p = 0, family = "bernoulli", seed = 1), "p >= 1" ) }) test_that("sim_var rejects wrong alpha length", { expect_error( sim_var(N = 3, T_obs = 20, p = 3, family = "gaussian", alpha = c(1, 2), seed = 1, burnin = 0), "alpha" ) }) test_that("sim_var rejects wrong Phi dimensions", { expect_error( sim_var(N = 3, T_obs = 20, p = 3, K = 1, family = "gaussian", Phi = matrix(0, 2, 2), seed = 1, burnin = 0), "Phi" ) }) test_that("sim_var rejects negative sigma", { expect_error( sim_var(N = 3, T_obs = 20, p = 2, family = "gaussian", sigma = c(-1, 1), seed = 1, burnin = 0), "sigma.*> 0" ) }) test_that("sim_var rejects C < 2 for ordinal", { expect_error( sim_var(N = 3, T_obs = 20, p = 2, family = "ordinal", C = 1, seed = 1, burnin = 0), "C >= 2" ) }) test_that("sim_var rejects negative burnin", { expect_error( sim_var(N = 3, T_obs = 20, p = 2, family = "bernoulli", burnin = -1, seed = 1), "burnin >= 0" ) }) test_that("sim_var rejects unordered kappa", { expect_error( sim_var(N = 3, T_obs = 20, p = 2, family = "ordinal", C = 3, kappa = list(c(1, 0), c(0, 1)), # first one is unordered seed = 1, burnin = 0), "unsorted|kappa" ) }) test_that("sim_var rejects wrong kappa length", { expect_error( sim_var(N = 3, T_obs = 20, p = 2, family = "ordinal", C = 4, kappa = list(c(-1, 0), c(-1, 0, 1)), # first has 2 instead of 3 seed = 1, burnin = 0), "kappa" ) }) test_that("sim_var rejects wrong gamma dimensions", { expect_error( sim_var(N = 3, T_obs = 20, p = 3, q = 2, family = "gaussian", gamma = matrix(0, 1, 3), # should be 2x3 seed = 1, burnin = 0), "gamma" ) }) # ═══════════════════════════════════════════════════════════════════════════════ # §2 to_stan_data() validation # ═══════════════════════════════════════════════════════════════════════════════ test_that("to_stan_data rejects invalid family", { df <- make_test_df(N = 3, T_obs = 10, p = 2) expect_error( to_stan_data(df, "poisson", "id", "t", paste0("y_", 1:2), character(0), K = 1), "arg" ) }) test_that("to_stan_data where ordinal Y has values < 1 gives error", { df <- make_test_df(N = 3, T_obs = 10, p = 2, family = "ordinal") df$y_1 <- df$y_1 - 1 # now contains 0s expect_error( to_stan_data(df, "ordinal", "id", "t", paste0("y_", 1:2), character(0), K = 1), "< 1" ) }) test_that("to_stan_data stops when all observations are removed", { # A very small dataset where NA + K causes all data to be removed df <- data.frame(id = c(1, 1), t = c(1, 2), y_1 = c(NA, 0L), y_2 = c(1L, NA)) expect_error( to_stan_data(df, "bernoulli", "id", "t", paste0("y_", 1:2), character(0), K = 1), "All observations removed" ) }) test_that("to_stan_data rejects re_cols not found in X", { df <- make_test_df(N = 3, T_obs = 15, p = 2, q = 1, family = "bernoulli") expect_error( to_stan_data(df, "bernoulli", "id", "t", paste0("y_", 1:2), "x_1", re_cols = "nonexistent_col", K = 1), "re_cols not found" ) }) # ═══════════════════════════════════════════════════════════════════════════════ # §3 normalize_terms() validation # ═══════════════════════════════════════════════════════════════════════════════ test_that("normalize_terms rejects non-list input", { expect_error( bvarnet:::normalize_terms("x_1"), "list" ) }) test_that("normalize_terms rejects single-factor terms", { expect_error( bvarnet:::normalize_terms(list(c("x_1"))), "at least 2" ) }) test_that("normalize_terms rejects duplicate factors", { expect_error( bvarnet:::normalize_terms(list(c("x_1", "x_1"))), "Duplicate" ) }) test_that("normalize_terms rejects empty factor names", { expect_error( bvarnet:::normalize_terms(list(c("x_1", ""))), "Empty" ) }) test_that("normalize_terms accepts valid terms", { result <- bvarnet:::normalize_terms(list(c("x_1", "x_2"), c("lag", "x_1"))) expect_length(result, 2) expect_equal(result[[1]], c("x_1", "x_2")) }) test_that("normalize_terms returns empty list for NULL", { expect_equal(bvarnet:::normalize_terms(NULL), list()) }) # ═══════════════════════════════════════════════════════════════════════════════ # §4 Interaction term validation # ═══════════════════════════════════════════════════════════════════════════════ test_that("add_terms_to_X rejects unknown factor names", { X <- matrix(1, 10, 2, dimnames = list(NULL, c("Intercept", "x_1"))) B <- matrix(0, 10, 2, dimnames = list(NULL, c("lag1_y_1", "lag1_y_2"))) expect_error( bvarnet:::add_terms_to_X(X, B, list(c("x_1", "nonexistent"))), "Unknown factor" ) }) test_that("add_terms_to_X rejects duplicate columns", { X <- matrix(1, 10, 3, dimnames = list(NULL, c("Intercept", "x_1", "x_1:x_2"))) B <- matrix(0, 10, 2, dimnames = list(NULL, c("lag1_y_1", "lag1_y_2"))) # x_1:x_2 already exists in X, so adding the interaction should fail # First we need x_2 in X for the term to be valid X2 <- cbind(X, x_2 = 1) expect_error( bvarnet:::add_terms_to_X(X2, B, list(c("x_1", "x_2"))), "already exist" ) }) test_that("add_re_interactions_from_X rejects missing FE interactions", { Z <- matrix(0, 10, 0) X <- matrix(1, 10, 2, dimnames = list(NULL, c("Intercept", "x_1"))) B <- matrix(0, 10, 2, dimnames = list(NULL, c("lag1_y_1", "lag1_y_2"))) # Ask for RE interaction on c("x_1","x_2") which has not been added to FE expect_error( bvarnet:::add_re_interactions_from_X(Z, X, B, list(c("x_1", "x_2"))), "not present in fixed effects" ) }) # ═══════════════════════════════════════════════════════════════════════════════ # §5 Edge cases # ═══════════════════════════════════════════════════════════════════════════════ test_that("single subject (N=1) works", { result <- sim_var(N = 1, T_obs = 30, p = 2, family = "gaussian", seed = 400, burnin = 10) expect_equal(nrow(result$data), 30) expect_equal(result$truth$N, 1L) expect_equal(nrow(result$truth$alpha_i), 1) }) test_that("single node (p=1) works", { result <- sim_var(N = 3, T_obs = 20, p = 1, family = "bernoulli", seed = 401, burnin = 10) expect_equal(ncol(result$truth$Phi), 1) expect_true("y_1" %in% names(result$data)) }) test_that("minimum ordinal C = 2 works", { result <- sim_var(N = 3, T_obs = 20, p = 2, family = "ordinal", C = 2, seed = 402, burnin = 10) expect_true(all(result$data$y_1 %in% c(1L, 2L))) }) test_that("minimum T_obs = K + 1 works", { result <- sim_var(N = 3, T_obs = 2, p = 2, K = 1, family = "gaussian", seed = 403, burnin = 10) expect_equal(nrow(result$data), 3 * 2) }) test_that("to_stan_data works with minimal data (1 subject, K+1 rows)", { df <- data.frame(id = 1L, t = 1:3, y_1 = rnorm(3), y_2 = rnorm(3)) sd <- to_stan_data(df, "gaussian", "id", "t", c("y_1", "y_2"), character(0), K = 1) expect_equal(sd$n_obs, 2) # T=3, K=1 → 2 modeled rows expect_equal(sd$J, 1) }) # ═══════════════════════════════════════════════════════════════════════════════ # §6 bvar() input validation (no Stan required) # ═══════════════════════════════════════════════════════════════════════════════ test_that("bvar rejects invalid family string", { df <- make_test_df(N = 3, T_obs = 10, p = 2) expect_error( bvar(id_col = "id", time_col = "t", y_cols = paste0("y_", 1:2), x_cols = character(0), data = df, family = "poisson", K = 1), "arg" ) })