canic-cli 0.110.23

Operator CLI for Canic fleet setup, builds, evidence, catalog, backup, and restore workflows
Documentation
use super::*;
use canic_core::dto::component_provisioning::FleetComponentProvisioningPhase;
use canic_host::fleet_ensure::dto::{FleetEnsurePhase, FleetProvisioningProgress};

fn waiting(elapsed_seconds: u64) -> FleetEnsureProgress {
    FleetEnsureProgress {
        operation_id: "operation".into(),
        plan_sha256: "plan".into(),
        phase: FleetEnsurePhase::WorkloadProvisioning,
        applied_effects: 51,
        reviewed_effects: 53,
        state: FleetEnsureProgressState::AwaitingProgress {
            elapsed_seconds,
            provisioning: None,
        },
    }
}

#[test]
fn unchanged_polls_emit_only_at_heartbeat_and_keep_current_details() {
    let mut output = ProgressOutput::default();
    let start = Instant::now();
    let emitted = (0..=65)
        .filter_map(|seconds| {
            let progress = waiting(seconds);
            output
                .should_emit(&progress, start + Duration::from_secs(seconds))
                .then(|| super::super::render_progress(&progress, true))
        })
        .map(|line| serde_json::from_str::<serde_json::Value>(&line).unwrap())
        .collect::<Vec<_>>();
    assert_eq!(
        emitted
            .iter()
            .map(|event| event["progress"]["state"]["elapsed_seconds"]
                .as_u64()
                .unwrap())
            .collect::<Vec<_>>(),
        [0, 30, 60]
    );
    assert!(
        emitted
            .iter()
            .all(|event| event["progress"]["phase"] == "workload_provisioning")
    );
}

#[test]
fn meaningful_changes_emit_immediately_and_restart_heartbeat() {
    let start = Instant::now();
    let mut output = ProgressOutput::default();
    let mut progress = waiting(0);
    assert!(output.should_emit(&progress, start));
    progress.applied_effects += 1;
    assert!(output.should_emit(&progress, start + Duration::from_secs(1)));
    progress.reviewed_effects += 1;
    assert!(output.should_emit(&progress, start + Duration::from_secs(2)));
    progress.phase = FleetEnsurePhase::TerminalVerification;
    assert!(output.should_emit(&progress, start + Duration::from_secs(3)));
    progress.plan_sha256 = "successor-plan".into();
    assert!(output.should_emit(&progress, start + Duration::from_secs(4)));
    progress.operation_id = "new-operation".into();
    assert!(output.should_emit(&progress, start + Duration::from_secs(5)));
    assert!(!output.should_emit(&progress, start + Duration::from_secs(34)));
    assert!(output.should_emit(&progress, start + Duration::from_secs(35)));
}

#[test]
fn provisioning_changes_are_meaningful_but_elapsed_time_is_not() {
    let start = Instant::now();
    let mut output = ProgressOutput::default();
    let mut progress = waiting(0);
    assert!(output.should_emit(&progress, start));
    let mut provisioning = FleetProvisioningProgress {
        phase: FleetComponentProvisioningPhase::ActivatingRuntimes,
        root_batch_count: 2,
        accepted_root_count: 0,
        provisioned_root_count: 0,
        directory_confirmed_root_count: 0,
        directory_confirmation_root_count: 2,
        runtime_activated_root_count: 0,
        component_count: 3,
    };
    progress.state = FleetEnsureProgressState::AwaitingProgress {
        elapsed_seconds: 1,
        provisioning: Some(provisioning.clone()),
    };
    assert!(output.should_emit(&progress, start + Duration::from_secs(1)));
    provisioning.accepted_root_count = 1;
    progress.state = FleetEnsureProgressState::AwaitingProgress {
        elapsed_seconds: 2,
        provisioning: Some(provisioning.clone()),
    };
    assert!(output.should_emit(&progress, start + Duration::from_secs(2)));
    progress.state = FleetEnsureProgressState::AwaitingProgress {
        elapsed_seconds: 3,
        provisioning: Some(provisioning),
    };
    assert!(!output.should_emit(&progress, start + Duration::from_secs(3)));
}

#[test]
fn actionable_and_terminal_events_are_never_suppressed() {
    let start = Instant::now();
    let mut output = ProgressOutput::default();
    for state in [
        FleetEnsureProgressState::Advancing,
        FleetEnsureProgressState::FundingRequired,
        FleetEnsureProgressState::PrerequisiteComplete,
        FleetEnsureProgressState::ReviewRequired {
            reason:
                canic_host::fleet_ensure::model::FleetEnsureSuccessorReviewReason::AdditionalEffect,
            review: None,
        },
        FleetEnsureProgressState::Complete,
    ] {
        let mut progress = waiting(0);
        assert!(output.should_emit(&progress, start));
        progress.state = state;
        assert!(output.should_emit(&progress, start));
        assert!(output.should_emit(&progress, start));
    }
}