suppressPackageStartupMessages({ library(asa) }) args <- commandArgs(trailingOnly = FALSE) file_arg <- "--file=" script_arg <- args[grepl(paste0("^", file_arg), args)] script_path <- if (length(script_arg)) { normalizePath(sub(file_arg, "", script_arg[[1]]), mustWork = TRUE) } else { normalizePath("Tests/api_contract_smoke.R", mustWork = TRUE) } repo_root <- normalizePath(file.path(dirname(script_path), ".."), mustWork = TRUE) source(file.path(repo_root, "R", "asa_api_helpers.R")) assert_true <- function(condition, message) { if (!isTRUE(condition)) { stop(message, call. = FALSE) } } expect_error_contains <- function(expr, pattern) { error_message <- NULL tryCatch( force(expr), error = function(e) { error_message <<- conditionMessage(e) NULL } ) if (is.null(error_message)) { stop(sprintf("Expected error containing %s, but no error was raised.", sQuote(pattern)), call. = FALSE) } if (!grepl(pattern, error_message, fixed = TRUE)) { stop( sprintf( "Expected error containing %s, got: %s", sQuote(pattern), error_message ), call. = FALSE ) } invisible(error_message) } prompts <- asa_api_require_prompts(list(prompts = list(" Hello ", "World "))) assert_true(identical(prompts, c("Hello", "World")), "Valid prompt arrays should be trimmed and preserved.") expect_error_contains( asa_api_require_prompts(list(prompts = list(list(prompt = "Q1", id = "row1")))), "Structured prompt objects are not supported by `/v1/batch`." ) expect_error_contains( asa_api_require_prompts(list(prompts = list("Hello", TRUE))), "`prompts` must be a JSON array of non-empty strings." ) expect_error_contains( asa_api_require_prompts(list(prompts = list("Hello", " "))), "Each entry in `prompts` must be a non-empty string." ) asa_api_validate_batch_supported_fields(list( prompts = list("Hello", "World"), run = list(output_format = "text", performance_profile = "balanced") )) expect_error_contains( asa_api_validate_batch_supported_fields(list( prompts = list("Hello"), run = list(expected_schema = list(type = "object")) )), "Unsupported `/v1/batch` `run` keys:" ) expect_error_contains( asa_api_validate_batch_supported_fields(list( prompts = list("Hello"), use_plan_mode = TRUE )), "Unsupported `/v1/batch` top-level keys:" ) run_args <- asa_api_build_run_args(list(run = list( expected_schema = list(type = "object"), use_plan_mode = TRUE, performance_profile = "balanced" ))) assert_true( all(c("output_format", "expected_schema", "use_plan_mode", "performance_profile") %in% names(run_args)), "`/v1/run` should continue forwarding newer upstream run_task options." ) batch_args <- asa_api_build_batch_args(list( run = list(output_format = "json", performance_profile = "quality"), parallel = FALSE )) assert_true( all(c("output_format", "performance_profile", "parallel", "progress") %in% names(batch_args)), "Batch-compatible shared options should still flow into run_task_batch." ) assert_true( identical(asa_api_has_run_direct_task(), !is.null(asa_api_get_run_direct_task(optional = TRUE))), "Direct-provider capability checks should agree on run_direct_task availability." ) mock_request <- function(path = "/v1/run", authorization = NULL, x_api_key = NULL) { headers <- list() if (!is.null(authorization)) { headers$authorization <- authorization } if (!is.null(x_api_key)) { headers[["x-api-key"]] <- x_api_key } list( PATH_INFO = path, HEADERS = headers ) } assert_true( isTRUE(asa_api_path_requires_bearer_auth("/v1/run")) && isTRUE(asa_api_path_requires_bearer_auth("/v1/batch")), "`/v1/*` routes should require bearer auth." ) assert_true( !isTRUE(asa_api_path_requires_bearer_auth("/healthz")) && !isTRUE(asa_api_path_requires_bearer_auth("/gui/query")), "Health and GUI routes should remain outside bearer auth scope." ) assert_true( identical(asa_api_required_bearer_token(), "999"), "Bearer auth should require the fixed token 999." ) assert_true( identical( asa_api_extract_bearer_token(mock_request(authorization = "Bearer 999")), "999" ), "Bearer extraction should accept the required token." ) assert_true( identical( asa_api_extract_bearer_token(list( PATH_INFO = "/v1/run", HEADERS = list(Authorization = "Bearer 999") )), "999" ), "Bearer extraction should match Authorization headers case-insensitively." ) assert_true( identical( asa_api_extract_bearer_token(mock_request(authorization = "Basic 999")), "" ), "Non-bearer Authorization schemes should not be accepted." ) assert_true( isTRUE(asa_api_has_required_bearer_token(mock_request(authorization = "Bearer 999"))), "Bearer auth should accept Authorization: Bearer 999." ) assert_true( !isTRUE(asa_api_has_required_bearer_token(mock_request(authorization = "Bearer wrong"))), "Bearer auth should reject the wrong token." ) assert_true( !isTRUE(asa_api_has_required_bearer_token(mock_request(x_api_key = "999"))), "Legacy x-api-key auth should no longer be accepted." ) health_payload <- asa_api_health_payload() assert_true( identical(isTRUE(health_payload$direct_provider_available), isTRUE(asa_api_has_run_direct_task())), "Health payload should report direct-provider availability from the same capability check." ) if (isTRUE(health_payload$direct_provider_available)) { assert_true( is.null(health_payload$direct_provider_note), "Health payload should omit the direct-provider note when the capability is available." ) } else { assert_true( is.character(health_payload$direct_provider_note) && nzchar(trimws(health_payload$direct_provider_note)), "Health payload should explain why direct-provider mode is unavailable." ) } { original_asa <- asa_api_run_single_via_asa original_direct <- asa_api_run_single_via_direct dispatch_calls <- character(0) asa_api_run_single_via_asa <- function(payload) { dispatch_calls <<- c(dispatch_calls, "asa") list(status = "success", execution = list(mode = "asa_agent")) } asa_api_run_single_via_direct <- function(payload) { dispatch_calls <<- c(dispatch_calls, "direct") list(status = "success", execution = list(mode = "provider_direct")) } asa_api_run_single( list(prompt = "Hello", use_direct_provider = TRUE), allow_direct_provider = FALSE ) assert_true( identical(dispatch_calls, "asa"), "Single-run API should ignore the private direct-provider flag." ) dispatch_calls <- character(0) gui_direct <- asa_api_run_single( list(prompt = "Hello", use_direct_provider = TRUE), allow_direct_provider = TRUE ) assert_true( identical(dispatch_calls, "direct"), "GUI single-run path should dispatch to direct-provider mode when enabled." ) assert_true( identical(gui_direct$execution$mode, "provider_direct"), "Direct-provider dispatch should preserve provider_direct mode metadata." ) asa_api_run_single_via_asa <- original_asa asa_api_run_single_via_direct <- original_direct } gui_result <- asa_api_sanitize_gui_result(list( status = "success", execution = list( mode = "provider_direct", config_snapshot = list(backend = "gemini", model = "gemini-3-flash-preview"), payload_integrity = list( backend = "gemini", model = "gemini-3-flash-preview", released_from = "message_text" ), trace_metadata = list( structured_output_backend = "gemini", model_name = "gemini-3-flash-preview" ) ) )) assert_true( is.null(gui_result$execution$config_snapshot$backend) && is.null(gui_result$execution$config_snapshot$model), "GUI responses should not expose backend/model selection in execution metadata." ) assert_true( is.null(gui_result$execution$payload_integrity$backend) && is.null(gui_result$execution$payload_integrity$model), "GUI payload integrity metadata should hide backend/model fields." ) cat("asa-api contract smoke checks passed\n")