test_that("buildConceptSetQuery validates inputs", { expect_error( OdysseusCostModule::buildConceptSetQuery(list(items = list()), vocabularyDatabaseSchema = NA_character_), "must not be NA or empty" ) expect_error( OdysseusCostModule::buildConceptSetQuery("{bad json}"), "not valid JSON" ) expect_error( OdysseusCostModule::buildConceptSetQuery(list()), "must contain an `items` element" ) expect_error( OdysseusCostModule::buildConceptSetQuery(list(items = "x")), "items` must be a list" ) expect_error( OdysseusCostModule::buildConceptSetQuery(list(items = list("x"))), "must be a list" ) expect_error( OdysseusCostModule::buildConceptSetQuery(list(items = list(list()))), "must contain a `concept` element" ) expect_error( OdysseusCostModule::buildConceptSetQuery(list(items = list(list(concept = list())))), "must contain a `CONCEPT_ID`" ) expect_error( OdysseusCostModule::buildConceptSetQuery(list(items = list(list(concept = list(CONCEPT_ID = 1.5))))), "must be a whole number" ) expect_error( OdysseusCostModule::buildConceptSetQuery( list(items = list(list(concept = list(CONCEPT_ID = 1), includeMapped = c(TRUE, FALSE)))) ), "must be a single logical value" ) }) test_that("buildConceptSetQuery handles empty and JSON inputs", { expect_identical( OdysseusCostModule::buildConceptSetQuery(list(items = list())), "" ) concept_set <- list(items = list(list(concept = list(CONCEPT_ID = 123)))) json_input <- jsonlite::toJSON(concept_set, auto_unbox = TRUE) sql <- OdysseusCostModule::buildConceptSetQuery(json_input, vocabularyDatabaseSchema = "vocab") expect_match(sql, "vocab\\.CONCEPT") expect_match(sql, "123") }) test_that("buildConceptSetQuery emits SQL for included and excluded branches", { concept_set <- list( items = list( list(concept = list(CONCEPT_ID = 1)), list(concept = list(CONCEPT_ID = 2), includeDescendants = TRUE), list(concept = list(CONCEPT_ID = 3), includeMapped = TRUE), list(concept = list(CONCEPT_ID = 4), includeDescendants = TRUE, includeMapped = TRUE), list(concept = list(CONCEPT_ID = 5), isExcluded = TRUE) ) ) sql <- OdysseusCostModule::buildConceptSetQuery(concept_set, vocabularyDatabaseSchema = "cdm_vocab") expect_match(sql, "distinct.*cs_name.*I\\.concept_id", perl = TRUE) expect_match(sql, "cdm_vocab\\.CONCEPT where \\(concept_id in \\(1,2,3,4\\)\\)") expect_match(sql, "CONCEPT_ANCESTOR") expect_match(sql, "CONCEPT_RELATIONSHIP") expect_match(sql, "LEFT JOIN") expect_match(sql, "WHERE E\\.concept_id is null") }) test_that("buildConceptSetQuery warns on NA logical flags", { concept_set <- list(items = list(list(concept = list(CONCEPT_ID = 9), includeDescendants = NA))) expect_warning( sql <- OdysseusCostModule::buildConceptSetQuery(concept_set, vocabularyDatabaseSchema = "vocab"), "treating as FALSE" ) expect_match(sql, "concept_id in \\(9\\)") }) test_that("buildConceptSetQueries returns named SQL list", { concept_sets <- list( one = list(items = list(list(concept = list(CONCEPT_ID = 11)))), two = list(items = list(list(concept = list(CONCEPT_ID = 22), includeMapped = TRUE))) ) sql_list <- OdysseusCostModule::buildConceptSetQueries(concept_sets, vocabularyDatabaseSchema = "vocab") expect_named(sql_list, c("one", "two")) expect_length(sql_list, 2) expect_true(all(vapply(sql_list, is.character, logical(1)))) })