canic-host 0.103.0

Host-side App build, Fleet install, deployment, and release-set library for Canic workspaces
Documentation
use super::*;

#[test]
fn install_truth_gate_lines_include_warning_codes() {
    let root = temp_dir("canic-install-truth-warning-lines");
    let config_path = root.join("apps/demo/canic.toml");
    fs::create_dir_all(config_path.parent().expect("config parent")).expect("create config dir");
    fs::write(
        &config_path,
        r#"
[app]
name = "demo"
init_mode = "enabled"

[roles.root]
kind = "root"
package = "root"

[roles.app]
kind = "canister"
package = "app"

[roles.project_registry]
kind = "canister"
package = "project_registry"

[roles.oracle_pokemon]
kind = "canister"
package = "oracle_pokemon"

[roles.user_hub]
kind = "canister"
package = "user_hub"

[roles.user_shard]
kind = "canister"
package = "user_shard"

[roles.scale_hub]
kind = "canister"
package = "scale_hub"

[roles.scale_replica]
kind = "canister"
package = "scale"

[roles.role_baseline]
kind = "canister"
package = "role_baseline"

[roles.worker]
kind = "canister"
package = "worker"
[app.whitelist]

"#,
    )
    .expect("write config");
    write_wasm_gz_artifact(&root, "fleet_coordinator", b"fleet-coordinator-artifact");
    write_wasm_gz_artifact(&root, "root", b"root-artifact");
    write_wasm_gz_artifact(&root, "wasm_store", b"wasm-store-artifact");
    write_local_network_authority(&root, "local");

    let options = InstallRootOptions {
        root_canister: "root".to_string(),
        root_build_target: "root".to_string(),
        icp_executable: "icp".to_string(),
        environment: "local".to_string(),
        fleet_name: "demo".to_string(),
        icp_root: Some(root.clone()),
        build_profile: Some(CanisterBuildProfile::Fast),
        release_build_id: None,
        config_path: Some("apps/demo/canic.toml".to_string()),
        fleet_install_input_path: None,
        expected_app: Some("demo".to_string()),
        interactive_config_selection: false,
        deployment_plan_override: None,
    };

    let mut check = current_install_deployment_truth_check_at(
        &options,
        &root,
        &root,
        &config_path,
        "demo",
        "2026-05-22T00:00:00Z".to_string(),
    )
    .expect("deployment truth check");
    check.report.warnings.push(SafetyFindingV1 {
        code: "observation_gap".to_string(),
        message: "live root status was not observed".to_string(),
        severity: SafetySeverityV1::Warning,
        subject: Some("live_canister_status.root".to_string()),
    });

    let receipt = install_deployment_truth_gate_receipt(
        &check,
        "start".to_string(),
        vec![artifact_gate_phase_receipt(
            &check,
            "start",
            Some("finish".into()),
        )],
        artifact_gate_role_phase_receipts(&check),
    );
    let lines = install_deployment_truth_gate_lines(&check, &receipt);
    let text = lines.join("\n");
    let display_summary = deployment_truth_display_summary(&check);

    assert!(text.contains("operation: local:local:demo:check:materialize_artifacts"));
    assert!(text.contains("Complete"));
    assert!(text.contains("materialize_artifacts"));
    assert!(text.contains("observation_gap"));
    assert!(text.contains("live_canister_status.root"));
    assert!(text.contains("pending until emit_manifest"));
    assert!(!text.contains("Deployment truth role receipt:"));
    assert!(display_summary.contains("pending manifest checks"));
    assert_ne!(display_summary, check.report.summary);

    fs::remove_dir_all(root).expect("clean temp dir");
}

#[test]
fn repeated_authority_blockers_render_as_one_bounded_group() {
    let findings = (0..6)
        .map(|index| SafetyFindingV1 {
            code: "authority_observation_missing".to_string(),
            message: "expected canister authority was not observed".to_string(),
            severity: SafetySeverityV1::HardFailure,
            subject: Some(format!("role_{index}")),
        })
        .collect::<Vec<_>>();
    let finding_refs = findings.iter().collect::<Vec<_>>();

    let summary = deployment_truth_findings_summary(&finding_refs);

    assert_eq!(summary.matches("authority_observation_missing").count(), 1);
    assert!(summary.contains("role_0, role_1, role_2, role_3, +2"));
    assert!(!summary.contains("role_4"));
}

#[test]
fn install_truth_gate_lines_distinguish_plan_assumptions() {
    let root = temp_dir("canic-install-truth-plan-assumption-lines");
    let config_path = root.join("apps/demo/canic.toml");
    fs::create_dir_all(config_path.parent().expect("config parent")).expect("create config dir");
    fs::write(
        &config_path,
        r#"
[app]
name = "demo"
init_mode = "enabled"


[roles.root]
kind = "root"
package = "root"

[roles.app]
kind = "canister"
package = "app"

[roles.project_registry]
kind = "canister"
package = "project_registry"

[roles.oracle_pokemon]
kind = "canister"
package = "oracle_pokemon"

[roles.user_hub]
kind = "canister"
package = "user_hub"

[roles.user_shard]
kind = "canister"
package = "user_shard"

[roles.scale_hub]
kind = "canister"
package = "scale_hub"

[roles.scale_replica]
kind = "canister"
package = "scale"

[roles.role_baseline]
kind = "canister"
package = "role_baseline"

[roles.worker]
kind = "canister"
package = "worker"
[app.whitelist]


"#,
    )
    .expect("write config");
    write_wasm_gz_artifact(&root, "root", b"root-artifact");
    write_local_network_authority(&root, "local");

    let options = InstallRootOptions {
        root_canister: "root".to_string(),
        root_build_target: "root".to_string(),
        icp_executable: "icp".to_string(),
        environment: "local".to_string(),
        fleet_name: "demo".to_string(),
        icp_root: Some(root.clone()),
        build_profile: Some(CanisterBuildProfile::Fast),
        release_build_id: None,
        config_path: Some("apps/demo/canic.toml".to_string()),
        fleet_install_input_path: None,
        expected_app: Some("demo".to_string()),
        interactive_config_selection: false,
        deployment_plan_override: None,
    };

    let check = current_install_deployment_truth_check_at(
        &options,
        &root,
        &root,
        &config_path,
        "demo",
        "2026-05-22T00:00:00Z".to_string(),
    )
    .expect("deployment truth check");
    let receipt = install_deployment_truth_gate_receipt(
        &check,
        "start".to_string(),
        vec![artifact_gate_phase_receipt(
            &check,
            "start",
            Some("finish".into()),
        )],
        artifact_gate_role_phase_receipts(&check),
    );
    let lines = install_deployment_truth_gate_lines(&check, &receipt);
    let text = lines.join("\n");

    assert!(text.contains("plan_assumption"));
    assert!(text.contains("fleet_catalog.coordinator_principal.missing"));

    fs::remove_dir_all(root).expect("clean temp dir");
}