use std::fs;
use std::os::unix::fs::{PermissionsExt, symlink};
use std::path::Path;
use serde_json::{Value, json};
use super::{Fixture, TestResult, install};
fn installed_json(fixture: &Fixture, name: &str) -> Result<Value, Box<dyn std::error::Error>> {
let path = fixture.bundle.join(name);
assert!(path.is_file(), "bundle payload missing: {name}");
assert_eq!(fs::metadata(&path)?.permissions().mode() & 0o777, 0o600);
Ok(serde_json::from_slice(&fs::read(path)?)?)
}
#[test]
fn bundle_plugin_mcp_uses_existing_absolute_paths_under_the_canonical_destination() -> TestResult {
let fixture = Fixture::new()?;
let alias = fixture.directory.path().join("bundle-parent");
symlink(
fixture.bundle.parent().ok_or("bundle parent missing")?,
&alias,
)?;
let report = install(&fixture.binary, &fixture.config, &alias.join("bundle"))?;
assert_eq!(
report.directory,
fixture.bundle.to_str().ok_or("bundle path")?
);
let mcp = installed_json(&fixture, ".mcp.json")?;
let server = &mcp["mcpServers"]["haematite"];
assert_eq!(
server,
&json!({
"command": fixture.bundle.join("scripts/mcp.sh"),
"env": {
"HAEMATITE_BIN": fixture.bundle.join("bin/haematite")
},
"env_vars": [
"HAEMATITE_LYS_AGENT", "HAEMATITE_LYS_ISSUER", "HAEMATITE_KEY_FILE",
"HAEMATITE_CREDENTIAL_FILE", "HAEMATITE_RUN_PASS_ENV", "HAEMATITE_URL",
"LYS_AGENT_PASS", "HOME", "PATH"
]
})
);
for value in [&server["command"], &server["env"]["HAEMATITE_BIN"]] {
let path = Path::new(value.as_str().ok_or("MCP path missing")?);
assert!(path.is_absolute());
assert!(path.is_file());
}
Ok(())
}
#[test]
fn bundle_plugin_codex_manifest_registers_the_bundled_skills_and_mcp_file() -> TestResult {
let fixture = Fixture::new()?;
install(&fixture.binary, &fixture.config, &fixture.bundle)?;
let plugin = installed_json(&fixture, ".codex-plugin/plugin.json")?;
assert_eq!(plugin["name"], "haematite");
assert_eq!(plugin["skills"], "./skills/");
assert_eq!(plugin["mcpServers"], "./.mcp.json");
assert!(fixture.bundle.join("skills/haematite/SKILL.md").is_file());
assert!(fixture.bundle.join(".mcp.json").is_file());
Ok(())
}
#[test]
fn bundle_plugin_claude_marketplace_points_at_its_own_directory() -> TestResult {
let fixture = Fixture::new()?;
install(&fixture.binary, &fixture.config, &fixture.bundle)?;
let marketplace = installed_json(&fixture, ".claude-plugin/marketplace.json")?;
let plugin: Value = serde_json::from_str(crate::mcp::schema::skill::MANIFEST)?;
assert_eq!(
marketplace,
json!({
"name": "haematite",
"description": plugin["description"],
"owner": {"name": "Ablative"},
"plugins": [{
"name": "haematite", "source": "./", "description": plugin["description"]
}]
})
);
Ok(())
}
#[test]
fn bundle_plugin_codex_marketplace_points_at_its_own_directory() -> TestResult {
let fixture = Fixture::new()?;
install(&fixture.binary, &fixture.config, &fixture.bundle)?;
let marketplace = installed_json(&fixture, ".agents/plugins/marketplace.json")?;
assert_eq!(
marketplace,
json!({
"name": "haematite",
"interface": {"displayName": "Haematite"},
"plugins": [{
"name": "haematite", "source": {"source": "local", "path": "./"},
"policy": {"installation": "AVAILABLE", "authentication": "ON_INSTALL"},
"category": "Productivity"
}]
})
);
Ok(())
}