tests/testthat/test-utils-extra.R

# Additional tests for R/utils.R — coverage for utility functions

# Extra tests only run in test-coverage and local; skip on container CI
skip_on_cran()
skip_if(nzchar(Sys.getenv("CI_TEST_DB")), "Skipping extra tests on container CI")

# --- .inSchema ---

test_that(".inSchema returns Id for basic schema.table", {
  result <- CDMConnector:::.inSchema("my_schema", "my_table")
  expect_s4_class(result, "Id")
  expect_equal(result@name[["schema"]], "my_schema")
  expect_equal(result@name[["table"]], "my_table")
})

test_that(".inSchema returns Id for 2-part schema", {
  result <- CDMConnector:::.inSchema(c("catalog1", "schema1"), "my_table", dbms = "snowflake")
  expect_s4_class(result, "Id")
  expect_equal(result@name[["catalog"]], "catalog1")
  expect_equal(result@name[["schema"]], "schema1")
})

test_that(".inSchema handles prefix with uppercase table", {
  schema <- c(schema = "main", prefix = "tmp_")
  result <- CDMConnector:::.inSchema(schema, "PERSON")
  # prefix should be uppercased for UPPERCASE tables
  expect_s4_class(result, "Id")
  expect_true(grepl("TMP_PERSON", result@name[["table"]]))
})

test_that(".inSchema handles prefix with lowercase table", {
  schema <- c(schema = "main", prefix = "tmp_")
  result <- CDMConnector:::.inSchema(schema, "person")
  expect_s4_class(result, "Id")
  expect_equal(result@name[["table"]], "tmp_person")
})

test_that(".inSchema returns plain name for duckdb main schema", {
  result <- CDMConnector:::.inSchema("main", "person", dbms = "duckdb")
  expect_equal(result, "person")
})

test_that(".inSchema handles NULL schema for non-sql-server", {
  result <- CDMConnector:::.inSchema(NULL, "person", dbms = "duckdb")
  expect_s4_class(result, "Id")
  expect_equal(result@name[["table"]], "person")
})

test_that(".inSchema handles NULL schema for sql server (adds # prefix)", {
  result <- CDMConnector:::.inSchema(NULL, "person", dbms = "sql server")
  expect_s4_class(result, "Id")
  expect_equal(result@name[["table"]], "#person")
})

# --- .qualifiedNameForSql ---

test_that(".qualifiedNameForSql handles character string", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  result <- CDMConnector:::.qualifiedNameForSql(con, "my_table")
  expect_true(grepl("my_table", result))
})

test_that(".qualifiedNameForSql handles DBI::Id", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  id <- DBI::Id(schema = "main", table = "my_table")
  result <- CDMConnector:::.qualifiedNameForSql(con, id)
  expect_true(grepl("main", result))
  expect_true(grepl("my_table", result))
})

# --- isInstalled ---

test_that("isInstalled returns TRUE for base", {
  expect_true(CDMConnector:::isInstalled("base"))
})

test_that("isInstalled returns FALSE for non-existent package", {
  expect_false(CDMConnector:::isInstalled("this_package_surely_does_not_exist"))
})

test_that("isInstalled checks version", {
  expect_true(CDMConnector:::isInstalled("base", "0"))
  # Extremely high version should fail
  expect_false(CDMConnector:::isInstalled("base", "999.999"))
})

# --- ensureInstalled ---

test_that("ensureInstalled does not error for installed package", {
  expect_no_error(CDMConnector:::ensureInstalled("base"))
})

test_that("ensureInstalled errors in non-interactive for missing package", {
  expect_error(
    CDMConnector:::ensureInstalled("this_package_surely_does_not_exist"),
    "must be installed"
  )
})

# --- mapTypes ---

test_that("mapTypes returns type unchanged for duckdb", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)
  expect_equal(CDMConnector:::mapTypes(con, "integer"), "integer")
  expect_equal(CDMConnector:::mapTypes(con, "character"), "character")
})

# --- unique_prefix ---

test_that("unique_prefix returns a positive number", {
  result <- CDMConnector:::unique_prefix()
  expect_true(is.numeric(result))
  expect_true(result > 0)
})

