canic-host 0.70.6

Host-side build, install, deployment, and fleet-template library for Canic workspaces
Documentation
use super::*;

#[test]
fn brownfield_fixture_reports_observed_roles_without_claiming_them() {
    let inventory = inventory(vec![
        observed_canister(
            "aaaaa-aa",
            Some("root"),
            CanisterControlClassV1::DeploymentControlled,
            None,
        ),
        observed_canister(
            "bbbbb-bb",
            Some("api"),
            CanisterControlClassV1::UserControlled,
            Some("api-hash"),
        ),
        observed_canister(
            "ccccc-cc",
            Some("store"),
            CanisterControlClassV1::ExternallyImported,
            Some("store-hash"),
        ),
        observed_canister(
            "ddddd-dd",
            None,
            CanisterControlClassV1::UnknownUnsafe,
            Some("unknown-hash"),
        ),
    ]);
    let report = report_with_profile(
        AdoptionProfileV1::Brownfield,
        BROWNFIELD_CONFIG,
        Some(&inventory),
        Vec::new(),
    );
    let api = role(&report, "api");
    let store = role(&report, "store");

    assert_eq!(report.profile, AdoptionProfileV1::Brownfield);
    assert_eq!(report.summary.managed_configured_roles, 1);
    assert_eq!(report.summary.observed_only_canisters, 3);
    assert_eq!(report.summary.user_controlled_canisters, 1);
    assert_eq!(report.summary.external_controller_required, 2);
    assert_eq!(report.summary.mutating_actions_performed, 0);
    assert!(
        api.classifications
            .contains(&AdoptionClassificationV1::ObservedOnly)
    );
    assert_eq!(
        api.authority_state,
        AdoptionAuthorityStateV1::UserControlled
    );
    assert!(
        store
            .classifications
            .contains(&AdoptionClassificationV1::ExternalControllerRequired)
    );
    assert_eq!(store.artifact_state, AdoptionArtifactStateV1::ExternalWasm);
    assert!(
        report
            .recommendations
            .iter()
            .filter(|recommendation| recommendation.kind == "declare_role")
            .all(|recommendation| recommendation.suggested_action_support
                == AdoptionSuggestedActionSupportV1::UnsupportedByAdoption
                && recommendation.suggested_action_availability
                    == AdoptionSuggestedActionAvailabilityV1::BlockedIn0500)
    );
}

#[test]
fn partial_fixture_preserves_managed_and_external_roles_separately() {
    let inventory = inventory(vec![
        observed_canister(
            "aaaaa-aa",
            Some("root"),
            CanisterControlClassV1::DeploymentControlled,
            None,
        ),
        observed_canister(
            "bbbbb-bb",
            Some("api"),
            CanisterControlClassV1::DeploymentControlled,
            Some("api-hash"),
        ),
        observed_canister(
            "ccccc-cc",
            Some("external_app"),
            CanisterControlClassV1::UserControlled,
            Some("external_app-hash"),
        ),
    ]);
    let report = report_with_profile(
        AdoptionProfileV1::Partial,
        CONFIG,
        Some(&inventory),
        Vec::new(),
    );
    let api = role(&report, "api");
    let store = role(&report, "store");
    let external_app = role(&report, "external_app");

    assert_eq!(report.profile, AdoptionProfileV1::Partial);
    assert_eq!(report.summary.managed_configured_roles, 2);
    assert_eq!(report.summary.declared_only_roles, 1);
    assert_eq!(report.summary.attached_unobserved_roles, 0);
    assert_eq!(report.summary.observed_only_canisters, 1);
    assert_eq!(api.observation_state, AdoptionObservationStateV1::Observed);
    assert!(
        api.classifications
            .contains(&AdoptionClassificationV1::Managed)
    );
    assert!(
        store
            .classifications
            .contains(&AdoptionClassificationV1::DeclaredOnly)
    );
    assert_eq!(
        external_app.authority_state,
        AdoptionAuthorityStateV1::UserControlled
    );
}

#[test]
fn observed_only_fixture_without_role_stays_unmatched() {
    let inventory = inventory(vec![observed_canister(
        "zzzzz-zz",
        None,
        CanisterControlClassV1::UnknownUnsafe,
        Some("unknown-hash"),
    )]);
    let report = report(BROWNFIELD_CONFIG, Some(&inventory), Vec::new());
    let observed = report
        .observed_canisters
        .iter()
        .find(|finding| finding.canister_id == "zzzzz-zz")
        .expect("observed-only canister finding");

    assert_eq!(report.summary.observed_only_canisters, 1);
    assert_eq!(observed.matched_role, None);
    assert_eq!(observed.confidence, AdoptionMatchConfidenceV1::None);
    assert!(
        observed
            .classifications
            .contains(&AdoptionClassificationV1::ObservedOnly)
    );
    assert!(
        observed.recommendations.is_empty(),
        "name-free observations must not invent declaration actions"
    );
}

