ic_canister_runtime/stub/mod.rs
1#[cfg(test)]
2mod tests;
3
4use crate::{IcError, Runtime};
5use async_trait::async_trait;
6use candid::{utils::ArgumentEncoder, CandidType, Decode, Encode, Principal};
7use serde::de::DeserializeOwned;
8use std::{collections::VecDeque, sync::Mutex};
9
10/// An implementation of [`Runtime`] that returns pre-defined results from a queue.
11/// This runtime is primarily intended for testing purposes.
12///
13/// # Examples
14///
15/// ```rust
16/// # #[tokio::main]
17/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
18/// use candid::Principal;
19/// use ic_canister_runtime::{IcError, Runtime, StubRuntime};
20///
21/// const PRINCIPAL: Principal = Principal::from_slice(&[0x9d, 0xf7, 0x01]);
22/// const METHOD: &str = "method";
23/// const ARGS: (&str,) = ("args",);
24///
25/// let runtime = StubRuntime::new()
26/// .add_stub_response(1_u64)
27/// .add_stub_response("two")
28/// .add_stub_response(Some(3_u128));
29///
30/// let result_1: Result<u64, IcError> = runtime
31/// .update_call(PRINCIPAL, METHOD, ARGS, 0)
32/// .await;
33/// assert_eq!(result_1, Ok(1_u64));
34///
35/// let result_2: Result<String, IcError> = runtime
36/// .query_call(PRINCIPAL, METHOD, ARGS)
37/// .await;
38/// assert_eq!(result_2, Ok("two".to_string()));
39///
40/// let result_3: Result<Option<u128>, IcError> = runtime
41/// .update_call(PRINCIPAL, METHOD, ARGS, 0)
42/// .await;
43/// assert_eq!(result_3, Ok(Some
44/// (3_u128)));
45/// # Ok(())
46/// # }
47/// ```
48#[derive(Debug, Default)]
49pub struct StubRuntime {
50 // Use a mutex so that this struct is Send and Sync
51 call_results: Mutex<VecDeque<Vec<u8>>>,
52}
53
54impl StubRuntime {
55 /// Create a new empty [`StubRuntime`].
56 pub fn new() -> Self {
57 Self::default()
58 }
59
60 /// Mutate the [`StubRuntime`] instance to add the given stub response.
61 ///
62 /// Panics if the stub response cannot be encoded using Candid.
63 pub fn add_stub_response<Out: CandidType>(self, stub_response: Out) -> Self {
64 let result = Encode!(&stub_response).expect("Failed to encode Candid stub response");
65 self.call_results.try_lock().unwrap().push_back(result);
66 self
67 }
68
69 fn call<Out>(&self) -> Result<Out, IcError>
70 where
71 Out: CandidType + DeserializeOwned,
72 {
73 let bytes = self
74 .call_results
75 .try_lock()
76 .unwrap()
77 .pop_front()
78 .unwrap_or_else(|| panic!("No available call response"));
79 Ok(Decode!(&bytes, Out).expect("Failed to decode Candid stub response"))
80 }
81}
82
83#[async_trait]
84impl Runtime for StubRuntime {
85 async fn update_call<In, Out>(
86 &self,
87 _id: Principal,
88 _method: &str,
89 _args: In,
90 _cycles: u128,
91 ) -> Result<Out, IcError>
92 where
93 In: ArgumentEncoder + Send,
94 Out: CandidType + DeserializeOwned,
95 {
96 self.call()
97 }
98
99 async fn query_call<In, Out>(
100 &self,
101 _id: Principal,
102 _method: &str,
103 _args: In,
104 ) -> Result<Out, IcError>
105 where
106 In: ArgumentEncoder + Send,
107 Out: CandidType + DeserializeOwned,
108 {
109 self.call()
110 }
111}