1use crate::assertions::{RuntimeAssertions, StorageAssertions};
7use crate::mock_runtime::{MockRuntime, MockStorageContext};
8use neo_types::NeoByteString;
9
10pub type TestResult<T = ()> = Result<T, TestError>;
11
12#[derive(Debug, Clone)]
13pub struct TestError {
14 pub message: String,
15 pub context: String,
16}
17
18impl TestError {
19 pub fn new(message: impl Into<String>) -> Self {
20 Self {
21 message: message.into(),
22 context: String::new(),
23 }
24 }
25
26 pub fn with_context(mut self, context: impl Into<String>) -> Self {
27 self.context = context.into();
28 self
29 }
30}
31
32impl std::fmt::Display for TestError {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 if self.context.is_empty() {
35 write!(f, "{}", self.message)
36 } else {
37 write!(f, "{}: {}", self.context, self.message)
38 }
39 }
40}
41
42impl std::error::Error for TestError {}
43
44pub struct TestEnvironment {
46 runtime: MockRuntime,
47 deployment: Option<DeploymentState>,
48}
49
50#[derive(Debug, Clone)]
51struct DeploymentState {
52 script: Vec<u8>,
53 manifest: Vec<u8>,
54}
55
56impl TestEnvironment {
57 pub fn new() -> Self {
58 Self {
59 runtime: MockRuntime::new(),
60 deployment: None,
61 }
62 }
63
64 pub fn with_runtime(mut self, runtime: MockRuntime) -> Self {
65 self.runtime = runtime;
66 self
67 }
68
69 pub fn runtime(&self) -> &MockRuntime {
70 &self.runtime
71 }
72
73 pub fn runtime_mut(&mut self) -> &mut MockRuntime {
74 &mut self.runtime
75 }
76
77 pub fn set_storage(&mut self, key: &[u8], value: &[u8]) {
78 self.runtime.storage_mut().put(key, value);
83 let _ = neo_syscalls::NeoVMSyscall::seed_storage(&[(key, value)]);
84 }
85
86 pub fn get_storage_context(&mut self) -> MockStorageContext {
87 self.runtime.get_storage_context()
88 }
89
90 pub fn get_read_only_storage_context(&mut self) -> MockStorageContext {
91 self.runtime.get_read_only_storage_context()
92 }
93
94 pub fn put_storage_with_context(
95 &mut self,
96 context: &MockStorageContext,
97 key: &[u8],
98 value: &[u8],
99 ) -> Result<(), neo_types::NeoError> {
100 self.runtime.storage_put_with_context(context, key, value)
101 }
102
103 pub fn delete_storage_with_context(
104 &mut self,
105 context: &MockStorageContext,
106 key: &[u8],
107 ) -> Result<(), neo_types::NeoError> {
108 self.runtime.storage_delete_with_context(context, key)
109 }
110
111 pub fn get_storage(&self, key: &[u8]) -> Option<Vec<u8>> {
112 self.runtime.storage_ref().get(key)
113 }
114
115 pub fn delete_storage(&mut self, key: &[u8]) {
116 self.runtime.storage_mut().delete(key);
117 }
118
119 pub fn set_trigger(&mut self, trigger: i32) {
120 self.runtime.trigger = trigger;
121 }
122
123 pub fn set_time(&mut self, time: i64) {
124 self.runtime.time = time;
125 }
126
127 pub fn set_network(&mut self, network: i64) {
128 self.runtime.network = network;
129 }
130
131 pub fn add_witness(&mut self, address: &[u8]) {
132 self.runtime
133 .witnesses_mut()
134 .push(NeoByteString::from_slice(address));
135 }
136
137 pub fn check_witness(&self, address: &[u8]) -> bool {
138 self.runtime.check_witness(address)
139 }
140
141 pub fn add_log(&mut self, message: &str) {
142 self.runtime.add_log(message);
143 }
144
145 pub fn logs(&self) -> &[String] {
146 self.runtime.logs()
147 }
148
149 pub fn clear_logs(&mut self) {
150 self.runtime.clear_logs();
151 }
152
153 pub fn assert_runtime(&self) -> RuntimeAssertions<'_> {
154 RuntimeAssertions::new(&self.runtime)
155 }
156
157 pub fn assert_storage(&self) -> StorageAssertions<'_> {
158 StorageAssertions::new(self.runtime.storage_ref())
159 }
160
161 pub fn call_method<F, R>(&self, name: &str, args: &[neo_types::NeoValue], f: F) -> R
174 where
175 F: FnOnce() -> R,
176 {
177 debug_assert!(!name.is_empty(), "call_method requires a method name");
178 let _ = args;
179 f()
180 }
181
182 pub fn deploy(&mut self, script: &[u8], manifest: &[u8]) -> TestResult {
183 if self.deployment.is_some() {
184 return Err(TestError::new("contract is already deployed").with_context("deploy"));
185 }
186 if script.is_empty() {
187 return Err(TestError::new("script cannot be empty").with_context("deploy"));
188 }
189 if manifest.is_empty() {
190 return Err(TestError::new("manifest cannot be empty").with_context("deploy"));
191 }
192
193 self.deployment = Some(DeploymentState {
194 script: script.to_vec(),
195 manifest: manifest.to_vec(),
196 });
197 Ok(())
198 }
199
200 pub fn update(&mut self, script: &[u8]) -> TestResult {
201 let existing_manifest = self
202 .deployment
203 .as_ref()
204 .ok_or_else(|| TestError::new("contract is not deployed").with_context("update"))?
205 .manifest
206 .clone();
207 self.update_with_manifest(script, &existing_manifest)
208 }
209
210 pub fn update_with_manifest(&mut self, script: &[u8], manifest: &[u8]) -> TestResult {
211 if script.is_empty() {
212 return Err(TestError::new("script cannot be empty").with_context("update"));
213 }
214 if manifest.is_empty() {
215 return Err(TestError::new("manifest cannot be empty").with_context("update"));
216 }
217
218 let deployment = self
219 .deployment
220 .as_mut()
221 .ok_or_else(|| TestError::new("contract is not deployed").with_context("update"))?;
222 deployment.script.clear();
223 deployment.script.extend_from_slice(script);
224 deployment.manifest.clear();
225 deployment.manifest.extend_from_slice(manifest);
226 Ok(())
227 }
228
229 pub fn destroy(&mut self) -> TestResult {
230 if self.deployment.take().is_none() {
231 return Err(TestError::new("contract is not deployed").with_context("destroy"));
232 }
233
234 self.runtime.storage_mut().clear();
236 self.runtime.clear_storage_contexts();
237
238 Ok(())
239 }
240
241 pub fn is_deployed(&self) -> bool {
242 self.deployment.is_some()
243 }
244
245 pub fn deployed_script(&self) -> Option<&[u8]> {
246 self.deployment
247 .as_ref()
248 .map(|deployment| deployment.script.as_slice())
249 }
250
251 pub fn deployed_manifest(&self) -> Option<&[u8]> {
252 self.deployment
253 .as_ref()
254 .map(|deployment| deployment.manifest.as_slice())
255 }
256
257 pub fn reset(&mut self) {
258 self.runtime = MockRuntime::new();
259 self.deployment = None;
260 }
261}
262
263impl Default for TestEnvironment {
264 fn default() -> Self {
265 Self::new()
266 }
267}
268
269pub struct ContractTest<T> {
270 env: TestEnvironment,
271 contract: T,
272}
273
274impl<T> ContractTest<T> {
275 pub fn new(contract: T) -> Self {
276 Self {
277 env: TestEnvironment::new(),
278 contract,
279 }
280 }
281
282 pub fn with_env(mut self, env: TestEnvironment) -> Self {
283 self.env = env;
284 self
285 }
286
287 pub fn env(&self) -> &TestEnvironment {
288 &self.env
289 }
290
291 pub fn env_mut(&mut self) -> &mut TestEnvironment {
292 &mut self.env
293 }
294
295 pub fn contract(&self) -> &T {
296 &self.contract
297 }
298
299 pub fn contract_mut(&mut self) -> &mut T {
300 &mut self.contract
301 }
302}
303
304pub struct TestBuilder {
305 env: TestEnvironment,
306}
307
308impl TestBuilder {
309 pub fn new() -> Self {
310 Self {
311 env: TestEnvironment::new(),
312 }
313 }
314
315 pub fn storage(mut self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> Self {
316 self.env.set_storage(key.as_ref(), value.as_ref());
317 self
318 }
319
320 pub fn time(mut self, time: i64) -> Self {
321 self.env.set_time(time);
322 self
323 }
324
325 pub fn network(mut self, network: i64) -> Self {
326 self.env.set_network(network);
327 self
328 }
329
330 pub fn witness(mut self, address: impl AsRef<[u8]>) -> Self {
331 self.env.add_witness(address.as_ref());
332 self
333 }
334
335 pub fn trigger(mut self, trigger: i32) -> Self {
336 self.env.set_trigger(trigger);
337 self
338 }
339
340 pub fn build(self) -> TestEnvironment {
341 self.env
342 }
343}
344
345impl Default for TestBuilder {
346 fn default() -> Self {
347 Self::new()
348 }
349}
350
351#[macro_export]
352macro_rules! assert_neo {
353 ($expr:expr, $expected:expr) => {
354 assert_eq!($expr.as_i32_saturating(), $expected, "Assertion failed")
355 };
356}
357
358#[macro_export]
359macro_rules! test_env {
360 () => {{
361 neo_test::TestEnvironment::new()
362 }};
363}