From 7133ba1276e78a89d5244cc37f97d5135f50323d Mon Sep 17 00:00:00 2001 From: Dave Date: Tue, 24 Feb 2026 00:11:55 +0000 Subject: [PATCH] story-kit: merge 126_story_test_coverage_http_anthropic_rs --- server/src/http/anthropic.rs | 155 ++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 1 deletion(-) diff --git a/server/src/http/anthropic.rs b/server/src/http/anthropic.rs index 7123bb5..8213e00 100644 --- a/server/src/http/anthropic.rs +++ b/server/src/http/anthropic.rs @@ -85,6 +85,15 @@ impl AnthropicApi { /// List available Anthropic models. #[oai(path = "/anthropic/models", method = "get")] async fn list_anthropic_models(&self) -> OpenApiResult>> { + self.list_anthropic_models_from(ANTHROPIC_MODELS_URL).await + } +} + +impl AnthropicApi { + async fn list_anthropic_models_from( + &self, + url: &str, + ) -> OpenApiResult>> { let api_key = get_anthropic_api_key(self.ctx.as_ref()).map_err(bad_request)?; let client = reqwest::Client::new(); let mut headers = HeaderMap::new(); @@ -98,7 +107,7 @@ impl AnthropicApi { ); let response = client - .get(ANTHROPIC_MODELS_URL) + .get(url) .headers(headers) .send() .await @@ -124,3 +133,147 @@ impl AnthropicApi { Ok(Json(models)) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::context::AppContext; + use serde_json::json; + use std::sync::Arc; + use tempfile::TempDir; + + fn test_ctx(dir: &TempDir) -> AppContext { + AppContext::new_test(dir.path().to_path_buf()) + } + + fn make_api(dir: &TempDir) -> AnthropicApi { + AnthropicApi::new(Arc::new(test_ctx(dir))) + } + + // -- get_anthropic_api_key (private helper) -- + + #[test] + fn get_api_key_returns_err_when_not_set() { + let dir = TempDir::new().unwrap(); + let ctx = test_ctx(&dir); + let result = get_anthropic_api_key(&ctx); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("not found")); + } + + #[test] + fn get_api_key_returns_err_when_empty() { + let dir = TempDir::new().unwrap(); + let ctx = test_ctx(&dir); + ctx.store.set(KEY_ANTHROPIC_API_KEY, json!("")); + let result = get_anthropic_api_key(&ctx); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("empty")); + } + + #[test] + fn get_api_key_returns_err_when_not_string() { + let dir = TempDir::new().unwrap(); + let ctx = test_ctx(&dir); + ctx.store.set(KEY_ANTHROPIC_API_KEY, json!(12345)); + let result = get_anthropic_api_key(&ctx); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("not a string")); + } + + #[test] + fn get_api_key_returns_key_when_set() { + let dir = TempDir::new().unwrap(); + let ctx = test_ctx(&dir); + ctx.store.set(KEY_ANTHROPIC_API_KEY, json!("sk-ant-test123")); + let result = get_anthropic_api_key(&ctx); + assert_eq!(result.unwrap(), "sk-ant-test123"); + } + + // -- get_anthropic_api_key_exists endpoint -- + + #[tokio::test] + async fn key_exists_returns_false_when_not_set() { + let dir = TempDir::new().unwrap(); + let api = make_api(&dir); + let result = api.get_anthropic_api_key_exists().await.unwrap(); + assert!(!result.0); + } + + #[tokio::test] + async fn key_exists_returns_true_when_set() { + let dir = TempDir::new().unwrap(); + let ctx = AppContext::new_test(dir.path().to_path_buf()); + ctx.store.set(KEY_ANTHROPIC_API_KEY, json!("sk-ant-test123")); + let api = AnthropicApi::new(Arc::new(ctx)); + let result = api.get_anthropic_api_key_exists().await.unwrap(); + assert!(result.0); + } + + // -- set_anthropic_api_key endpoint -- + + #[tokio::test] + async fn set_api_key_returns_true() { + let dir = TempDir::new().unwrap(); + let api = make_api(&dir); + let payload = Json(ApiKeyPayload { + api_key: "sk-ant-test123".to_string(), + }); + let result = api.set_anthropic_api_key(payload).await.unwrap(); + assert!(result.0); + } + + #[tokio::test] + async fn set_then_exists_returns_true() { + let dir = TempDir::new().unwrap(); + let ctx = Arc::new(AppContext::new_test(dir.path().to_path_buf())); + let api = AnthropicApi::new(ctx); + api.set_anthropic_api_key(Json(ApiKeyPayload { + api_key: "sk-ant-test123".to_string(), + })) + .await + .unwrap(); + let result = api.get_anthropic_api_key_exists().await.unwrap(); + assert!(result.0); + } + + // -- list_anthropic_models endpoint -- + + #[tokio::test] + async fn list_models_fails_when_no_key() { + let dir = TempDir::new().unwrap(); + let api = make_api(&dir); + let result = api.list_anthropic_models().await; + assert!(result.is_err()); + } + + #[tokio::test] + async fn list_models_fails_with_invalid_header_value() { + let dir = TempDir::new().unwrap(); + let ctx = AppContext::new_test(dir.path().to_path_buf()); + // A header value containing a newline is invalid + ctx.store + .set(KEY_ANTHROPIC_API_KEY, json!("bad\nvalue")); + let api = AnthropicApi::new(Arc::new(ctx)); + let result = api.list_anthropic_models_from("http://127.0.0.1:1").await; + assert!(result.is_err()); + } + + #[tokio::test] + async fn list_models_fails_when_server_unreachable() { + let dir = TempDir::new().unwrap(); + let ctx = AppContext::new_test(dir.path().to_path_buf()); + ctx.store + .set(KEY_ANTHROPIC_API_KEY, json!("sk-ant-test123")); + let api = AnthropicApi::new(Arc::new(ctx)); + // Port 1 is reserved and should immediately refuse the connection + let result = api.list_anthropic_models_from("http://127.0.0.1:1").await; + assert!(result.is_err()); + } + + #[test] + fn new_creates_api_instance() { + let dir = TempDir::new().unwrap(); + let _api = make_api(&dir); + } +}