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, StandaloneCanisterInstallError,
7};
8
9/// One caller-built PocketIC instance and one installed canister.
10///
11/// This fixture owns the instance directly. It does not impose process-wide
12/// synchronization or hide PocketIC methods behind a wrapper.
13pub struct StandaloneCanisterFixture {
14    pocket_ic: PocketIc,
15    canister_id: Principal,
16}
17
18impl StandaloneCanisterFixture {
19    /// Install one canister into a caller-configured PocketIC instance.
20    ///
21    /// # Panics
22    ///
23    /// Panics with the contextual install error when installation fails.
24    #[must_use]
25    pub fn install(pocket_ic: PocketIc, spec: InstallSpec) -> Self {
26        Self::try_install(pocket_ic, spec)
27            .unwrap_or_else(|err| panic!("failed to install standalone canister fixture: {err}"))
28    }
29
30    /// Fallible counterpart to [`install`](Self::install).
31    ///
32    /// On failure, the error retains both the caller's PocketIC instance and
33    /// the structured install failure.
34    pub fn try_install(
35        pocket_ic: PocketIc,
36        spec: InstallSpec,
37    ) -> Result<Self, StandaloneCanisterInstallError> {
38        let canister_id = match pocket_ic.try_create_and_install(spec) {
39            Ok(canister_id) => canister_id,
40            Err(error) => return Err(StandaloneCanisterInstallError::new(pocket_ic, error)),
41        };
42
43        Ok(Self {
44            pocket_ic,
45            canister_id,
46        })
47    }
48
49    /// Borrow the PocketIC instance that owns this standalone fixture.
50    #[must_use]
51    pub const fn pocket_ic(&self) -> &PocketIc {
52        &self.pocket_ic
53    }
54
55    /// Read the installed canister id for this standalone fixture.
56    #[must_use]
57    pub const fn canister_id(&self) -> Principal {
58        self.canister_id
59    }
60
61    /// Consume the fixture and return the owned PocketIC instance and canister id.
62    #[must_use]
63    pub fn into_parts(self) -> (PocketIc, Principal) {
64        (self.pocket_ic, self.canister_id)
65    }
66
67    /// Forward one typed update call to this fixture's canister id.
68    pub fn update_candid<T, A>(&self, method: &str, args: A) -> Result<T, CandidCallError>
69    where
70        T: CandidType + DeserializeOwned,
71        A: ArgumentEncoder,
72    {
73        self.pocket_ic.update_candid(self.canister_id, method, args)
74    }
75
76    /// Forward one typed update call to this fixture's canister id, panicking
77    /// on rejection or Candid codec failure.
78    ///
79    /// This does not unwrap application-level results. For example,
80    /// `update_candid_or_panic::<Result<T, E>, _>(...)` returns `Result<T, E>`.
81    #[track_caller]
82    pub fn update_candid_or_panic<T, A>(&self, method: &str, args: A) -> T
83    where
84        T: CandidType + DeserializeOwned,
85        A: ArgumentEncoder,
86    {
87        self.pocket_ic
88            .update_candid_or_panic(self.canister_id, method, args)
89    }
90
91    /// Forward one typed update call with an explicit caller to this fixture's canister id.
92    pub fn update_candid_as<T, A>(
93        &self,
94        caller: Principal,
95        method: &str,
96        args: A,
97    ) -> Result<T, CandidCallError>
98    where
99        T: CandidType + DeserializeOwned,
100        A: ArgumentEncoder,
101    {
102        self.pocket_ic
103            .update_candid_as(self.canister_id, caller, method, args)
104    }
105
106    /// Forward one typed update call with an explicit caller to this fixture's
107    /// canister id, panicking on rejection or Candid codec failure.
108    ///
109    /// This does not unwrap application-level results. For example,
110    /// `update_candid_as_or_panic::<Result<T, E>, _>(...)` returns `Result<T, E>`.
111    #[track_caller]
112    pub fn update_candid_as_or_panic<T, A>(&self, caller: Principal, method: &str, args: A) -> T
113    where
114        T: CandidType + DeserializeOwned,
115        A: ArgumentEncoder,
116    {
117        self.pocket_ic
118            .update_candid_as_or_panic(self.canister_id, caller, method, args)
119    }
120
121    /// Forward one typed query call to this fixture's canister id.
122    pub fn query_candid<T, A>(&self, method: &str, args: A) -> Result<T, CandidCallError>
123    where
124        T: CandidType + DeserializeOwned,
125        A: ArgumentEncoder,
126    {
127        self.pocket_ic.query_candid(self.canister_id, method, args)
128    }
129
130    /// Forward one typed query call to this fixture's canister id, panicking on
131    /// rejection or Candid codec failure.
132    ///
133    /// This does not unwrap application-level results. For example,
134    /// `query_candid_or_panic::<Result<T, E>, _>(...)` returns `Result<T, E>`.
135    #[track_caller]
136    pub fn query_candid_or_panic<T, A>(&self, method: &str, args: A) -> T
137    where
138        T: CandidType + DeserializeOwned,
139        A: ArgumentEncoder,
140    {
141        self.pocket_ic
142            .query_candid_or_panic(self.canister_id, method, args)
143    }
144
145    /// Forward one typed query call with an explicit caller to this fixture's canister id.
146    pub fn query_candid_as<T, A>(
147        &self,
148        caller: Principal,
149        method: &str,
150        args: A,
151    ) -> Result<T, CandidCallError>
152    where
153        T: CandidType + DeserializeOwned,
154        A: ArgumentEncoder,
155    {
156        self.pocket_ic
157            .query_candid_as(self.canister_id, caller, method, args)
158    }
159
160    /// Forward one typed query call with an explicit caller to this fixture's
161    /// canister id, panicking on rejection or Candid codec failure.
162    ///
163    /// This does not unwrap application-level results. For example,
164    /// `query_candid_as_or_panic::<Result<T, E>, _>(...)` returns `Result<T, E>`.
165    #[track_caller]
166    pub fn query_candid_as_or_panic<T, A>(&self, caller: Principal, method: &str, args: A) -> T
167    where
168        T: CandidType + DeserializeOwned,
169        A: ArgumentEncoder,
170    {
171        self.pocket_ic
172            .query_candid_as_or_panic(self.canister_id, caller, method, args)
173    }
174}