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);
79 }
80
81 pub fn get_storage_context(&mut self) -> MockStorageContext {
82 self.runtime.get_storage_context()
83 }
84
85 pub fn get_read_only_storage_context(&mut self) -> MockStorageContext {
86 self.runtime.get_read_only_storage_context()
87 }
88
89 pub fn put_storage_with_context(
90 &mut self,
91 context: &MockStorageContext,
92 key: &[u8],
93 value: &[u8],
94 ) -> Result<(), neo_types::NeoError> {
95 self.runtime.storage_put_with_context(context, key, value)
96 }
97
98 pub fn delete_storage_with_context(
99 &mut self,
100 context: &MockStorageContext,
101 key: &[u8],
102 ) -> Result<(), neo_types::NeoError> {
103 self.runtime.storage_delete_with_context(context, key)
104 }
105
106 pub fn get_storage(&self, key: &[u8]) -> Option<Vec<u8>> {
107 self.runtime.storage_ref().get(key)
108 }
109
110 pub fn delete_storage(&mut self, key: &[u8]) {
111 self.runtime.storage_mut().delete(key);
112 }
113
114 pub fn set_trigger(&mut self, trigger: i32) {
115 self.runtime.trigger = trigger;
116 }
117
118 pub fn set_time(&mut self, time: i64) {
119 self.runtime.time = time;
120 }
121
122 pub fn set_network(&mut self, network: i64) {
123 self.runtime.network = network;
124 }
125
126 pub fn add_witness(&mut self, address: &[u8]) {
127 self.runtime
128 .witnesses_mut()
129 .push(NeoByteString::from_slice(address));
130 }
131
132 pub fn check_witness(&self, address: &[u8]) -> bool {
133 self.runtime.check_witness(address)
134 }
135
136 pub fn add_log(&mut self, message: &str) {
137 self.runtime.add_log(message);
138 }
139
140 pub fn logs(&self) -> &[String] {
141 self.runtime.logs()
142 }
143
144 pub fn clear_logs(&mut self) {
145 self.runtime.clear_logs();
146 }
147
148 pub fn assert_runtime(&self) -> RuntimeAssertions<'_> {
149 RuntimeAssertions::new(&self.runtime)
150 }
151
152 pub fn assert_storage(&self) -> StorageAssertions<'_> {
153 StorageAssertions::new(self.runtime.storage_ref())
154 }
155
156 pub fn call_method<F, R>(&self, _name: &str, _args: &[neo_types::NeoValue], _f: F) -> R
157 where
158 F: FnOnce() -> R,
159 {
160 _f()
161 }
162
163 pub fn deploy(&mut self, script: &[u8], manifest: &[u8]) -> TestResult {
164 if self.deployment.is_some() {
165 return Err(TestError::new("contract is already deployed").with_context("deploy"));
166 }
167 if script.is_empty() {
168 return Err(TestError::new("script cannot be empty").with_context("deploy"));
169 }
170 if manifest.is_empty() {
171 return Err(TestError::new("manifest cannot be empty").with_context("deploy"));
172 }
173
174 self.deployment = Some(DeploymentState {
175 script: script.to_vec(),
176 manifest: manifest.to_vec(),
177 });
178 Ok(())
179 }
180
181 pub fn update(&mut self, script: &[u8]) -> TestResult {
182 let existing_manifest = self
183 .deployment
184 .as_ref()
185 .ok_or_else(|| TestError::new("contract is not deployed").with_context("update"))?
186 .manifest
187 .clone();
188 self.update_with_manifest(script, &existing_manifest)
189 }
190
191 pub fn update_with_manifest(&mut self, script: &[u8], manifest: &[u8]) -> TestResult {
192 if script.is_empty() {
193 return Err(TestError::new("script cannot be empty").with_context("update"));
194 }
195 if manifest.is_empty() {
196 return Err(TestError::new("manifest cannot be empty").with_context("update"));
197 }
198
199 let deployment = self
200 .deployment
201 .as_mut()
202 .ok_or_else(|| TestError::new("contract is not deployed").with_context("update"))?;
203 deployment.script.clear();
204 deployment.script.extend_from_slice(script);
205 deployment.manifest.clear();
206 deployment.manifest.extend_from_slice(manifest);
207 Ok(())
208 }
209
210 pub fn destroy(&mut self) -> TestResult {
211 if self.deployment.take().is_none() {
212 return Err(TestError::new("contract is not deployed").with_context("destroy"));
213 }
214
215 self.runtime.storage_mut().clear();
217 self.runtime.clear_storage_contexts();
218
219 Ok(())
220 }
221
222 pub fn is_deployed(&self) -> bool {
223 self.deployment.is_some()
224 }
225
226 pub fn deployed_script(&self) -> Option<&[u8]> {
227 self.deployment
228 .as_ref()
229 .map(|deployment| deployment.script.as_slice())
230 }
231
232 pub fn deployed_manifest(&self) -> Option<&[u8]> {
233 self.deployment
234 .as_ref()
235 .map(|deployment| deployment.manifest.as_slice())
236 }
237
238 pub fn reset(&mut self) {
239 self.runtime = MockRuntime::new();
240 self.deployment = None;
241 }
242}
243
244impl Default for TestEnvironment {
245 fn default() -> Self {
246 Self::new()
247 }
248}
249
250pub struct ContractTest<T> {
251 env: TestEnvironment,
252 contract: T,
253}
254
255impl<T> ContractTest<T> {
256 pub fn new(contract: T) -> Self {
257 Self {
258 env: TestEnvironment::new(),
259 contract,
260 }
261 }
262
263 pub fn with_env(mut self, env: TestEnvironment) -> Self {
264 self.env = env;
265 self
266 }
267
268 pub fn env(&self) -> &TestEnvironment {
269 &self.env
270 }
271
272 pub fn env_mut(&mut self) -> &mut TestEnvironment {
273 &mut self.env
274 }
275
276 pub fn contract(&self) -> &T {
277 &self.contract
278 }
279
280 pub fn contract_mut(&mut self) -> &mut T {
281 &mut self.contract
282 }
283}
284
285pub struct TestBuilder {
286 env: TestEnvironment,
287}
288
289impl TestBuilder {
290 pub fn new() -> Self {
291 Self {
292 env: TestEnvironment::new(),
293 }
294 }
295
296 pub fn storage(mut self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> Self {
297 self.env.set_storage(key.as_ref(), value.as_ref());
298 self
299 }
300
301 pub fn time(mut self, time: i64) -> Self {
302 self.env.set_time(time);
303 self
304 }
305
306 pub fn network(mut self, network: i64) -> Self {
307 self.env.set_network(network);
308 self
309 }
310
311 pub fn witness(mut self, address: impl AsRef<[u8]>) -> Self {
312 self.env.add_witness(address.as_ref());
313 self
314 }
315
316 pub fn trigger(mut self, trigger: i32) -> Self {
317 self.env.set_trigger(trigger);
318 self
319 }
320
321 pub fn build(self) -> TestEnvironment {
322 self.env
323 }
324}
325
326impl Default for TestBuilder {
327 fn default() -> Self {
328 Self::new()
329 }
330}
331
332#[macro_export]
333macro_rules! assert_neo {
334 ($expr:expr, $expected:expr) => {
335 assert_eq!($expr.as_i32_saturating(), $expected, "Assertion failed")
336 };
337}
338
339#[macro_export]
340macro_rules! test_env {
341 () => {{
342 neo_test::TestEnvironment::new()
343 }};
344}