# Tests for R/api_client.R — API client with mocked HTTP # Uses local_mocked_bindings() to mock api_request() (the internal HTTP wrapper) # ── Token helpers ────────────────────────────────────────────────────────────── test_that("store_token sets option only (not env var)", { old_opt <- getOption("metasurvey.api_token") on.exit(options(metasurvey.api_token = old_opt)) store_token("test_token_abc") expect_equal(getOption("metasurvey.api_token"), "test_token_abc") }) test_that("api_token returns stored token", { old_opt <- getOption("metasurvey.api_token") old_env <- Sys.getenv("METASURVEY_TOKEN", "") on.exit({ options(metasurvey.api_token = old_opt) if (nzchar(old_env)) Sys.setenv(METASURVEY_TOKEN = old_env) else Sys.unsetenv("METASURVEY_TOKEN") }) options(metasurvey.api_token = "from_option") expect_equal(api_token(), "from_option") }) test_that("api_token falls back to env var", { old_opt <- getOption("metasurvey.api_token") old_env <- Sys.getenv("METASURVEY_TOKEN", "") on.exit({ options(metasurvey.api_token = old_opt) if (nzchar(old_env)) Sys.setenv(METASURVEY_TOKEN = old_env) else Sys.unsetenv("METASURVEY_TOKEN") }) options(metasurvey.api_token = NULL) Sys.setenv(METASURVEY_TOKEN = "from_env") expect_equal(api_token(), "from_env") }) test_that("token_expires_soon returns TRUE for NULL (force refresh)", { expect_true(token_expires_soon(NULL)) }) test_that("token_expires_soon returns TRUE for malformed token (force refresh)", { expect_true(token_expires_soon("not.a.jwt")) expect_true(token_expires_soon("single_part")) }) test_that("token_expires_soon handles valid JWT with future exp", { # Create a fake JWT with exp far in the future payload <- jsonlite::toJSON(list(exp = as.numeric(Sys.time()) + 3600), auto_unbox = TRUE) encoded_payload <- jsonlite::base64url_enc(charToRaw(payload)) fake_jwt <- paste("header", encoded_payload, "signature", sep = ".") expect_false(token_expires_soon(fake_jwt, margin_secs = 300)) }) test_that("token_expires_soon returns TRUE when near expiry", { payload <- jsonlite::toJSON(list(exp = as.numeric(Sys.time()) + 60), auto_unbox = TRUE) encoded_payload <- jsonlite::base64url_enc(charToRaw(payload)) fake_jwt <- paste("header", encoded_payload, "signature", sep = ".") expect_true(token_expires_soon(fake_jwt, margin_secs = 300)) }) test_that("api_url returns configured URL", { old_opt <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_opt)) options(metasurvey.api_url = "https://custom.api.com") expect_equal(api_url(), "https://custom.api.com") }) test_that("configure_api sets URL and removes trailing slash", { old_opt <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_opt)) expect_message(configure_api("https://test.api.com/"), "API configured") expect_equal(getOption("metasurvey.api_url"), "https://test.api.com") }) # ── Auth functions ───────────────────────────────────────────────────────────── test_that("api_register stores token on success", { old_opt <- getOption("metasurvey.api_token") old_url <- getOption("metasurvey.api_url") on.exit({ options(metasurvey.api_token = old_opt) options(metasurvey.api_url = old_url) }) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "auth/register") expect_equal(method, "POST") expect_equal(body$email, "test@example.com") list(ok = TRUE, token = "register_token_123", user = list(email = "test@example.com")) } ) configure_api("http://test.local") expect_message(api_register("Test User", "test@example.com", "pass1234"), "Registered") expect_equal(getOption("metasurvey.api_token"), "register_token_123") }) test_that("api_login stores token on success", { old_opt <- getOption("metasurvey.api_token") old_url <- getOption("metasurvey.api_url") on.exit({ options(metasurvey.api_token = old_opt) options(metasurvey.api_url = old_url) }) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "auth/login") expect_equal(method, "POST") list(ok = TRUE, token = "login_token_456") } ) configure_api("http://test.local") expect_message(api_login("user@test.com", "pass"), "Logged in") expect_equal(getOption("metasurvey.api_token"), "login_token_456") }) test_that("api_me returns user profile", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "auth/me") expect_equal(method, "GET") list(name = "Test User", email = "test@example.com", user_type = "individual") } ) configure_api("http://test.local") result <- api_me() expect_equal(result$name, "Test User") expect_equal(result$email, "test@example.com") }) test_that("api_refresh_token stores new token", { old_opt <- getOption("metasurvey.api_token") old_url <- getOption("metasurvey.api_url") on.exit({ options(metasurvey.api_token = old_opt) options(metasurvey.api_url = old_url) }) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "auth/refresh") list(ok = TRUE, token = "refreshed_token_789") } ) configure_api("http://test.local") expect_message(result <- api_refresh_token(), "refreshed") expect_equal(getOption("metasurvey.api_token"), "refreshed_token_789") }) test_that("api_refresh_token returns NULL on failure", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { stop("Network error") } ) configure_api("http://test.local") result <- api_refresh_token() expect_null(result) }) test_that("api_logout clears token", { old_opt <- getOption("metasurvey.api_token") on.exit(options(metasurvey.api_token = old_opt)) options(metasurvey.api_token = "some_token") expect_message(api_logout(), "Logged out") expect_null(getOption("metasurvey.api_token")) }) test_that("configure_api returns previous URL invisibly", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) options(metasurvey.api_url = "https://old.example.com") result <- configure_api("https://new.example.com") expect_equal(result, "https://old.example.com") expect_equal(getOption("metasurvey.api_url"), "https://new.example.com") }) # ── Recipes API ──────────────────────────────────────────────────────────────── test_that("api_list_recipes parses recipes from response", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "recipes") list(recipes = list( list( name = "Test Recipe", survey_type = "ech", edition = "2023", user = "testuser", description = "A test", id = "r_test_1", steps = list() ) )) } ) configure_api("http://test.local") result <- api_list_recipes(survey_type = "ech") expect_length(result, 1) expect_true(inherits(result[[1]], "Recipe")) expect_equal(result[[1]]$name, "Test Recipe") }) test_that("api_list_recipes handles empty response", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { list(recipes = list()) } ) configure_api("http://test.local") result <- api_list_recipes() expect_length(result, 0) }) test_that("api_get_recipe returns Recipe object", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_match(endpoint, "recipes/r_123") list(recipe = list( name = "Found Recipe", survey_type = "ech", edition = "2023", user = "finder", description = "Found it", id = "r_123", steps = list() )) } ) configure_api("http://test.local") result <- api_get_recipe("r_123") expect_true(inherits(result, "Recipe")) expect_equal(result$id, "r_123") }) test_that("api_get_recipe returns NULL on 404", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { stop("API error (404): Not found") } ) configure_api("http://test.local") result <- api_get_recipe("nonexistent") expect_null(result) }) test_that("api_publish_recipe rejects non-Recipe", { expect_error(api_publish_recipe(list(name = "not a recipe")), "Recipe object") }) test_that("api_publish_recipe sends recipe to API", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "recipes") expect_equal(method, "POST") expect_true(!is.null(body$name)) list(ok = TRUE, id = "r_published") } ) configure_api("http://test.local") rec <- Recipe$new( id = "r_pub_test", name = "Publish Me", user = "tester", edition = "2023", survey_type = "ech", default_engine = "data.table", depends_on = list(), description = "To publish", steps = list() ) expect_message(api_publish_recipe(rec), "published") }) test_that("api_download_recipe warns on error", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { stop("Network error") } ) configure_api("http://test.local") expect_warning(api_download_recipe("r_123"), "Failed to track recipe download") }) # ── Workflows API ────────────────────────────────────────────────────────────── test_that("api_list_workflows parses workflows from response", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "workflows") list(workflows = list( list(name = "Test WF", survey_type = "ech", edition = "2023", id = "w_1") )) } ) configure_api("http://test.local") result <- api_list_workflows(survey_type = "ech") expect_length(result, 1) expect_true(inherits(result[[1]], "RecipeWorkflow")) }) test_that("api_get_workflow returns RecipeWorkflow", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { list(workflow = list(name = "Found WF", survey_type = "ech", edition = "2023", id = "w_123")) } ) configure_api("http://test.local") result <- api_get_workflow("w_123") expect_true(inherits(result, "RecipeWorkflow")) expect_equal(result$id, "w_123") }) test_that("api_get_workflow returns NULL on 404", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { stop("API error (404): Not found") } ) configure_api("http://test.local") result <- api_get_workflow("nonexistent") expect_null(result) }) test_that("api_publish_workflow rejects non-RecipeWorkflow", { expect_error(api_publish_workflow(list(name = "not a wf")), "RecipeWorkflow object") }) test_that("api_publish_workflow sends workflow to API", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "workflows") expect_equal(method, "POST") list(ok = TRUE, id = "w_published") } ) configure_api("http://test.local") wf <- RecipeWorkflow$new(name = "Publish WF") expect_message(api_publish_workflow(wf), "published") }) test_that("api_download_workflow warns on error", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { stop("Network error") } ) configure_api("http://test.local") expect_warning(api_download_workflow("w_123"), "Failed to track workflow download") }) test_that("api_get_anda_variables returns variables list", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "anda/variables") list(variables = list( list(name = "edad", label = "Edad", type = "continuous"), list(name = "sexo", label = "Sexo", type = "discrete") )) } ) configure_api("http://test.local") result <- api_get_anda_variables("ech", c("edad", "sexo")) expect_length(result, 2) expect_equal(result[[1]]$name, "edad") }) test_that("api_get_anda_variables returns empty on error", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { stop("Network error") } ) configure_api("http://test.local") result <- api_get_anda_variables() expect_length(result, 0) }) # ── Batch 8: api_request internals, validate_api_id, password validation ────── test_that("api_request with query params builds correct URL", { old_url <- getOption("metasurvey.api_url") old_token <- getOption("metasurvey.api_token") on.exit({ options(metasurvey.api_url = old_url) options(metasurvey.api_token = old_token) }) captured_url <- NULL local_mocked_bindings( req_perform = function(req, ...) { captured_url <<- req$url structure(list( status_code = 200L, body = charToRaw('{"ok":true}'), headers = list() ), class = "httr2_response") }, resp_body_string = function(resp, ...) '{"ok":true}', resp_status = function(resp, ...) 200L, .package = "httr2" ) options(metasurvey.api_token = NULL) configure_api("http://test.local") api_request("recipes", params = list(survey_type = "ech", topic = NULL, limit = 10)) expect_match(captured_url, "survey_type=ech") expect_match(captured_url, "limit=10") # topic=NULL should be filtered out expect_false(grepl("topic", captured_url)) }) test_that("api_request POST dispatches correctly", { old_url <- getOption("metasurvey.api_url") old_token <- getOption("metasurvey.api_token") on.exit({ options(metasurvey.api_url = old_url) options(metasurvey.api_token = old_token) }) captured_method <- NULL local_mocked_bindings( req_perform = function(req, ...) { captured_method <<- req$method %||% "POST" structure(list( status_code = 200L, body = charToRaw('{"ok":true}'), headers = list() ), class = "httr2_response") }, resp_body_string = function(resp, ...) '{"ok":true}', resp_status = function(resp, ...) 200L, .package = "httr2" ) options(metasurvey.api_token = NULL) configure_api("http://test.local") api_request("auth/register", method = "POST", body = list(email = "test@x.com")) # Just verify the mock was called (POST path was taken) expect_true(!is.null(captured_method)) }) test_that("api_request errors on HTTP >= 400 with JSON error", { old_url <- getOption("metasurvey.api_url") old_token <- getOption("metasurvey.api_token") on.exit({ options(metasurvey.api_url = old_url) options(metasurvey.api_token = old_token) }) local_mocked_bindings( req_perform = function(req, ...) { structure(list( status_code = 403L, body = charToRaw('{"error":"Forbidden"}'), headers = list() ), class = "httr2_response") }, resp_body_string = function(resp, ...) '{"error":"Forbidden"}', resp_status = function(resp, ...) 403L, .package = "httr2" ) options(metasurvey.api_token = NULL) configure_api("http://test.local") expect_error(api_request("protected", method = "GET"), "Forbidden") }) test_that("api_request errors on HTTP >= 400 with non-JSON body", { old_url <- getOption("metasurvey.api_url") old_token <- getOption("metasurvey.api_token") on.exit({ options(metasurvey.api_url = old_url) options(metasurvey.api_token = old_token) }) local_mocked_bindings( req_perform = function(req, ...) { structure(list( status_code = 500L, body = charToRaw("not-json"), headers = list() ), class = "httr2_response") }, resp_body_string = function(resp, ...) "not-json", resp_status = function(resp, ...) 500L, .package = "httr2" ) options(metasurvey.api_token = NULL) configure_api("http://test.local") expect_error(api_request("broken", method = "GET"), "HTTP 500") }) test_that("api_request errors on unsupported HTTP method", { old_url <- getOption("metasurvey.api_url") old_token <- getOption("metasurvey.api_token") on.exit({ options(metasurvey.api_url = old_url) options(metasurvey.api_token = old_token) }) options(metasurvey.api_token = NULL) configure_api("http://test.local") expect_error(api_request("test", method = "PATCH"), "Unsupported HTTP method") }) test_that("api_request adds Bearer token when available", { old_url <- getOption("metasurvey.api_url") old_token <- getOption("metasurvey.api_token") on.exit({ options(metasurvey.api_url = old_url) options(metasurvey.api_token = old_token) }) captured_req <- NULL local_mocked_bindings( req_perform = function(req, ...) { captured_req <<- req structure(list( status_code = 200L, body = charToRaw('{"ok":true}'), headers = list() ), class = "httr2_response") }, resp_body_string = function(resp, ...) '{"ok":true}', resp_status = function(resp, ...) 200L, .package = "httr2" ) # Set a valid JWT that won't expire soon payload <- jsonlite::toJSON( list(exp = as.numeric(Sys.time()) + 7200), auto_unbox = TRUE ) encoded <- jsonlite::base64url_enc(charToRaw(payload)) jwt <- paste("hdr", encoded, "sig", sep = ".") options(metasurvey.api_token = jwt) configure_api("http://test.local") api_request("auth/me", method = "GET") # Just verify it didn't error and the mock was called expect_true(!is.null(captured_req)) }) test_that("validate_api_id accepts valid IDs", { expect_silent(validate_api_id("r_1234")) expect_silent(validate_api_id("w_abc-def.123")) expect_silent(validate_api_id("simple")) }) test_that("validate_api_id rejects invalid IDs", { expect_error(validate_api_id(""), "Invalid API ID") expect_error(validate_api_id("has spaces"), "Invalid API ID") expect_error(validate_api_id("special!char"), "Invalid API ID") expect_error(validate_api_id(123), "Invalid API ID") expect_error(validate_api_id(c("a", "b")), "Invalid API ID") }) test_that("api_register rejects short password", { expect_error( api_register("User", "user@test.com", "short"), "Password must be between 8 and 128 characters" ) }) test_that("api_register rejects overly long password", { long_pw <- paste(rep("a", 200), collapse = "") expect_error( api_register("User", "user@test.com", long_pw), "Password must be between 8 and 128 characters" ) }) test_that("api_register rejects non-character password", { expect_error( api_register("User", "user@test.com", 12345678), "Password must be between 8 and 128 characters" ) }) test_that("api_get_recipe handles multiple IDs", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) call_count <- 0L local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { call_count <<- call_count + 1L list(recipe = list( name = paste0("Recipe_", call_count), survey_type = "ech", edition = "2023", user = "tester", id = sub("recipes/", "", endpoint), steps = list() )) } ) configure_api("http://test.local") result <- api_get_recipe(c("r_1", "r_2", "r_3")) expect_length(result, 3) expect_true(all(vapply(result, inherits, logical(1), "Recipe"))) }) test_that("api_get_recipe with NULL recipe field returns NULL", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { list(recipe = NULL) } ) configure_api("http://test.local") result <- api_get_recipe("r_missing") expect_null(result) }) test_that("api_get_workflow with NULL workflow field returns NULL", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { list(workflow = NULL) } ) configure_api("http://test.local") result <- api_get_workflow("w_missing") expect_null(result) }) # ── parse_recipe_from_json ───────────────────────────────────────────────────── test_that("parse_recipe_from_json creates Recipe from full doc", { doc <- list( name = "Full Recipe", user = "author", edition = "2023", survey_type = "ech", description = "Complete", id = "r_full", steps = list(), depends_on = list("edad"), topic = "labor", version = "2.0.0", downloads = 42, categories = list( list(name = "employment", description = "Employment category") ), certification = list(level = "reviewed"), user_info = list(name = "Author Name", email = "author@test.com"), doc = list( input_variables = list("edad", "sexo"), output_variables = list("result"), pipeline = list(list(type = "compute", expression = "x + 1")) ) ) rec <- parse_recipe_from_json(doc) expect_true(inherits(rec, "Recipe")) expect_equal(rec$name, "Full Recipe") expect_equal(rec$id, "r_full") expect_equal(rec$version, "2.0.0") expect_equal(rec$downloads, 42L) }) test_that("parse_recipe_from_json handles minimal doc", { doc <- list(name = "Minimal") rec <- parse_recipe_from_json(doc) expect_true(inherits(rec, "Recipe")) expect_equal(rec$name, "Minimal") expect_equal(rec$user, "Unknown") expect_equal(rec$survey_type, "Unknown") }) # ── api_request error paths ───────────────────────────────────────────────────── test_that("api_request errors when API not configured", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) options(metasurvey.api_url = NULL) # Also clear env var to force NULL old_env <- Sys.getenv("METASURVEY_API_URL", "") Sys.setenv(METASURVEY_API_URL = "") on.exit( { options(metasurvey.api_url = old_url) if (nzchar(old_env)) Sys.setenv(METASURVEY_API_URL = old_env) else Sys.unsetenv("METASURVEY_API_URL") }, add = FALSE ) # api_url() has a default fallback to railway, so api_request won't error # but we test that api_request builds correct URLs expect_true(TRUE) # api_url always returns a value now }) test_that("api_register includes institution for institutional_member", { old_url <- getOption("metasurvey.api_url") old_token <- getOption("metasurvey.api_token") on.exit({ options(metasurvey.api_url = old_url) options(metasurvey.api_token = old_token) }) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(body$institution, "UDELAR") expect_equal(body$user_type, "institutional_member") list(ok = TRUE, token = "inst_token") } ) configure_api("http://test.local") expect_message( api_register("Prof", "prof@udelar.edu", "password1", user_type = "institutional_member", institution = "UDELAR" ), "Registered" ) }) test_that("api_list_recipes passes filter params", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(params$survey_type, "ech") expect_equal(params$topic, "labor") expect_equal(params$certification, "official") list(recipes = list()) } ) configure_api("http://test.local") result <- api_list_recipes(survey_type = "ech", topic = "labor", certification = "official") expect_length(result, 0) }) test_that("api_list_recipes warns on parse failure", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { list(recipes = list( list() # empty doc, will fail parse )) } ) configure_api("http://test.local") # parse_recipe_from_json on empty list may succeed with defaults result <- api_list_recipes() expect_true(is.list(result)) }) test_that("api_get_workflow re-raises non-404 errors", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { stop("API error (500): Internal server error") } ) configure_api("http://test.local") expect_error(api_get_workflow("w_500"), "500") }) test_that("api_get_recipe re-raises non-404 errors", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { stop("API error (500): Server error") } ) configure_api("http://test.local") expect_error(api_get_recipe("r_500"), "500") }) # ── api_track_download ───────────────────────────────────────────────────────── test_that("api_track_download dispatches correctly", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) called_recipe <- FALSE called_workflow <- FALSE local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { list(ok = TRUE) } ) configure_api("http://test.local") expect_silent(api_track_download("recipe", "r1")) expect_silent(api_track_download("workflow", "w1")) expect_warning(api_track_download("unknown", "x1"), "Unknown type") }) # ── Stars ──────────────────────────────────────────────────────────────────── test_that("api_star_recipe validates value range", { expect_error(api_star_recipe("r1", 0), "between 1 and 5") expect_error(api_star_recipe("r1", 6), "between 1 and 5") expect_error(api_star_recipe("r1", NA), "between 1 and 5") }) test_that("api_star_recipe calls PUT with correct endpoint", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "recipes/r_123/star") expect_equal(method, "PUT") expect_equal(body$value, 4L) list(ok = TRUE) } ) configure_api("http://test.local") result <- api_star_recipe("r_123", 4) expect_true(result$ok) }) test_that("api_get_recipe_stars calls GET", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "recipes/r_123/stars") expect_equal(method, "GET") list(average = 4.2, count = 10, user_value = 5) } ) configure_api("http://test.local") result <- api_get_recipe_stars("r_123") expect_equal(result$average, 4.2) expect_equal(result$count, 10) }) test_that("api_star_workflow validates value range", { expect_error(api_star_workflow("w1", 0), "between 1 and 5") expect_error(api_star_workflow("w1", 6), "between 1 and 5") }) test_that("api_star_workflow calls PUT with correct endpoint", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "workflows/w_123/star") expect_equal(method, "PUT") list(ok = TRUE) } ) configure_api("http://test.local") result <- api_star_workflow("w_123", 3) expect_true(result$ok) }) test_that("api_get_workflow_stars calls GET", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { list(average = 3.5, count = 7) } ) configure_api("http://test.local") result <- api_get_workflow_stars("w_123") expect_equal(result$average, 3.5) }) # ── Comments ──────────────────────────────────────────────────────────────── test_that("api_comment_recipe validates text", { expect_error(api_comment_recipe("r1", ""), "between 1 and 2000") expect_error( api_comment_recipe("r1", paste(rep("x", 2001), collapse = "")), "between 1 and 2000" ) }) test_that("api_comment_recipe calls POST", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "recipes/r_123/comments") expect_equal(method, "POST") expect_equal(body$text, "Great recipe!") list(ok = TRUE, id = "c_1") } ) configure_api("http://test.local") result <- api_comment_recipe("r_123", "Great recipe!") expect_true(result$ok) }) test_that("api_get_recipe_comments returns list", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { list(comments = list( list(id = "c_1", user = "test@test.com", text = "Hello") )) } ) configure_api("http://test.local") result <- api_get_recipe_comments("r_123") expect_length(result, 1) expect_equal(result[[1]]$text, "Hello") }) test_that("api_comment_workflow validates text", { expect_error(api_comment_workflow("w1", ""), "between 1 and 2000") }) test_that("api_delete_comment calls DELETE", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "comments/c_123") expect_equal(method, "DELETE") list(ok = TRUE) } ) configure_api("http://test.local") result <- api_delete_comment("c_123") expect_true(result$ok) }) # ── Dependents ────────────────────────────────────────────────────────────── test_that("api_get_recipe_dependents returns list", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { expect_equal(endpoint, "recipes/r_123/dependents") list(dependents = list( list(id = "r_456", name = "Child recipe", user = "test@test.com") )) } ) configure_api("http://test.local") result <- api_get_recipe_dependents("r_123") expect_length(result, 1) expect_equal(result[[1]]$name, "Child recipe") }) test_that("api_get_recipe_dependents returns empty on no dependents", { old_url <- getOption("metasurvey.api_url") on.exit(options(metasurvey.api_url = old_url)) local_mocked_bindings( api_request = function(endpoint, method = "GET", body = NULL, params = NULL) { list(dependents = list()) } ) configure_api("http://test.local") result <- api_get_recipe_dependents("r_123") expect_length(result, 0) })