#[test]
fn declared_only_fixture_reports_compile_only_role() {
    let report = report_with_profile(
        AdoptionProfileV1::Partial,
        CONFIG,
        None,
        matching_metadata(),
    );
    let store = role(&report, "store");

    assert_eq!(store.package_state, AdoptionPackageStateV1::Matches);
    assert_eq!(store.topology_state, AdoptionTopologyStateV1::Unattached);
    assert!(
        store
            .classifications
            .contains(&AdoptionClassificationV1::DeclaredOnly)
    );
    assert!(
        store
            .recommendations
            .iter()
            .any(|recommendation| recommendation.kind == "attach_role_later"
                && recommendation.suggested_action_effect
                    == AdoptionSuggestedActionEffectV1::MutatesState
                && recommendation.suggested_action_support
                    == AdoptionSuggestedActionSupportV1::UnsupportedByAdoption)
    );
}

#[test]
fn standalone_fixture_keeps_compile_only_role_unattached() {
    let report = report_with_profile(
        AdoptionProfileV1::Standalone,
        STANDALONE_CONFIG,
        None,
        vec![AdoptionPackageMetadataV1 {
            package: "worker".to_string(),
            fleet: Some("demo".to_string()),
            role: Some("worker".to_string()),
        }],
    );
    let worker = role(&report, "worker");

    assert_eq!(report.profile, AdoptionProfileV1::Standalone);
    assert_eq!(report.summary.managed_configured_roles, 0);
    assert_eq!(report.summary.declared_only_roles, 1);
    assert_eq!(report.summary.attached_unobserved_roles, 0);
    assert_eq!(worker.package_state, AdoptionPackageStateV1::Matches);
    assert_eq!(worker.topology_state, AdoptionTopologyStateV1::Unattached);
    assert_eq!(
        worker.observation_state,
        AdoptionObservationStateV1::Unobserved
    );
    assert!(
        worker
            .classifications
            .contains(&AdoptionClassificationV1::DeclaredOnly)
    );
    assert!(
        worker
            .evidence
            .iter()
            .any(|evidence| evidence == "no topology attachment exists")
    );
    assert!(
        report
            .blocked_actions
            .contains(&"topology attachment".to_string())
    );
}

#[test]
fn leaf_only_fixture_does_not_recommend_authority_hub_adoption() {
    let inventory = inventory(vec![
        observed_canister(
            "aaaaa-aa",
            Some("governance"),
            CanisterControlClassV1::UserControlled,
            Some("governance-hash"),
        ),
        observed_canister(
            "bbbbb-bb",
            Some("app"),
            CanisterControlClassV1::DeploymentControlled,
            Some("app-hash"),
        ),
    ]);
    let report = report_with_profile(
        AdoptionProfileV1::LeafOnly,
        LEAF_ONLY_CONFIG,
        Some(&inventory),
        Vec::new(),
    );
    let app = role(&report, "app");
    let governance = role(&report, "governance");
    let governance_observation = report
        .observed_canisters
        .iter()
        .find(|finding| finding.matched_role.as_deref() == Some("governance"))
        .expect("governance observation");

    assert_eq!(report.profile, AdoptionProfileV1::LeafOnly);
    assert!(
        app.classifications
            .contains(&AdoptionClassificationV1::Managed)
    );
    assert_eq!(app.observation_state, AdoptionObservationStateV1::Observed);
    assert!(
        governance
            .classifications
            .contains(&AdoptionClassificationV1::ObservedOnly)
    );
    assert!(
        governance
            .classifications
            .contains(&AdoptionClassificationV1::ExternalControllerRequired)
    );
    assert!(governance.recommendations.is_empty());
    assert!(
        governance
            .warnings
            .iter()
            .any(|warning| warning.contains("leaf-only profile"))
    );
    assert!(governance_observation.recommendations.is_empty());
    assert!(
        governance_observation
            .warnings
            .iter()
            .any(|warning| warning.contains("leaf-only profile"))
    );
    assert!(
        !report
            .recommendations
            .iter()
            .any(|recommendation| recommendation.suggested_action.as_deref()
                == Some("canic fleet role declare demo governance --package <path>"))
    );
}