use super::*;
fn gated(stem: &str, markers: &[&str]) -> GatedAgent {
GatedAgent {
stem: stem.to_string(),
markers: markers.iter().map(|m| (*m).to_string()).collect(),
}
}
#[test]
fn manifest_roundtrip() {
let manifest = HarnessManifest {
version: Some(MANIFEST_VERSION),
agents: Some(AgentSet {
include: vec!["rust-engineer".into(), "qa".into()],
exclude: vec!["*-ops".into()],
ignore_staleness: vec!["engineer".into()],
source: ContentSource::Catalog,
}),
agent_categories: Some(AgentCategories {
universal: vec!["qa".into()],
language: vec![gated("rust-engineer", &["Cargo.toml"])],
framework: vec![gated("react-engineer", &["package.json::\"react\""])],
platform: vec![gated("vercel-ops", &["vercel.json"])],
deprecated: vec!["ops".into()],
}),
skill_categories: Some(SkillCategories {
universal: vec!["tm".into()],
}),
skills: Some(SkillSet {
include: vec!["example-skill".into()],
exclude: vec![],
source: ContentSource::Bundled,
}),
instructions: Some(InstructionLayers {
system: Some(true),
contextual: Some(true),
domain: Some(false),
}),
style: Some(StyleSelection {
active: Some("trusty-mpm-teacher".into()),
}),
mcp: Some(McpServers {
trusty_memory: Some(true),
trusty_search: Some(false),
custom: BTreeMap::from([(
"duetto-memory".to_string(),
CustomMcpServer::Http {
url: "https://mcp.example/memory".to_string(),
headers: BTreeMap::new(),
},
)]),
}),
models: Some(ModelTiers {
lightweight: Some("haiku".into()),
standard: None,
high: None,
intensive: Some("opus".into()),
}),
};
let toml = manifest.to_toml().expect("serialize");
let parsed = HarnessManifest::from_toml(&toml).expect("parse");
assert_eq!(manifest, parsed);
}
#[test]
fn manifest_partial_parse() {
let toml = r#"
version = 1
[style]
active = "trusty-mpm-research"
"#;
let parsed = HarnessManifest::from_toml(toml).expect("partial parse");
assert_eq!(parsed.version, Some(1));
assert_eq!(
parsed.style.as_ref().and_then(|s| s.active.as_deref()),
Some("trusty-mpm-research")
);
assert!(parsed.agents.is_none());
assert!(parsed.skills.is_none());
assert!(parsed.mcp.is_none());
}
#[test]
fn manifest_merge_overrides() {
let lower = HarnessManifest {
version: Some(1),
agents: Some(AgentSet {
include: vec!["a".into()],
..AgentSet::default()
}),
style: Some(StyleSelection {
active: Some("trusty-mpm".into()),
}),
mcp: Some(McpServers {
trusty_memory: Some(true),
trusty_search: Some(true),
..McpServers::default()
}),
..HarnessManifest::default()
};
let higher = HarnessManifest {
style: Some(StyleSelection {
active: Some("trusty-mpm-teacher".into()),
}),
..HarnessManifest::default()
};
let merged = lower.merge(higher);
assert_eq!(
merged.style.as_ref().and_then(|s| s.active.as_deref()),
Some("trusty-mpm-teacher")
);
assert_eq!(
merged.agents.as_ref().map(|a| a.include.clone()),
Some(vec!["a".to_string()])
);
assert_eq!(
merged.mcp.as_ref().and_then(|m| m.trusty_search),
Some(true)
);
}
#[test]
fn manifest_merge_keeps_lower_when_absent() {
let lower = HarnessManifest {
version: Some(1),
style: Some(StyleSelection {
active: Some("trusty-mpm".into()),
}),
..HarnessManifest::default()
};
let merged = lower.clone().merge(HarnessManifest::default());
assert_eq!(lower, merged);
}
#[test]
fn manifest_merge_mcp_field_level() {
let lower = HarnessManifest {
mcp: Some(McpServers {
trusty_memory: Some(true),
trusty_search: Some(true),
..McpServers::default()
}),
..HarnessManifest::default()
};
let higher = HarnessManifest {
mcp: Some(McpServers {
trusty_memory: None,
trusty_search: Some(false),
..McpServers::default()
}),
..HarnessManifest::default()
};
let merged = lower.merge(higher);
let mcp = merged.mcp.expect("mcp section present");
assert_eq!(
mcp.trusty_memory,
Some(true),
"the unmentioned toggle must survive the partial override"
);
assert_eq!(
mcp.trusty_search,
Some(false),
"the overridden toggle must take the higher layer's value"
);
}
#[test]
fn instruction_layers_field_merge() {
let lower = HarnessManifest {
instructions: Some(InstructionLayers {
system: Some(true),
contextual: Some(true),
domain: Some(true),
}),
..HarnessManifest::default()
};
let higher = HarnessManifest {
instructions: Some(InstructionLayers {
system: None,
contextual: None,
domain: Some(false),
}),
..HarnessManifest::default()
};
let layers = lower.merge(higher).instructions.expect("layers present");
assert_eq!(layers.system, Some(true), "system inherited from lower");
assert_eq!(
layers.contextual,
Some(true),
"contextual inherited from lower"
);
assert_eq!(layers.domain, Some(false), "domain overridden by higher");
}
#[test]
fn selection_matches() {
assert!(super::selection_matches("anything", &[], &[]));
let inc = vec!["rust-engineer".to_string()];
assert!(super::selection_matches("rust-engineer", &inc, &[]));
assert!(!super::selection_matches("qa", &inc, &[]));
let glob_inc = vec!["*-engineer".to_string()];
assert!(super::selection_matches("rust-engineer", &glob_inc, &[]));
assert!(!super::selection_matches("rust-ops", &glob_inc, &[]));
let multi_inc = vec!["*-engineer-*".to_string()];
assert!(super::selection_matches(
"rust-engineer-ops",
&multi_inc,
&[]
));
assert!(!super::selection_matches("rust-engineer", &multi_inc, &[]));
let exc = vec!["rust-engineer".to_string()];
assert!(!super::selection_matches("rust-engineer", &[], &exc));
let inc_and_exc = ["rust-engineer".to_string()];
assert!(!super::selection_matches(
"rust-engineer",
&inc_and_exc,
&exc
));
}
#[test]
fn glob_matching() {
assert!(super::glob_matches("*", "anything"));
assert!(super::glob_matches("*", ""));
assert!(super::glob_matches("rust-engineer", "rust-engineer"));
assert!(!super::glob_matches("rust-engineer", "qa"));
assert!(!super::glob_matches("rust-engineer", "rust-engineer-2"));
assert!(super::glob_matches("*-engineer", "rust-engineer"));
assert!(!super::glob_matches("*-engineer", "rust-engineer-ops"));
assert!(super::glob_matches("engineer-*", "engineer-rust"));
assert!(!super::glob_matches("engineer-*", "rust-engineer"));
assert!(super::glob_matches("rust-*", "rust-engineer"));
assert!(super::glob_matches("a*b", "axxb"));
assert!(super::glob_matches("a*b", "ab"), "interior * matches empty");
assert!(!super::glob_matches("a*b", "axx"));
assert!(super::glob_matches("*-engineer-*", "rust-engineer-ops"));
assert!(super::glob_matches("*-engineer-*", "x-engineer-y"));
assert!(
!super::glob_matches("*-engineer-*", "rust-engineer"),
"no trailing segment after -engineer- → no match"
);
assert!(super::glob_matches("a*b*c", "aXbYc"));
assert!(super::glob_matches("a*b*c", "abc"));
assert!(
!super::glob_matches("a*b*c", "acb"),
"segments must appear in order"
);
assert!(
!super::glob_matches("a*b*c", "aXbY"),
"missing the trailing c suffix"
);
}
#[test]
fn content_source_roundtrip() {
let serialized = toml::to_string(&AgentSet {
source: ContentSource::Catalog,
..AgentSet::default()
})
.unwrap();
assert!(serialized.contains("catalog"));
}
#[test]
fn agent_set_source_default_is_bundled() {
let toml = "[agents]\ninclude = []\n";
let parsed = HarnessManifest::from_toml(toml).unwrap();
assert_eq!(parsed.agents.unwrap().source, ContentSource::Bundled);
}
#[test]
fn custom_mcp_server_stdio_roundtrip() {
let toml = r#"
[mcp.custom.my-local-tool]
type = "stdio"
command = "my-tool"
args = ["serve"]
[mcp.custom.my-local-tool.env]
API_KEY = "secret"
"#;
let parsed = HarnessManifest::from_toml(toml).expect("parse");
let custom = &parsed.mcp.expect("mcp section present").custom;
let entry = custom.get("my-local-tool").expect("entry present");
match entry {
CustomMcpServer::Stdio { command, args, env } => {
assert_eq!(command, "my-tool");
assert_eq!(args, &vec!["serve".to_string()]);
assert_eq!(env.get("API_KEY").map(String::as_str), Some("secret"));
}
other => panic!("expected Stdio, got {other:?}"),
}
}
#[test]
fn custom_mcp_server_remote_roundtrip() {
let toml = r#"
[mcp.custom.duetto-memory]
type = "http"
url = "https://mcp-services.dev.duettosystems.com/memory/mcp"
"#;
let parsed = HarnessManifest::from_toml(toml).expect("parse");
let custom = parsed.mcp.expect("mcp section present").custom;
match custom.get("duetto-memory").expect("entry present") {
CustomMcpServer::Http { url, headers } => {
assert_eq!(url, "https://mcp-services.dev.duettosystems.com/memory/mcp");
assert!(headers.is_empty());
}
other => panic!("expected Http, got {other:?}"),
}
let manifest = HarnessManifest {
mcp: Some(McpServers {
custom: BTreeMap::from([
(
"a".to_string(),
CustomMcpServer::Http {
url: "https://a.example/mcp".to_string(),
headers: BTreeMap::new(),
},
),
(
"b".to_string(),
CustomMcpServer::Sse {
url: "https://b.example/sse".to_string(),
headers: BTreeMap::new(),
},
),
]),
..McpServers::default()
}),
..HarnessManifest::default()
};
let toml = manifest.to_toml().expect("serialize");
let parsed = HarnessManifest::from_toml(&toml).expect("parse");
assert_eq!(manifest, parsed);
}
#[test]
fn mcp_servers_merge_unions_custom() {
let lower = McpServers {
custom: BTreeMap::from([
(
"shared".to_string(),
CustomMcpServer::Stdio {
command: "lower-cmd".to_string(),
args: vec![],
env: BTreeMap::new(),
},
),
(
"lower-only".to_string(),
CustomMcpServer::Stdio {
command: "lower-only-cmd".to_string(),
args: vec![],
env: BTreeMap::new(),
},
),
]),
..McpServers::default()
};
let higher = McpServers {
custom: BTreeMap::from([(
"shared".to_string(),
CustomMcpServer::Stdio {
command: "higher-cmd".to_string(),
args: vec![],
env: BTreeMap::new(),
},
)]),
..McpServers::default()
};
let merged = lower.merge(higher);
assert_eq!(merged.custom.len(), 2, "both names survive the union");
match &merged.custom["shared"] {
CustomMcpServer::Stdio { command, .. } => {
assert_eq!(command, "higher-cmd", "higher layer wins the shared name");
}
other => panic!("expected Stdio, got {other:?}"),
}
assert!(merged.custom.contains_key("lower-only"));
}
#[test]
fn agent_categories_roundtrip() {
let raw = r#"
version = 1
[agent_categories]
universal = ["qa"]
deprecated = ["ops"]
[[agent_categories.language]]
stem = "rust-engineer"
markers = ["Cargo.toml"]
[[agent_categories.platform]]
stem = "vercel-ops"
markers = ["vercel.json", ".vercelignore"]
[skill_categories]
universal = ["tm"]
"#;
let parsed = HarnessManifest::from_toml(raw).expect("parses");
let cats = parsed.agent_categories.clone().expect("section present");
assert_eq!(cats.universal, vec!["qa".to_string()]);
assert_eq!(cats.deprecated, vec!["ops".to_string()]);
assert_eq!(cats.language, vec![gated("rust-engineer", &["Cargo.toml"])]);
assert_eq!(
cats.platform,
vec![gated("vercel-ops", &["vercel.json", ".vercelignore"])]
);
assert_eq!(
parsed
.skill_categories
.clone()
.expect("skill section present")
.universal,
vec!["tm".to_string()]
);
let round =
HarnessManifest::from_toml(&parsed.to_toml().expect("serializes")).expect("re-parses");
assert_eq!(round, parsed);
}
#[test]
fn skill_categories_roundtrip() {
let raw = "[skill_categories]\nuniversal = [\"tm\", \"tm-doctor\"]\n";
let parsed = HarnessManifest::from_toml(raw).expect("parses");
let skills = parsed
.skill_categories
.clone()
.expect("section present")
.universal;
assert_eq!(skills, vec!["tm".to_string(), "tm-doctor".to_string()]);
let round =
HarnessManifest::from_toml(&parsed.to_toml().expect("serializes")).expect("re-parses");
assert_eq!(round, parsed);
let without = HarnessManifest::from_toml("[agents]\ninclude = [\"qa\"]\n").expect("parses");
assert!(
without.skill_categories.is_none(),
"the section is additive — every other layer omits it"
);
}
#[test]
fn agent_categories_absent_parses_as_none() {
let parsed = HarnessManifest::from_toml("[agents]\ninclude = [\"qa\"]\n").expect("parses");
assert!(parsed.agent_categories.is_none());
}
#[test]
fn agent_categories_merge_is_whole_section() {
let lower = HarnessManifest {
agent_categories: Some(AgentCategories {
universal: vec!["qa".into()],
..AgentCategories::default()
}),
..HarnessManifest::default()
};
let higher = HarnessManifest {
agent_categories: Some(AgentCategories {
universal: vec!["engineer".into()],
..AgentCategories::default()
}),
..HarnessManifest::default()
};
let merged = lower.clone().merge(higher);
assert_eq!(
merged.agent_categories.unwrap().universal,
vec!["engineer".to_string()]
);
let inherited = lower.merge(HarnessManifest::default());
assert_eq!(
inherited.agent_categories.unwrap().universal,
vec!["qa".to_string()]
);
}