Skip to main content

neo_test/
assertions.rs

1// Copyright (c) 2025-2026 R3E Network
2// Licensed under the MIT License
3
4//! Test Assertions
5
6use crate::mock_runtime::{MockRuntime, MockStorage};
7use neo_types::*;
8use num_traits::{Signed, ToPrimitive};
9
10fn saturating_i64(value: &NeoInteger) -> i64 {
11    value.as_bigint().to_i64().unwrap_or_else(|| {
12        if value.as_bigint().is_negative() {
13            i64::MIN
14        } else {
15            i64::MAX
16        }
17    })
18}
19
20/// Result of a contract method call for assertions
21pub struct MethodCallResult {
22    result: Result<NeoValue, NeoError>,
23}
24
25impl MethodCallResult {
26    pub fn new(result: Result<NeoValue, NeoError>) -> Self {
27        Self { result }
28    }
29
30    pub fn ok(result: NeoValue) -> Self {
31        Self { result: Ok(result) }
32    }
33
34    pub fn err(error: NeoError) -> Self {
35        Self { result: Err(error) }
36    }
37
38    pub fn assert_ok(&self) {
39        assert!(
40            self.result.is_ok(),
41            "Expected Ok, got Err: {:?}",
42            self.result.as_ref().err()
43        );
44    }
45
46    pub fn assert_err(&self) {
47        assert!(
48            self.result.is_err(),
49            "Expected Err, got Ok: {:?}",
50            self.result.as_ref().ok()
51        );
52    }
53
54    pub fn assert_returns(&self, expected: i64) {
55        let value = self.expect_ok_value();
56        let integer = value
57            .as_integer()
58            .unwrap_or_else(|| panic!("Expected integer return value, got {:?}", value));
59        let actual = saturating_i64(integer);
60        assert_eq!(
61            actual, expected,
62            "Expected return value {}, got {}",
63            expected, actual
64        );
65    }
66
67    pub fn assert_returns_bool(&self, expected: bool) {
68        let value = self.expect_ok_value();
69        let actual = value
70            .as_boolean()
71            .unwrap_or_else(|| panic!("Expected boolean return value, got {:?}", value))
72            .as_bool();
73        assert_eq!(
74            actual, expected,
75            "Expected return value {}, got {}",
76            expected, actual
77        );
78    }
79
80    pub fn assert_returns_slice(&self, expected: &[u8]) {
81        let value = self.expect_ok_value();
82        let actual = value
83            .as_byte_string()
84            .unwrap_or_else(|| panic!("Expected byte string return value, got {:?}", value))
85            .as_slice();
86        assert_eq!(
87            actual, expected,
88            "Expected return value {:?}, got {:?}",
89            expected, actual
90        );
91    }
92
93    pub fn assert_returns_string(&self, expected: &str) {
94        let value = self.expect_ok_value();
95        let actual = value
96            .as_string()
97            .unwrap_or_else(|| panic!("Expected string return value, got {:?}", value))
98            .as_str();
99        assert_eq!(
100            actual, expected,
101            "Expected return value '{}', got '{}'",
102            expected, actual
103        );
104    }
105
106    pub fn assert_error(&self, expected: NeoError) {
107        match self.result.as_ref() {
108            Ok(value) => panic!("Expected Err({:?}), got Ok({:?})", expected, value),
109            Err(actual) => {
110                assert_eq!(
111                    actual, &expected,
112                    "Expected error {:?}, got {:?}",
113                    expected, actual
114                )
115            }
116        }
117    }
118
119    pub fn value(&self) -> &NeoValue {
120        self.expect_ok_value()
121    }
122
123    pub fn error(&self) -> Option<&NeoError> {
124        self.result.as_ref().err()
125    }
126
127    fn expect_ok_value(&self) -> &NeoValue {
128        match self.result.as_ref() {
129            Ok(value) => value,
130            Err(error) => panic!("Expected Ok result, got Err: {:?}", error),
131        }
132    }
133}
134
135impl<T: Into<NeoValue>> From<Result<T, NeoError>> for MethodCallResult {
136    fn from(result: Result<T, NeoError>) -> Self {
137        Self::new(result.map(|v| v.into()))
138    }
139}
140
141/// Storage assertions
142pub struct StorageAssertions<'a> {
143    storage: &'a MockStorage,
144}
145
146impl<'a> StorageAssertions<'a> {
147    pub fn new(storage: &'a MockStorage) -> Self {
148        Self { storage }
149    }
150
151    pub fn contains(&self, key: &[u8]) -> bool {
152        self.storage.contains(key)
153    }
154
155    pub fn assert_contains(&self, key: &[u8]) {
156        assert!(
157            self.storage.contains(key),
158            "Expected storage to contain key {:?}",
159            key
160        );
161    }
162
163    pub fn assert_not_contains(&self, key: &[u8]) {
164        assert!(
165            !self.storage.contains(key),
166            "Expected storage to NOT contain key {:?}",
167            key
168        );
169    }
170
171    pub fn assert_value(&self, key: &[u8], expected: &[u8]) {
172        let actual = self.storage.get(key);
173        assert_eq!(
174            actual.as_deref(),
175            Some(expected),
176            "Expected storage key {:?} to have value {:?}, got {:?}",
177            key,
178            expected,
179            actual
180        );
181    }
182
183    pub fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
184        self.storage.get(key)
185    }
186}
187
188/// Runtime assertions
189pub struct RuntimeAssertions<'a> {
190    runtime: &'a MockRuntime,
191}
192
193impl<'a> RuntimeAssertions<'a> {
194    pub fn new(runtime: &'a MockRuntime) -> Self {
195        Self { runtime }
196    }
197
198    pub fn assert_log_contains(&self, message: &str) {
199        let logs: &[String] = self.runtime.logs();
200        assert!(
201            logs.iter().any(|l: &String| l.contains(message)),
202            "Expected log to contain '{}', got {:?}",
203            message,
204            logs
205        );
206    }
207
208    pub fn assert_log_count(&self, count: usize) {
209        assert_eq!(
210            self.runtime.logs().len(),
211            count,
212            "Expected {} logs, got {}",
213            count,
214            self.runtime.logs().len()
215        );
216    }
217
218    pub fn assert_notification_count(&self, count: usize) {
219        assert_eq!(
220            self.runtime.notifications().len(),
221            count,
222            "Expected {} notifications, got {}",
223            count,
224            self.runtime.notifications().len()
225        );
226    }
227
228    pub fn assert_witness(&self, address: &[u8]) {
229        assert!(
230            self.runtime.check_witness(address),
231            "Expected witness for address {:?}",
232            address
233        );
234    }
235
236    pub fn logs(&self) -> &[String] {
237        self.runtime.logs()
238    }
239
240    pub fn notifications(&self) -> &[(NeoString, NeoArray<NeoValue>)] {
241        self.runtime.notifications()
242    }
243}