test_that("unique_prefix returns different values over time", {
  p1 <- CDMConnector:::unique_prefix()
  Sys.sleep(0.2)
  p2 <- CDMConnector:::unique_prefix()
  expect_true(is.numeric(p1))
  expect_true(is.numeric(p2))
})

# --- .dbCreateTable ---

test_that(".dbCreateTable creates table via DBI", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  fields <- c(id = "INTEGER", name = "VARCHAR")
  CDMConnector:::.dbCreateTable(con, "test_tbl", fields)
  expect_true("test_tbl" %in% DBI::dbListTables(con))
})

# --- .formatDatesForSparkInsert ---

test_that(".formatDatesForSparkInsert converts Date and POSIXt to character", {
  df <- data.frame(
    d = as.Date("2020-01-15"),
    t = as.POSIXct("2020-01-15 10:30:00", tz = "UTC"),
    x = 42L
  )
  result <- CDMConnector:::.formatDatesForSparkInsert(df)
  expect_type(result$d, "character")
  expect_type(result$t, "character")
  expect_equal(result$x, 42L)
  expect_equal(result$d, "2020-01-15")
})

# --- dcCreateTable ---

test_that("dcCreateTable generates SQL for tibble fields", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  fields <- tibble::tibble(id = integer(0), name = character(0))
  sql <- CDMConnector:::dcCreateTable(con, "test_tbl", fields)
  expect_type(sql, "character")
  expect_true(grepl("CREATE TABLE", sql))
})

test_that("dcCreateTable generates SQL for named character fields", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  fields <- c(id = "INTEGER", name = "VARCHAR")
  sql <- CDMConnector:::dcCreateTable(con, "test_tbl2", fields)
  expect_type(sql, "character")
  expect_true(grepl("CREATE TABLE", sql))
})

# --- listTables ---

test_that("listTables works with duckdb", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  DBI::dbExecute(con, "CREATE TABLE test_table (id INTEGER)")
  tables <- listTables(con, schema = "main")
  expect_true("test_table" %in% tables)
})

test_that("listTables returns all tables with NULL schema", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  DBI::dbExecute(con, "CREATE TABLE test_null_schema (id INTEGER)")
  tables <- listTables(con, schema = NULL)
  expect_true("test_null_schema" %in% tables)
})

test_that("listTables handles prefix filtering", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  DBI::dbExecute(con, "CREATE TABLE tmp_abc (id INTEGER)")
  DBI::dbExecute(con, "CREATE TABLE tmp_def (id INTEGER)")
  DBI::dbExecute(con, "CREATE TABLE other (id INTEGER)")
  tables <- listTables(con, schema = c(schema = "main", prefix = "tmp_"))
  expect_true("abc" %in% tables)
  expect_true("def" %in% tables)
  expect_false("other" %in% tables)
})

test_that("listTables errors on bad dot-separated schema", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  expect_error(
    listTables(con, schema = "a.b.c"),
    "one dot"
  )
})

# --- execute_ddl ---

test_that("execute_ddl creates CDM tables", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  CDMConnector:::execute_ddl(con, "main", tables = c("person", "observation_period"))
  tables <- DBI::dbListTables(con)
  expect_true("person" %in% tables)
  expect_true("observation_period" %in% tables)
})

# --- .dbIsValid ---

test_that(".dbIsValid works for duckdb connection", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  expect_true(CDMConnector:::.dbIsValid(con))
  DBI::dbDisconnect(con, shutdown = TRUE)
})


# --- cdmCommentContents ---

test_that("cdmCommentContents returns invisibly NULL in non-interactive", {
  # In non-interactive sessions, the function should return invisible NULL
  result <- cdmCommentContents(NULL)
  expect_null(result)
})

# --- .cdm_comment_interactive ---

test_that(".cdm_comment_interactive returns a logical", {
  result <- CDMConnector:::.cdm_comment_interactive()
  expect_type(result, "logical")
})

# --- listTables with duckdb 2-part schema ---

test_that("listTables works with 2-part duckdb schema", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  DBI::dbExecute(con, "CREATE TABLE test_2part (id INTEGER)")
  tables <- listTables(con, schema = c("memory", "main"))
  expect_true("test_2part" %in% tables)
})

