ic-backup 0.62.0

Host-side snapshot backup and same-release recovery for Internet Computer canisters
Documentation
//! Shared original-plan fixture builders; assertions remain beside their owners.

use crate::model::artifacts::ArtifactChecksumRecord;
use crate::model::operation_plan::*;
use crate::model::{
    attempt_journal::AttemptBudgetRecord,
    inventory::{InventoryRecord, InventoryTargetRecord, InventoryTargetRequest},
};

pub(crate) const ROOT: &str = "aaaaa-aa";
pub(crate) const APP: &str = "renrk-eyaaa-aaaaa-aaada-cai";
pub(crate) fn request() -> OperationPlanRequest {
    let inventory = InventoryRecord::new(
        [(ROOT, None), (APP, Some(ROOT))]
            .into_iter()
            .map(|(id, parent)| {
                InventoryTargetRecord::new(&InventoryTargetRequest {
                    canister_id: id.into(),
                    parent_canister_id: parent.map(str::to_owned),
                    role: None,
                    module_hash: None,
                })
                .expect("row")
            })
            .collect(),
    )
    .expect("inventory");
    OperationPlanRequest {
        context: PlanContextRecord::new(&PlanContextRequest {
            network: "ab".repeat(32),
            caller: "2vxsx-fae".into(),
            release: "cd".repeat(32),
        })
        .expect("context"),
        inventory,
        selected_targets: vec![APP.into()],
        operations: vec![
            operation(0, APP, "ef", 1, 1),
            PlannedOperationRecord::new(PlannedOperationRequest {
                depends_on: vec![0],
                ..operation_request(&operation(7, APP, "01", 2, 1))
            })
            .unwrap(),
        ],
        budget: PlanBudgetRecord::new(3, 2).expect("aggregate ceilings"),
    }
}
/// Replace the operation catalog and aggregate budget together.
/// Context, inventory and selected targets stay with the fixture's declaration.
pub(crate) fn single_operation_request(
    mut original: OperationPlanRequest,
    mut operation: PlannedOperationRequest,
) -> OperationPlanRequest {
    operation.depends_on.clear();
    original.budget = PlanBudgetRecord::new(
        operation.budget.mutations(),
        operation.budget.observations(),
    )
    .unwrap();
    original.operations = vec![PlannedOperationRecord::new(operation).unwrap()];
    original
}

pub(crate) fn operation(
    id: u64,
    target: &str,
    hash: &str,
    mutations: u32,
    observations: u32,
) -> PlannedOperationRecord {
    PlannedOperationRecord::new(PlannedOperationRequest {
        depends_on: vec![],
        operation_sequence: id,
        target: target.into(),
        request: hash.repeat(32),
        budget: AttemptBudgetRecord::new(mutations, observations).expect("finite operation"),
    })
    .expect("operation")
}

/// Copy a valid declaration for typed fixture changes, without a JSON round trip.
pub(crate) fn declaration(plan: &OperationPlanRecord) -> OperationPlanRequest {
    OperationPlanRequest {
        context: plan.context().clone(),
        inventory: plan.inventory().clone(),
        selected_targets: plan.selected_targets().to_vec(),
        operations: plan.operations().to_vec(),
        budget: plan.budget().clone(),
    }
}

pub(crate) fn operation_request(operation: &PlannedOperationRecord) -> PlannedOperationRequest {
    PlannedOperationRequest {
        depends_on: operation.depends_on().to_vec(),
        operation_sequence: operation.operation_sequence(),
        target: operation.target().into(),
        request: operation.request().into(),
        budget: operation.budget().clone(),
    }
}

pub(crate) fn set_dependencies(
    plan: &mut OperationPlanRequest,
    dependencies: impl IntoIterator<Item = (u64, Vec<u64>)>,
) {
    for (sequence, depends_on) in dependencies {
        let row = plan
            .operations
            .iter_mut()
            .find(|row| row.operation_sequence() == sequence)
            .unwrap();
        let mut request = operation_request(row);
        request.depends_on = depends_on;
        *row = PlannedOperationRecord::new(request).unwrap();
    }
}

pub fn plan() -> OperationPlanRecord {
    OperationPlanRecord::new(request()).expect("original qualified plan fixture")
}

pub(crate) fn plan_for_request(
    digest: &crate::model::artifacts::ArtifactChecksumRecord,
) -> OperationPlanRecord {
    let mut original = request();
    original.operations[1] = PlannedOperationRecord::new(PlannedOperationRequest {
        request: digest.hash().into(),
        ..operation_request(&original.operations[1])
    })
    .unwrap();
    OperationPlanRecord::new(original).unwrap()
}

pub fn hash(pair: &str) -> crate::model::artifacts::ArtifactChecksumRecord {
    ArtifactChecksumRecord::from_hash(&pair.repeat(32)).expect("opaque exact SHA-256")
}