use super::*;
use crate::test_support::temp_dir;
use std::fs;
#[test]
fn parses_medic_options() {
let options = MedicOptions::parse_info([
OsString::from("demo"),
OsString::from(crate::cli::globals::INTERNAL_NETWORK_OPTION),
OsString::from("local"),
OsString::from(crate::cli::globals::INTERNAL_ICP_OPTION),
OsString::from("/tmp/icp"),
])
.expect("parse medic options");
assert_eq!(options.deployment, "demo");
assert_eq!(options.blob_storage, None);
assert_eq!(options.network, "local");
assert_eq!(options.icp, "/tmp/icp");
}
#[test]
fn parses_blob_storage_medic_target() {
let options = MedicOptions::parse_info([
OsString::from("demo"),
OsString::from("--blob-storage"),
OsString::from("backend"),
])
.expect("parse medic options");
assert_eq!(options.deployment, "demo");
assert_eq!(options.blob_storage.as_deref(), Some("backend"));
}
#[test]
fn medic_usage_includes_examples() {
let text = info_usage();
assert!(text.contains("Diagnose local Canic deployment target setup"));
assert!(text.contains("Usage: canic info medic [OPTIONS] <deployment>"));
assert!(text.contains("<deployment>"));
assert!(text.contains("--blob-storage <canister-or-role>"));
assert!(!text.contains("--fleet <name>"));
assert!(!text.contains("--network"));
assert!(!text.contains("--icp"));
assert!(text.contains("Examples:"));
}
#[test]
fn renders_medic_report() {
let report = render_medic_report(&[
MedicCheck::ok("network", "local", "-"),
MedicCheck::warn(
"deployment state",
"no installed deployment found",
"run canic install",
),
]);
assert!(report.starts_with("network [ok]"));
assert!(report.contains("\n detail: local\n"));
assert!(report.contains("deployment state [warn]"));
assert!(report.contains(" next: run canic install"));
assert!(!report.contains("CHECK"));
}
#[test]
fn renders_blob_storage_medic_summary() {
let check = blob_storage_medic_check_from_summary(BlobStorageMedicSummary {
status: BlobStorageMedicStatus::Blocked,
detail: "readiness=blocked; configured=true; gateways=0; funding=funding_needed"
.to_string(),
next: "canic blob-storage sync-gateways demo backend".to_string(),
});
let report = render_medic_report(&[check]);
assert!(report.contains("blob-storage billing [warn]"));
assert!(report.contains("readiness=blocked"));
assert!(report.contains("canic blob-storage sync-gateways demo backend"));
}
#[test]
fn passive_blob_storage_hint_uses_local_candid_only() {
let root = temp_dir("canic-cli-medic-blob-storage-passive");
write_candid(
&root,
"local",
"backend",
r#"
service : {
get_blob_storage_status : () -> () query;
"_immutableObjectStorageUpdateGatewayPrincipals" : () -> ();
"_immutableObjectStorageFundFromProjectCycles" : (nat) -> ();
}
"#,
);
write_candid(
&root,
"local",
"other",
r"
service : {
get_blob_storage_status : () -> () query;
}
",
);
write_candid(
&root,
"local",
"partial",
r#"
service : {
get_blob_storage_status : () -> () query;
"_immutableObjectStorageUpdateGatewayPrincipals" : () -> ();
}
"#,
);
let roles = blob_storage_billing_roles_from_candid_dir(&root, "local");
let options = MedicOptions {
deployment: "demo".to_string(),
blob_storage: None,
network: "local".to_string(),
icp: "icp".to_string(),
};
let check = check_blob_storage_passive_hint(&options, &root).expect("passive hint");
assert_eq!(roles, vec!["backend".to_string()]);
assert_eq!(check.status, MedicStatus::Ok);
assert!(check.detail.contains("backend"));
assert_eq!(
check.next,
"run canic info medic demo --blob-storage backend"
);
fs::remove_dir_all(root).expect("remove temp root");
}
#[test]
fn blob_storage_passive_detection_rejects_partial_or_unrelated_candid() {
assert!(candid_declares_blob_storage_billing(
r#"
service : {
get_blob_storage_status : () -> () query;
"_immutableObjectStorageUpdateGatewayPrincipals" : () -> ();
"_immutableObjectStorageFundFromProjectCycles" : (nat) -> ();
}
"#
));
assert!(!candid_declares_blob_storage_billing(
r#"
service : {
get_blob_storage_status : () -> () query;
"_immutableObjectStorageUpdateGatewayPrincipals" : () -> ();
}
"#
));
assert!(!candid_declares_blob_storage_billing(
r"
service : {
canic_ready : () -> (bool) query;
}
"
));
}
#[test]
fn wraps_long_medic_report_fields() {
let report = render_medic_report(&[MedicCheck::warn(
"deployment state",
"this is a deliberately long diagnostic message that should wrap across multiple indented lines instead of widening a terminal table",
"run canic install <fleet-template> or canic deploy register <deployment> --fleet-template <fleet-template> --root <principal> --allow-unverified",
)]);
assert!(report.contains("deployment state [warn]"));
assert!(
report
.lines()
.all(|line| line.chars().count() <= MEDIC_REPORT_WIDTH)
);
assert!(
report
.lines()
.any(|line| line.starts_with(" ") && !line.trim().is_empty())
);
}
#[test]
fn icp_identity_session_cache_hint_is_informational() {
let check = check_icp_identity_session_cache_hint();
assert_eq!(check.status, MedicStatus::Ok);
assert_eq!(check.name, "icp identity session");
assert!(check.detail.contains("PEM identities"));
assert!(check.next.contains("icp settings session-length"));
assert!(check.next.contains("icp identity reauth"));
assert!(!check.next.contains("1.0.0"));
}
#[test]
fn missing_installed_deployment_error_is_warnable() {
assert!(is_missing_installed_deployment(
"deployment target demo is not installed on network local"
));
assert!(!is_missing_installed_deployment(
"failed to read canic deployment state: bad json"
));
}
fn write_candid(root: &std::path::Path, network: &str, role: &str, candid: &str) {
let path = local_canister_candid_path(root, network, role);
fs::create_dir_all(path.parent().expect("candid parent")).expect("create candid parent");
fs::write(path, candid).expect("write candid");
}