test_that("listTables with named 2-part schema", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  DBI::dbExecute(con, "CREATE TABLE named_2part (id INTEGER)")
  tables <- listTables(con, schema = c(catalog = "memory", schema = "main"))
  expect_true("named_2part" %in% tables)
})

test_that("listTables with dot-separated catalog.schema", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  DBI::dbExecute(con, "CREATE TABLE dot_separated (id INTEGER)")
  tables <- listTables(con, schema = "memory.main")
  expect_true("dot_separated" %in% tables)
})

test_that("listTables with Id object", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  DBI::dbExecute(con, "CREATE TABLE id_test (id INTEGER)")
  schema_id <- DBI::Id(schema = "main")
  tables <- listTables(con, schema = schema_id)
  expect_true("id_test" %in% tables)
})

# --- execute_ddl ---

test_that("execute_ddl creates specified tables only", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  CDMConnector:::execute_ddl(con, "main", tables = c("person"))
  tables <- DBI::dbListTables(con)
  expect_true("person" %in% tables)
})

test_that("execute_ddl creates 5.4 tables", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  CDMConnector:::execute_ddl(con, "main", cdm_version = "5.4", tables = c("person", "observation_period"))
  tables <- DBI::dbListTables(con)
  expect_true("person" %in% tables)
  expect_true("observation_period" %in% tables)
})

test_that("execute_ddl with prefix", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  CDMConnector:::execute_ddl(con, "main", tables = c("person"), prefix = "test_")
  tables <- DBI::dbListTables(con)
  expect_true("test_person" %in% tables)
})

# --- .inSchema edge cases ---

test_that(".inSchema with bigquery and 2-part schema returns string", {
  result <- CDMConnector:::.inSchema(c("project", "dataset"), "my_table", dbms = "bigquery")
  expect_type(result, "character")
  expect_true(grepl("project", result))
  expect_true(grepl("dataset", result))
  expect_true(grepl("my_table", result))
})

test_that(".inSchema with snowflake and 2-part schema returns Id", {
  result <- CDMConnector:::.inSchema(c("cat", "sch"), "my_table", dbms = "snowflake")
  expect_s4_class(result, "Id")
  expect_equal(result@name[["catalog"]], "cat")
  expect_equal(result@name[["schema"]], "sch")
})

test_that(".inSchema with 1-part schema returns Id", {
  result <- CDMConnector:::.inSchema("myschema", "my_table", dbms = "postgresql")
  expect_s4_class(result, "Id")
  expect_equal(result@name[["schema"]], "myschema")
  expect_equal(result@name[["table"]], "my_table")
})

# --- mapTypes ---

test_that("mapTypes returns type for non-bigquery", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)
  expect_equal(CDMConnector:::mapTypes(con, "Date"), "Date")
  expect_equal(CDMConnector:::mapTypes(con, "logical"), "logical")
})

# --- dcCreateTable ---

test_that("dcCreateTable with Id name", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  name <- DBI::Id(schema = "main", table = "test_tbl")
  fields <- c(id = "INTEGER", name = "VARCHAR")
  sql <- CDMConnector:::dcCreateTable(con, name, fields)
  expect_true(grepl("CREATE TABLE", sql))
})

# --- .qualifiedNameForSql edge cases ---

test_that(".qualifiedNameForSql handles plain non-char/non-Id", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  # Pass numeric (edge case: function returns it unchanged)
  result <- CDMConnector:::.qualifiedNameForSql(con, 42)
  expect_equal(result, 42)
})

# --- .formatDatesForSparkInsert ---

test_that(".formatDatesForSparkInsert preserves non-date columns", {
  df <- data.frame(x = 1:3, name = c("a", "b", "c"), stringsAsFactors = FALSE)
  result <- CDMConnector:::.formatDatesForSparkInsert(df)
  expect_equal(result$x, 1:3)
  expect_equal(result$name, c("a", "b", "c"))
})

test_that(".formatDatesForSparkInsert converts POSIXct", {
  df <- data.frame(
    ts = as.POSIXct(c("2020-01-01 12:00:00", "2020-06-15 08:30:00"), tz = "UTC"),
    val = 1:2
  )
  result <- CDMConnector:::.formatDatesForSparkInsert(df)
  expect_type(result$ts, "character")
  expect_equal(result$val, 1:2)
})

