canic-host 0.111.0

Host-side App build and desired-state Fleet reconciliation library for Canic workspaces
Documentation
use super::*;

fn fixture() -> (EnsureAction, LiveCanister) {
    (
        EnsureAction::DeleteSnapshot {
            name: "root".into(),
            principal: "source".into(),
            snapshot_id: "ab01".into(),
            expected_snapshots: vec!["ab01".into(), "cd02".into()],
            expected_module_sha256: Some("module".into()),
        },
        LiveCanister {
            principal: "source".into(),
            controllers: vec!["operator".into()],
            module_sha256: Some("module".into()),
            canister_version: Some(5),
            reinstall_required: false,
            root_owned_lifecycle: None,
            status: CanisterRuntimeStatus::Stopped,
            cycles: 100,
        },
    )
}

#[test]
fn snapshot_reconciliation_requires_exact_remaining_inventory() {
    let (action, live) = fixture();
    let before = vec!["ab01".into(), "cd02".into()];
    let after = vec!["cd02".into()];
    assert_eq!(
        reconcile(&action, "operator", Some(&live), &before),
        Ok(false)
    );
    assert_eq!(
        reconcile(&action, "operator", Some(&live), &after),
        Ok(true)
    );
    for inventory in [
        vec![],
        vec!["ab01".into()],
        vec!["cd02".into(), "ee03".into()],
        vec!["cd02".into(), "cd02".into()],
    ] {
        assert_eq!(
            reconcile(&action, "operator", Some(&live), &inventory),
            Err(SnapshotRemovalError::Inventory)
        );
    }
}

#[test]
fn snapshot_deletion_never_uses_running_shared_or_changed_custody() {
    let (action, live) = fixture();
    let snapshots = vec!["ab01".into(), "cd02".into()];
    let mut wrong = live.clone();
    wrong.controllers.push("old-root".into());
    let mut running = live.clone();
    running.status = CanisterRuntimeStatus::Running;
    let mut replaced = live.clone();
    replaced.module_sha256 = None;
    let mut substituted = live;
    substituted.principal = "other".into();
    for changed in [
        None,
        Some(wrong),
        Some(running),
        Some(replaced),
        Some(substituted),
    ] {
        assert_eq!(
            reconcile(&action, "operator", changed.as_ref(), &snapshots),
            Err(SnapshotRemovalError::Custody)
        );
    }
}

#[test]
fn malformed_snapshot_review_cannot_authorize_deletion() {
    let (mut action, live) = fixture();
    for inventory in [
        vec![],
        vec!["AB01".into()],
        vec!["ab01".into(), "ab01".into()],
        vec!["abc".into()],
        vec!["cd02".into(), "ab01".into()],
    ] {
        let EnsureAction::DeleteSnapshot {
            expected_snapshots, ..
        } = &mut action
        else {
            unreachable!()
        };
        *expected_snapshots = inventory.clone();
        assert_eq!(
            reconcile(&action, "operator", Some(&live), &inventory),
            Err(SnapshotRemovalError::Inventory)
        );
    }
}