canic-host 0.67.5

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

#[test]
fn adoption_report_preserves_unresolved_evidence_gaps() {
    let mut inventory = inventory(Vec::new());
    inventory
        .unresolved_observations
        .push(DeploymentObservationGapV1 {
            key: "canister-status:api".to_string(),
            description: "status query failed".to_string(),
        });
    let mut manifest = external_api_artifact_manifest();
    manifest
        .unresolved_artifacts
        .push(DeploymentObservationGapV1 {
            key: "artifact:api".to_string(),
            description: "artifact file missing".to_string(),
        });

    let report = adoption_report_from_config_source(AdoptionReportRequest {
        report_id: "adoption-1",
        generated_at: "2026-05-30T00:00:00Z",
        profile: AdoptionProfileV1::Partial,
        config_source: CONFIG,
        inventory: Some(&inventory),
        artifact_manifest: Some(&manifest),
        package_metadata: Vec::new(),
    })
    .expect("adoption report");

    assert!(
        report
            .inputs
            .missing_or_stale_evidence
            .iter()
            .any(|evidence| {
                evidence
                    == "unresolved inventory observation canister-status:api: status query failed"
            })
    );
    assert!(
        report
            .inputs
            .missing_or_stale_evidence
            .iter()
            .any(|evidence| {
                evidence == "unresolved artifact evidence artifact:api: artifact file missing"
            })
    );
}

#[test]
fn adoption_report_marks_role_only_package_metadata_as_conflict() {
    let report = report(
        CONFIG,
        None,
        vec![AdoptionPackageMetadataV1 {
            package: "store".to_string(),
            fleet: None,
            role: Some("store".to_string()),
        }],
    );
    let store = role(&report, "store");

    assert_eq!(store.package_state, AdoptionPackageStateV1::MissingFleet);
    assert!(
        store
            .classifications
            .contains(&AdoptionClassificationV1::EvidenceConflict)
    );
}

#[test]
fn adoption_report_marks_duplicate_observed_role_as_evidence_conflict() {
    let inventory = inventory(vec![
        observed_canister(
            "aaaaa-aa",
            Some("api"),
            CanisterControlClassV1::DeploymentControlled,
            Some("api-hash-a"),
        ),
        observed_canister(
            "bbbbb-bb",
            Some("api"),
            CanisterControlClassV1::DeploymentControlled,
            Some("api-hash-b"),
        ),
    ]);
    let report = report(CONFIG, Some(&inventory), Vec::new());
    let api = role(&report, "api");

    assert_eq!(
        api.observation_state,
        AdoptionObservationStateV1::ConflictingMatch
    );
    assert!(
        api.classifications
            .contains(&AdoptionClassificationV1::EvidenceConflict)
    );
    assert_eq!(report.summary.evidence_conflicts, 1);
}

#[test]
fn adoption_report_marks_reverse_conflicting_artifact_evidence() {
    let mut inventory = inventory(Vec::new());
    inventory
        .observed_artifacts
        .push(observed_external_api_artifact());
    let manifest = RoleArtifactManifestV1 {
        schema_version: 1,
        manifest_id: "local-manifest-1".to_string(),
        network: "local".to_string(),
        artifact_root: None,
        role_artifacts: vec![RoleArtifactV1 {
            source: ArtifactSourceV1::LocalBuild,
            build_profile: "fast".to_string(),
            ..external_api_role_artifact()
        }],
        unresolved_artifacts: Vec::new(),
    };

    let report = adoption_report_from_config_source(AdoptionReportRequest {
        report_id: "artifact-conflict-2",
        generated_at: "2026-05-30T00:00:00Z",
        profile: AdoptionProfileV1::HybridExternalWasm,
        config_source: CONFIG,
        inventory: Some(&inventory),
        artifact_manifest: Some(&manifest),
        package_metadata: Vec::new(),
    })
    .expect("adoption report");
    let api = role(&report, "api");

    assert!(
        api.classifications
            .contains(&AdoptionClassificationV1::EvidenceConflict)
    );
    assert!(
        api.warnings
            .iter()
            .any(|warning| warning == "artifact evidence contains conflicting role facts")
    );
    assert_eq!(report.summary.evidence_conflicts, 1);
}

