1use crate::error::ForgeError;
6
7#[macro_export]
17macro_rules! assert_ok {
18 ($expr:expr) => {
19 match &$expr {
20 Ok(_) => (),
21 Err(e) => panic!("assertion failed: expected Ok, got Err({:?})", e),
22 }
23 };
24 ($expr:expr, $($arg:tt)+) => {
25 match &$expr {
26 Ok(_) => (),
27 Err(e) => panic!("assertion failed: {}: expected Ok, got Err({:?})", format_args!($($arg)+), e),
28 }
29 };
30}
31
32#[macro_export]
41macro_rules! assert_err {
42 ($expr:expr) => {
43 match &$expr {
44 Err(_) => (),
45 Ok(v) => panic!("assertion failed: expected Err, got Ok({:?})", v),
46 }
47 };
48 ($expr:expr, $($arg:tt)+) => {
49 match &$expr {
50 Err(_) => (),
51 Ok(v) => panic!("assertion failed: {}: expected Err, got Ok({:?})", format_args!($($arg)+), v),
52 }
53 };
54}
55
56#[macro_export]
65macro_rules! assert_err_variant {
66 ($expr:expr, $variant:pat) => {
67 match &$expr {
68 Err($variant) => (),
69 Err(e) => panic!(
70 "assertion failed: expected {}, got {:?}",
71 stringify!($variant),
72 e
73 ),
74 Ok(v) => panic!(
75 "assertion failed: expected Err({}), got Ok({:?})",
76 stringify!($variant),
77 v
78 ),
79 }
80 };
81}
82
83#[macro_export]
92macro_rules! assert_job_dispatched {
93 ($ctx:expr, $job_type:expr) => {
94 $ctx.job_dispatch().assert_dispatched($job_type);
95 };
96 ($ctx:expr, $job_type:expr, $predicate:expr) => {
97 $ctx.job_dispatch()
98 .assert_dispatched_with($job_type, $predicate);
99 };
100}
101
102#[macro_export]
110macro_rules! assert_job_not_dispatched {
111 ($ctx:expr, $job_type:expr) => {
112 $ctx.job_dispatch().assert_not_dispatched($job_type);
113 };
114}
115
116#[macro_export]
125macro_rules! assert_workflow_started {
126 ($ctx:expr, $workflow_name:expr) => {
127 $ctx.workflow_dispatch().assert_started($workflow_name);
128 };
129 ($ctx:expr, $workflow_name:expr, $predicate:expr) => {
130 $ctx.workflow_dispatch()
131 .assert_started_with($workflow_name, $predicate);
132 };
133}
134
135#[macro_export]
143macro_rules! assert_workflow_not_started {
144 ($ctx:expr, $workflow_name:expr) => {
145 $ctx.workflow_dispatch().assert_not_started($workflow_name);
146 };
147}
148
149#[macro_export]
157macro_rules! assert_http_called {
158 ($ctx:expr, $pattern:expr) => {
159 $ctx.http().assert_called($pattern);
160 };
161}
162
163#[macro_export]
171macro_rules! assert_http_not_called {
172 ($ctx:expr, $pattern:expr) => {
173 $ctx.http().assert_not_called($pattern);
174 };
175}
176
177pub fn error_contains(error: &ForgeError, substring: &str) -> bool {
183 error.to_string().contains(substring)
184}
185
186pub fn validation_error_for_field(error: &ForgeError, field: &str) -> bool {
188 match error {
189 ForgeError::Validation(msg) => msg.contains(field),
190 _ => false,
191 }
192}
193
194pub fn assert_json_matches(actual: &serde_json::Value, pattern: &serde_json::Value) -> bool {
207 match (actual, pattern) {
208 (serde_json::Value::Object(a), serde_json::Value::Object(p)) => {
209 for (key, expected_value) in p {
210 match a.get(key) {
211 Some(actual_value) => {
212 if !assert_json_matches(actual_value, expected_value) {
213 return false;
214 }
215 }
216 None => return false,
217 }
218 }
219 true
220 }
221 (serde_json::Value::Array(a), serde_json::Value::Array(p)) => {
222 if a.len() != p.len() {
223 return false;
224 }
225 a.iter()
226 .zip(p.iter())
227 .all(|(a, p)| assert_json_matches(a, p))
228 }
229 (a, p) => a == p,
230 }
231}
232
233pub fn assert_contains<T, F>(items: &[T], predicate: F) -> bool
235where
236 F: Fn(&T) -> bool,
237{
238 items.iter().any(predicate)
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use crate::error::ForgeError;
245
246 #[test]
247 fn test_assert_ok_macro() {
248 let result: Result<i32, String> = Ok(42);
249 assert_ok!(result);
250 }
251
252 #[test]
253 #[should_panic(expected = "expected Ok")]
254 fn test_assert_ok_macro_fails() {
255 let result: Result<i32, String> = Err("error".to_string());
256 assert_ok!(result);
257 }
258
259 #[test]
260 fn test_assert_err_macro() {
261 let result: Result<i32, String> = Err("error".to_string());
262 assert_err!(result);
263 }
264
265 #[test]
266 #[should_panic(expected = "expected Err")]
267 fn test_assert_err_macro_fails() {
268 let result: Result<i32, String> = Ok(42);
269 assert_err!(result);
270 }
271
272 #[test]
273 fn test_error_contains() {
274 let error = ForgeError::Validation("email is required".to_string());
275 assert!(error_contains(&error, "email"));
276 assert!(error_contains(&error, "required"));
277 assert!(!error_contains(&error, "password"));
278 }
279
280 #[test]
281 fn test_validation_error_for_field() {
282 let error = ForgeError::Validation("email: is invalid".to_string());
283 assert!(validation_error_for_field(&error, "email"));
284 assert!(!validation_error_for_field(&error, "password"));
285
286 let other_error = ForgeError::Internal("internal error".to_string());
287 assert!(!validation_error_for_field(&other_error, "email"));
288 }
289
290 #[test]
291 fn test_assert_json_matches() {
292 let actual = serde_json::json!({
293 "id": 123,
294 "name": "Test",
295 "nested": {
296 "foo": "bar"
297 }
298 });
299
300 assert!(assert_json_matches(
302 &actual,
303 &serde_json::json!({"id": 123})
304 ));
305 assert!(assert_json_matches(
306 &actual,
307 &serde_json::json!({"name": "Test"})
308 ));
309 assert!(assert_json_matches(
310 &actual,
311 &serde_json::json!({"nested": {"foo": "bar"}})
312 ));
313
314 assert!(!assert_json_matches(
316 &actual,
317 &serde_json::json!({"id": 456})
318 ));
319 assert!(!assert_json_matches(
320 &actual,
321 &serde_json::json!({"missing": true})
322 ));
323 }
324
325 #[test]
326 fn test_assert_json_matches_arrays() {
327 let actual = serde_json::json!([1, 2, 3]);
328 assert!(assert_json_matches(&actual, &serde_json::json!([1, 2, 3])));
329 assert!(!assert_json_matches(&actual, &serde_json::json!([1, 2])));
330 assert!(!assert_json_matches(&actual, &serde_json::json!([1, 2, 4])));
331 }
332
333 #[test]
334 fn test_assert_contains() {
335 let items = vec![1, 2, 3, 4, 5];
336 assert!(assert_contains(&items, |x| *x == 3));
337 assert!(!assert_contains(&items, |x| *x == 6));
338 }
339}