ic-testkit 0.1.10

PocketIC-oriented test utilities for IC canister tests
Documentation
use candid::{CandidType, Principal, utils::ArgumentEncoder};
use serde::de::DeserializeOwned;

use super::{
    InstallSpec, Pic, PicCallError, PicSerialGuard, StandaloneCanisterFixtureError,
    try_acquire_pic_serial_guard, try_pic,
};

const DEFAULT_EXTRA_INSTALL_CYCLES: u128 = 0;

///
/// StandaloneCanisterFixture
///

pub struct StandaloneCanisterFixture {
    pic: Pic,
    canister_id: Principal,
    _serial_guard: PicSerialGuard,
}

impl StandaloneCanisterFixture {
    /// Borrow the PocketIC instance that owns this standalone fixture.
    #[must_use]
    pub const fn pic(&self) -> &Pic {
        &self.pic
    }

    /// Mutably borrow the PocketIC instance that owns this standalone fixture.
    #[must_use]
    pub const fn pic_mut(&mut self) -> &mut Pic {
        &mut self.pic
    }

    /// Read the installed canister id for this standalone fixture.
    #[must_use]
    pub const fn canister_id(&self) -> Principal {
        self.canister_id
    }

    /// Consume the fixture and return the owned PocketIC instance and canister id.
    #[must_use]
    pub fn into_parts(self) -> (Pic, Principal) {
        (self.pic, self.canister_id)
    }

    /// Forward one typed update call to this fixture's canister id.
    pub fn update_call<T, A>(&self, method: &str, args: A) -> Result<T, PicCallError>
    where
        T: CandidType + DeserializeOwned,
        A: ArgumentEncoder,
    {
        self.pic.update_call(self.canister_id, method, args)
    }

    /// Forward one typed update call to this fixture's canister id, panicking
    /// on transport or Candid codec failure.
    ///
    /// This does not unwrap application-level results. For example,
    /// `update_call_or_panic::<Result<T, E>, _>(...)` returns `Result<T, E>`.
    #[track_caller]
    pub fn update_call_or_panic<T, A>(&self, method: &str, args: A) -> T
    where
        T: CandidType + DeserializeOwned,
        A: ArgumentEncoder,
    {
        self.pic
            .update_call_or_panic(self.canister_id, method, args)
    }

    /// Forward one typed update call with an explicit caller to this fixture's canister id.
    pub fn update_call_as<T, A>(
        &self,
        caller: Principal,
        method: &str,
        args: A,
    ) -> Result<T, PicCallError>
    where
        T: CandidType + DeserializeOwned,
        A: ArgumentEncoder,
    {
        self.pic
            .update_call_as(self.canister_id, caller, method, args)
    }

    /// Forward one typed update call with an explicit caller to this fixture's
    /// canister id, panicking on transport or Candid codec failure.
    ///
    /// This does not unwrap application-level results. For example,
    /// `update_call_as_or_panic::<Result<T, E>, _>(...)` returns `Result<T, E>`.
    #[track_caller]
    pub fn update_call_as_or_panic<T, A>(&self, caller: Principal, method: &str, args: A) -> T
    where
        T: CandidType + DeserializeOwned,
        A: ArgumentEncoder,
    {
        self.pic
            .update_call_as_or_panic(self.canister_id, caller, method, args)
    }

    /// Forward one typed query call to this fixture's canister id.
    pub fn query_call<T, A>(&self, method: &str, args: A) -> Result<T, PicCallError>
    where
        T: CandidType + DeserializeOwned,
        A: ArgumentEncoder,
    {
        self.pic.query_call(self.canister_id, method, args)
    }

    /// Forward one typed query call to this fixture's canister id, panicking on
    /// transport or Candid codec failure.
    ///
    /// This does not unwrap application-level results. For example,
    /// `query_call_or_panic::<Result<T, E>, _>(...)` returns `Result<T, E>`.
    #[track_caller]
    pub fn query_call_or_panic<T, A>(&self, method: &str, args: A) -> T
    where
        T: CandidType + DeserializeOwned,
        A: ArgumentEncoder,
    {
        self.pic.query_call_or_panic(self.canister_id, method, args)
    }

