tests/testthat/helper-env.R

# ============================================
# Test Environment Configuration Helper
# ============================================
# This helper manages project-level API keys for testing
# It ensures that API tests only run when keys are available
# and prevents accidental commits of sensitive information

#' Skip test if API tests are not enabled
#'
#' @param provider Provider name for the message
#' @export
#' Skip test if offline
#'
#' @export
skip_if_offline <- function() {
  if (!curl::has_internet()) {
    testthat::skip("Offline: curl::has_internet() is FALSE. Skipping internet-dependent test.")
  }
}

#' Create a test evaluation environment with package namespace fallback
#'
#' @param parent Parent environment. Defaults to the installed aisdk namespace
#'   when available, otherwise the global environment.
#' @return A new environment used by source-based tests.
#' @keywords internal
aisdk_test_env <- function(parent = NULL) {
  ns <- NULL
  if ("aisdk" %in% loadedNamespaces()) {
    ns <- asNamespace("aisdk")
    parent <- ns
  } else if (is.null(parent)) {
    parent <- globalenv()
  }

  env <- new.env(parent = parent)
  if (!is.null(ns)) {
    ns_names <- ls(ns, all.names = TRUE)
    ns_values <- mget(ns_names, envir = ns, inherits = FALSE)
    if (".engine_env" %in% names(ns_values)) {
      ns_values[[".engine_env"]] <- new.env(parent = emptyenv())
    }
    ns_values <- lapply(ns_values, function(value) {
      if (is.function(value) && !is.primitive(value)) {
        environment(value) <- env
      }
      value
    })
    list2env(ns_values, envir = env)
  }
  env$`%||%` <- rlang::`%||%`
  env
}

#' Source a package file from the local source tree when available
#'
#' @param env Environment to source into.
#' @param path File path relative to the package `R/` directory.
#' @return Invisibly returns `TRUE` when a local source file was found and
#'   sourced, otherwise `FALSE`.
#' @keywords internal
source_local_aisdk_file <- function(env, path) {
  candidates <- c(
    file.path("R", path),
    file.path("..", "R", path),
    file.path("..", "..", "R", path)
  )

  existing <- candidates[file.exists(candidates)]
  if (!length(existing)) {
    return(invisible(FALSE))
  }

  sys.source(existing[[1]], envir = env)
  invisible(TRUE)
}

#' Skip test if API tests are not enabled
#'
#' @param provider Provider name for the message
#' @export
skip_if_no_api_key <- function(provider = "API") {
  # Standardize provider name
  provider_key <- tolower(provider)

  skip_if_offline()

  if (nzchar(Sys.getenv("CI")) && toupper(Sys.getenv("AISDK_RUN_LIVE_API_TESTS_ON_CI")) != "TRUE") {
    testthat::skip("Live API tests are disabled on CI unless AISDK_RUN_LIVE_API_TESTS_ON_CI=TRUE.")
  }

  if (!aisdk::enable_api_tests()) {
    testthat::skip("API tests disabled: set ENABLE_API_TESTS=TRUE to run live API tests.")
  }

  # has_api_key is now in R/utils_env.R
  if (!aisdk::has_api_key(provider_key)) {
    testthat::skip(paste0(provider, " tests skipped: Set ", toupper(provider), "_API_KEY in project .Renviron or set ENABLE_API_TESTS=TRUE"))
  }
}

#' Get provider with warning suppression
#'
#' Creates a provider instance and suppresses API key warnings
#' @param provider Provider function (create_openai or create_anthropic)
#' @param ... Additional arguments to pass to provider
#' @return Provider instance
#' @export
safe_create_provider <- function(provider, ...) {
  suppressWarnings(provider(...))
}

#' Print API test configuration status
#'
#' @export
print_api_test_config <- function() {
  cat("\n========================================\n")
  cat("AISDK API Test Configuration\n")
  cat("========================================\n")
  # Functions now in R/utils_env.R
  cat("API Tests Enabled:", aisdk::enable_api_tests(), "\n")
  cat("OpenAI Key Available:", aisdk::has_api_key("openai"), "\n")
  cat("OpenAI Model:", aisdk::get_openai_model(), "\n")
  cat("Anthropic Key Available:", aisdk::has_api_key("anthropic"), "\n")
  cat("Anthropic Model:", aisdk::get_anthropic_model(), "\n")
  cat("DeepSeek Key Available:", aisdk::has_api_key("deepseek"), "\n")

  if (!aisdk::enable_api_tests()) {
    cat("\nTo enable API tests:\n")
    cat("Option 1 (Recommended - Persistent):")
    cat("\n  1. Run: usethis::edit_r_environ(scope = 'project')\n")
    cat("  2. Copy contents from .Renviron.example\n")
    cat("  3. Fill in your API keys and model names\n")
    cat("  4. Run: reload_env() to apply changes without restarting\n")
    cat("\nOption 2 (Temporary - Session only):")
    cat("\n  1. Run: options(OPENAI_API_KEY = 'your-key')\n")
    cat("  2. Use: getOption('OPENAI_API_KEY')\n")
    cat("\nOption 3 (Modern - dotenv package):")
    cat("\n  1. Create .env file (not .Renviron)\n")
    cat("  2. Run: dotenv::load_dot_env()\n")
    cat("  3. Use: Sys.getenv('OPENAI_API_KEY')\n")
  }
  cat("========================================\n\n")
}

# Print configuration on package load (only during development)
if (interactive() && getwd() == file.path(system.file(package = "aisdk"), "..")) {
  # Only print if running in development mode
  print_api_test_config()
}

Try the aisdk package in your browser

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

aisdk documentation built on May 29, 2026, 9:07 a.m.