#[test]
fn adoption_report_marks_conflicting_artifact_evidence() {
    let mut inventory = inventory(Vec::new());
    inventory.observed_artifacts.push(ObservedArtifactV1 {
        role: "api".to_string(),
        artifact_path: "local/api.wasm.gz".to_string(),
        file_sha256: None,
        file_sha256_source: Some(ArtifactDigestSourceV1::ObservedFileDigest),
        payload_sha256: None,
        payload_size_bytes: None,
        source: ArtifactSourceV1::LocalBuild,
    });
    let manifest = external_api_artifact_manifest();
    let report = adoption_report_from_config_source(AdoptionReportRequest {
        report_id: "artifact-conflict-1",
        generated_at: "2026-05-30T00:00:00Z",
        profile: AdoptionProfileV1::HybridExternalWasm,
        config_source: CONFIG,
        inventory: Some(&inventory),
        artifact_manifest: Some(&manifest),
        package_metadata: Vec::new(),
    })
    .expect("adoption report");
    let api = role(&report, "api");

    assert!(
        api.classifications
            .contains(&AdoptionClassificationV1::EvidenceConflict)
    );
    assert!(
        api.warnings
            .iter()
            .any(|warning| warning == "artifact evidence contains conflicting role facts")
    );
    assert_eq!(report.summary.evidence_conflicts, 1);
}

#[test]
fn adoption_report_classifies_pool_candidates_as_resources() {
    let mut inventory = inventory(Vec::new());
    inventory.observed_pool.push(ObservedPoolCanisterV1 {
        pool: "users".to_string(),
        canister_id: "ccccc-cc".to_string(),
        role: Some("user_shard".to_string()),
        control_class: CanisterControlClassV1::UnknownUnsafe,
    });

    let report = report(CONFIG, Some(&inventory), Vec::new());
    let pool = report
        .observed_canisters
        .iter()
        .find(|finding| finding.canister_id == "ccccc-cc")
        .expect("pool candidate finding");

    assert!(
        pool.classifications
            .contains(&AdoptionClassificationV1::ImportedPoolCandidate)
    );
    assert_eq!(pool.matched_role.as_deref(), Some("user_shard"));
}

#[test]
fn hybrid_external_wasm_fixture_reports_hashes_without_import() {
    let mut inventory = inventory(vec![observed_canister(
        "bbbbb-bb",
        Some("api"),
        CanisterControlClassV1::DeploymentControlled,
        Some("api-module-hash"),
    )]);
    inventory
        .observed_artifacts
        .push(observed_external_api_artifact());
    let manifest = external_api_artifact_manifest();

    let report = adoption_report_from_config_source(AdoptionReportRequest {
        report_id: "hybrid-1",
        generated_at: "2026-05-30T00:00:00Z",
        profile: AdoptionProfileV1::HybridExternalWasm,
        config_source: CONFIG,
        inventory: Some(&inventory),
        artifact_manifest: Some(&manifest),
        package_metadata: Vec::new(),
    })
    .expect("adoption report");
    let api = role(&report, "api");
    let observed_api = report
        .observed_canisters
        .iter()
        .find(|finding| finding.matched_role.as_deref() == Some("api"))
        .expect("api observation");

    assert_eq!(api.artifact_state, AdoptionArtifactStateV1::ExternalWasm);
    assert!(
        api.evidence
            .iter()
            .any(|evidence| evidence == "observed canister module_hash=api-module-hash")
    );
    assert!(
        api.evidence
            .iter()
            .any(|evidence| evidence == "artifact manifest source=external")
    );
    assert!(
        api.evidence
            .iter()
            .any(|evidence| evidence
                == "artifact manifest installed_module_hash=api-installed-module")
    );
    assert!(
        api.evidence
            .iter()
            .any(|evidence| evidence == "observed artifact file_sha256=api-file-sha")
    );
    assert!(
        api.warnings
            .iter()
            .any(|warning| warning.contains("artifact registry import is outside"))
    );
    assert_eq!(
        observed_api.wasm_evidence.as_deref(),
        Some("module_hash=api-module-hash")
    );
    assert!(
        report
            .blocked_actions
            .contains(&"artifact registry import".to_string())
    );
    assert!(
        report
            .recommendations
            .iter()
            .all(|recommendation| !recommendation.kind.contains("artifact"))
    );
}