use super::*;
use crate::test_support::temp_dir;
use canic_backup::manifest::{
BackupUnit, BackupUnitKind, ConsistencySection, FleetMember, FleetSection, IdentityMode,
SourceMetadata, SourceSnapshot, ToolMetadata, VerificationCheck, VerificationPlan,
};
const ROOT: &str = "aaaaa-aa";
const HASH: &str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
#[test]
fn parses_manifest_validate_options() {
let options = ManifestValidateOptions::parse([
OsString::from("--manifest"),
OsString::from("manifest.json"),
OsString::from("--out"),
OsString::from("summary.json"),
])
.expect("parse options");
assert_eq!(options.manifest, PathBuf::from("manifest.json"));
assert_eq!(options.out, Some(PathBuf::from("summary.json")));
}
#[test]
fn missing_manifest_validate_option_names_required_path() {
let err = ManifestValidateOptions::parse([]).expect_err("missing manifest option");
assert!(matches!(err, ManifestCommandError::Usage(_)));
assert!(err.to_string().contains("--manifest <file>"));
assert!(err.to_string().contains("canic manifest validate"));
}
#[test]
fn validate_manifest_reads_and_validates_manifest() {
let root = temp_dir("canic-cli-manifest-validate");
fs::create_dir_all(&root).expect("create temp root");
let manifest_path = root.join("manifest.json");
fs::write(
&manifest_path,
serde_json::to_vec(&valid_manifest()).expect("serialize manifest"),
)
.expect("write manifest");
let options = ManifestValidateOptions {
manifest: manifest_path,
out: None,
};
let manifest = validate_manifest(&options).expect("validate manifest");
fs::remove_dir_all(root).expect("remove temp root");
assert_eq!(manifest.backup_id, "backup-test");
assert_eq!(manifest.fleet.members.len(), 1);
}
#[test]
fn write_validation_summary_writes_out_file() {
let root = temp_dir("canic-cli-manifest-summary");
fs::create_dir_all(&root).expect("create temp root");
let out = root.join("summary.json");
let options = ManifestValidateOptions {
manifest: root.join("manifest.json"),
out: Some(out.clone()),
};
write_validation_summary(&options, &valid_manifest()).expect("write summary");
let summary: serde_json::Value =
serde_json::from_slice(&fs::read(&out).expect("read summary")).expect("parse summary");
fs::remove_dir_all(root).expect("remove temp root");
assert_eq!(summary["status"], "valid");
assert_eq!(summary["backup_id"], "backup-test");
assert_eq!(summary["members"], 1);
assert_eq!(summary["backup_unit_count"], 1);
assert_eq!(summary["topology_validation_status"], "validated");
assert_eq!(summary["backup_unit_kinds"]["subtree"], 1);
assert_eq!(summary["backup_units"][0]["unit_id"], "fleet");
assert_eq!(summary["backup_units"][0]["kind"], "subtree");
assert_eq!(summary["backup_units"][0]["role_count"], 1);
}
fn valid_manifest() -> FleetBackupManifest {
FleetBackupManifest {
manifest_version: 1,
backup_id: "backup-test".to_string(),
created_at: "2026-05-03T00:00:00Z".to_string(),
tool: ToolMetadata {
name: "canic".to_string(),
version: "0.30.1".to_string(),
},
source: SourceMetadata {
environment: "local".to_string(),
root_canister: ROOT.to_string(),
},
consistency: ConsistencySection {
backup_units: vec![BackupUnit {
unit_id: "fleet".to_string(),
kind: BackupUnitKind::Subtree,
roles: vec!["root".to_string()],
}],
},
fleet: FleetSection {
topology_hash_algorithm: "sha256".to_string(),
topology_hash_input: "sorted(pid,parent_pid,role,module_hash)".to_string(),
discovery_topology_hash: HASH.to_string(),
pre_snapshot_topology_hash: HASH.to_string(),
topology_hash: HASH.to_string(),
members: vec![fleet_member()],
},
verification: VerificationPlan::default(),
}
}
fn fleet_member() -> FleetMember {
FleetMember {
role: "root".to_string(),
canister_id: ROOT.to_string(),
parent_canister_id: None,
subnet_canister_id: Some(ROOT.to_string()),
controller_hint: None,
identity_mode: IdentityMode::Fixed,
verification_checks: vec![VerificationCheck {
kind: "status".to_string(),
roles: vec!["root".to_string()],
}],
source_snapshot: SourceSnapshot {
snapshot_id: "root-snapshot".to_string(),
module_hash: None,
code_version: Some("v0.30.1".to_string()),
artifact_path: "artifacts/root".to_string(),
checksum_algorithm: "sha256".to_string(),
checksum: Some(HASH.to_string()),
},
}
}