Skip to main content

ic_testkit/pic/
standalone.rs

1use candid::{CandidType, Principal, utils::ArgumentEncoder};
2use pocket_ic::PocketIc;
3use serde::de::DeserializeOwned;
4
5use super::{
6    CandidCallError, CandidCallExt, CanisterInstallExt, InstallSpec,
7    StandaloneCanisterFixtureError, try_pic,
8};
9
10const DEFAULT_EXTRA_INSTALL_CYCLES: u128 = 0;
11
12///
13/// StandaloneCanisterFixture
14///
15
16pub struct StandaloneCanisterFixture {
17    pocket_ic: PocketIc,
18    canister_id: Principal,
19}
20
21impl StandaloneCanisterFixture {
22    /// Borrow the PocketIC instance that owns this standalone fixture.
23    #[must_use]
24    pub const fn pocket_ic(&self) -> &PocketIc {
25        &self.pocket_ic
26    }
27
28    /// Read the installed canister id for this standalone fixture.
29    #[must_use]
30    pub const fn canister_id(&self) -> Principal {
31        self.canister_id
32    }
33
34    /// Consume the fixture and return the owned PocketIC instance and canister id.
35    #[must_use]
36    pub fn into_parts(self) -> (PocketIc, Principal) {
37        (self.pocket_ic, self.canister_id)
38    }
39
40    /// Forward one typed update call to this fixture's canister id.
41    pub fn update_call<T, A>(&self, method: &str, args: A) -> Result<T, CandidCallError>
42    where
43        T: CandidType + DeserializeOwned,
44        A: ArgumentEncoder,
45    {
46        self.pocket_ic.update_candid(self.canister_id, method, args)
47    }
48
49    /// Forward one typed update call to this fixture's canister id, panicking
50    /// on rejection or Candid codec failure.
51    ///
52    /// This does not unwrap application-level results. For example,
53    /// `update_call_or_panic::<Result<T, E>, _>(...)` returns `Result<T, E>`.
54    #[track_caller]
55    pub fn update_call_or_panic<T, A>(&self, method: &str, args: A) -> T
56    where
57        T: CandidType + DeserializeOwned,
58        A: ArgumentEncoder,
59    {
60        self.pocket_ic
61            .update_candid_or_panic(self.canister_id, method, args)
62    }
63
64    /// Forward one typed update call with an explicit caller to this fixture's canister id.
65    pub fn update_call_as<T, A>(
66        &self,
67        caller: Principal,
68        method: &str,
69        args: A,
70    ) -> Result<T, CandidCallError>
71    where
72        T: CandidType + DeserializeOwned,
73        A: ArgumentEncoder,
74    {
75        self.pocket_ic
76            .update_candid_as(self.canister_id, caller, method, args)
77    }
78
79    /// Forward one typed update call with an explicit caller to this fixture's
80    /// canister id, panicking on rejection or Candid codec failure.
81    ///
82    /// This does not unwrap application-level results. For example,
83    /// `update_call_as_or_panic::<Result<T, E>, _>(...)` returns `Result<T, E>`.
84    #[track_caller]
85    pub fn update_call_as_or_panic<T, A>(&self, caller: Principal, method: &str, args: A) -> T
86    where
87        T: CandidType + DeserializeOwned,
88        A: ArgumentEncoder,
89    {
90        self.pocket_ic
91            .update_candid_as_or_panic(self.canister_id, caller, method, args)
92    }
93
94    /// Forward one typed query call to this fixture's canister id.
95    pub fn query_call<T, A>(&self, method: &str, args: A) -> Result<T, CandidCallError>
96    where
97        T: CandidType + DeserializeOwned,
98        A: ArgumentEncoder,
99    {
100        self.pocket_ic.query_candid(self.canister_id, method, args)
101    }
102
103    /// Forward one typed query call to this fixture's canister id, panicking on
104    /// rejection or Candid codec failure.
105    ///
106    /// This does not unwrap application-level results. For example,
107    /// `query_call_or_panic::<Result<T, E>, _>(...)` returns `Result<T, E>`.
108    #[track_caller]
109    pub fn query_call_or_panic<T, A>(&self, method: &str, args: A) -> T
110    where
111        T: CandidType + DeserializeOwned,
112        A: ArgumentEncoder,
113    {
114        self.pocket_ic
115            .query_candid_or_panic(self.canister_id, method, args)
116    }
117
118    /// Forward one typed query call with an explicit caller to this fixture's canister id.
119    pub fn query_call_as<T, A>(
120        &self,
121        caller: Principal,
122        method: &str,
123        args: A,
124    ) -> Result<T, CandidCallError>
125    where
126        T: CandidType + DeserializeOwned,
127        A: ArgumentEncoder,
128    {
129        self.pocket_ic
130            .query_candid_as(self.canister_id, caller, method, args)
131    }
132
133    /// Forward one typed query call with an explicit caller to this fixture's
134    /// canister id, panicking on rejection or Candid codec failure.
135    ///
136    /// This does not unwrap application-level results. For example,
137    /// `query_call_as_or_panic::<Result<T, E>, _>(...)` returns `Result<T, E>`.
138    #[track_caller]
139    pub fn query_call_as_or_panic<T, A>(&self, caller: Principal, method: &str, args: A) -> T
140    where
141        T: CandidType + DeserializeOwned,
142        A: ArgumentEncoder,
143    {
144        self.pocket_ic
145            .query_candid_as_or_panic(self.canister_id, caller, method, args)
146    }
147}
148
149// Install one already-built wasm module into a fresh PocketIC instance with
150// caller-provided init args and no application-specific bootstrap assumptions.
151#[must_use]
152pub fn install_prebuilt_canister(wasm: Vec<u8>, init_bytes: Vec<u8>) -> StandaloneCanisterFixture {
153    try_install_prebuilt_canister(wasm, init_bytes)
154        .unwrap_or_else(|err| panic!("failed to install prebuilt canister fixture: {err}"))
155}
156
157// Install one already-built wasm module into a fresh PocketIC instance with
158// caller-provided init args and no application-specific bootstrap assumptions.
159pub fn try_install_prebuilt_canister(
160    wasm: Vec<u8>,
161    init_bytes: Vec<u8>,
162) -> Result<StandaloneCanisterFixture, StandaloneCanisterFixtureError> {
163    try_install_prebuilt_canister_from_spec(InstallSpec::new(
164        wasm,
165        init_bytes,
166        DEFAULT_EXTRA_INSTALL_CYCLES,
167    ))
168}
169
170// Install one already-built wasm module into a fresh PocketIC instance with
171// caller-provided init args and explicit install cycles.
172#[must_use]
173pub fn install_prebuilt_canister_with_cycles(
174    wasm: Vec<u8>,
175    init_bytes: Vec<u8>,
176    install_cycles: u128,
177) -> StandaloneCanisterFixture {
178    try_install_prebuilt_canister_with_cycles(wasm, init_bytes, install_cycles)
179        .unwrap_or_else(|err| panic!("failed to install prebuilt canister fixture: {err}"))
180}
181
182// Install one already-built wasm module into a fresh PocketIC instance with
183// caller-provided init args and explicit install cycles.
184pub fn try_install_prebuilt_canister_with_cycles(
185    wasm: Vec<u8>,
186    init_bytes: Vec<u8>,
187    install_cycles: u128,
188) -> Result<StandaloneCanisterFixture, StandaloneCanisterFixtureError> {
189    try_install_prebuilt_canister_from_spec(InstallSpec::new(wasm, init_bytes, install_cycles))
190}
191
192// Install one already-built wasm module from a generic install specification
193// into a fresh PocketIC instance.
194#[must_use]
195pub fn install_prebuilt_canister_from_spec(spec: InstallSpec) -> StandaloneCanisterFixture {
196    try_install_prebuilt_canister_from_spec(spec)
197        .unwrap_or_else(|err| panic!("failed to install prebuilt canister fixture: {err}"))
198}
199
200// Install one already-built wasm module from a generic install specification
201// into a fresh PocketIC instance.
202pub fn try_install_prebuilt_canister_from_spec(
203    spec: InstallSpec,
204) -> Result<StandaloneCanisterFixture, StandaloneCanisterFixtureError> {
205    let pocket_ic = try_pic().map_err(StandaloneCanisterFixtureError::Start)?;
206    let canister_id = pocket_ic
207        .try_create_and_install(spec)
208        .map_err(StandaloneCanisterFixtureError::Install)?;
209
210    Ok(StandaloneCanisterFixture {
211        pocket_ic,
212        canister_id,
213    })
214}