Nothing
# test-validation.R
# Tests for validation_utils.R internal functions
# -----------------------------------------------------------------------------
# Key API facts discovered from function source:
# .check_prior_data_compat(prior, data_summary)
# - data_summary is a LIST (type=, x=, n=, ...), NOT a bare string
# - always returns list(ok, msg, severity); never throws an error
# - mismatched family/type returns ok=TRUE with severity="warning"
#
# .check_pooling_compat(priors)
# - takes a LIST of bayprior objects
# - incompatible supports: returns list(ok=FALSE, severity="error")
# - same support, mixed families: returns list(ok=TRUE, severity="warning")
# - does NOT throw errors -- caller decides what to do with $ok
#
# .check_sensitivity_compat(prior)
# - takes ONLY the prior -- no param_grid argument
# - returns list(ok, msg, severity)
# - Exponential (single-param): ok=TRUE, severity="info"
# -----------------------------------------------------------------------------
library(testthat)
library(bayprior)
# -- .check_prior_data_compat() ------------------------------------------------
test_that(".check_prior_data_compat passes for compatible family-type pairs", {
r1 <- .check_prior_data_compat(
elicit_beta(mean = 0.3, sd = 0.1, method = "moments"),
list(type = "binary", x = 10, n = 30))
expect_true(r1$ok)
expect_equal(r1$severity, "none")
r2 <- .check_prior_data_compat(
elicit_normal(mean = 0.3, sd = 0.1, method = "moments"),
list(type = "continuous", x = 0.3, sd = 0.1, n = 30))
expect_true(r2$ok)
expect_equal(r2$severity, "none")
r3 <- .check_prior_data_compat(
elicit_gamma(mean = 3.0, sd = 1.0, method = "moments"),
list(type = "poisson", x = 9, n = 3))
expect_true(r3$ok)
expect_equal(r3$severity, "none")
r4 <- .check_prior_data_compat(
elicit_exponential(mean = 2.0, method = "moments"),
list(type = "poisson", x = 5, n = 10))
expect_true(r4$ok)
})
test_that(".check_prior_data_compat returns warning (not error) for mismatches", {
# Normal supports binary and continuous -- NOT poisson
r1 <- .check_prior_data_compat(
elicit_normal(mean = 0.3, sd = 0.1, method = "moments"),
list(type = "poisson", x = 10, n = 30))
expect_true(r1$ok) # ok=TRUE -- proceeds with approximation
expect_equal(r1$severity, "warning")
expect_true(nchar(r1$msg) > 0)
# LogNormal only supports continuous -- binary is a mismatch
r2 <- .check_prior_data_compat(
elicit_lognormal(mean = 1.0, sd = 0.3, method = "moments"),
list(type = "binary", x = 10, n = 30))
expect_true(r2$ok)
expect_equal(r2$severity, "warning")
})
test_that(".check_prior_data_compat returns list with expected fields", {
r <- .check_prior_data_compat(
elicit_beta(mean = 0.3, sd = 0.1, method = "moments"),
list(type = "binary", x = 10, n = 30))
expect_true("ok" %in% names(r))
expect_true("severity" %in% names(r))
})
# -- .check_pooling_compat() ---------------------------------------------------
test_that(".check_pooling_compat returns ok=TRUE for same-family priors", {
p1 <- elicit_beta(mean = 0.3, sd = 0.1, method = "moments")
p2 <- elicit_beta(mean = 0.5, sd = 0.1, method = "moments")
p3 <- elicit_beta(mean = 0.7, sd = 0.1, method = "moments")
r <- .check_pooling_compat(list(p1, p2, p3))
expect_true(r$ok)
expect_equal(r$severity, "none")
})
test_that(".check_pooling_compat returns ok=FALSE for incompatible supports", {
# Beta (unit support) + Normal (real support) -- incompatible
p1 <- elicit_beta(mean = 0.3, sd = 0.1, method = "moments")
p2 <- elicit_normal(mean = 0.0, sd = 0.5, method = "moments")
r <- .check_pooling_compat(list(p1, p2))
expect_false(r$ok)
expect_equal(r$severity, "error")
expect_true(nchar(r$msgs) > 0)
})
test_that(".check_pooling_compat returns warning for same-support mixed families", {
# Gamma + Exponential: both positive support -- warning, not error
p1 <- elicit_gamma(mean = 3.0, sd = 1.5, method = "moments")
p2 <- elicit_exponential(mean = 2.0, method = "moments")
r <- .check_pooling_compat(list(p1, p2))
expect_true(r$ok)
expect_equal(r$severity, "warning")
})
# -- .check_sensitivity_compat() -----------------------------------------------
test_that(".check_sensitivity_compat returns ok=TRUE for standard priors", {
for (pr in list(
elicit_beta(mean = 0.3, sd = 0.1, method = "moments"),
elicit_normal(mean = 0.0, sd = 0.5, method = "moments"),
elicit_gamma(mean = 3.0, sd = 1.5, method = "moments"),
elicit_lognormal(mean = 1.0, sd = 0.3, method = "moments")
)) {
r <- .check_sensitivity_compat(pr)
expect_true(r$ok, label = paste("ok for dist:", pr$dist))
}
})
test_that(".check_sensitivity_compat returns info for Exponential (single-param)", {
pr <- elicit_exponential(mean = 2.0, method = "moments")
r <- .check_sensitivity_compat(pr)
expect_true(r$ok)
expect_equal(r$severity, "info")
expect_true(nchar(r$msg) > 0)
})
test_that(".check_sensitivity_compat returns list with expected fields", {
r <- .check_sensitivity_compat(
elicit_beta(mean = 0.3, sd = 0.1, method = "moments"))
expect_true("ok" %in% names(r))
expect_true("severity" %in% names(r))
})
# -- .dist_support() -----------------------------------------------------------
# No prior direct coverage -- only exercised incidentally via other functions.
test_that(".dist_support classifies each known family correctly", {
expect_equal(.dist_support("beta"), "unit")
expect_equal(.dist_support("normal"), "real")
expect_equal(.dist_support("gamma"), "positive")
expect_equal(.dist_support("lognormal"), "positive")
expect_equal(.dist_support("exponential"), "positive")
expect_equal(.dist_support("weibull"), "positive")
expect_equal(.dist_support("mixture"), "mixed")
})
test_that(".dist_support falls back to \"unknown\" for an unrecognised family", {
# e.g. "log_pool" -- .log_pool() sets dist = "log_pool", not "mixture",
# so it is not one of the switch()'s named cases and falls through.
expect_equal(.dist_support("log_pool"), "unknown")
expect_equal(.dist_support("nonsense"), "unknown")
})
# -- .prior_support() ----------------------------------------------------------
# No prior coverage at all.
test_that(".prior_support passes through for non-mixture priors", {
expect_equal(.prior_support(elicit_beta(mean = 0.3, sd = 0.1, method = "moments")),
"unit")
expect_equal(.prior_support(elicit_gamma(mean = 3, sd = 1, method = "moments")),
"positive")
})
test_that(".prior_support returns a single support for a same-support mixture", {
e1 <- elicit_gamma(mean = 3, sd = 1, method = "moments", expert_id = "E1")
e2 <- elicit_exponential(mean = 2, method = "moments", expert_id = "E2")
# aggregate_experts() -> elicit_mixture() warns when components share a
# support but differ in family, since the mixture density then has to be
# evaluated numerically rather than analytically. Expected here, since
# this test specifically constructs a mixed-family, same-support pool.
expect_warning(
mix <- aggregate_experts(list(E1 = e1, E2 = e2), weights = c(0.5, 0.5),
method = "linear"),
"different distribution families"
)
expect_equal(.prior_support(mix), "positive")
})
test_that(".prior_support returns \"mixed\" for an incompatible-support mixture", {
# Bypasses aggregate_experts()'s own compatibility check (which would
# normally reject this combination) by constructing the mixture object
# directly, to isolate .prior_support()'s own logic.
e1 <- elicit_beta(mean = 0.3, sd = 0.1, method = "moments", expert_id = "E1")
e2 <- elicit_normal(mean = 0.0, sd = 0.5, method = "moments", expert_id = "E2")
mix <- structure(
list(dist = "mixture", components = list(e1, e2), weights = c(0.5, 0.5)),
class = "bayprior"
)
expect_equal(.prior_support(mix), "mixed")
})
# -- .check_prior_data_compat() -- mixture and log_pool handling -------------
# The existing tests above never pass a mixture or log-pooled prior.
test_that(".check_prior_data_compat resolves a mixture to its dominant component", {
e1 <- elicit_beta(mean = 0.30, sd = 0.08, method = "moments", expert_id = "E1")
e2 <- elicit_beta(mean = 0.42, sd = 0.10, method = "moments", expert_id = "E2")
pool <- aggregate_experts(list(E1 = e1, E2 = e2), weights = c(0.7, 0.3),
method = "linear")
# Dominant component (weight 0.7) is Beta -> binary data is compatible.
r <- .check_prior_data_compat(pool, list(type = "binary", x = 18, n = 40))
expect_true(r$ok)
expect_equal(r$severity, "none")
})
test_that(".check_prior_data_compat silently skips validation for a
logarithmically-pooled prior", {
# .log_pool() sets dist = "log_pool", which is neither "mixture" (so the
# dominant-component extraction at line 35 never fires) nor one of the six
# keys in the compat list -- so `recommended` is NULL and the function
# returns ok=TRUE/severity="none" unconditionally, regardless of whether
# the data type actually suits the pooled experts' family. This documents
# that behavior explicitly rather than leaving it silently untested.
e1 <- elicit_beta(mean = 0.30, sd = 0.08, method = "moments", expert_id = "E1")
e2 <- elicit_beta(mean = 0.42, sd = 0.10, method = "moments", expert_id = "E2")
pool_log <- aggregate_experts(list(E1 = e1, E2 = e2), weights = c(0.5, 0.5),
method = "logarithmic")
expect_equal(pool_log$dist, "log_pool")
# Even a data type nothing here supports (e.g. survival, unrelated to
# Beta experts) is reported as fully compatible, with no warning.
r <- .check_prior_data_compat(pool_log, list(type = "survival", x = 5, n = 40))
expect_true(r$ok)
expect_equal(r$severity, "none")
expect_null(r$msg)
})
# -- .check_pooling_compat() -- nested mixture ---------------------------------
test_that(".check_pooling_compat treats a nested mixture as an incompatible support", {
# A mixture passed inside the list of priors to pool: .dist_support()
# classifies dist = "mixture" as support "mixed", which never matches
# another prior's "unit"/"positive"/"real" support, so this is always
# flagged as an incompatible-support error rather than silently accepted.
beta1 <- elicit_beta(mean = 0.3, sd = 0.1, method = "moments", expert_id = "E1")
beta2 <- elicit_beta(mean = 0.5, sd = 0.1, method = "moments", expert_id = "E2")
inner_pool <- aggregate_experts(list(E1 = beta1, E2 = beta2),
weights = c(0.5, 0.5), method = "linear")
beta3 <- elicit_beta(mean = 0.4, sd = 0.1, method = "moments", expert_id = "E3")
r <- .check_pooling_compat(list(inner_pool, beta3))
expect_false(r$ok)
expect_equal(r$severity, "error")
})
# -- .check_sensitivity_compat() -- mixture branches (previously 0% covered) --
test_that(".check_sensitivity_compat warns for a same-support mixed-family mixture", {
e1 <- elicit_gamma(mean = 3, sd = 1, method = "moments", expert_id = "E1")
e2 <- elicit_exponential(mean = 2, method = "moments", expert_id = "E2")
expect_warning(
pool <- aggregate_experts(list(E1 = e1, E2 = e2), weights = c(0.5, 0.5),
method = "linear"),
"different distribution families"
)
r <- .check_sensitivity_compat(pool)
expect_true(r$ok)
expect_equal(r$severity, "warning")
expect_true(nchar(r$msg) > 0)
})
test_that(".check_sensitivity_compat errors for an incompatible-support mixture", {
e1 <- elicit_beta(mean = 0.3, sd = 0.1, method = "moments", expert_id = "E1")
e2 <- elicit_normal(mean = 0.0, sd = 0.5, method = "moments", expert_id = "E2")
mix <- structure(
list(dist = "mixture", components = list(e1, e2), weights = c(0.5, 0.5)),
class = "bayprior"
)
r <- .check_sensitivity_compat(mix)
expect_false(r$ok)
expect_equal(r$severity, "error")
})
# -- .validation_alert() -- 0% covered, no tests existed ----------------------
test_that(".validation_alert returns NULL for NULL or empty message", {
expect_null(.validation_alert(NULL))
expect_null(.validation_alert(""))
})
test_that(".validation_alert renders each severity with the right CSS class", {
info_alert <- .validation_alert("An info message", severity = "info")
expect_true(grepl("alert-info", as.character(info_alert), fixed = TRUE))
warn_alert <- .validation_alert("A warning message", severity = "warning")
expect_true(grepl("alert-warning", as.character(warn_alert), fixed = TRUE))
err_alert <- .validation_alert("An error message", severity = "error")
expect_true(grepl("alert-danger", as.character(err_alert), fixed = TRUE))
})
test_that(".validation_alert falls back to the info style for an unrecognised severity", {
alert <- .validation_alert("A message", severity = "not_a_real_severity")
expect_true(grepl("alert-info", as.character(alert), fixed = 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.