| suppressPackageStartupMessages({ |
| library(asa) |
| }) |
|
|
| auth_fixture <- c( |
| ASA_API_BEARER_TOKEN = "test-bearer-secret", |
| GUI_PASSWORD = "test-gui-secret" |
| ) |
| tor_fixture_names <- c( |
| "ASA_ENABLE_TOR", |
| "ASA_PROXY", |
| "TOR_CONTROL_PORT", |
| "ASA_TOR_CONTROL_COOKIE" |
| ) |
| managed_env_names <- c(names(auth_fixture), tor_fixture_names) |
| previous_managed_env <- Sys.getenv(managed_env_names, unset = NA_character_) |
| restore_managed_env <- function() { |
| for (name in names(previous_managed_env)) { |
| value <- previous_managed_env[[name]] |
| if (is.na(value)) { |
| Sys.unsetenv(name) |
| } else { |
| do.call(Sys.setenv, stats::setNames(list(value), name)) |
| } |
| } |
| } |
| on.exit({ |
| restore_managed_env() |
| if (exists("asa_api_clear_auth_cache", mode = "function")) { |
| asa_api_clear_auth_cache() |
| } |
| }, add = TRUE) |
| do.call(Sys.setenv, as.list(auth_fixture)) |
|
|
| 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")) |
| asa_api_refresh_auth_cache(force = TRUE) |
|
|
| 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_missing_auth_env_vars(), character(0)), |
| "Auth bootstrap should require explicit bearer-token and GUI-password env vars." |
| ) |
| auth_config_boot_failure <- asa_api_boot_failure( |
| "Missing required authentication environment variable(s): `GUI_PASSWORD`, `ASA_API_BEARER_TOKEN`." |
| ) |
| assert_true( |
| identical(auth_config_boot_failure$status_code, 503L) && |
| identical(auth_config_boot_failure$error_code, "auth_config_missing") && |
| identical( |
| sort(auth_config_boot_failure$details$missing_env_vars), |
| sort(c("GUI_PASSWORD", "ASA_API_BEARER_TOKEN")) |
| ), |
| "Boot failures caused by missing auth env vars should expose a structured safe error." |
| ) |
| generic_boot_failure <- asa_api_boot_failure("Package `asa` is not installed in this environment.") |
| assert_true( |
| identical(generic_boot_failure$status_code, 503L) && |
| identical(generic_boot_failure$message, "Service unavailable.") && |
| is.null(generic_boot_failure$error_code), |
| "Non-auth boot failures should remain generic in request responses." |
| ) |
| auth_config_payload <- asa_api_error_fields( |
| auth_config_boot_failure$message, |
| auth_config_boot_failure$error_code, |
| auth_config_boot_failure$details |
| ) |
| assert_true( |
| identical(auth_config_payload$error_code, "auth_config_missing") && |
| identical( |
| sort(auth_config_payload$details$missing_env_vars), |
| sort(c("GUI_PASSWORD", "ASA_API_BEARER_TOKEN")) |
| ), |
| "Structured auth-config failures should render with error_code and missing_env_vars details." |
| ) |
| assert_true( |
| is.character(.asa_api_auth_cache$api_bearer_token_hash) && |
| nzchar(.asa_api_auth_cache$api_bearer_token_hash) && |
| !identical(.asa_api_auth_cache$api_bearer_token_hash, auth_fixture[["ASA_API_BEARER_TOKEN"]]), |
| "Bearer auth should store a derived hash rather than the raw bearer token." |
| ) |
| assert_true( |
| identical( |
| asa_api_extract_bearer_token(mock_request(authorization = sprintf("Bearer %s", auth_fixture[["ASA_API_BEARER_TOKEN"]]))), |
| auth_fixture[["ASA_API_BEARER_TOKEN"]] |
| ), |
| "Bearer extraction should accept the required token." |
| ) |
| assert_true( |
| identical( |
| asa_api_extract_bearer_token(list( |
| PATH_INFO = "/v1/run", |
| HEADERS = list(Authorization = sprintf("Bearer %s", auth_fixture[["ASA_API_BEARER_TOKEN"]])) |
| )), |
| auth_fixture[["ASA_API_BEARER_TOKEN"]] |
| ), |
| "Bearer extraction should match Authorization headers case-insensitively." |
| ) |
| assert_true( |
| identical( |
| asa_api_extract_bearer_token(mock_request(authorization = sprintf("Basic %s", auth_fixture[["ASA_API_BEARER_TOKEN"]]))), |
| "" |
| ), |
| "Non-bearer Authorization schemes should not be accepted." |
| ) |
| assert_true( |
| isTRUE(asa_api_has_required_bearer_token(mock_request( |
| authorization = sprintf("Bearer %s", auth_fixture[["ASA_API_BEARER_TOKEN"]]) |
| ))), |
| "Bearer auth should accept the configured Authorization header." |
| ) |
| 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 = auth_fixture[["ASA_API_BEARER_TOKEN"]]))), |
| "Legacy x-api-key auth should no longer be accepted." |
| ) |
| assert_true( |
| isTRUE(asa_api_has_required_gui_password(auth_fixture[["GUI_PASSWORD"]])), |
| "GUI auth should accept the configured password." |
| ) |
| gui_mismatch <- asa_api_check_gui_password("wrong-password") |
| assert_true( |
| identical(gui_mismatch$ok, FALSE) && |
| identical(gui_mismatch$status_code, 401L) && |
| identical(gui_mismatch$error_code, "credential_mismatch") && |
| identical(gui_mismatch$details$auth_target, "gui_password"), |
| "GUI auth should report credential mismatches with a structured safe error." |
| ) |
| gui_mismatch_payload <- asa_api_error_fields( |
| gui_mismatch$message, |
| gui_mismatch$error_code, |
| gui_mismatch$details |
| ) |
| assert_true( |
| identical( |
| gui_mismatch_payload$error, |
| "Unauthorized: provided credential did not match the configured value." |
| ) && |
| identical(gui_mismatch_payload$error_code, "credential_mismatch") && |
| identical(gui_mismatch_payload$details$auth_target, "gui_password"), |
| "GUI credential mismatches should render the expected response fields." |
| ) |
| api_mismatch <- asa_api_check_bearer_token(mock_request(authorization = "Bearer wrong")) |
| assert_true( |
| identical(api_mismatch$ok, FALSE) && |
| identical(api_mismatch$status_code, 401L) && |
| identical(api_mismatch$error_code, "credential_mismatch") && |
| identical(api_mismatch$details$auth_target, "api_bearer_token"), |
| "API auth should report bearer-token mismatches with a structured safe error." |
| ) |
|
|
| asa_api_clear_auth_cache() |
| Sys.unsetenv("ASA_API_BEARER_TOKEN") |
| expect_error_contains( |
| asa_api_refresh_auth_cache(force = TRUE), |
| "ASA_API_BEARER_TOKEN" |
| ) |
| gui_missing_auth_config <- asa_api_check_gui_password("anything") |
| assert_true( |
| identical(gui_missing_auth_config$ok, FALSE) && |
| identical(gui_missing_auth_config$status_code, 503L) && |
| identical(gui_missing_auth_config$error_code, "auth_config_missing") && |
| identical(gui_missing_auth_config$details$missing_env_vars, "ASA_API_BEARER_TOKEN"), |
| "Missing auth env vars should surface as structured startup misconfiguration errors." |
| ) |
| do.call(Sys.setenv, stats::setNames(list(auth_fixture[["ASA_API_BEARER_TOKEN"]]), "ASA_API_BEARER_TOKEN")) |
|
|
| asa_api_clear_auth_cache() |
| Sys.unsetenv("GUI_PASSWORD") |
| expect_error_contains( |
| asa_api_refresh_auth_cache(force = TRUE), |
| "GUI_PASSWORD" |
| ) |
| do.call(Sys.setenv, stats::setNames(list(auth_fixture[["GUI_PASSWORD"]]), "GUI_PASSWORD")) |
| asa_api_refresh_auth_cache(force = TRUE) |
|
|
| source(file.path(repo_root, "R", "plumber.R")) |
| res_env <- new.env(parent = emptyenv()) |
| rendered_payload <- asa_api_error_payload( |
| res_env, |
| gui_mismatch$status_code, |
| gui_mismatch$message, |
| gui_mismatch$error_code, |
| gui_mismatch$details |
| ) |
| assert_true( |
| identical(res_env$status, 401L) && |
| identical(rendered_payload$error_code, "credential_mismatch") && |
| identical(rendered_payload$details$auth_target, "gui_password"), |
| "Route error rendering should preserve structured auth diagnostics." |
| ) |
|
|
| assert_true( |
| identical(asa_api_runtime_error_code("asa_agent_batch"), "agent_batch_failure") && |
| identical(asa_api_runtime_error_code("provider_direct_single"), "direct_provider_failure") && |
| identical(asa_api_runtime_error_code("asa_agent_single"), "agent_pipeline_failure"), |
| "Runtime failures should map to stable mode-specific error codes." |
| ) |
| assert_true( |
| identical( |
| asa_api_error_status("Unsupported `/v1/batch` `run` keys: `expected_schema`."), |
| 400L |
| ), |
| "Unsupported adapter inputs should continue to classify as client errors." |
| ) |
|
|
| diagnostic_prompt <- paste(rep("diagnostic prompt segment", 16), collapse = " ") |
| expected_excerpt <- asa_api_redact_text_excerpt(diagnostic_prompt) |
| diagnostic_log_lines <- character(0) |
| diagnostic_log_con <- textConnection("diagnostic_log_lines", "w", local = TRUE) |
| runtime_error <- NULL |
| tryCatch( |
| asa_api_invoke_with_runtime_diagnostics( |
| fn = function() { |
| stop("subscript out of bounds", call. = FALSE) |
| }, |
| mode = "asa_agent_single", |
| route = "/v1/run", |
| payload = list( |
| include_raw_output = TRUE, |
| include_trace_json = FALSE |
| ), |
| prompt = diagnostic_prompt, |
| config = list( |
| backend = "gemini", |
| model = "gemini-2.5-pro", |
| conda_env = "asa_env", |
| use_browser = FALSE |
| ), |
| forwarded_arg_names = c("output_format", "performance_profile"), |
| request_shape = list( |
| output_format = "json", |
| performance_profile = "quality" |
| ), |
| stage_ref = "invoke", |
| log_con = diagnostic_log_con |
| ), |
| error = function(e) { |
| runtime_error <<- e |
| NULL |
| } |
| ) |
| close(diagnostic_log_con) |
| assert_true( |
| inherits(runtime_error, "asa_api_runtime_error") && |
| identical(runtime_error$error_code, "agent_pipeline_failure"), |
| "Runtime diagnostics should rethrow a structured asa_api_runtime_error." |
| ) |
| assert_true( |
| identical(runtime_error$details$route, "/v1/run") && |
| identical(runtime_error$details$stage, "invoke") && |
| is.null(runtime_error$details$request) && |
| identical(runtime_error$details_full$request$output_format, "json") && |
| identical(runtime_error$details_full$request$include_raw_output, TRUE), |
| "Runtime errors should expose only the safe summary in `details` while retaining full internals separately." |
| ) |
| runtime_failure <- asa_api_error_failure(runtime_error) |
| assert_true( |
| identical(runtime_failure$status_code, 500L) && |
| identical(runtime_failure$error_code, "agent_pipeline_failure") && |
| identical(runtime_failure$details$route, "/v1/run") && |
| identical(runtime_failure$details$stage, "invoke") && |
| is.null(runtime_failure$details$request) && |
| nzchar(runtime_failure$details$diagnostic_id), |
| "Structured runtime failures should preserve diagnostic metadata for API responses." |
| ) |
| diagnostic_log_text <- paste(diagnostic_log_lines, collapse = "\n") |
| assert_true( |
| grepl("diagnostic_id=", diagnostic_log_text, fixed = TRUE) && |
| grepl("subscript out of bounds", diagnostic_log_text, fixed = TRUE), |
| "Runtime diagnostics should log the failure message with a correlation id." |
| ) |
| assert_true( |
| grepl(expected_excerpt, diagnostic_log_text, fixed = TRUE) && |
| !grepl(diagnostic_prompt, diagnostic_log_text, fixed = TRUE), |
| "Runtime diagnostics should log only a redacted prompt excerpt, not the full prompt." |
| ) |
| runtime_res_env <- new.env(parent = emptyenv()) |
| runtime_payload <- asa_api_failure_payload(runtime_res_env, runtime_failure) |
| assert_true( |
| identical(runtime_res_env$status, 500L) && |
| identical(runtime_payload$error_code, "agent_pipeline_failure") && |
| identical(runtime_payload$details$diagnostic_id, runtime_failure$details$diagnostic_id), |
| "Route error rendering should preserve structured runtime diagnostics." |
| ) |
|
|
| { |
| original_build_config <- asa_api_build_config |
| build_config_log_lines <- character(0) |
| build_config_log_con <- textConnection("build_config_log_lines", "w", local = TRUE) |
| build_config_error <- NULL |
|
|
| asa_api_build_config <- function(payload) { |
| stop("subscript out of bounds", call. = FALSE) |
| } |
|
|
| tryCatch( |
| asa_api_run_single_via_asa( |
| list(prompt = "Hello", run = list(output_format = "json")), |
| request_context = list(route = "/v1/run", log_con = build_config_log_con) |
| ), |
| error = function(e) { |
| build_config_error <<- e |
| NULL |
| } |
| ) |
| close(build_config_log_con) |
| asa_api_build_config <- original_build_config |
|
|
| build_config_failure <- asa_api_error_failure(build_config_error) |
| build_config_log_text <- paste(build_config_log_lines, collapse = "\n") |
| assert_true( |
| inherits(build_config_error, "asa_api_runtime_error") && |
| identical(build_config_failure$error_code, "agent_pipeline_failure") && |
| identical(build_config_failure$details$stage, "build_config") && |
| grepl("stage=build_config", build_config_log_text, fixed = TRUE), |
| "Pre-invocation internal failures should still emit structured runtime diagnostics." |
| ) |
| } |
|
|
| { |
| original_run_single <- asa_api_run_single |
| original_project_gui_result <- asa_api_project_gui_result |
| gui_log_lines <- character(0) |
| gui_log_con <- textConnection("gui_log_lines", "w", local = TRUE) |
| gui_error <- NULL |
|
|
| asa_api_run_single <- function(payload, allow_direct_provider = FALSE, request_context = NULL) { |
| list( |
| status = "success", |
| message = "ok", |
| execution = list(mode = "asa_agent") |
| ) |
| } |
| asa_api_project_gui_result <- function(result, response_mode = NULL) { |
| stop("subscript out of bounds", call. = FALSE) |
| } |
|
|
| tryCatch( |
| asa_api_run_gui_query( |
| list(prompt = "Hello", run = list(output_format = "json")), |
| request_context = list(route = "/gui/query", log_con = gui_log_con) |
| ), |
| error = function(e) { |
| gui_error <<- e |
| NULL |
| } |
| ) |
| close(gui_log_con) |
| asa_api_run_single <- original_run_single |
| asa_api_project_gui_result <- original_project_gui_result |
|
|
| gui_failure <- asa_api_error_failure(gui_error) |
| gui_res_env <- new.env(parent = emptyenv()) |
| gui_payload <- asa_api_failure_payload(gui_res_env, gui_failure) |
| gui_log_text <- paste(gui_log_lines, collapse = "\n") |
| assert_true( |
| inherits(gui_error, "asa_api_runtime_error") && |
| identical(gui_failure$details$route, "/gui/query") && |
| identical(gui_failure$details$stage, "project_gui_result") && |
| identical(gui_payload$details$stage, "project_gui_result") && |
| grepl("stage=project_gui_result", gui_log_text, fixed = TRUE), |
| "GUI projection failures should return the safe diagnostic summary and emit matching logs." |
| ) |
| } |
|
|
| 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." |
| ) |
| } |
|
|
| Sys.unsetenv(tor_fixture_names) |
| tor_health_disabled <- asa_api_tor_health() |
| assert_true( |
| identical(tor_health_disabled$tor_enabled, FALSE) && |
| identical(tor_health_disabled$tor_ready, FALSE), |
| "Tor health should report disabled when the proxy env vars are absent." |
| ) |
| assert_true( |
| is.null(tor_health_disabled$tor_proxy) && |
| is.null(tor_health_disabled$tor_control_port), |
| "Tor health should omit proxy details when Tor is disabled." |
| ) |
| health_without_tor <- asa_api_health_payload() |
| assert_true( |
| identical(health_without_tor$status, "ok"), |
| "Service health should remain ok when Tor is intentionally disabled." |
| ) |
| do.call(Sys.setenv, as.list(auth_fixture)) |
| asa_api_refresh_auth_cache(force = TRUE) |
|
|
| { |
| 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, request_context = NULL) { |
| dispatch_calls <<- c(dispatch_calls, "asa") |
| list(status = "success", execution = list(mode = "asa_agent")) |
| } |
| asa_api_run_single_via_direct <- function(payload, request_context = NULL) { |
| 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_project_gui_result(list( |
| status = "success", |
| message = "ok", |
| parsed = list(capital = "Paris"), |
| elapsed_time_min = 0.25, |
| search_tier = "unknown", |
| parsing_status = list(valid = TRUE), |
| execution = list( |
| thread_id = list("asa_123"), |
| backend_status = list("success"), |
| status_code = list(200L), |
| tool_calls_used = list(2L), |
| search_calls_used = list(1L), |
| action_step_count = list(6L), |
| tool_quality_events = list(list( |
| message_index_in_round = list(1L), |
| tool_name = list("update_plan"), |
| is_empty = list(FALSE), |
| is_off_target = list(FALSE), |
| is_error = list(FALSE), |
| error_type = NULL, |
| elapsed_ms_estimate = list(0L), |
| quality_version = list("v1") |
| )), |
| config_snapshot = list(backend = "gemini", model = "gemini-3-flash-preview") |
| ) |
| )) |
| assert_true( |
| identical( |
| names(gui_result$execution), |
| c( |
| "mode", |
| "thread_id", |
| "backend_status", |
| "status_code", |
| "tool_calls_used", |
| "search_calls_used", |
| "action_step_count" |
| ) |
| ) && |
| identical(gui_result$execution$mode, "asa_agent") && |
| identical(gui_result$execution$thread_id, "asa_123") && |
| identical(gui_result$execution$tool_calls_used, 2L), |
| "GUI success responses should expose only a compact execution summary." |
| ) |
| assert_true( |
| !("tool_quality_events" %in% names(gui_result$execution)) && |
| !("config_snapshot" %in% names(gui_result$execution)), |
| "GUI projection should avoid recursing through nested execution diagnostics." |
| ) |
|
|
| gui_direct_result <- asa_api_project_gui_result( |
| list( |
| status = "success", |
| execution = list( |
| mode = list("provider_direct"), |
| status_code = list(200L) |
| ) |
| ), |
| response_mode = "provider_direct_single" |
| ) |
| assert_true( |
| identical(gui_direct_result$execution$mode, "provider_direct") && |
| identical(gui_direct_result$execution$status_code, 200L), |
| "GUI projection should preserve direct-provider mode in the compact execution summary." |
| ) |
|
|
| cat("asa-api contract smoke checks passed\n") |
|
|