# --- dbplyr_edition.BigQueryConnection ---

test_that("dbplyr_edition.BigQueryConnection returns 2L", {
  # Create a mock BigQueryConnection-like object
  mock_con <- structure(list(), class = "BigQueryConnection")
  result <- dbplyr::dbplyr_edition(mock_con)
  expect_equal(result, 2L)
})


# --- .cdm_comment helper functions ---

test_that(".cdm_comment_interactive returns logical", {
  result <- CDMConnector:::.cdm_comment_interactive()
  expect_type(result, "logical")
})

test_that(".cdm_comment_require_rstudioapi returns logical", {
  result <- CDMConnector:::.cdm_comment_require_rstudioapi()
  expect_type(result, "logical")
})

test_that(".cdm_comment_collect collects a data frame", {
  df <- data.frame(x = 1:3)
  result <- CDMConnector:::.cdm_comment_collect(df)
  expect_true(is.data.frame(result))
  expect_equal(nrow(result), 3)
})

test_that(".cdm_comment_flatten works on CDM", {
  skip_if_not_installed("duckdb")
  con <- local_eunomia_con()

  cdm <- cdmFromCon(con, cdmSchema = "main", writeSchema = "main")
  result <- CDMConnector:::.cdm_comment_flatten(cdm)
  expect_true(!is.null(result))
})

# --- mapTypes ---

test_that("mapTypes returns type unchanged for non-bigquery", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  expect_equal(CDMConnector:::mapTypes(con, "integer"), "integer")
  expect_equal(CDMConnector:::mapTypes(con, "character"), "character")
  expect_equal(CDMConnector:::mapTypes(con, "numeric"), "numeric")
})

# --- isInstalled ---

test_that("isInstalled detects installed packages", {
  expect_true(CDMConnector:::isInstalled("dplyr"))
  expect_false(CDMConnector:::isInstalled("nonexistent_package_xyz"))
})

test_that("isInstalled checks version", {
  expect_true(CDMConnector:::isInstalled("dplyr", "0.1"))
  expect_false(CDMConnector:::isInstalled("dplyr", "999.0"))
})

# --- listTables with prefix ---

test_that("listTables filters by prefix", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  DBI::dbWriteTable(con, "pre_table1", data.frame(x = 1))
  DBI::dbWriteTable(con, "pre_table2", data.frame(x = 2))
  DBI::dbWriteTable(con, "other_table", data.frame(x = 3))

  tables <- listTables(con, schema = c(schema = "main", prefix = "pre_"))
  expect_true("table1" %in% tables)
  expect_true("table2" %in% tables)
  expect_false("other_table" %in% tables)
})

# --- listTables with dot-separated schema ---

test_that("listTables handles catalog.schema form", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  DBI::dbWriteTable(con, "test_tbl", data.frame(x = 1))

  # memory.main is the default catalog.schema for in-memory duckdb
  tables <- listTables(con, schema = "memory.main")
  expect_true("test_tbl" %in% tables)
})

# --- listTables errors on multiple dots ---

test_that("listTables errors on schema with multiple dots", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  expect_error(listTables(con, schema = "a.b.c"), "one dot")
})

# --- listTables with Id schema ---

test_that("listTables handles Id schema object", {
  skip_if_not_installed("duckdb")
  con <- DBI::dbConnect(duckdb::duckdb(), ":memory:")
  on.exit(DBI::dbDisconnect(con, shutdown = TRUE), add = TRUE)

  DBI::dbWriteTable(con, "test_tbl", data.frame(x = 1))
  schema_id <- DBI::Id(schema = "main")
  tables <- listTables(con, schema = schema_id)
  expect_true("test_tbl" %in% tables)
})

# --- .inSchema ---

test_that(".inSchema returns Id for unknown dbms", {
  result <- CDMConnector:::.inSchema("main", "person")
  expect_s4_class(result, "Id")
})

test_that(".inSchema returns string for duckdb", {
  result <- CDMConnector:::.inSchema("main", "person", dbms = "duckdb")
  expect_true(is.character(result))
})

Try the CDMConnector package in your browser

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

CDMConnector documentation built on April 3, 2026, 9:09 a.m.