ic-backup 0.5.1

Host-side snapshot backup and same-release recovery for Internet Computer canisters
Documentation
use super::*;
use crate::test_support::{
    membership::{hash, plan},
    restore_safety,
};
use serde_json::json;

fn capture(plan: &OperationPlanRecord) -> ConsistencyRequirementRecord {
    ConsistencyRequirementRecord::new(plan, ConsistencyGuaranteeRecord::ApplicationCoordinated)
}
fn fence() -> ApplicationFenceBinding {
    ApplicationFenceBinding {
        identity: hash("56"),
        membership_revision: hash("78"),
    }
}
#[test]
fn strict_schema_and_independent_binary_goldens_admit_only_original_fields() {
    let schema: serde_json::Value = serde_json::from_str(include_str!(
        "../../../../../../docs/contracts/fence-obligation.schema.json"
    ))
    .unwrap();
    for sample in schema["x-canonical-digest"].as_array().unwrap() {
        let record: FenceObligationRecord =
            serde_json::from_value(sample["record"].clone()).unwrap();
        assert_eq!(record.digest().hash(), sample["sha256"].as_str().unwrap());
        let mut value = sample["record"].clone();
        value["plan_intent"]["hash"] = json!(
            value["plan_intent"]["hash"]
                .as_str()
                .unwrap()
                .to_uppercase()
        );
        assert_eq!(
            serde_json::from_value::<FenceObligationRecord>(value).unwrap(),
            record
        );
        for field in ["version", "plan_intent", "acquisition_operation", "scope"] {
            let mut value = sample["record"].clone();
            value.as_object_mut().unwrap().remove(field);
            assert!(serde_json::from_value::<FenceObligationRecord>(value).is_err());
        }
        for location in ["", "/scope", "/plan_intent"] {
            let mut value = sample["record"].clone();
            value.pointer_mut(location).unwrap()["released"] = json!(true);
            assert!(serde_json::from_value::<FenceObligationRecord>(value).is_err());
        }
        let mut value = sample["record"].clone();
        value["version"] = json!(2);
        assert!(serde_json::from_value::<FenceObligationRecord>(value).is_err());
        let mut value = sample["record"].clone();
        value["scope"]["purpose"] = json!("released");
        assert!(serde_json::from_value::<FenceObligationRecord>(value).is_err());
        let mut value = sample["record"].clone();
        value["plan_intent"]["hash"] = json!("invalid");
        assert!(serde_json::from_value::<FenceObligationRecord>(value).is_err());
    }
}
#[test]
fn capture_binds_full_original_plan_requirement_fence_and_explicit_acquisition() {
    let plan = plan();
    let requirement = capture(&plan);
    let record = FenceObligationRecord::for_capture(&plan, &requirement, 0, &fence()).unwrap();
    record
        .validate_capture(&plan, &requirement, &fence())
        .unwrap();
    assert_eq!(record.plan_intent(), &plan.digest());
    assert_eq!(record.acquisition_operation(), 0);
    let changed = ApplicationFenceBinding {
        identity: hash("90"),
        ..fence()
    };
    assert!(matches!(
        record.validate_capture(&plan, &requirement, &changed),
        Err(FenceObligationError::BindingMismatch)
    ));
    let changed = ApplicationFenceBinding {
        membership_revision: hash("90"),
        ..fence()
    };
    assert!(
        record
            .validate_capture(&plan, &requirement, &changed)
            .is_err()
    );
    let weaker = ConsistencyRequirementRecord::new(&plan, ConsistencyGuaranteeRecord::PerCanister);
    assert!(matches!(
        FenceObligationRecord::for_capture(&plan, &weaker, 0, &fence()),
        Err(FenceObligationError::FenceNotRequired)
    ));
    assert!(matches!(
        FenceObligationRecord::for_capture(&plan, &requirement, 99, &fence()),
        Err(FenceObligationError::Plan(_))
    ));
    for pointer in [
        "/context/network",
        "/context/release",
        "/operations/0/request",
    ] {
        let mut value = serde_json::to_value(&plan).unwrap();
        *value.pointer_mut(pointer).unwrap() = json!(hash("90").hash());
        let changed = serde_json::from_value(value).unwrap();
        assert!(matches!(
            record.validate_plan(&changed),
            Err(FenceObligationError::PlanMismatch)
        ));
    }
}
#[test]
fn zero_original_mutation_allowance_cannot_declare_acquisition() {
    let mut value = serde_json::to_value(plan()).unwrap();
    value["operations"][0]["budget"]["mutations"] = json!(0);
    let plan = serde_json::from_value(value).unwrap();
    assert!(matches!(
        FenceObligationRecord::for_capture(&plan, &capture(&plan), 0, &fence()),
        Err(FenceObligationError::NoAcquisitionAllowance)
    ));
}
#[test]
fn restore_derives_exact_source_and_fence_revisions_from_original_requirement() {
    let plan = plan();
    let source = restore_safety::source();
    let requirement =
        restore_safety::requirement(&plan, &source, RestoreSafetyLaneRecord::ApplicationFenced);
    let record = FenceObligationRecord::for_restore(&plan, &source, &requirement, 0).unwrap();
    record
        .validate_restore(&plan, &source, &requirement)
        .unwrap();
    for pointer in [
        "/source_artifacts/hash",
        "/expected_fence/identity/hash",
        "/expected_fence/membership_revision/hash",
        "/expected_fence/external_obligations_revision/hash",
    ] {
        let mut value = serde_json::to_value(&requirement).unwrap();
        *value.pointer_mut(pointer).unwrap() = json!(hash("ef").hash());
        let changed = serde_json::from_value(value).unwrap();
        assert!(matches!(
            record.validate_restore(&plan, &source, &changed),
            Err(FenceObligationError::BindingMismatch)
        ));
    }
    let weaker = restore_safety::requirement(
        &plan,
        &source,
        RestoreSafetyLaneRecord::NoIrreversibleEffects,
    );
    assert!(matches!(
        FenceObligationRecord::for_restore(&plan, &source, &weaker, 0),
        Err(FenceObligationError::FenceNotRequired)
    ));
    assert!(
        record
            .validate_capture(&plan, &capture(&plan), &fence())
            .is_err()
    );
}