canic-host 0.70.1

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

#[test]
fn artifact_promotion_execution_receipt_round_trips_through_json() {
    let receipt = sample_artifact_promotion_execution_receipt();

    assert_json_round_trip(&receipt);
    let encoded = serde_json::to_value(&receipt).expect("execution receipt should encode");
    assert_object_keys(
        &encoded,
        &[
            "schema_version",
            "receipt_id",
            "execution_receipt_digest",
            "artifact_promotion_plan_id",
            "artifact_promotion_plan_digest",
            "provenance_report_id",
            "provenance_report_digest",
            "provenance_status",
            "promoted_plan_id",
            "promotion_plan_lineage_digest",
            "operation_id",
            "operation_status",
            "command_result",
            "started_at",
            "finished_at",
            "deployment_receipt",
            "roles",
        ],
    );
    assert_eq!(encoded["receipt_id"], "promotion-execution-receipt-1");
    assert!(
        encoded["execution_receipt_digest"]
            .as_str()
            .is_some_and(|digest| digest.len() == 64)
    );
    assert_eq!(
        encoded["artifact_promotion_plan_id"],
        "artifact-promotion-plan-1"
    );
    assert!(
        encoded["artifact_promotion_plan_digest"]
            .as_str()
            .is_some_and(|digest| digest.len() == 64)
    );
    assert_eq!(encoded["provenance_report_id"], "promotion-provenance-1");
    assert!(
        encoded["provenance_report_digest"]
            .as_str()
            .is_some_and(|digest| digest.len() == 64)
    );
    assert_eq!(encoded["provenance_status"], "Ready");
    assert_eq!(encoded["promoted_plan_id"], "promoted-plan-1");
    assert_eq!(encoded["operation_id"], "promoted-operation-1");
    assert_eq!(encoded["roles"][0]["role"], "root");
    assert!(encoded["roles"][0]["materialization_evidence_digest"].is_string());
    assert!(encoded["roles"][0]["wasm_store_catalog_observation_digest"].is_string());
}

#[test]
fn artifact_promotion_execution_receipt_links_deployment_receipt() {
    let receipt = sample_artifact_promotion_execution_receipt();

    assert_eq!(
        receipt.operation_status,
        DeploymentExecutionStatusV1::Complete
    );
    assert_eq!(receipt.command_result, DeploymentCommandResultV1::Succeeded);
    assert_eq!(receipt.deployment_receipt.plan_id, receipt.promoted_plan_id);
    assert_eq!(
        receipt.deployment_receipt.operation_id,
        receipt.operation_id
    );
    assert_eq!(receipt.artifact_promotion_plan_digest.len(), 64);
    assert_eq!(
        receipt.roles[0].role_phase_result,
        Some(RolePhaseResultV1::Applied)
    );
    assert_eq!(receipt.provenance_report_digest.len(), 64);
    assert_eq!(receipt.execution_receipt_digest.len(), 64);
    assert_eq!(
        receipt.roles[0].artifact_digest.as_deref(),
        Some(sample_sha256("5").as_str())
    );
    assert!(
        receipt.roles[0]
            .materialization_evidence_digest
            .as_deref()
            .is_some_and(|digest| digest.len() == 64)
    );
    assert_eq!(
        receipt.roles[0].observed_module_hash_after.as_deref(),
        Some(sample_sha256("7").as_str())
    );
    assert!(
        receipt.roles[0]
            .wasm_store_catalog_observation_digest
            .as_deref()
            .is_some_and(|digest| digest.len() == 64)
    );
}

#[test]
fn artifact_promotion_execution_receipt_validation_rejects_stale_digest() {
    let mut receipt = sample_artifact_promotion_execution_receipt();
    receipt.execution_receipt_digest = sample_sha256("9");

    let err = validate_artifact_promotion_execution_receipt(&receipt)
        .expect_err("stale execution receipt digest should fail");

    std::assert_matches!(
        err,
        ArtifactPromotionExecutionReceiptError::LinkageMismatch {
            field: "execution_receipt_digest"
        }
    );
}

#[test]
fn artifact_promotion_execution_receipt_validation_rejects_stale_materialization_digest() {
    let mut receipt = sample_artifact_promotion_execution_receipt();
    receipt.roles[0].materialization_evidence_digest = Some(sample_sha256("9"));

    let err = validate_artifact_promotion_execution_receipt(&receipt)
        .expect_err("stale role materialization digest should fail");

    std::assert_matches!(
        err,
        ArtifactPromotionExecutionReceiptError::LinkageMismatch {
            field: "execution_receipt_digest"
        }
    );
}

#[test]
fn artifact_promotion_execution_receipt_validation_rejects_stale_plan_digest_link() {
    let mut receipt = sample_artifact_promotion_execution_receipt();
    receipt.artifact_promotion_plan_digest = sample_sha256("9");

    let err = validate_artifact_promotion_execution_receipt(&receipt)
        .expect_err("stale cited plan digest should fail");

    std::assert_matches!(
        err,
        ArtifactPromotionExecutionReceiptError::LinkageMismatch {
            field: "execution_receipt_digest"
        }
    );
}

#[test]
fn artifact_promotion_execution_receipt_validation_rejects_nested_receipt_drift() {
    let mut receipt = sample_artifact_promotion_execution_receipt();
    receipt.deployment_receipt.phase_receipts[0]
        .verified_postcondition
        .evidence
        .push("stale:evidence".to_string());

    let err = validate_artifact_promotion_execution_receipt(&receipt)
        .expect_err("nested deployment receipt drift should fail");

    std::assert_matches!(
        err,
        ArtifactPromotionExecutionReceiptError::LinkageMismatch {
            field: "execution_receipt_digest"
        }
    );
}

