canic-host 0.109.3

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

fn test_icp(root: &Path, environment: &str) -> InstallIcpContext {
    InstallIcpContext::new("/opt/icp", root, environment)
}

#[test]
fn icp_canister_command_carries_selected_environment() {
    let icp = test_icp(Path::new("/tmp/canic-icp-root"), "ic");
    let mut command = icp_canister_command(&icp);
    command.args(["status", "root"]);
    icp.add_target_args(&mut command);

    assert_eq!(command.get_program(), "/opt/icp");
    assert_eq!(
        command
            .get_args()
            .map(|arg| arg.to_string_lossy().into_owned())
            .collect::<Vec<_>>(),
        [
            "--project-root-override",
            "/tmp/canic-icp-root",
            "canister",
            "status",
            "root",
            "-e",
            "ic"
        ]
    );
}

#[test]
fn local_canister_command_uses_http_target_when_configured() {
    let target = LocalReplicaTarget {
        url: "http://127.0.0.1:8000".to_string(),
        root_key: "abcd".to_string(),
    };
    let icp = InstallIcpContext::new("/opt/icp", Path::new("/tmp/canic-icp-root"), "local")
        .with_local_replica(Some(target));
    let mut command = icp_canister_command(&icp);
    command.env("ICP_ENVIRONMENT", "local");
    command.args(["status", "root"]);
    icp.add_target_args(&mut command);

    assert_eq!(
        command
            .get_args()
            .map(|arg| arg.to_string_lossy().into_owned())
            .collect::<Vec<_>>(),
        [
            "--project-root-override",
            "/tmp/canic-icp-root",
            "canister",
            "status",
            "root",
            "-n",
            "http://127.0.0.1:8000",
            "-k",
            "abcd"
        ]
    );
    assert!(
        command
            .get_envs()
            .any(|(key, value)| key == "ICP_ENVIRONMENT" && value.is_none())
    );
}

#[test]
fn creation_result_decoding_accepts_icp_json_and_plain_principal() {
    let canister = candid::Principal::from_slice(&[43]);
    let root = temp_dir("canic-creation-result-decoding");
    let path = root.join("created.json");
    fs::create_dir_all(&root).expect("create temp root");
    fs::write(
        &path,
        serde_json::json!({ "canister_id": canister.to_text() }).to_string(),
    )
    .expect("write creation result");

    assert_eq!(
        read_created_canister(&path).expect("read creation result"),
        Some(canister)
    );
    fs::write(&path, canister.to_text()).expect("write plain creation result");
    assert_eq!(
        read_created_canister(&path).expect("read plain creation result"),
        Some(canister)
    );
    fs::remove_dir_all(root).expect("remove temp root");
}

#[test]
fn install_command_uses_binary_candid_file() {
    let canister = candid::Principal::from_slice(&[44]);
    let command = icp_canister_install_binary_args_command(
        &test_icp(Path::new("/workspace"), "caelum-backend"),
        canister,
        Path::new("/artifacts/root.wasm"),
        Path::new("/state/root-install-args.bin"),
    );

    assert_eq!(
        crate::icp::command_display(&command),
        format!(
            "/opt/icp --project-root-override /workspace canister install {canister} --mode=install -y --wasm /artifacts/root.wasm --args-file /state/root-install-args.bin --args-format bin -e caelum-backend"
        )
    );
}

#[test]
fn create_command_binds_subnet_and_exact_cycles() {
    let subnet = canic_core::ids::SubnetId::from_principal(candid::Principal::from_slice(&[41]));
    let command = icp_canister_create_command(
        &test_icp(Path::new("/workspace"), "staging"),
        subnet,
        &crate::fleet_install_plan::PlannedCanisterCreationFunding::Cycles {
            cycles: 2_000_000_000_000,
        },
        &[],
    );

    assert_eq!(
        crate::icp::command_display(&command),
        format!(
            "/opt/icp --project-root-override /workspace canister create --detached --json --subnet {subnet} --cycles 2000000000000 -e staging"
        )
    );
}

#[test]
fn create_command_preserves_every_explicit_controller() {
    let subnet = canic_core::ids::SubnetId::from_principal(candid::Principal::from_slice(&[41]));
    let first = candid::Principal::from_slice(&[42]);
    let second = candid::Principal::from_slice(&[43]);
    let command = icp_canister_create_command(
        &test_icp(Path::new("/workspace"), "staging"),
        subnet,
        &crate::fleet_install_plan::PlannedCanisterCreationFunding::Cycles {
            cycles: 2_000_000_000_000,
        },
        &[first, second],
    );

    assert_eq!(
        crate::icp::command_display(&command),
        format!(
            "/opt/icp --project-root-override /workspace canister create --detached --json --subnet {subnet} --cycles 2000000000000 --controller {first} --controller {second} -e staging"
        )
    );
}

#[test]
fn create_command_preserves_exact_icp_e8s() {
    let subnet = canic_core::ids::SubnetId::from_principal(candid::Principal::from_slice(&[42]));
    let command = icp_canister_create_command(
        &test_icp(Path::new("/workspace"), "ic"),
        subnet,
        &crate::fleet_install_plan::PlannedCanisterCreationFunding::Icp { e8s: 1 },
        &[],
    );

    assert_eq!(
        crate::icp::command_display(&command),
        format!(
            "/opt/icp --project-root-override /workspace canister create --detached --json --subnet {subnet} --with-icp 0.00000001 -e ic"
        )
    );
}

