Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pkg-r/NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# querychat (development version)

## Bug fixes

* Tests no longer fail on systems where Suggests packages like duckdb or RSQLite aren't installed (as on some CRAN check flavors): test fixtures now skip when a required database engine is missing instead of erroring or silently falling back to a different engine. (#317)

# querychat 0.4.0

## New features
Expand Down
37 changes: 33 additions & 4 deletions pkg-r/tests/testthat/helper-fixtures.R
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,33 @@ local_sqlite_connection <- function(
list(conn = conn, path = temp_db)
}

# Skip test if no DataFrameSource engine is available
skip_if_no_dataframe_engine <- function() {
if (!rlang::is_installed("duckdb") && !rlang::is_installed("RSQLite")) {
skip("Neither duckdb nor RSQLite is installed")
# Skip test if no DataFrameSource engine is available.
#
# When `engine` is NULL, skip only if neither duckdb nor RSQLite is installed
# (mirroring how DataFrameSource resolves a default engine). When a specific
# engine is requested, skip unless that engine's package is installed, so
# tests never silently fall back to a different engine (e.g. SQLite) and then
# fail their DuckDB-specific expectations.
skip_if_no_dataframe_engine <- function(engine = NULL) {
if (is.null(engine)) {
engine <- getOption("querychat.DataFrameSource.engine", NULL)
}

if (is.null(engine)) {
if (!rlang::is_installed("duckdb") && !rlang::is_installed("RSQLite")) {
skip("Neither duckdb nor RSQLite is installed")
}
return(invisible())
}

engine <- tolower(engine)
if (engine == "duckdb") {
skip_if_not_installed("duckdb")
} else if (engine == "sqlite") {
skip_if_not_installed("RSQLite")
}

invisible()
}

# Create a DataFrameSource with automatic cleanup
Expand All @@ -90,6 +112,7 @@ local_data_frame_source <- function(
engine = "duckdb",
env = parent.frame()
) {
skip_if_no_dataframe_engine(engine)
df_source <- DataFrameSource$new(data, table_name, engine = engine)
withr::defer(df_source$cleanup(), envir = env)
df_source
Expand Down Expand Up @@ -119,6 +142,7 @@ local_recording_data_frame_source <- function(
engine = "duckdb",
env = parent.frame()
) {
skip_if_no_dataframe_engine(engine)
state <- new.env(parent = emptyenv())
state$get_data_calls <- 0L
state$get_data_error <- NULL
Expand Down Expand Up @@ -339,6 +363,11 @@ local_querychat <- function(
...,
env = parent.frame()
) {
# Plain data frames are wrapped in a DataFrameSource using the default
# engine, which requires duckdb or RSQLite to be installed.
if (is.data.frame(data_source)) {
skip_if_no_dataframe_engine()
}
qc <- QueryChat$new(data_source, table_name, ...)
withr::defer(qc$cleanup(), envir = env)
qc
Expand Down
17 changes: 17 additions & 0 deletions pkg-r/tests/testthat/test-QueryChat.R
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ describe("QueryChat integration with DBISource", {
})

describe("QueryChat$cleanup()", {
skip_if_no_dataframe_engine()

it("cleans up data source resources", {
test_df <- new_test_df()
qc <- QueryChat$new(test_df, greeting = "Test")
Expand All @@ -235,6 +237,8 @@ describe("QueryChat$cleanup()", {
})

describe("QueryChat$system_prompt", {
skip_if_no_dataframe_engine()

it("returns the system prompt from the client", {
test_df <- new_test_df()
qc <- QueryChat$new(test_df, greeting = "Test")
Expand Down Expand Up @@ -328,6 +332,8 @@ describe("QueryChat$data_source", {
})

describe("QueryChat$client()", {
skip_if_no_dataframe_engine()

it("uses default tools when tools = NA", {
qc <- QueryChat$new(
new_test_df(),
Expand Down Expand Up @@ -678,6 +684,8 @@ describe("QueryChat$client()", {
})

test_that("QueryChat$generate_greeting() generates a greeting using the LLM client", {
skip_if_no_dataframe_engine()

client <- mock_ellmer_chat_client(
public = list(
chat = function(message, ...) {
Expand All @@ -698,6 +706,7 @@ test_that("QueryChat$generate_greeting() generates a greeting using the LLM clie
})

test_that("QueryChat$server() errors when called outside Shiny context", {
skip_if_no_dataframe_engine()
withr::local_envvar(OPENAI_API_KEY = "boop")

test_df <- new_test_df()
Expand All @@ -710,6 +719,8 @@ test_that("QueryChat$server() errors when called outside Shiny context", {
})

test_that("QueryChat$new() validates history and stores it verbatim", {
skip_if_no_dataframe_engine()

test_df <- new_test_df()

qc_default <- QueryChat$new(test_df, greeting = "Test")
Expand Down Expand Up @@ -835,6 +846,8 @@ test_that("QueryChat$app_obj() infers Shiny bookmarking from history's restore_m
})

describe("QueryChat internal client handoff availability", {
skip_if_no_dataframe_engine()

local_mocked_r6_class(
QueryChat,
public = list(
Expand Down Expand Up @@ -914,6 +927,8 @@ describe("querychat()", {
})

describe("QueryChat$console()", {
skip_if_no_dataframe_engine()

local_mocked_r6_class(
QueryChat,
public = list(
Expand Down Expand Up @@ -1086,6 +1101,8 @@ test_that("querychat_app() only cleans up data frame sources on exit", {
})

describe("QueryChat$server() client override", {
skip_if_no_dataframe_engine()

it("accepts a client parameter", {
withr::local_envvar(OPENAI_API_KEY = "boop")
test_df <- new_test_df()
Expand Down
10 changes: 10 additions & 0 deletions pkg-r/tests/testthat/test-QueryChatSystemPrompt.R
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ describe("QueryChatSystemPrompt$new()", {
})

describe("QueryChatSystemPrompt$render()", {
skip_if_no_dataframe_engine()

it("renders handoff guidance only when available", {
df <- new_test_df()
ds <- DataFrameSource$new(df, "test_table")
Expand Down Expand Up @@ -268,6 +270,8 @@ describe("QueryChatSystemPrompt$render()", {
})

it("includes db_type in rendered output", {
skip_if_not_installed("duckdb")

df <- new_test_df()
ds <- DataFrameSource$new(df, "test_table")
withr::defer(ds$cleanup())
Expand Down Expand Up @@ -370,6 +374,8 @@ describe("QueryChatSystemPrompt$render()", {
})

it("detects DuckDB correctly", {
skip_if_not_installed("duckdb")

df <- new_test_df()
ds <- DataFrameSource$new(df, "test_table")
withr::defer(ds$cleanup())
Expand Down Expand Up @@ -405,6 +411,8 @@ describe("QueryChatSystemPrompt$render()", {
})

describe("QueryChatSystemPrompt with full prompt.md template", {
skip_if_no_dataframe_engine()

it("renders full template with data_description", {
df <- new_test_df(3)
ds <- DataFrameSource$new(df, "test_table")
Expand All @@ -428,6 +436,8 @@ describe("QueryChatSystemPrompt with full prompt.md template", {
})

it("includes DuckDB-specific content for DuckDB sources", {
skip_if_not_installed("duckdb")

df <- new_test_df()
ds <- DataFrameSource$new(df, "test_table")
withr::defer(ds$cleanup())
Expand Down
Loading