use super::*;
use crate::test_support::temp_dir;
use canic_backup::{persistence::BackupLayout, restore::RestorePlan};
use serde_json::json;
use std::{ffi::OsString, fs};
#[test]
fn plan_restore_reads_manifest_from_backup_dir() {
let root = temp_dir("canic-cli-restore-plan-layout");
let layout = BackupLayout::new(root.clone());
layout
.write_manifest(&valid_manifest())
.expect("write manifest");
let options = RestorePlanOptions {
manifest: None,
backup_dir: Some(root.clone()),
mapping: None,
out: None,
require_verified: false,
require_restore_ready: false,
};
let plan = plan_restore(&options).expect("plan restore");
fs::remove_dir_all(root).expect("remove temp root");
assert_eq!(plan.backup_id, "backup-test");
assert_eq!(plan.member_count, 2);
}
#[test]
fn parse_rejects_conflicting_manifest_sources() {
let err = RestorePlanOptions::parse([
OsString::from("--manifest"),
OsString::from("manifest.json"),
OsString::from("--backup-dir"),
OsString::from("backups/run"),
])
.expect_err("conflicting sources should fail");
assert!(matches!(err, RestoreCommandError::Usage(_)));
}
#[test]
fn parse_rejects_require_verified_with_manifest_source() {
let err = RestorePlanOptions::parse([
OsString::from("--manifest"),
OsString::from("manifest.json"),
OsString::from("--require-verified"),
])
.expect_err("verification should require a backup layout");
assert!(matches!(err, RestoreCommandError::Usage(_)));
}
#[test]
fn plan_restore_requires_verified_backup_layout() {
let root = temp_dir("canic-cli-restore-plan-verified");
let layout = BackupLayout::new(root.clone());
let manifest = valid_manifest();
write_verified_layout(&root, &layout, &manifest);
let options = RestorePlanOptions {
manifest: None,
backup_dir: Some(root.clone()),
mapping: None,
out: None,
require_verified: true,
require_restore_ready: false,
};
let plan = plan_restore(&options).expect("plan verified restore");
fs::remove_dir_all(root).expect("remove temp root");
assert_eq!(plan.backup_id, "backup-test");
assert_eq!(plan.member_count, 2);
}
#[test]
fn plan_restore_rejects_unverified_backup_layout() {
let root = temp_dir("canic-cli-restore-plan-unverified");
let layout = BackupLayout::new(root.clone());
layout
.write_manifest(&valid_manifest())
.expect("write manifest");
let options = RestorePlanOptions {
manifest: None,
backup_dir: Some(root.clone()),
mapping: None,
out: None,
require_verified: true,
require_restore_ready: false,
};
let err = plan_restore(&options).expect_err("missing journal should fail");
fs::remove_dir_all(root).expect("remove temp root");
assert!(matches!(err, RestoreCommandError::Persistence(_)));
}
#[test]
fn plan_restore_reads_manifest_and_mapping() {
let root = temp_dir("canic-cli-restore-plan");
fs::create_dir_all(&root).expect("create temp root");
let manifest_path = root.join("manifest.json");
let mapping_path = root.join("mapping.json");
fs::write(
&manifest_path,
serde_json::to_vec(&valid_manifest()).expect("serialize manifest"),
)
.expect("write manifest");
fs::write(
&mapping_path,
json!({
"members": [
{"source_canister": ROOT, "target_canister": ROOT},
{"source_canister": CHILD, "target_canister": MAPPED_CHILD}
]
})
.to_string(),
)
.expect("write mapping");
let options = RestorePlanOptions {
manifest: Some(manifest_path),
backup_dir: None,
mapping: Some(mapping_path),
out: None,
require_verified: false,
require_restore_ready: false,
};
let plan = plan_restore(&options).expect("plan restore");
fs::remove_dir_all(root).expect("remove temp root");
let members = plan.ordered_members();
assert_eq!(members.len(), 2);
assert_eq!(members[0].source_canister, ROOT);
assert_eq!(members[1].target_canister, MAPPED_CHILD);
}
#[test]
fn run_restore_plan_require_restore_ready_writes_plan_then_fails() {
let root = temp_dir("canic-cli-restore-plan-require-ready");
fs::create_dir_all(&root).expect("create temp root");
let manifest_path = root.join("manifest.json");
let out_path = root.join("plan.json");
fs::write(
&manifest_path,
serde_json::to_vec(&valid_manifest()).expect("serialize manifest"),
)
.expect("write manifest");
let err = run([
OsString::from("plan"),
OsString::from("--manifest"),
OsString::from(manifest_path.as_os_str()),
OsString::from("--out"),
OsString::from(out_path.as_os_str()),
OsString::from("--require-restore-ready"),
])
.expect_err("restore readiness should be enforced");
assert!(out_path.exists());
let plan: RestorePlan =
serde_json::from_slice(&fs::read(&out_path).expect("read plan")).expect("decode plan");
fs::remove_dir_all(root).expect("remove temp root");
assert!(!plan.readiness_summary.ready);
assert!(matches!(
err,
RestoreCommandError::RestoreNotReady {
reasons,
..
} if reasons == ["missing-snapshot-checksum"]
));
}
#[test]
fn run_restore_plan_require_restore_ready_accepts_ready_plan() {
let root = temp_dir("canic-cli-restore-plan-ready");
fs::create_dir_all(&root).expect("create temp root");
let manifest_path = root.join("manifest.json");
let out_path = root.join("plan.json");
fs::write(
&manifest_path,
serde_json::to_vec(&restore_ready_manifest()).expect("serialize manifest"),
)
.expect("write manifest");
run([
OsString::from("plan"),
OsString::from("--manifest"),
OsString::from(manifest_path.as_os_str()),
OsString::from("--out"),
OsString::from(out_path.as_os_str()),
OsString::from("--require-restore-ready"),
])
.expect("restore-ready plan should pass");
let plan: RestorePlan =
serde_json::from_slice(&fs::read(&out_path).expect("read plan")).expect("decode plan");
fs::remove_dir_all(root).expect("remove temp root");
assert!(plan.readiness_summary.ready);
assert!(plan.readiness_summary.reasons.is_empty());
}