#[test]
fn artifact_promotion_execution_receipt_rejects_other_promoted_plan() {
    let err = artifact_promotion_execution_receipt(ArtifactPromotionExecutionReceiptRequest {
        receipt_id: "promotion-execution-receipt-1".to_string(),
        provenance_report: sample_artifact_promotion_provenance_report(),
        deployment_receipt: sample_receipt_with_phase(
            "other-plan",
            Some("aaaaa-aa"),
            ObservationStatusV1::Observed,
            RolePhaseResultV1::Applied,
        ),
    })
    .expect_err("deployment receipt must match promoted plan");

    std::assert_matches!(
        err,
        ArtifactPromotionExecutionReceiptError::LinkageMismatch {
            field: "deployment_receipt.plan_id"
        }
    );
}

#[test]
fn artifact_promotion_execution_receipt_rejects_blocked_provenance() {
    let mut receipt = sample_wasm_store_staging_receipt();
    receipt.role = "unknown".to_string();
    let wasm_store_report = promotion_wasm_store_identity_report_from_staging(
        PromotionWasmStoreIdentityReportRequest {
            report_id: "wasm-store-identity-1".to_string(),
            staging_receipts: vec![receipt],
        },
    )
    .expect("wasm-store identity report should validate");
    let provenance_report =
        artifact_promotion_provenance_report(ArtifactPromotionProvenanceReportRequest {
            report_id: "promotion-provenance-1".to_string(),
            artifact_promotion_plan: sample_artifact_promotion_plan(),
            wasm_store_identity_report: Some(wasm_store_report),
            wasm_store_catalog_verification: None,
            materialization_identity_report: None,
        })
        .expect("blocked provenance report should still be reportable");

    let err = artifact_promotion_execution_receipt(ArtifactPromotionExecutionReceiptRequest {
        receipt_id: "promotion-execution-receipt-1".to_string(),
        provenance_report,
        deployment_receipt: sample_promoted_deployment_receipt(),
    })
    .expect_err("blocked provenance cannot become execution receipt");

    std::assert_matches!(
        err,
        ArtifactPromotionExecutionReceiptError::ProvenanceNotReady {
            status: PromotionReadinessStatusV1::Blocked
        }
    );
}

#[test]
fn artifact_promotion_execution_receipt_validation_rejects_stale_operation_status() {
    let mut receipt = sample_artifact_promotion_execution_receipt();
    receipt.operation_status = DeploymentExecutionStatusV1::FailedBeforeMutation;

    let err = validate_artifact_promotion_execution_receipt(&receipt)
        .expect_err("wrapper status must match nested deployment receipt");

    std::assert_matches!(
        err,
        ArtifactPromotionExecutionReceiptError::LinkageMismatch {
            field: "operation_status"
        }
    );
}

#[test]
fn artifact_promotion_execution_receipt_validation_rejects_stale_provenance_status() {
    let mut receipt = sample_artifact_promotion_execution_receipt();
    receipt.provenance_status = PromotionReadinessStatusV1::Blocked;

    let err = validate_artifact_promotion_execution_receipt(&receipt)
        .expect_err("archived execution receipt must preserve ready provenance");

    std::assert_matches!(
        err,
        ArtifactPromotionExecutionReceiptError::ProvenanceNotReady {
            status: PromotionReadinessStatusV1::Blocked
        }
    );
}

#[test]
fn artifact_promotion_execution_receipt_validation_rejects_missing_deployment_role() {
    let mut receipt = sample_artifact_promotion_execution_receipt();
    receipt.deployment_receipt.role_phase_receipts.clear();

    let err = validate_artifact_promotion_execution_receipt(&receipt)
        .expect_err("promotion execution receipt must cite deployment role evidence");

    std::assert_matches!(
        err,
        ArtifactPromotionExecutionReceiptError::MissingDeploymentRole { role } if role == "root"
    );
}

#[test]
fn artifact_promotion_execution_receipt_validation_rejects_unknown_deployment_role() {
    let mut receipt = sample_artifact_promotion_execution_receipt();
    let mut extra = receipt.deployment_receipt.role_phase_receipts[0].clone();
    extra.role = "worker".to_string();
    receipt.deployment_receipt.role_phase_receipts.push(extra);

    let err = validate_artifact_promotion_execution_receipt(&receipt)
        .expect_err("deployment receipt cannot add roles outside promotion provenance");

    std::assert_matches!(
        err,
        ArtifactPromotionExecutionReceiptError::UnknownDeploymentRole { role } if role == "worker"
    );
}

#[test]
fn artifact_promotion_execution_receipt_text_reports_execution_summary() {
    let receipt = sample_artifact_promotion_execution_receipt();

    let text = artifact_promotion_execution_receipt_text(&receipt);

    assert!(text.contains("Artifact promotion execution receipt"));
    assert!(text.contains("mode: execution_receipt"));
    assert!(text.contains("receipt_id: promotion-execution-receipt-1"));
    assert!(text.contains("execution_receipt_digest:"));
    assert!(text.contains("artifact_promotion_plan_id: artifact-promotion-plan-1"));
    assert!(text.contains("artifact_promotion_plan_digest:"));
    assert!(text.contains("provenance_report_id: promotion-provenance-1"));
    assert!(text.contains("provenance_report_digest:"));
    assert!(text.contains("promoted_plan_id: promoted-plan-1"));
    assert!(text.contains("operation_id: promoted-operation-1"));
    assert!(text.contains("provenance_status: ready"));
    assert!(text.contains("deployment_phase_receipts: 1"));
    assert!(text.contains("root SealedWasm: result=Applied"));
    assert!(text.contains("catalog_digest="));
}