use std::{collections::BTreeSet, fs, path::PathBuf};
use alma::{
config::AppConfig,
plugin::{
CURRENT_WIT_WORLD, MAX_PLUGIN_FUEL_PER_UPDATE, MAX_PLUGIN_INTENT_BATCHES,
MAX_PLUGIN_MEMORY_BYTES, MAX_PLUGIN_MESSAGE_BYTES, MAX_PLUGIN_TIMEOUT_MS, PluginManifest,
},
};
use serde_json::Value;
const VALID_CONFIG_FIXTURES: &[&str] = &[
"config-minimal-valid.json",
"config-maximal-explicit.json",
"config-disabled.json",
"config-whole-workspace-grant.json",
];
const INVALID_CONFIG_FIXTURES: &[&str] = &[
"config-unknown-plugin-field.json",
"config-invalid-runtime-limit.json",
"config-invalid-intent-queue-limit.json",
"config-invalid-proposal-policy.json",
];
const VALID_MANIFEST_FIXTURES: &[&str] = &["manifest-valid.json"];
const INVALID_MANIFEST_FIXTURES: &[&str] = &[
"manifest-mismatched-world.json",
"manifest-unknown-capability.json",
"manifest-unknown-field.json",
];
const REQUIRED_PLUGIN_GOLDEN_COVERAGE: &[&str] = &[
"plugin.config.schema.checked-in",
"plugin.config.schema.current-wit-world",
"plugin.config.schema.runtime-limit-caps",
"plugin.config.schema.valid-minimal",
"plugin.config.schema.valid-maximal",
"plugin.config.schema.disabled",
"plugin.config.schema.whole-workspace-grant",
"plugin.config.schema.unknown-field",
"plugin.config.schema.invalid-runtime-limit",
"plugin.config.schema.invalid-intent-queue-limit",
"plugin.config.schema.invalid-proposal-policy",
"plugin.config.schema.proposal-policy-vocabulary",
"plugin.manifest.schema.checked-in",
"plugin.manifest.schema.current-wit-world",
"plugin.manifest.schema.valid",
"plugin.manifest.schema.mismatched-world",
"plugin.manifest.schema.unknown-capability",
"plugin.manifest.schema.unknown-field",
"plugin.capability.schema.reviewed-vocabulary",
];
const PLUGIN_GOLDEN_COVERAGE: &[(&str, &[&str])] = &[
(
"config-minimal-valid.json",
&["plugin.config.schema.valid-minimal"],
),
(
"config-maximal-explicit.json",
&["plugin.config.schema.valid-maximal"],
),
("config-disabled.json", &["plugin.config.schema.disabled"]),
(
"config-whole-workspace-grant.json",
&["plugin.config.schema.whole-workspace-grant"],
),
(
"config-unknown-plugin-field.json",
&["plugin.config.schema.unknown-field"],
),
(
"config-invalid-runtime-limit.json",
&["plugin.config.schema.invalid-runtime-limit"],
),
(
"config-invalid-intent-queue-limit.json",
&["plugin.config.schema.invalid-intent-queue-limit"],
),
(
"config-invalid-proposal-policy.json",
&["plugin.config.schema.invalid-proposal-policy"],
),
("manifest-valid.json", &["plugin.manifest.schema.valid"]),
(
"manifest-mismatched-world.json",
&["plugin.manifest.schema.mismatched-world"],
),
(
"manifest-unknown-capability.json",
&["plugin.manifest.schema.unknown-capability"],
),
(
"manifest-unknown-field.json",
&["plugin.manifest.schema.unknown-field"],
),
(
"plugin-config.schema.json",
&[
"plugin.config.schema.checked-in",
"plugin.config.schema.current-wit-world",
"plugin.config.schema.runtime-limit-caps",
"plugin.config.schema.proposal-policy-vocabulary",
"plugin.capability.schema.reviewed-vocabulary",
],
),
(
"plugin-manifest.schema.json",
&[
"plugin.manifest.schema.checked-in",
"plugin.manifest.schema.current-wit-world",
"plugin.capability.schema.reviewed-vocabulary",
],
),
];
#[test]
fn plugin_goldens_cover_required_behavior() {
let required = REQUIRED_PLUGIN_GOLDEN_COVERAGE
.iter()
.copied()
.collect::<BTreeSet<_>>();
let mut covered = BTreeSet::new();
let mut fixture_names = BTreeSet::<String>::new();
for (name, labels) in PLUGIN_GOLDEN_COVERAGE {
assert!(
fixture_names.insert((*name).to_owned()),
"duplicate plugin golden coverage entry for {name}"
);
for label in *labels {
assert!(
required.contains(label),
"{name} declares unknown plugin golden coverage label {label:?}"
);
covered.insert(*label);
}
}
let missing = required.difference(&covered).copied().collect::<Vec<_>>();
assert!(
missing.is_empty(),
"missing required plugin golden coverage labels: {missing:?}"
);
let orphan_fixtures = plugin_golden_fixture_names()
.difference(&fixture_names)
.cloned()
.collect::<Vec<_>>();
assert!(
orphan_fixtures.is_empty(),
"plugin golden fixtures without coverage entries: {orphan_fixtures:?}"
);
}
#[test]
fn checked_in_plugin_config_schema_accepts_valid_fixtures() {
let schema = load_schema("plugin-config.schema.json");
for name in VALID_CONFIG_FIXTURES {
let value = load_json(name);
assert_schema_accepts(&schema, &value, name);
let _config = AppConfig::from_json_slice(
&serde_json::to_vec(&value).expect("fixture JSON should serialize"),
&fixture_path(name),
)
.unwrap_or_else(|error| panic!("{name} should load through typed config: {error}"));
}
}
#[test]
fn checked_in_plugin_config_schema_rejects_invalid_fixtures() {
let schema = load_schema("plugin-config.schema.json");
for name in INVALID_CONFIG_FIXTURES {
let value = load_json(name);
assert_schema_rejects(&schema, &value, name);
assert!(
AppConfig::from_json_slice(
&serde_json::to_vec(&value).expect("fixture JSON should serialize"),
&fixture_path(name),
)
.is_err(),
"{name} should fail typed config loading"
);
}
}
#[test]
fn checked_in_plugin_manifest_schema_accepts_valid_fixtures() {
let schema = load_schema("plugin-manifest.schema.json");
for name in VALID_MANIFEST_FIXTURES {
let value = load_json(name);
assert_schema_accepts(&schema, &value, name);
let _manifest = PluginManifest::from_json_slice(
&serde_json::to_vec(&value).expect("fixture JSON should serialize"),
Some(&fixture_path(name)),
)
.unwrap_or_else(|error| panic!("{name} should load through typed manifest: {error}"));
}
}
#[test]
fn checked_in_plugin_manifest_schema_rejects_invalid_fixtures() {
let schema = load_schema("plugin-manifest.schema.json");
for name in INVALID_MANIFEST_FIXTURES {
let value = load_json(name);
assert_schema_rejects(&schema, &value, name);
let parsed = PluginManifest::from_json_slice(
&serde_json::to_vec(&value).expect("fixture JSON should serialize"),
Some(&fixture_path(name)),
);
if *name == "manifest-mismatched-world.json" {
assert!(
parsed.is_ok(),
"{name} should parse as untrusted manifest data before authorization"
);
} else {
assert!(parsed.is_err(), "{name} should fail typed manifest loading");
}
}
}
#[test]
fn plugin_schemas_name_only_reviewed_capabilities() {
for name in ["plugin-config.schema.json", "plugin-manifest.schema.json"] {
let schema = load_schema(name);
let capability_properties = schema
.pointer("/$defs/capabilities/properties")
.and_then(Value::as_object)
.expect("capability schema should expose reviewed properties");
assert_eq!(
capability_properties.keys().collect::<Vec<_>>(),
[
"buffer.observe",
"buffer.propose_edit",
"status.publish",
"workspace.artifact_write",
"workspace.observe",
],
"{name} capability vocabulary drifted"
);
}
}
#[test]
fn plugin_schemas_match_current_wit_world() {
let config_schema = load_schema("plugin-config.schema.json");
assert_eq!(
config_schema
.pointer("/$defs/plugin/properties/wit_world/const")
.and_then(Value::as_str),
Some(CURRENT_WIT_WORLD)
);
let manifest_schema = load_schema("plugin-manifest.schema.json");
assert_eq!(
manifest_schema
.pointer("/properties/wit_world/const")
.and_then(Value::as_str),
Some(CURRENT_WIT_WORLD)
);
}
#[test]
fn plugin_config_schema_matches_runtime_limit_caps() {
let schema = load_schema("plugin-config.schema.json");
let limits = [
("max_memory_bytes", MAX_PLUGIN_MEMORY_BYTES),
("max_message_bytes", MAX_PLUGIN_MESSAGE_BYTES),
("fuel_per_update", MAX_PLUGIN_FUEL_PER_UPDATE),
("timeout_ms", MAX_PLUGIN_TIMEOUT_MS),
("max_intent_batches", MAX_PLUGIN_INTENT_BATCHES),
];
for (field, max) in limits {
let limit = schema
.pointer(&format!("/$defs/runtime_limits/properties/{field}"))
.unwrap_or_else(|| panic!("runtime limit schema missing {field}"));
assert_eq!(
limit.pointer("/minimum").and_then(Value::as_u64),
Some(1),
"{field} schema minimum drifted"
);
assert_eq!(
limit.pointer("/maximum").and_then(Value::as_u64),
Some(max),
"{field} schema maximum drifted"
);
}
}
#[test]
fn plugin_config_schema_names_only_reviewed_proposal_modes() {
let schema = load_schema("plugin-config.schema.json");
let modes = schema
.pointer("/$defs/proposal_review_mode/enum")
.and_then(Value::as_array)
.expect("proposal review mode schema should expose reviewed enum values");
let expected = [Value::from("auto_apply"), Value::from("manual_review")];
assert_eq!(modes.as_slice(), expected);
}
fn load_schema(name: &str) -> Value {
load_json(name)
}
fn load_json(name: &str) -> Value {
let path = fixture_path(name);
serde_json::from_slice(&fs::read(&path).expect("fixture should read"))
.unwrap_or_else(|error| panic!("{} should parse as JSON: {error}", path.display()))
}
fn assert_schema_accepts(schema: &Value, value: &Value, name: &str) {
let validator = jsonschema::validator_for(schema).expect("schema should compile");
if let Err(error) = validator.validate(value) {
panic!("{name} should satisfy checked-in schema: {error}");
}
}
fn assert_schema_rejects(schema: &Value, value: &Value, name: &str) {
let validator = jsonschema::validator_for(schema).expect("schema should compile");
assert!(
validator.validate(value).is_err(),
"{name} should fail checked-in schema"
);
}
fn fixture_path(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/goldens/plugin")
.join(name)
}
fn plugin_golden_fixture_names() -> BTreeSet<String> {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/goldens/plugin");
fs::read_dir(root)
.expect("plugin golden directory should read")
.filter_map(|entry| {
let path = entry.expect("plugin golden entry should read").path();
path.extension()
.is_some_and(|extension| extension == "json")
.then(|| {
path.file_name()
.and_then(|name| name.to_str())
.expect("plugin golden fixture name should be UTF-8")
.to_owned()
})
})
.collect()
}