mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-09-01 00:59:33 +00:00
fix(tests): replace hardcoded /tmp paths with tempdir + add 300 unit tests (#659)
* test: add unit tests across 20 modules for coverage push Add 300+ unit tests covering config, context, evaluation, extensions, LLM, secrets, tools/builder, and tools/mcp modules. All tests are pure unit tests (no mocks) exercising serde roundtrips, edge cases, error paths, and business logic. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix(tests): replace hardcoded /tmp paths with tempfile::tempdir The e2e_metrics_test::test_metrics_collected_from_tool_trace test was failing because setup_test_dir() created /tmp/ironclaw_metrics_test but the fixture referenced /tmp/ironclaw_e2e_test/hello.txt (path mismatch). Added LlmTrace::replace_paths() to substitute fixture paths at runtime, then converted all 12 test files from hardcoded /tmp/ironclaw_* paths to tempfile::tempdir(). Tests are now isolated, parallel-safe, and leave no debris on disk. Regression test: test_metrics_collected_from_tool_trace now passes consistently regardless of prior /tmp state. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
8fbb782090
commit
cf96a3253c
@@ -325,4 +325,180 @@ mod tests {
|
||||
// Just make sure it constructs without panicking
|
||||
let _discovery = OnlineDiscovery::new();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_titlecase_single_char() {
|
||||
assert_eq!(titlecase("a"), "A");
|
||||
assert_eq!(titlecase("Z"), "Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_titlecase_mixed_case() {
|
||||
assert_eq!(titlecase("hELLO wORLD"), "HELLO WORLD");
|
||||
// Only first char is uppercased, rest is left as-is
|
||||
assert_eq!(titlecase("alREADY weird"), "AlREADY Weird");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_titlecase_multiple_spaces() {
|
||||
// split_whitespace collapses multiple spaces
|
||||
assert_eq!(titlecase("hello world"), "Hello World");
|
||||
assert_eq!(titlecase(" leading trailing "), "Leading Trailing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_titlecase_punctuation() {
|
||||
assert_eq!(titlecase("hello-world"), "Hello-world");
|
||||
assert_eq!(titlecase("it's fine"), "It's Fine");
|
||||
assert_eq!(titlecase("one. two"), "One. Two");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_source_wasm_download() {
|
||||
let src = ExtensionSource::WasmDownload {
|
||||
wasm_url: "https://example.com/tool.wasm".to_string(),
|
||||
capabilities_url: Some("https://example.com/caps.json".to_string()),
|
||||
};
|
||||
assert_eq!(extract_source(&src), "https://example.com/tool.wasm");
|
||||
|
||||
let src_no_caps = ExtensionSource::WasmDownload {
|
||||
wasm_url: "https://other.com/bin.wasm".to_string(),
|
||||
capabilities_url: None,
|
||||
};
|
||||
assert_eq!(extract_source(&src_no_caps), "https://other.com/bin.wasm");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_source_wasm_buildable() {
|
||||
let src = ExtensionSource::WasmBuildable {
|
||||
source_dir: "/home/user/my-tool".to_string(),
|
||||
build_dir: Some("/home/user/my-tool/target".to_string()),
|
||||
crate_name: Some("my_tool".to_string()),
|
||||
};
|
||||
assert_eq!(extract_source(&src), "/home/user/my-tool");
|
||||
|
||||
let src_minimal = ExtensionSource::WasmBuildable {
|
||||
source_dir: "./src".to_string(),
|
||||
build_dir: None,
|
||||
crate_name: None,
|
||||
};
|
||||
assert_eq!(extract_source(&src_minimal), "./src");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_online_discovery_default() {
|
||||
let d = OnlineDiscovery::default();
|
||||
// Verify it constructed (no panic) and the client is usable
|
||||
let _ = d.http_client;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_github_search_response_empty_items() {
|
||||
let json = r#"{"total_count": 0, "items": []}"#;
|
||||
let resp: super::GitHubSearchResponse = serde_json::from_str(json).unwrap();
|
||||
assert!(resp.items.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_github_search_response_missing_items_field() {
|
||||
// items has #[serde(default)], so missing field should give empty vec
|
||||
let json = r#"{"total_count": 0}"#;
|
||||
let resp: super::GitHubSearchResponse = serde_json::from_str(json).unwrap();
|
||||
assert!(resp.items.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_github_search_response_multiple_items() {
|
||||
let json = r#"{
|
||||
"items": [
|
||||
{
|
||||
"name": "mcp-server-a",
|
||||
"full_name": "org/mcp-server-a",
|
||||
"html_url": "https://github.com/org/mcp-server-a",
|
||||
"description": "First server",
|
||||
"topics": ["mcp"]
|
||||
},
|
||||
{
|
||||
"name": "mcp-server-b",
|
||||
"full_name": "org/mcp-server-b",
|
||||
"html_url": "https://github.com/org/mcp-server-b",
|
||||
"description": null,
|
||||
"topics": ["mcp", "tools"]
|
||||
}
|
||||
]
|
||||
}"#;
|
||||
let resp: super::GitHubSearchResponse = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(resp.items.len(), 2);
|
||||
assert_eq!(resp.items[0].name, "mcp-server-a");
|
||||
assert_eq!(resp.items[1].name, "mcp-server-b");
|
||||
assert_eq!(resp.items[0].description, Some("First server".to_string()));
|
||||
assert!(resp.items[1].description.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_github_repo_all_fields() {
|
||||
let json = r#"{
|
||||
"name": "cool-mcp",
|
||||
"full_name": "user/cool-mcp",
|
||||
"html_url": "https://github.com/user/cool-mcp",
|
||||
"description": "A cool MCP server",
|
||||
"homepage": "https://cool-mcp.dev",
|
||||
"topics": ["mcp-server", "model-context-protocol", "rust"]
|
||||
}"#;
|
||||
let repo: super::GitHubRepo = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(repo.name, "cool-mcp");
|
||||
assert_eq!(repo.full_name, "user/cool-mcp");
|
||||
assert_eq!(repo.html_url, "https://github.com/user/cool-mcp");
|
||||
assert_eq!(repo.description.as_deref(), Some("A cool MCP server"));
|
||||
assert_eq!(repo.homepage.as_deref(), Some("https://cool-mcp.dev"));
|
||||
assert_eq!(repo.topics.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_github_repo_missing_optional_fields() {
|
||||
let json = r#"{
|
||||
"name": "bare-repo",
|
||||
"full_name": "user/bare-repo",
|
||||
"html_url": "https://github.com/user/bare-repo"
|
||||
}"#;
|
||||
let repo: super::GitHubRepo = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(repo.name, "bare-repo");
|
||||
assert!(repo.description.is_none());
|
||||
assert!(repo.homepage.is_none());
|
||||
assert!(repo.topics.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_with_timeout_completes() {
|
||||
use crate::extensions::discovery::with_timeout;
|
||||
|
||||
let result = with_timeout(async { 42 }, std::time::Duration::from_secs(1)).await;
|
||||
assert_eq!(result, Some(42));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_with_timeout_expires() {
|
||||
use crate::extensions::discovery::with_timeout;
|
||||
|
||||
let result = with_timeout(
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)),
|
||||
std::time::Duration::from_millis(10),
|
||||
)
|
||||
.await;
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_discover_empty_query() {
|
||||
let discovery = OnlineDiscovery::new();
|
||||
let results = discovery.discover("").await;
|
||||
assert!(results.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_discover_whitespace_only_query() {
|
||||
let discovery = OnlineDiscovery::new();
|
||||
let results = discovery.discover(" \t\n ").await;
|
||||
assert!(results.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,4 +617,418 @@ mod tests {
|
||||
assert!(result.instructions().is_none());
|
||||
assert!(result.setup_url().is_none());
|
||||
}
|
||||
|
||||
// ── ExtensionKind ────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn extension_kind_display() {
|
||||
assert_eq!(ExtensionKind::McpServer.to_string(), "mcp_server");
|
||||
assert_eq!(ExtensionKind::WasmTool.to_string(), "wasm_tool");
|
||||
assert_eq!(ExtensionKind::WasmChannel.to_string(), "wasm_channel");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_kind_serde_roundtrip() {
|
||||
for kind in [
|
||||
ExtensionKind::McpServer,
|
||||
ExtensionKind::WasmTool,
|
||||
ExtensionKind::WasmChannel,
|
||||
] {
|
||||
let json = serde_json::to_value(kind).unwrap();
|
||||
let back: ExtensionKind = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(back, kind);
|
||||
}
|
||||
// Verify the serialized strings match rename_all = "snake_case"
|
||||
assert_eq!(
|
||||
serde_json::to_value(ExtensionKind::McpServer).unwrap(),
|
||||
"mcp_server"
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(ExtensionKind::WasmTool).unwrap(),
|
||||
"wasm_tool"
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(ExtensionKind::WasmChannel).unwrap(),
|
||||
"wasm_channel"
|
||||
);
|
||||
}
|
||||
|
||||
// ── ExtensionSource ──────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn extension_source_serde_mcp_url() {
|
||||
let src = ExtensionSource::McpUrl {
|
||||
url: "https://mcp.example.com".to_string(),
|
||||
};
|
||||
let json = serde_json::to_value(&src).unwrap();
|
||||
assert_eq!(json["type"], "mcp_url");
|
||||
assert_eq!(json["url"], "https://mcp.example.com");
|
||||
let back: ExtensionSource = serde_json::from_value(json).unwrap();
|
||||
assert!(
|
||||
matches!(back, ExtensionSource::McpUrl { url } if url == "https://mcp.example.com")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_source_serde_wasm_download() {
|
||||
let src = ExtensionSource::WasmDownload {
|
||||
wasm_url: "https://cdn.example.com/tool.wasm".to_string(),
|
||||
capabilities_url: Some("https://cdn.example.com/caps.json".to_string()),
|
||||
};
|
||||
let json = serde_json::to_value(&src).unwrap();
|
||||
assert_eq!(json["type"], "wasm_download");
|
||||
assert_eq!(json["wasm_url"], "https://cdn.example.com/tool.wasm");
|
||||
assert_eq!(
|
||||
json["capabilities_url"],
|
||||
"https://cdn.example.com/caps.json"
|
||||
);
|
||||
let back: ExtensionSource = serde_json::from_value(json).unwrap();
|
||||
assert!(
|
||||
matches!(back, ExtensionSource::WasmDownload { capabilities_url: Some(c), .. } if c.contains("caps.json"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_source_serde_wasm_buildable() {
|
||||
let src = ExtensionSource::WasmBuildable {
|
||||
source_dir: "/home/user/tools/my-tool".to_string(),
|
||||
build_dir: Some("target/wasm32-wasip2/release".to_string()),
|
||||
crate_name: Some("my_tool".to_string()),
|
||||
};
|
||||
let json = serde_json::to_value(&src).unwrap();
|
||||
assert_eq!(json["type"], "wasm_buildable");
|
||||
assert_eq!(json["source_dir"], "/home/user/tools/my-tool");
|
||||
let back: ExtensionSource = serde_json::from_value(json).unwrap();
|
||||
assert!(
|
||||
matches!(back, ExtensionSource::WasmBuildable { source_dir, .. } if source_dir.contains("my-tool"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_source_serde_discovered() {
|
||||
let src = ExtensionSource::Discovered {
|
||||
url: "https://discovered.example.com".to_string(),
|
||||
};
|
||||
let json = serde_json::to_value(&src).unwrap();
|
||||
assert_eq!(json["type"], "discovered");
|
||||
let back: ExtensionSource = serde_json::from_value(json).unwrap();
|
||||
assert!(matches!(back, ExtensionSource::Discovered { url } if url.contains("discovered")));
|
||||
}
|
||||
|
||||
// ── AuthHint ─────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn auth_hint_serde_all_variants() {
|
||||
// Dcr
|
||||
let json = serde_json::to_value(&AuthHint::Dcr).unwrap();
|
||||
assert_eq!(json["type"], "dcr");
|
||||
let back: AuthHint = serde_json::from_value(json).unwrap();
|
||||
assert!(matches!(back, AuthHint::Dcr));
|
||||
|
||||
// OAuthPreConfigured
|
||||
let hint = AuthHint::OAuthPreConfigured {
|
||||
setup_url: "https://dev.example.com/apps".to_string(),
|
||||
};
|
||||
let json = serde_json::to_value(&hint).unwrap();
|
||||
assert_eq!(json["type"], "o_auth_pre_configured");
|
||||
assert_eq!(json["setup_url"], "https://dev.example.com/apps");
|
||||
let back: AuthHint = serde_json::from_value(json).unwrap();
|
||||
assert!(
|
||||
matches!(back, AuthHint::OAuthPreConfigured { setup_url } if setup_url.contains("dev.example"))
|
||||
);
|
||||
|
||||
// CapabilitiesAuth
|
||||
let json = serde_json::to_value(&AuthHint::CapabilitiesAuth).unwrap();
|
||||
assert_eq!(json["type"], "capabilities_auth");
|
||||
let back: AuthHint = serde_json::from_value(json).unwrap();
|
||||
assert!(matches!(back, AuthHint::CapabilitiesAuth));
|
||||
|
||||
// None
|
||||
let json = serde_json::to_value(&AuthHint::None).unwrap();
|
||||
assert_eq!(json["type"], "none");
|
||||
let back: AuthHint = serde_json::from_value(json).unwrap();
|
||||
assert!(matches!(back, AuthHint::None));
|
||||
}
|
||||
|
||||
// ── SearchResult ─────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn search_result_serde_registry_source() {
|
||||
// SearchResult uses #[serde(flatten)] on entry, which means
|
||||
// RegistryEntry.source (ExtensionSource) and SearchResult.source
|
||||
// (ResultSource) collide on the "source" key. The last writer wins
|
||||
// during serialization, so we test serialize-only (no roundtrip).
|
||||
let entry = RegistryEntry {
|
||||
name: "notion".to_string(),
|
||||
display_name: "Notion".to_string(),
|
||||
kind: ExtensionKind::McpServer,
|
||||
description: "Notion integration".to_string(),
|
||||
keywords: vec!["notes".to_string(), "wiki".to_string()],
|
||||
source: ExtensionSource::McpUrl {
|
||||
url: "https://mcp.notion.so".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::Dcr,
|
||||
};
|
||||
let sr = SearchResult {
|
||||
entry,
|
||||
source: ResultSource::Registry,
|
||||
validated: false,
|
||||
};
|
||||
let json = serde_json::to_value(&sr).unwrap();
|
||||
assert_eq!(json["name"], "notion");
|
||||
assert_eq!(json["kind"], "mcp_server");
|
||||
assert_eq!(json["description"], "Notion integration");
|
||||
assert_eq!(json["validated"], false);
|
||||
// The flattened entry fields are present at the top level
|
||||
assert!(json.get("auth_hint").is_some());
|
||||
assert_eq!(json["keywords"].as_array().unwrap().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_result_serde_discovered_source() {
|
||||
let entry = RegistryEntry {
|
||||
name: "custom-api".to_string(),
|
||||
display_name: "Custom API".to_string(),
|
||||
kind: ExtensionKind::McpServer,
|
||||
description: "Discovered MCP server".to_string(),
|
||||
keywords: vec![],
|
||||
source: ExtensionSource::Discovered {
|
||||
url: "https://custom.example.com/.well-known/mcp".to_string(),
|
||||
},
|
||||
fallback_source: None,
|
||||
auth_hint: AuthHint::None,
|
||||
};
|
||||
let sr = SearchResult {
|
||||
entry,
|
||||
source: ResultSource::Discovered,
|
||||
validated: true,
|
||||
};
|
||||
let json = serde_json::to_value(&sr).unwrap();
|
||||
assert_eq!(json["name"], "custom-api");
|
||||
assert_eq!(json["display_name"], "Custom API");
|
||||
assert_eq!(json["validated"], true);
|
||||
assert!(json.get("keywords").is_some());
|
||||
}
|
||||
|
||||
// ── InstallResult ────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn install_result_serde_roundtrip() {
|
||||
let ir = InstallResult {
|
||||
name: "weather".to_string(),
|
||||
kind: ExtensionKind::WasmTool,
|
||||
message: "Installed successfully".to_string(),
|
||||
};
|
||||
let json = serde_json::to_value(&ir).unwrap();
|
||||
assert_eq!(json["name"], "weather");
|
||||
assert_eq!(json["kind"], "wasm_tool");
|
||||
assert_eq!(json["message"], "Installed successfully");
|
||||
let back: InstallResult = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(back.name, "weather");
|
||||
assert_eq!(back.kind, ExtensionKind::WasmTool);
|
||||
}
|
||||
|
||||
// ── ActivateResult ───────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn activate_result_serde_roundtrip() {
|
||||
let ar = ActivateResult {
|
||||
name: "slack".to_string(),
|
||||
kind: ExtensionKind::WasmChannel,
|
||||
tools_loaded: vec!["send_message".to_string(), "read_channel".to_string()],
|
||||
message: "Activated with 2 tools".to_string(),
|
||||
};
|
||||
let json = serde_json::to_value(&ar).unwrap();
|
||||
assert_eq!(json["name"], "slack");
|
||||
assert_eq!(json["kind"], "wasm_channel");
|
||||
assert_eq!(json["tools_loaded"].as_array().unwrap().len(), 2);
|
||||
let back: ActivateResult = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(back.tools_loaded, vec!["send_message", "read_channel"]);
|
||||
}
|
||||
|
||||
// ── InstalledExtension ───────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn installed_extension_serde_defaults() {
|
||||
// Minimal JSON: optional fields absent, defaults kick in
|
||||
let json = serde_json::json!({
|
||||
"name": "echo",
|
||||
"kind": "wasm_tool",
|
||||
"authenticated": false,
|
||||
"active": false,
|
||||
});
|
||||
let ext: InstalledExtension = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(ext.name, "echo");
|
||||
assert!(ext.installed, "installed should default to true");
|
||||
assert!(!ext.needs_setup, "needs_setup should default to false");
|
||||
assert!(!ext.has_auth);
|
||||
assert!(ext.tools.is_empty());
|
||||
assert!(ext.display_name.is_none());
|
||||
assert!(ext.description.is_none());
|
||||
assert!(ext.url.is_none());
|
||||
assert!(ext.activation_error.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn installed_extension_serde_all_fields() {
|
||||
let ext = InstalledExtension {
|
||||
name: "gmail".to_string(),
|
||||
kind: ExtensionKind::WasmTool,
|
||||
display_name: Some("Gmail Tool".to_string()),
|
||||
description: Some("Read and send emails".to_string()),
|
||||
url: Some("https://gmail.example.com".to_string()),
|
||||
authenticated: true,
|
||||
active: true,
|
||||
tools: vec!["send_email".to_string(), "read_inbox".to_string()],
|
||||
needs_setup: true,
|
||||
has_auth: true,
|
||||
installed: false,
|
||||
activation_error: Some("token expired".to_string()),
|
||||
};
|
||||
let json = serde_json::to_value(&ext).unwrap();
|
||||
assert_eq!(json["display_name"], "Gmail Tool");
|
||||
assert_eq!(json["description"], "Read and send emails");
|
||||
assert_eq!(json["url"], "https://gmail.example.com");
|
||||
assert_eq!(json["needs_setup"], true);
|
||||
assert_eq!(json["installed"], false);
|
||||
assert_eq!(json["activation_error"], "token expired");
|
||||
|
||||
let back: InstalledExtension = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(back.name, "gmail");
|
||||
assert_eq!(back.tools.len(), 2);
|
||||
assert!(back.needs_setup);
|
||||
assert!(!back.installed);
|
||||
assert_eq!(back.activation_error.as_deref(), Some("token expired"));
|
||||
}
|
||||
|
||||
// ── ExtensionError Display ───────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn extension_error_display_all_variants() {
|
||||
let cases: Vec<(ExtensionError, &str)> = vec![
|
||||
(
|
||||
ExtensionError::NotFound("foo".into()),
|
||||
"Extension not found: foo",
|
||||
),
|
||||
(
|
||||
ExtensionError::AlreadyInstalled("bar".into()),
|
||||
"Extension already installed: bar",
|
||||
),
|
||||
(
|
||||
ExtensionError::NotInstalled("baz".into()),
|
||||
"Extension not installed: baz",
|
||||
),
|
||||
(
|
||||
ExtensionError::AuthFailed("bad token".into()),
|
||||
"Authentication failed: bad token",
|
||||
),
|
||||
(
|
||||
ExtensionError::ActivationFailed("crash".into()),
|
||||
"Activation failed: crash",
|
||||
),
|
||||
(
|
||||
ExtensionError::InstallFailed("disk full".into()),
|
||||
"Installation failed: disk full",
|
||||
),
|
||||
(
|
||||
ExtensionError::DiscoveryFailed("timeout".into()),
|
||||
"Discovery failed: timeout",
|
||||
),
|
||||
(
|
||||
ExtensionError::InvalidUrl("not a url".into()),
|
||||
"Invalid URL: not a url",
|
||||
),
|
||||
(
|
||||
ExtensionError::DownloadFailed("404".into()),
|
||||
"Download failed: 404",
|
||||
),
|
||||
(
|
||||
ExtensionError::Config("missing key".into()),
|
||||
"Config error: missing key",
|
||||
),
|
||||
(
|
||||
ExtensionError::Other("something broke".into()),
|
||||
"something broke",
|
||||
),
|
||||
(
|
||||
ExtensionError::FallbackFailed {
|
||||
primary: Box::new(ExtensionError::DownloadFailed("404".into())),
|
||||
fallback: Box::new(ExtensionError::InstallFailed("no cargo".into())),
|
||||
},
|
||||
"Primary install failed: Download failed: 404; fallback install also failed: Installation failed: no cargo",
|
||||
),
|
||||
];
|
||||
for (err, expected) in cases {
|
||||
assert_eq!(err.to_string(), expected);
|
||||
}
|
||||
}
|
||||
|
||||
// ── ToolAuthState ────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn tool_auth_state_equality() {
|
||||
assert_eq!(ToolAuthState::Ready, ToolAuthState::Ready);
|
||||
assert_eq!(ToolAuthState::NeedsAuth, ToolAuthState::NeedsAuth);
|
||||
assert_eq!(ToolAuthState::NeedsSetup, ToolAuthState::NeedsSetup);
|
||||
assert_eq!(ToolAuthState::NoAuth, ToolAuthState::NoAuth);
|
||||
|
||||
assert_ne!(ToolAuthState::Ready, ToolAuthState::NeedsAuth);
|
||||
assert_ne!(ToolAuthState::NeedsSetup, ToolAuthState::NoAuth);
|
||||
assert_ne!(ToolAuthState::Ready, ToolAuthState::NoAuth);
|
||||
}
|
||||
|
||||
// ── ResultSource ─────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn result_source_serde() {
|
||||
let json = serde_json::to_value(ResultSource::Registry).unwrap();
|
||||
assert_eq!(json, "registry");
|
||||
let back: ResultSource = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(back, ResultSource::Registry);
|
||||
|
||||
let json = serde_json::to_value(ResultSource::Discovered).unwrap();
|
||||
assert_eq!(json, "discovered");
|
||||
let back: ResultSource = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(back, ResultSource::Discovered);
|
||||
}
|
||||
|
||||
// ── AuthResult::status_str ───────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn auth_result_status_str_all_variants() {
|
||||
assert_eq!(
|
||||
AuthResult::authenticated("a", ExtensionKind::McpServer).status_str(),
|
||||
"authenticated"
|
||||
);
|
||||
assert_eq!(
|
||||
AuthResult::no_auth_required("b", ExtensionKind::WasmTool).status_str(),
|
||||
"no_auth_required"
|
||||
);
|
||||
assert_eq!(
|
||||
AuthResult::awaiting_authorization(
|
||||
"c",
|
||||
ExtensionKind::WasmChannel,
|
||||
"https://x.com".into(),
|
||||
"local".into(),
|
||||
)
|
||||
.status_str(),
|
||||
"awaiting_authorization"
|
||||
);
|
||||
assert_eq!(
|
||||
AuthResult::awaiting_token("d", ExtensionKind::WasmTool, "paste token".into(), None)
|
||||
.status_str(),
|
||||
"awaiting_token"
|
||||
);
|
||||
assert_eq!(
|
||||
AuthResult::needs_setup(
|
||||
"e",
|
||||
ExtensionKind::McpServer,
|
||||
"configure oauth".into(),
|
||||
Some("https://setup.example.com".into()),
|
||||
)
|
||||
.status_str(),
|
||||
"needs_setup"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user