use super::*;
use std::io::Write;
fn write_temp_manifest(content: &str) -> tempfile::NamedTempFile {
let mut f = tempfile::NamedTempFile::new().unwrap();
f.write_all(content.as_bytes()).unwrap();
f.flush().unwrap();
f
}
#[test]
fn manifest_valid_minimal() {
let f = write_temp_manifest(
r#"
[plugin]
name = "test-plugin"
world = "mother-child"
[capabilities]
host_log = true
[provides]
child = "test"
"#,
);
let m = PluginManifest::from_path(f.path()).unwrap();
assert_eq!(m.name, "test-plugin");
assert_eq!(m.world, PluginWorld::MotherChild);
assert_eq!(m.version, "0.0.0"); assert_eq!(m.capabilities, vec!["host_log"]);
assert_eq!(m.provides.child.as_deref(), Some("test"));
}
#[test]
fn manifest_valid_full() {
let f = write_temp_manifest(
r#"
[plugin]
name = "full-plugin"
version = "1.2.3"
description = "A full manifest"
world = "mother-child"
patina_min = "0.17.0"
[capabilities]
host_log = true
filesystem = false
[provides]
child = "full"
commands = ["cmd1", "cmd2"]
"#,
);
let m = PluginManifest::from_path(f.path()).unwrap();
assert_eq!(m.name, "full-plugin");
assert_eq!(m.version, "1.2.3");
assert_eq!(m.description, "A full manifest");
assert_eq!(m.patina_min, "0.17.0");
assert_eq!(m.capabilities, vec!["host_log"]);
assert_eq!(m.provides.commands, vec!["cmd1", "cmd2"]);
}
#[test]
fn manifest_missing_plugin_section() {
let f = write_temp_manifest("[other]\nfoo = 1\n");
let err = PluginManifest::from_path(f.path()).unwrap_err();
assert!(
err.to_string().contains("missing [plugin] section"),
"got: {}",
err
);
}
#[test]
fn manifest_missing_name() {
let f = write_temp_manifest("[plugin]\nworld = \"mother-child\"\n");
let err = PluginManifest::from_path(f.path()).unwrap_err();
assert!(
err.to_string().contains("missing plugin.name"),
"got: {}",
err
);
}
#[test]
fn manifest_missing_world() {
let f = write_temp_manifest("[plugin]\nname = \"test\"\n");
let err = PluginManifest::from_path(f.path()).unwrap_err();
assert!(
err.to_string().contains("missing plugin.world"),
"got: {}",
err
);
}
#[test]
fn manifest_parses_toy_commands() {
let f = write_temp_manifest(
r#"
[plugin]
name = "test-plugin"
world = "mother-child"
[capabilities]
host_log = true
[capabilities.toys]
commands = ["git", "patina"]
[provides]
child = "test"
"#,
);
let m = PluginManifest::from_path(f.path()).unwrap();
assert_eq!(m.allowed_toy_commands, vec!["git", "patina"]);
}
#[test]
fn manifest_no_toy_commands_defaults_empty() {
let f = write_temp_manifest(
r#"
[plugin]
name = "test-plugin"
world = "mother-child"
[capabilities]
host_log = true
[provides]
child = "test"
"#,
);
let m = PluginManifest::from_path(f.path()).unwrap();
assert!(m.allowed_toy_commands.is_empty());
}
#[test]
fn manifest_invalid_toml() {
let f = write_temp_manifest("this is not valid toml {{{}}}");
assert!(PluginManifest::from_path(f.path()).is_err());
}
#[test]
fn capabilities_all_granted() {
let m = PluginManifest {
name: "test".into(),
version: "0.1.0".into(),
description: String::new(),
world: PluginWorld::MotherChild,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
child: None,
commands: vec![],
..Default::default()
},
};
assert!(PluginEngine::check_capabilities(&m).is_ok());
}
#[test]
fn capabilities_empty() {
let m = PluginManifest {
name: "test".into(),
version: "0.1.0".into(),
description: String::new(),
world: PluginWorld::MotherChild,
patina_min: "0.0.0".into(),
capabilities: vec![],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
child: None,
commands: vec![],
..Default::default()
},
};
assert!(PluginEngine::check_capabilities(&m).is_ok());
}
#[test]
fn capabilities_denied() {
let m = PluginManifest {
name: "test".into(),
version: "0.1.0".into(),
description: String::new(),
world: PluginWorld::MotherChild,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into(), "filesystem".into(), "network".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
child: None,
commands: vec![],
..Default::default()
},
};
let err = PluginEngine::check_capabilities(&m).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("filesystem"), "got: {}", msg);
assert!(msg.contains("network"), "got: {}", msg);
assert!(
!msg.contains("host_log"),
"host_log should be granted: {}",
msg
);
}
#[test]
fn sanitize_strips_all_repos_for_current_project() {
let params = r#"{"query":"test","all_repos":true,"limit":5}"#;
let result = host_support::sanitize_query_params(params, &QueryScope::CurrentProject);
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
assert!(
parsed.get("all_repos").is_none(),
"all_repos should be stripped, got: {}",
result
);
assert_eq!(parsed.get("query").unwrap(), "test");
assert_eq!(parsed.get("limit").unwrap(), 5);
}
#[test]
fn sanitize_strips_repo_for_current_project() {
let params = r#"{"query":"test","repo":"other-project"}"#;
let result = host_support::sanitize_query_params(params, &QueryScope::CurrentProject);
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
assert!(
parsed.get("repo").is_none(),
"repo should be stripped, got: {}",
result
);
assert_eq!(parsed.get("query").unwrap(), "test");
}
#[test]
fn sanitize_strips_all_reserved_keys() {
let params =
r#"{"query":"test","all_repos":true,"repo":"x","project_root":"/tmp","db_path":"/hack"}"#;
let result = host_support::sanitize_query_params(params, &QueryScope::CurrentProject);
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
for key in &["all_repos", "repo", "project_root", "db_path"] {
assert!(
parsed.get(key).is_none(),
"reserved key '{}' should be stripped, got: {}",
key,
result
);
}
}
#[test]
fn sanitize_preserves_all_for_all_repos_scope() {
let params = r#"{"query":"test","all_repos":true,"repo":"other"}"#;
let result = host_support::sanitize_query_params(params, &QueryScope::AllRepos);
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
assert_eq!(parsed.get("all_repos").unwrap(), true);
assert_eq!(parsed.get("repo").unwrap(), "other");
}
#[test]
fn sanitize_handles_invalid_json() {
let params = "not json";
let result = host_support::sanitize_query_params(params, &QueryScope::CurrentProject);
assert_eq!(
result, "not json",
"invalid JSON should pass through unchanged"
);
}
#[test]
fn check_capabilities_rejects_unknown_query_kinds() {
let m = PluginManifest {
name: "test".into(),
version: "0.1.0".into(),
description: String::new(),
world: PluginWorld::Command,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec!["scry".into(), "magic_oracle".into()],
host_http_domains: vec![],
provides: PluginProvides {
child: None,
commands: vec![],
..Default::default()
},
};
let err = PluginEngine::check_capabilities(&m).unwrap_err();
assert!(
err.to_string().contains("magic_oracle"),
"should reject unknown kind, got: {}",
err
);
}
#[test]
fn check_capabilities_accepts_known_query_kinds() {
let m = PluginManifest {
name: "test".into(),
version: "0.1.0".into(),
description: String::new(),
world: PluginWorld::Command,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec!["scry".into(), "context".into(), "assay".into()],
host_http_domains: vec![],
provides: PluginProvides {
child: None,
commands: vec![],
..Default::default()
},
};
assert!(PluginEngine::check_capabilities(&m).is_ok());
}
#[test]
fn wasm_models_child_handle_roundtrip() {
let wasm_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/patina_plugin_models.wasm");
if !wasm_path.exists() {
panic!(
"test fixture missing: {}\n\
Build it with: cargo build --release -p patina-plugin-models --target wasm32-wasip2\n\
Then: cp target/wasm32-wasip2/release/patina_plugin_models.wasm tests/fixtures/",
wasm_path.display()
);
}
let engine = PluginEngine::new().expect("PluginEngine::new() failed");
let wasm_bytes = std::fs::read(&wasm_path).expect("failed to read .wasm fixture");
let component = engine
.load_component(&wasm_bytes)
.expect("load_component failed");
let manifest = PluginManifest {
name: "patina-models".into(),
version: "0.1.0".into(),
description: "test".into(),
world: PluginWorld::MotherChild,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
child: Some("models".into()),
commands: vec![],
..Default::default()
},
};
let child = engine
.instantiate_child(&component, &manifest, None)
.expect("instantiate_child failed");
assert_eq!(child.name(), "models");
let request = crate::mother::ChildRequest {
action: "resolve_model".into(),
payload: serde_json::json!({"name": "e5-small"}),
};
let response = child.handle(&request).expect("handle() failed");
let path = response.payload.get("path").and_then(|v| v.as_str());
assert!(
path.is_some_and(|p| p.contains("e5-small")),
"expected path containing 'e5-small', got: {:?}",
response.payload
);
}
#[test]
fn wasm_models_child_health() {
let wasm_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/patina_plugin_models.wasm");
if !wasm_path.exists() {
return; }
let engine = PluginEngine::new().unwrap();
let wasm_bytes = std::fs::read(&wasm_path).unwrap();
let component = engine.load_component(&wasm_bytes).unwrap();
let manifest = PluginManifest {
name: "patina-models".into(),
version: "0.1.0".into(),
description: "test".into(),
world: PluginWorld::MotherChild,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
child: Some("models".into()),
commands: vec![],
..Default::default()
},
};
let child = engine
.instantiate_child(&component, &manifest, None)
.unwrap();
match child.health() {
crate::mother::ChildHealth::Healthy => {} other => panic!("expected Healthy, got: {:?}", other),
}
}
fn load_repos_child() -> Option<Box<dyn crate::mother::MotherChild>> {
let wasm_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/patina_plugin_repos.wasm");
if !wasm_path.exists() {
return None;
}
let engine = PluginEngine::new().unwrap();
let wasm_bytes = std::fs::read(&wasm_path).unwrap();
let component = engine.load_component(&wasm_bytes).unwrap();
let manifest = PluginManifest {
name: "patina-repos".into(),
version: "0.1.0".into(),
description: "test".into(),
world: PluginWorld::MotherChild,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec!["git".into(), "patina".into()],
host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
child: Some("repos".into()),
commands: vec![],
..Default::default()
},
};
Some(
engine
.instantiate_child(&component, &manifest, None)
.unwrap(),
)
}
#[test]
fn wasm_repos_child_handle_roundtrip() {
let child = match load_repos_child() {
Some(c) => c,
None => {
panic!(
"test fixture missing: tests/fixtures/patina_plugin_repos.wasm\n\
Build: cargo build --release -p patina-plugin-repos --target wasm32-wasip2\n\
Copy: cp target/wasm32-wasip2/release/patina_plugin_repos.wasm tests/fixtures/"
);
}
};
assert_eq!(child.name(), "repos");
let request = crate::mother::ChildRequest {
action: "report_repo".into(),
payload: serde_json::json!({
"name": "test-repo",
"path": "/tmp/repos/test-repo",
"last_indexed": 0
}),
};
let response = child.handle(&request).expect("report_repo failed");
assert_eq!(
response.payload.get("status").and_then(|v| v.as_str()),
Some("registered")
);
assert_eq!(
response.payload.get("total_repos").and_then(|v| v.as_u64()),
Some(1)
);
let request = crate::mother::ChildRequest {
action: "check_freshness".into(),
payload: serde_json::json!({}),
};
let response = child.handle(&request).expect("check_freshness failed");
let stale_count = response.payload.get("stale_count").and_then(|v| v.as_u64());
assert_eq!(
stale_count,
Some(1),
"repo with last_indexed=0 should be stale"
);
}
#[test]
fn wasm_repos_child_tick_returns_toys() {
let mut child = match load_repos_child() {
Some(c) => c,
None => return, };
let request = crate::mother::ChildRequest {
action: "report_repo".into(),
payload: serde_json::json!({
"name": "stale-repo",
"path": "/home/user/.patina/cache/repos/stale-repo",
"last_indexed": 0
}),
};
child.handle(&request).expect("report_repo failed");
let toys = child.tick();
assert!(
toys.len() >= 2,
"expected at least 2 toys (pull + scrape), got {}",
toys.len()
);
let pull_toy = toys.iter().find(|t| t.name.contains("pull"));
assert!(pull_toy.is_some(), "expected a pull toy, got: {:?}", toys);
let pull = pull_toy.unwrap();
assert_eq!(pull.command, "git");
assert!(
pull.args.contains(&"-C".to_string()),
"pull toy should use -C flag"
);
let scrape_toy = toys.iter().find(|t| t.name.contains("scrape"));
assert!(
scrape_toy.is_some(),
"expected a scrape toy, got: {:?}",
toys
);
let scrape = scrape_toy.unwrap();
assert_eq!(scrape.command, "patina");
assert!(
scrape.args.contains(&"scrape".to_string()),
"scrape toy should include 'scrape' arg"
);
}
#[test]
fn wasm_repos_child_fresh_repo_no_toys() {
let mut child = match load_repos_child() {
Some(c) => c,
None => return,
};
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let request = crate::mother::ChildRequest {
action: "report_repo".into(),
payload: serde_json::json!({
"name": "fresh-repo",
"path": "/tmp/repos/fresh-repo",
"last_indexed": now
}),
};
child.handle(&request).expect("report_repo failed");
let toys = child.tick();
assert!(
toys.is_empty(),
"expected no toys for fresh repo, got: {:?}",
toys
);
}
#[test]
fn wasm_repos_child_health_reflects_staleness() {
let child = match load_repos_child() {
Some(c) => c,
None => return,
};
match child.health() {
crate::mother::ChildHealth::Healthy => {}
other => panic!("expected Healthy with no repos, got: {:?}", other),
}
let request = crate::mother::ChildRequest {
action: "report_repo".into(),
payload: serde_json::json!({
"name": "old-repo",
"path": "/tmp/repos/old-repo",
"last_indexed": 0
}),
};
child.handle(&request).expect("report_repo failed");
match child.health() {
crate::mother::ChildHealth::Degraded(_) => {} other => panic!("expected Degraded with stale repo, got: {:?}", other),
}
}
#[test]
fn wasm_repos_child_toy_capability_gating() {
let wasm_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/patina_plugin_repos.wasm");
if !wasm_path.exists() {
return; }
let engine = PluginEngine::new().unwrap();
let wasm_bytes = std::fs::read(&wasm_path).unwrap();
let component = engine.load_component(&wasm_bytes).unwrap();
let manifest = PluginManifest {
name: "patina-repos".into(),
version: "0.1.0".into(),
description: "test".into(),
world: PluginWorld::MotherChild,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec!["patina".into()], host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
child: Some("repos".into()),
commands: vec![],
..Default::default()
},
};
let mut child = engine
.instantiate_child(&component, &manifest, None)
.unwrap();
let request = crate::mother::ChildRequest {
action: "report_repo".into(),
payload: serde_json::json!({
"name": "gated-repo",
"path": "/tmp/repos/gated-repo",
"last_indexed": 0
}),
};
child.handle(&request).expect("report_repo failed");
let toys = child.tick();
for toy in &toys {
assert_eq!(
toy.command, "patina",
"expected only 'patina' toys, got command '{}' in toy '{}'",
toy.command, toy.name
);
}
assert!(
!toys.is_empty(),
"expected at least one patina toy to pass the filter"
);
}
#[test]
fn benchmark_plugin_performance() {
use std::time::Instant;
let wasm_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/patina_plugin_models.wasm");
if !wasm_path.exists() {
return;
}
let _ = PluginEngine::new();
let t0 = Instant::now();
let engine = PluginEngine::new().unwrap();
let engine_ms = t0.elapsed().as_secs_f64() * 1000.0;
let wasm_bytes = std::fs::read(&wasm_path).unwrap();
let t1 = Instant::now();
let component = engine.load_component(&wasm_bytes).unwrap();
let component_ms = t1.elapsed().as_secs_f64() * 1000.0;
let manifest = PluginManifest {
name: "patina-models".into(),
version: "0.1.0".into(),
description: "bench".into(),
world: PluginWorld::MotherChild,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
child: Some("models".into()),
commands: vec![],
..Default::default()
},
};
let t2 = Instant::now();
let child = engine
.instantiate_child(&component, &manifest, None)
.unwrap();
let instantiate_ms = t2.elapsed().as_secs_f64() * 1000.0;
let request = crate::mother::ChildRequest {
action: "resolve_model".into(),
payload: serde_json::json!({"name": "e5-small"}),
};
let _ = child.handle(&request).unwrap();
let t3 = Instant::now();
let iterations = 10;
for _ in 0..iterations {
let _ = child.handle(&request).unwrap();
}
let handle_avg_ms = t3.elapsed().as_secs_f64() * 1000.0 / iterations as f64;
eprintln!();
eprintln!("=== Plugin System Benchmarks (C2) ===");
eprintln!(
" PluginEngine::new(): {:.2}ms (threshold: <100ms) {}",
engine_ms,
if engine_ms < 100.0 { "PASS" } else { "FAIL" }
);
eprintln!(
" Component::new(): {:.2}ms (156KB WASM cranelift JIT)",
component_ms
);
eprintln!(
" instantiate_child(): {:.2}ms (WasiCtx + Store + init + name)",
instantiate_ms
);
eprintln!(
" handle() round-trip: {:.3}ms avg over {} calls (threshold: <1ms) {}",
handle_avg_ms,
iterations,
if handle_avg_ms < 1.0 { "PASS" } else { "FAIL" }
);
eprintln!("=====================================");
assert!(
engine_ms < 100.0,
"PluginEngine::new() took {:.2}ms, threshold is 100ms",
engine_ms
);
assert!(
handle_avg_ms < 1.0,
"handle() avg took {:.3}ms, threshold is 1ms",
handle_avg_ms
);
}
fn load_doctor_component() -> Option<(CommandEngine, wasmtime::component::Component)> {
let wasm_path =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/patina_doctor.wasm");
if !wasm_path.exists() {
return None;
}
let engine = CommandEngine::new().expect("CommandEngine::new() failed");
let wasm_bytes = std::fs::read(&wasm_path).expect("failed to read doctor wasm");
let component = engine
.load_component(&wasm_bytes)
.expect("load_component failed");
Some((engine, component))
}
#[test]
fn command_doctor_name() {
let (engine, component) = match load_doctor_component() {
Some(ec) => ec,
None => {
panic!(
"test fixture missing: tests/fixtures/patina_doctor.wasm\n\
Build: cargo build --release -p patina-doctor --target wasm32-wasip2\n\
Copy: cp target/wasm32-wasip2/release/patina_doctor.wasm tests/fixtures/"
);
}
};
let name = engine
.get_command_name(&component)
.expect("get_command_name failed");
assert_eq!(name, "doctor");
}
#[test]
fn command_doctor_description() {
let (engine, component) = match load_doctor_component() {
Some(ec) => ec,
None => return,
};
let desc = engine
.get_command_description(&component)
.expect("get_command_description failed");
assert!(
desc.contains("health"),
"expected description to mention 'health', got: {}",
desc
);
}
fn load_doctor_manifest() -> PluginManifest {
PluginManifest {
name: "patina-doctor".into(),
version: "0.1.0".into(),
description: "test".into(),
world: PluginWorld::Command,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into(), "host_layer".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
child: None,
commands: vec!["doctor".into()],
..Default::default()
},
}
}
#[test]
fn command_doctor_run() {
let (engine, component) = match load_doctor_component() {
Some(ec) => ec,
None => return,
};
let manifest = load_doctor_manifest();
let args = vec!["--json".to_string()];
let exit_code = engine
.run_command(&component, &manifest, &args, None)
.expect("run_command failed");
assert!(
[0, 1, 2, 3].contains(&exit_code),
"unexpected exit code: {}",
exit_code
);
}
#[test]
fn validate_http_url_valid_https() {
let domain = host_support::validate_http_url("https://api.github.com/repos").unwrap();
assert_eq!(domain, "api.github.com");
}
#[test]
fn validate_http_url_valid_https_with_port() {
let domain = host_support::validate_http_url("https://api.github.com:443/repos").unwrap();
assert_eq!(domain, "api.github.com");
}
#[test]
fn validate_http_url_rejects_http() {
let err = host_support::validate_http_url("http://api.github.com/repos").unwrap_err();
assert!(err.contains("HTTPS"), "expected HTTPS error, got: {}", err);
}
#[test]
fn validate_http_url_rejects_ipv4() {
let err = host_support::validate_http_url("https://192.168.1.1/api").unwrap_err();
assert!(err.contains("IP"), "expected IP error, got: {}", err);
}
#[test]
fn validate_http_url_rejects_ipv6() {
let err = host_support::validate_http_url("https://[::1]/api").unwrap_err();
assert!(err.contains("IP"), "expected IP error, got: {}", err);
}
#[test]
fn validate_http_url_rejects_localhost() {
let err = host_support::validate_http_url("https://localhost/api").unwrap_err();
assert!(
err.contains("localhost"),
"expected localhost error, got: {}",
err
);
}
#[test]
fn validate_http_url_rejects_invalid() {
let err = host_support::validate_http_url("not-a-url").unwrap_err();
assert!(
err.contains("invalid"),
"expected invalid URL error, got: {}",
err
);
}
#[test]
fn manifest_parses_http_domains() {
let f = write_temp_manifest(
r#"
[plugin]
name = "http-plugin"
world = "mother-child"
[capabilities]
host_log = true
host_http = ["api.github.com", "hooks.slack.com"]
[provides]
child = "webhook"
"#,
);
let m = PluginManifest::from_path(f.path()).unwrap();
assert_eq!(
m.host_http_domains,
vec!["api.github.com", "hooks.slack.com"]
);
}
#[test]
fn manifest_no_http_domains_defaults_empty() {
let f = write_temp_manifest(
r#"
[plugin]
name = "no-http"
world = "mother-child"
[capabilities]
host_log = true
[provides]
child = "test"
"#,
);
let m = PluginManifest::from_path(f.path()).unwrap();
assert!(m.host_http_domains.is_empty());
}
#[test]
fn check_capabilities_rejects_empty_http_domain() {
let m = PluginManifest {
name: "test".into(),
version: "0.1.0".into(),
description: String::new(),
world: PluginWorld::MotherChild,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec!["".into()],
provides: PluginProvides {
child: None,
commands: vec![],
..Default::default()
},
};
let err = PluginEngine::check_capabilities(&m).unwrap_err();
assert!(err.to_string().contains("empty"), "got: {}", err);
}
#[test]
fn check_capabilities_rejects_http_domain_with_path() {
let m = PluginManifest {
name: "test".into(),
version: "0.1.0".into(),
description: String::new(),
world: PluginWorld::MotherChild,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec!["api.github.com/repos".into()],
provides: PluginProvides {
child: None,
commands: vec![],
..Default::default()
},
};
let err = PluginEngine::check_capabilities(&m).unwrap_err();
assert!(err.to_string().contains("path"), "got: {}", err);
}
#[test]
fn check_capabilities_accepts_valid_http_domains() {
let m = PluginManifest {
name: "test".into(),
version: "0.1.0".into(),
description: String::new(),
world: PluginWorld::MotherChild,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec!["api.github.com".into(), "hooks.slack.com".into()],
provides: PluginProvides {
child: None,
commands: vec![],
..Default::default()
},
};
assert!(PluginEngine::check_capabilities(&m).is_ok());
}
#[test]
fn granted_capabilities_includes_http_domains() {
let m = PluginManifest {
name: "test".into(),
version: "0.1.0".into(),
description: String::new(),
world: PluginWorld::MotherChild,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec!["scry".into()],
host_http_domains: vec!["api.github.com".into()],
provides: PluginProvides {
child: None,
commands: vec![],
..Default::default()
},
};
let grants = m.granted_capabilities();
assert!(grants.http_domains.contains("api.github.com"));
assert!(grants.query_kinds.contains("scry"));
}
#[test]
fn conformance_http_domain_not_in_allowlist_denied() {
let grants = GrantedCapabilities {
http_domains: ["api.github.com".to_string()].into_iter().collect(),
..Default::default()
};
let domain = host_support::validate_http_url("https://evil.com/steal").unwrap();
assert!(
!grants.http_domains.contains(&domain),
"evil.com should not be in allowlist"
);
}
#[test]
fn conformance_http_rejects_plaintext() {
let err = host_support::validate_http_url("http://api.github.com/repos").unwrap_err();
assert!(
err.contains("HTTPS"),
"plaintext HTTP should be rejected: {}",
err
);
}
#[test]
fn conformance_http_rejects_ip_address() {
let err = host_support::validate_http_url("https://10.0.0.1/internal").unwrap_err();
assert!(err.contains("IP"), "IP address should be rejected: {}", err);
}
#[test]
fn conformance_http_rejects_localhost() {
let err = host_support::validate_http_url("https://localhost:8080/api").unwrap_err();
assert!(
err.contains("localhost"),
"localhost should be rejected: {}",
err
);
}
fn load_hello_task_component() -> Option<(task::TaskEngine, wasmtime::component::Component)> {
let wasm_path =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/hello_task.wasm");
if !wasm_path.exists() {
return None;
}
let engine = task::TaskEngine::new().expect("TaskEngine::new() failed");
let wasm_bytes = std::fs::read(&wasm_path).expect("failed to read hello-task wasm");
let component = engine
.load_component(&wasm_bytes)
.expect("load_component failed");
Some((engine, component))
}
fn hello_task_manifest() -> PluginManifest {
PluginManifest {
name: "hello-task".into(),
version: "0.1.0".into(),
description: "test".into(),
world: PluginWorld::Task,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into(), "host_layer".into()],
allowed_toy_commands: vec!["echo".into()],
host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
child: None,
commands: vec![],
..Default::default()
},
}
}
#[test]
fn task_hello_name() {
let (engine, component) = match load_hello_task_component() {
Some(ec) => ec,
None => {
panic!(
"test fixture missing: tests/fixtures/hello_task.wasm\n\
Build: cd tests/hello-task && cargo build --release --target wasm32-wasip2\n\
Copy: cp tests/hello-task/target/wasm32-wasip2/release/hello_task.wasm tests/fixtures/"
);
}
};
let name = engine
.get_task_name(&component)
.expect("get_task_name failed");
assert_eq!(name, "hello-task");
}
#[test]
fn task_hello_description() {
let (engine, component) = match load_hello_task_component() {
Some(ec) => ec,
None => return,
};
let desc = engine
.get_task_description(&component)
.expect("get_task_description failed");
assert!(
desc.contains("testing"),
"expected description to mention 'testing', got: {}",
desc
);
}
#[test]
fn task_hello_run_exit_code() {
let (engine, component) = match load_hello_task_component() {
Some(ec) => ec,
None => return,
};
let manifest = hello_task_manifest();
let (exit_code, _toys) = engine
.run_task(&component, &manifest, &[], None)
.expect("run_task failed");
assert_eq!(exit_code, 0, "hello-task should return exit code 0");
}
#[test]
fn task_hello_toys_filtered() {
let (engine, component) = match load_hello_task_component() {
Some(ec) => ec,
None => return,
};
let manifest = hello_task_manifest();
let (_exit_code, toys) = engine
.run_task(&component, &manifest, &[], None)
.expect("run_task failed");
assert_eq!(
toys.len(),
1,
"expected 1 approved toy (echo), got {}",
toys.len()
);
let toy = &toys[0];
assert_eq!(toy.name, "greet");
assert_eq!(toy.command, "echo");
assert_eq!(toy.args, vec!["hello"]);
}
#[test]
fn task_hello_unapproved_toy_denied() {
let (engine, component) = match load_hello_task_component() {
Some(ec) => ec,
None => return,
};
let manifest = PluginManifest {
name: "hello-task".into(),
version: "0.1.0".into(),
description: "test".into(),
world: PluginWorld::Task,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into(), "host_layer".into()],
allowed_toy_commands: vec![], host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
child: None,
commands: vec![],
..Default::default()
},
};
let (_exit_code, toys) = engine
.run_task(&component, &manifest, &[], None)
.expect("run_task failed");
assert!(
toys.is_empty(),
"expected no toys with empty allowed list, got {}",
toys.len()
);
}
fn load_echo_pipeline_component(
) -> Option<(pipeline::PipelineEngine, wasmtime::component::Component)> {
let wasm_path =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/echo_pipeline.wasm");
if !wasm_path.exists() {
return None;
}
let engine = pipeline::PipelineEngine::new().expect("PipelineEngine::new() failed");
let wasm_bytes = std::fs::read(&wasm_path).expect("failed to read echo-pipeline wasm");
let component = engine
.load_component(&wasm_bytes)
.expect("load_component failed");
Some((engine, component))
}
fn echo_pipeline_manifest() -> PluginManifest {
PluginManifest {
name: "echo-pipeline".into(),
version: "0.1.0".into(),
description: "test".into(),
world: PluginWorld::Pipeline,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
pipeline_ops: vec!["echo".into()],
..Default::default()
},
}
}
#[test]
fn pipeline_echo_name() {
let (engine, component) = match load_echo_pipeline_component() {
Some(ec) => ec,
None => {
panic!(
"test fixture missing: tests/fixtures/echo_pipeline.wasm\n\
Build: cd tests/echo-pipeline && cargo build --release --target wasm32-wasip2\n\
Copy: cp tests/echo-pipeline/target/wasm32-wasip2/release/echo_pipeline.wasm tests/fixtures/"
);
}
};
let name = engine.get_name(&component).expect("get_name failed");
assert_eq!(name, "echo-pipeline");
}
#[test]
fn pipeline_echo_handle_roundtrip() {
let (engine, component) = match load_echo_pipeline_component() {
Some(ec) => ec,
None => return,
};
let manifest = echo_pipeline_manifest();
let request = r#"{"op":"echo","version":"1","payload":{"key":"value","count":42}}"#;
let response = engine
.handle(&component, &manifest, request)
.expect("handle failed");
let parsed: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(parsed.get("key").and_then(|v| v.as_str()), Some("value"));
assert_eq!(parsed.get("count").and_then(|v| v.as_i64()), Some(42));
}
#[test]
fn pipeline_echo_unknown_op_error() {
let (engine, component) = match load_echo_pipeline_component() {
Some(ec) => ec,
None => return,
};
let manifest = echo_pipeline_manifest();
let request = r#"{"op":"frobnicate","version":"1","payload":{}}"#;
let result = engine.handle(&component, &manifest, request);
assert!(
result.is_err(),
"unknown op should return error, got: {:?}",
result
);
let err = result.unwrap_err().to_string();
assert!(
err.contains("unknown op"),
"error should mention 'unknown op', got: {}",
err
);
}
#[test]
fn pipeline_echo_version_mismatch_error() {
let (engine, component) = match load_echo_pipeline_component() {
Some(ec) => ec,
None => return,
};
let manifest = echo_pipeline_manifest();
let request = r#"{"op":"echo","version":"999","payload":{}}"#;
let result = engine.handle(&component, &manifest, request);
assert!(
result.is_err(),
"version mismatch should return error, got: {:?}",
result
);
let err = result.unwrap_err().to_string();
assert!(
err.contains("version"),
"error should mention 'version', got: {}",
err
);
}
fn load_panic_pipeline_component() -> Option<(PipelineEngine, wasmtime::component::Component)> {
let wasm_path =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/panic_pipeline.wasm");
if !wasm_path.exists() {
return None;
}
let engine = PipelineEngine::new().unwrap();
let wasm_bytes = std::fs::read(&wasm_path).unwrap();
let component = engine.load_component(&wasm_bytes).unwrap();
Some((engine, component))
}
#[test]
fn wasm_trap_pipeline_panic_returns_error() {
let (engine, component) = match load_panic_pipeline_component() {
Some(ec) => ec,
None => return,
};
let manifest = PluginManifest {
name: "panic-pipeline".into(),
version: "0.1.0".into(),
description: "deliberate panic".into(),
world: PluginWorld::Pipeline,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
pipeline_ops: vec!["echo".into()],
..Default::default()
},
};
let request = r#"{"op":"echo","version":"1","payload":{}}"#;
let result = engine.handle(&component, &manifest, request);
assert!(
result.is_err(),
"guest panic should be caught as error, not crash the host"
);
let err = result.unwrap_err().to_string();
assert!(
err.contains("unreachable") || err.contains("panic") || err.contains("trap"),
"error should indicate a WASM trap, got: {}",
err
);
}
#[test]
fn count_layer_files_rejects_path_traversal() {
let dir = tempfile::tempdir().unwrap();
let root = dir.path().to_path_buf();
let layer_core = root.join("layer").join("core");
std::fs::create_dir_all(&layer_core).unwrap();
std::fs::write(layer_core.join("test.md"), "# test").unwrap();
assert_eq!(
host_support::count_layer_files(&Some(root.clone()), "core"),
1
);
assert_eq!(
host_support::count_layer_files(&Some(root.clone()), "../../etc"),
0,
"parent dir traversal must return 0"
);
assert_eq!(
host_support::count_layer_files(&Some(root.clone()), "../.."),
0,
"bare parent traversal must return 0"
);
assert_eq!(
host_support::count_layer_files(&Some(root.clone()), "/etc"),
0,
"absolute path must return 0"
);
assert_eq!(
host_support::count_layer_files(&Some(root.clone()), "core/../../../etc"),
0,
"embedded traversal must return 0"
);
assert_eq!(host_support::count_layer_files(&None, "core"), 0);
}
#[test]
fn manifest_rejects_unknown_world() {
let f = write_temp_manifest(
r#"
[plugin]
name = "bad-world"
world = "oracle"
[capabilities]
host_log = true
[provides]
child = "test"
"#,
);
let err = PluginManifest::from_path(f.path()).unwrap_err();
assert!(
err.to_string().contains("unknown plugin world") && err.to_string().contains("oracle"),
"expected 'unknown plugin world: oracle', got: {}",
err
);
}
#[test]
fn check_capabilities_rejects_pipeline_with_query() {
let m = PluginManifest {
name: "bad-pipeline".into(),
version: "0.1.0".into(),
description: String::new(),
world: PluginWorld::Pipeline,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into(), "host_query".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec!["scry".into()],
host_http_domains: vec![],
provides: PluginProvides {
pipeline_ops: vec!["echo".into()],
..Default::default()
},
};
let err = PluginEngine::check_capabilities(&m).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("host_query") && msg.contains("not allowed for this world"),
"expected per-world capability rejection for host_query, got: {}",
msg
);
}
#[test]
fn check_capabilities_rejects_pipeline_with_http() {
let m = PluginManifest {
name: "bad-pipeline".into(),
version: "0.1.0".into(),
description: String::new(),
world: PluginWorld::Pipeline,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into(), "host_http".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec!["evil.com".into()],
provides: PluginProvides {
pipeline_ops: vec!["echo".into()],
..Default::default()
},
};
let err = PluginEngine::check_capabilities(&m).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("host_http"),
"expected host_http rejection, got: {}",
msg
);
}
#[test]
fn plugin_world_display() {
assert_eq!(PluginWorld::MotherChild.to_string(), "mother-child");
assert_eq!(PluginWorld::Command.to_string(), "command");
assert_eq!(PluginWorld::Task.to_string(), "task");
assert_eq!(PluginWorld::Pipeline.to_string(), "pipeline");
}
#[test]
fn plugin_world_roundtrip() {
for world in [
PluginWorld::MotherChild,
PluginWorld::Command,
PluginWorld::Task,
PluginWorld::Pipeline,
] {
let s = world.to_string();
let parsed = s.parse::<PluginWorld>().unwrap();
assert_eq!(parsed, world, "round-trip failed for {}", s);
}
}
#[test]
fn wasm_trap_mother_child_panic_returns_error() {
let wasm_path =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/panic_pipeline.wasm");
if !wasm_path.exists() {
return;
}
let engine = PluginEngine::new().unwrap();
let wasm_bytes = std::fs::read(&wasm_path).unwrap();
let component = engine.load_component(&wasm_bytes).unwrap();
let manifest = PluginManifest {
name: "wrong-world".into(),
version: "0.1.0".into(),
description: "world mismatch".into(),
world: PluginWorld::MotherChild,
patina_min: "0.0.0".into(),
capabilities: vec!["host_log".into()],
allowed_toy_commands: vec![],
host_query_kinds: vec![],
host_http_domains: vec![],
provides: PluginProvides {
child: Some("wrong".into()),
..Default::default()
},
};
let result = engine.instantiate_child(&component, &manifest, None);
assert!(
result.is_err(),
"wrong world instantiation should return Err, not crash"
);
}