    /// Forward one typed query call with an explicit caller to this fixture's canister id.
    pub fn query_call_as<T, A>(
        &self,
        caller: Principal,
        method: &str,
        args: A,
    ) -> Result<T, PicCallError>
    where
        T: CandidType + DeserializeOwned,
        A: ArgumentEncoder,
    {
        self.pic
            .query_call_as(self.canister_id, caller, method, args)
    }

    /// Forward one typed query call with an explicit caller to this fixture's
    /// canister id, panicking on transport or Candid codec failure.
    ///
    /// This does not unwrap application-level results. For example,
    /// `query_call_as_or_panic::<Result<T, E>, _>(...)` returns `Result<T, E>`.
    #[track_caller]
    pub fn query_call_as_or_panic<T, A>(&self, caller: Principal, method: &str, args: A) -> T
    where
        T: CandidType + DeserializeOwned,
        A: ArgumentEncoder,
    {
        self.pic
            .query_call_as_or_panic(self.canister_id, caller, method, args)
    }
}

// Install one already-built wasm module into a fresh PocketIC instance with
// caller-provided init args and no application-specific bootstrap assumptions.
#[must_use]
pub fn install_prebuilt_canister(wasm: Vec<u8>, init_bytes: Vec<u8>) -> StandaloneCanisterFixture {
    try_install_prebuilt_canister(wasm, init_bytes)
        .unwrap_or_else(|err| panic!("failed to install prebuilt canister fixture: {err}"))
}

// Install one already-built wasm module into a fresh PocketIC instance with
// caller-provided init args and no application-specific bootstrap assumptions.
pub fn try_install_prebuilt_canister(
    wasm: Vec<u8>,
    init_bytes: Vec<u8>,
) -> Result<StandaloneCanisterFixture, StandaloneCanisterFixtureError> {
    try_install_prebuilt_canister_from_spec(InstallSpec::new(
        wasm,
        init_bytes,
        DEFAULT_EXTRA_INSTALL_CYCLES,
    ))
}

// Install one already-built wasm module into a fresh PocketIC instance with
// caller-provided init args and explicit install cycles.
#[must_use]
pub fn install_prebuilt_canister_with_cycles(
    wasm: Vec<u8>,
    init_bytes: Vec<u8>,
    install_cycles: u128,
) -> StandaloneCanisterFixture {
    try_install_prebuilt_canister_with_cycles(wasm, init_bytes, install_cycles)
        .unwrap_or_else(|err| panic!("failed to install prebuilt canister fixture: {err}"))
}

// Install one already-built wasm module into a fresh PocketIC instance with
// caller-provided init args and explicit install cycles.
pub fn try_install_prebuilt_canister_with_cycles(
    wasm: Vec<u8>,
    init_bytes: Vec<u8>,
    install_cycles: u128,
) -> Result<StandaloneCanisterFixture, StandaloneCanisterFixtureError> {
    try_install_prebuilt_canister_from_spec(InstallSpec::new(wasm, init_bytes, install_cycles))
}

// Install one already-built wasm module from a generic install specification
// into a fresh PocketIC instance.
#[must_use]
pub fn install_prebuilt_canister_from_spec(spec: InstallSpec) -> StandaloneCanisterFixture {
    try_install_prebuilt_canister_from_spec(spec)
        .unwrap_or_else(|err| panic!("failed to install prebuilt canister fixture: {err}"))
}

// Install one already-built wasm module from a generic install specification
// into a fresh PocketIC instance.
pub fn try_install_prebuilt_canister_from_spec(
    spec: InstallSpec,
) -> Result<StandaloneCanisterFixture, StandaloneCanisterFixtureError> {
    let serial_guard =
        try_acquire_pic_serial_guard().map_err(StandaloneCanisterFixtureError::SerialGuard)?;
    let pic = try_pic().map_err(StandaloneCanisterFixtureError::Start)?;
    let canister_id = pic
        .try_create_and_install(spec)
        .map_err(StandaloneCanisterFixtureError::Install)?;

    Ok(StandaloneCanisterFixture {
        pic,
        canister_id,
        _serial_guard: serial_guard,
    })
}