abstract_testing/
map_tester.rs

1use std::fmt::Debug;
2
3use cosmwasm_std::{testing::mock_env, DepsMut, Env, MessageInfo, Order, Response, Storage};
4use cw_storage_plus::{KeyDeserialize, Map, PrimaryKey};
5use derive_builder::Builder;
6use serde::{de::DeserializeOwned, Serialize};
7use serde_json::json;
8
9use crate::MockDeps;
10
11#[derive(Builder)]
12#[builder(pattern = "owned")]
13pub struct CwMapTester<ExecMsg, TError, K, V, UncheckedK, UncheckedV>
14where
15    K: KeyDeserialize + Debug,
16    K::Output: 'static,
17{
18    info: MessageInfo,
19    map: Map<K, V>,
20    execute:
21        fn(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecMsg) -> Result<Response, TError>,
22    msg_builder: fn(to_add: Vec<(UncheckedK, UncheckedV)>, to_remove: Vec<UncheckedK>) -> ExecMsg,
23    mock_entry: (UncheckedK, UncheckedV),
24    from_checked_entry: fn((K::Output, V)) -> (UncheckedK, UncheckedV),
25}
26
27/// Sort the expected entries by *key*
28fn sort_expected<K, V>(expected: &mut [(K, V)])
29where
30    K: Clone + PartialEq + Debug + Serialize,
31    V: Clone + PartialEq + Debug,
32{
33    expected.sort_by(|a, b| json!(a.0).to_string().cmp(&json!(&b.0).to_string()));
34}
35
36#[allow(clippy::ptr_arg)]
37pub fn determine_expected<K, V>(to_add: &Vec<(K, V)>, to_remove: &[K]) -> Vec<(K, V)>
38where
39    K: Clone + PartialEq + Debug + Serialize,
40    V: Clone + PartialEq + Debug,
41{
42    let mut expected = to_add.clone();
43    expected.retain(|(k, _)| !to_remove.contains(k));
44    sort_expected(&mut expected);
45    expected.dedup();
46    expected
47}
48
49impl<'a, ExecMsg, TError, K, V, UncheckedK, UncheckedV>
50    CwMapTester<ExecMsg, TError, K, V, UncheckedK, UncheckedV>
51where
52    V: Serialize + DeserializeOwned + Clone + Debug,
53    K: PrimaryKey<'a> + KeyDeserialize + Debug,
54    (&'a <K as KeyDeserialize>::Output, V): PartialEq<(K, V)>,
55    K::Output: 'static,
56    UncheckedK: Clone + PartialEq + Debug + Serialize,
57    UncheckedV: Clone + PartialEq + Debug,
58    <K as KeyDeserialize>::Output: Debug,
59{
60    pub fn new(
61        info: MessageInfo,
62        map: Map<K, V>,
63        execute: fn(
64            deps: DepsMut,
65            env: Env,
66            info: MessageInfo,
67            msg: ExecMsg,
68        ) -> Result<Response, TError>,
69        msg_builder: fn(
70            to_add: Vec<(UncheckedK, UncheckedV)>,
71            to_remove: Vec<UncheckedK>,
72        ) -> ExecMsg,
73        mock_entry: (UncheckedK, UncheckedV),
74        from_checked_entry: fn((K::Output, V)) -> (UncheckedK, UncheckedV),
75    ) -> Self {
76        Self {
77            info,
78            map,
79            execute,
80            msg_builder,
81            mock_entry,
82            from_checked_entry,
83        }
84    }
85
86    pub fn msg_builder(
87        &self,
88        to_add: Vec<(UncheckedK, UncheckedV)>,
89        to_remove: Vec<UncheckedK>,
90    ) -> ExecMsg {
91        (self.msg_builder)(to_add, to_remove)
92    }
93
94    fn mock_entry_builder(&self) -> (UncheckedK, UncheckedV) {
95        self.mock_entry.clone()
96    }
97
98    /// Execute the msg with the mock env
99    pub fn execute(&mut self, deps: DepsMut, msg: ExecMsg) -> Result<(), TError> {
100        (self.execute)(deps, mock_env(), self.info.clone(), msg)?;
101        Ok(())
102    }
103
104    pub fn execute_update(
105        &mut self,
106        deps: DepsMut,
107        (to_add, to_remove): (Vec<(UncheckedK, UncheckedV)>, Vec<UncheckedK>),
108    ) -> Result<(), TError> {
109        let msg = self.msg_builder(to_add, to_remove);
110        self.execute(deps, msg)
111    }
112
113    #[allow(clippy::wrong_self_convention)]
114    fn from_checked_entry(&self, entry: (K::Output, V)) -> (UncheckedK, UncheckedV) {
115        (self.from_checked_entry)(entry)
116    }
117
118    pub fn assert_expected_entries(
119        &self,
120        storage: &'_ dyn Storage,
121        expected: Vec<(UncheckedK, UncheckedV)>,
122    ) {
123        let res: Result<Vec<(K::Output, V)>, _> = self
124            .map
125            .range(storage, None, None, Order::Ascending)
126            .collect();
127
128        let actual = res
129            .unwrap()
130            .into_iter()
131            .map(|(k, v)| self.from_checked_entry((k, v)))
132            .collect::<Vec<_>>();
133
134        // Sort, like map entries
135        let mut expected = expected;
136        sort_expected(&mut expected);
137
138        assert_eq!(actual, expected)
139    }
140
141    pub fn test_add_one(&mut self, deps: &mut MockDeps) -> Result<(), TError> {
142        let entry = self.mock_entry_builder();
143
144        let to_add: Vec<(UncheckedK, UncheckedV)> = vec![entry];
145        let to_remove: Vec<UncheckedK> = vec![];
146        let msg = self.msg_builder(to_add.clone(), to_remove.clone());
147
148        let expected = determine_expected(&to_add, &to_remove);
149
150        self.execute(deps.as_mut(), msg)?;
151
152        self.assert_expected_entries(&deps.storage, expected);
153
154        Ok(())
155    }
156
157    pub fn test_add_one_twice(&mut self, deps: &mut MockDeps) -> Result<(), TError> {
158        self.test_add_one(deps)?;
159        self.test_add_one(deps)
160    }
161
162    pub fn test_add_two_same(&mut self, deps: &mut MockDeps) -> Result<(), TError> {
163        let entry = self.mock_entry_builder();
164
165        let to_add: Vec<(UncheckedK, UncheckedV)> = vec![entry.clone(), entry];
166        let to_remove: Vec<UncheckedK> = vec![];
167        let msg = self.msg_builder(to_add.clone(), to_remove.clone());
168
169        let expected: Vec<(UncheckedK, UncheckedV)> = determine_expected(&to_add, &to_remove);
170
171        self.execute(deps.as_mut(), msg)?;
172
173        self.assert_expected_entries(&deps.storage, expected);
174
175        Ok(())
176    }
177
178    pub fn test_add_and_remove_same(&mut self, deps: &mut MockDeps) -> Result<(), TError> {
179        let entry = self.mock_entry_builder();
180
181        let to_add: Vec<(UncheckedK, UncheckedV)> = vec![entry.clone()];
182        let to_remove: Vec<UncheckedK> = vec![entry.0];
183        let msg = self.msg_builder(to_add, to_remove);
184
185        let expected: Vec<(UncheckedK, UncheckedV)> = vec![];
186
187        self.execute(deps.as_mut(), msg)?;
188
189        self.assert_expected_entries(&deps.storage, expected);
190
191        Ok(())
192    }
193
194    pub fn test_remove_nonexistent(&mut self, deps: &mut MockDeps) -> Result<(), TError> {
195        let entry = self.mock_entry_builder();
196
197        let to_add: Vec<(UncheckedK, UncheckedV)> = vec![];
198        let to_remove: Vec<UncheckedK> = vec![entry.0];
199        let msg = self.msg_builder(to_add, to_remove);
200
201        let expected: Vec<(UncheckedK, UncheckedV)> = vec![];
202
203        self.execute(deps.as_mut(), msg)?;
204
205        self.assert_expected_entries(&deps.storage, expected);
206
207        Ok(())
208    }
209
210    /// Run through all the preconfigured test scenarios
211    pub fn test_all(&mut self, deps: &mut MockDeps) -> Result<(), TError> {
212        self.test_add_one(deps)?;
213        self.test_add_one_twice(deps)?;
214        self.test_add_two_same(deps)?;
215        self.test_add_and_remove_same(deps)?;
216        self.test_remove_nonexistent(deps)?;
217
218        Ok(())
219    }
220
221    /// Test the manually provided arguments with the expected behavior, which is removing any duplicate entries that are within both add and remove
222    pub fn test_update_auto_expect(
223        &mut self,
224        deps: &mut MockDeps,
225        update: (Vec<(UncheckedK, UncheckedV)>, Vec<UncheckedK>),
226    ) -> Result<(), TError> {
227        let (to_add, to_remove) = update;
228        let msg = self.msg_builder(to_add.clone(), to_remove.clone());
229
230        let expected: Vec<(UncheckedK, UncheckedV)> = determine_expected(&to_add, &to_remove);
231
232        self.execute(deps.as_mut(), msg)?;
233
234        self.assert_expected_entries(&deps.storage, expected);
235
236        Ok(())
237    }
238
239    /// Provide an update nad expected result, and test that the expected result is returned
240    pub fn test_update_with_expected(
241        &mut self,
242        deps: &mut MockDeps,
243        update: (Vec<(UncheckedK, UncheckedV)>, Vec<UncheckedK>),
244        expected: Vec<(UncheckedK, UncheckedV)>,
245    ) -> Result<(), TError> {
246        let (to_add, to_remove) = update;
247        let msg = self.msg_builder(to_add, to_remove);
248
249        self.execute(deps.as_mut(), msg)?;
250
251        self.assert_expected_entries(&deps.storage, expected);
252
253        Ok(())
254    }
255}
256
257#[cfg(test)]
258mod test {
259    #![allow(clippy::needless_borrows_for_generic_args)]
260    use super::*;
261
262    mod determine_expected {
263        use super::*;
264
265        #[test]
266        fn removes_in_to_add() {
267            let to_add = vec![("a".to_string(), 1), ("b".to_string(), 2)];
268            let to_remove = vec!["a".to_string()];
269
270            let expected = vec![("b".to_string(), 2)];
271
272            assert_eq!(determine_expected(&to_add, &to_remove), expected);
273        }
274
275        #[test]
276        fn removes_all() {
277            let to_add = vec![("a".to_string(), 1), ("b".to_string(), 2)];
278            let to_remove = vec!["a".to_string(), "b".to_string()];
279
280            let expected: Vec<(String, i32)> = vec![];
281
282            assert_eq!(determine_expected(&to_add, &to_remove), expected);
283        }
284
285        #[test]
286        fn empty() {
287            let to_add: Vec<(String, i32)> = vec![];
288            let to_remove: Vec<String> = vec![];
289
290            let expected: Vec<(String, i32)> = vec![];
291
292            assert_eq!(determine_expected(&to_add, &to_remove), expected);
293        }
294    }
295
296    mod sort_expected {}
297}