#[test]
fn install_timing_summary_uses_padded_bordered_table_format() {
    let timings = InstallTimingSummary {
        activate_fleet: Duration::from_millis(1200),
        build_configured: Duration::from_millis(1800),
        build_infrastructure: Duration::from_millis(540),
        emit_manifest: Duration::from_millis(10),
        materialize_artifacts: Duration::ZERO,
        post_build_gate: Duration::from_millis(20),
        preflight: Duration::from_millis(100),
        reuse_artifacts: Duration::ZERO,
    };

    let table = render_install_timing_summary(&timings, Duration::from_millis(3900));

    assert_eq!(
        table.lines().take(3).collect::<Vec<_>>(),
        vec![
            "+-------------------------+---------+",
            "| PHASE                   | ELAPSED |",
            "+=========================+=========+",
        ]
    );
    assert!(table.contains("| activate_fleet          |   1.20s |"));
    assert!(
        table
            .lines()
            .any(|line| line.contains("post_build_gate") && line.contains("0.02s"))
    );
    assert!(table.contains("| planning_receipts_other |   0.23s |"));
    assert!(
        table
            .lines()
            .any(|line| line.contains("total") && line.contains("3.90s"))
    );
}

#[test]
fn displayed_install_timing_rows_add_exactly_to_displayed_total() {
    let timings = InstallTimingSummary {
        build_configured: Duration::from_millis(16),
        preflight: Duration::from_millis(16),
        ..InstallTimingSummary::default()
    };

    let table = render_install_timing_summary(&timings, Duration::from_millis(35));

    assert!(table.contains("| preflight               |   0.01s |"));
    assert!(table.contains("| build_configured        |   0.01s |"));
    assert!(table.contains("| planning_receipts_other |   0.01s |"));
    assert!(table.contains("| total                   |   0.03s |"));
}

#[test]
fn root_init_args_are_written_as_binary_candid() {
    use canic_core::{
        cdk::types::Cycles,
        dto::fleet_subnet_root::{FleetSubnetRootAuthority, FleetSubnetRootInitArgs},
        ids::{
            ComponentTopologyDigest, CyclesFundingBudget, FleetCoordinatorBinding,
            FleetRegistryAuthority, FleetSubnetRootBinding, FleetSubnetRootLimits,
            FleetSubnetRootReleaseSet, FleetSubnetWasmStoreAuthority, ReleaseSetDigest, SubnetId,
        },
    };

    let activation = sample_fleet_activation_identity();
    let fleet_subnet_root = candid::Principal::from_slice(&[42]);
    let placement_subnet = SubnetId::from_principal(candid::Principal::from_slice(&[43]));
    let registry_authority = FleetRegistryAuthority {
        binding: FleetCoordinatorBinding {
            fleet: activation.fleet.clone(),
            coordinator_subnet: SubnetId::from_principal(candid::Principal::from_slice(&[40])),
            coordinator: candid::Principal::from_slice(&[41]),
        },
        epoch: 1,
    };
    let identity = FleetSubnetRootInitArgs {
        authority: FleetSubnetRootAuthority {
            binding: FleetSubnetRootBinding {
                authority: registry_authority.clone(),
                placement_subnet,
                fleet_subnet_root,
                component_admissions: Vec::new(),
                component_topology_digest: ComponentTopologyDigest::from_bytes([5; 32]),
                limits: FleetSubnetRootLimits {
                    maximum_component_instances: 10,
                    maximum_registry_bytes: 1_048_576,
                    maximum_wasm_store_bytes: 10_000_000,
                    maximum_group_placements: 16,
                    canister_pool: canic_core::ids::FleetSubnetCanisterPoolConfig {
                        minimum_size: 1,
                        maximum_size: 10,
                        canister_cycles: Cycles::new(5_000_000_000_000),
                    },
                    cycles_funding: CyclesFundingBudget {
                        window_secs: 3_600,
                        maximum_cycles: Cycles::new(1_000_000_000_000),
                    },
                },
                funding: crate::test_support::fleet_subnet_root_funding_authority(),
            },
            initial_release_set: FleetSubnetRootReleaseSet {
                release_build_id: activation.release_build_id,
                manifest_digest: ReleaseSetDigest::from_bytes([6; 32]),
            },
            expected_module_hash: [10; 32],
            wasm_store_authority: FleetSubnetWasmStoreAuthority {
                authority: registry_authority,
                placement_subnet,
                fleet_subnet_root,
                wasm_store: candid::Principal::from_slice(&[44]),
                installation_controller: candid::Principal::from_slice(&[45]),
                release_build_id: activation.release_build_id,
                wasm_module_hash: [11; 32],
            },
        },
        install_id: activation.operation_id,
        canister_pool_imports: Vec::new(),
    };

    let root = temp_dir("canic-binary-root-install-args");
    let path = root.join("root-install-args.bin");
    write_candid_args(&path, &identity).expect("write binary init args");
    let bytes = fs::read(&path).expect("read binary init args");
    let decoded: FleetSubnetRootInitArgs =
        candid::decode_one(&bytes).expect("decode init identity");

    assert_eq!(decoded, identity);
    fs::remove_dir_all(root).expect("remove temp root");
}

#[test]
fn renders_exact_icp_e8s_without_float_rounding() {
    assert_eq!(icp_e8s_text(1), "0.00000001");
    assert_eq!(icp_e8s_text(10_000_000), "0.1");
    assert_eq!(icp_e8s_text(100_000_000), "1");
    assert_eq!(icp_e8s_text(123_456_789), "1.23456789");
}