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 {
179 error.to_string().contains(substring)
180}
181
182pub fn validation_error_for_field(error: &ForgeError, field: &str) -> bool {
184 match error {
185 ForgeError::Validation(msg) => msg.contains(field),
186 _ => false,
187 }
188}
189
190pub fn assert_json_matches(actual: &serde_json::Value, pattern: &serde_json::Value) -> bool {
203 match (actual, pattern) {
204 (serde_json::Value::Object(a), serde_json::Value::Object(p)) => {
205 for (key, expected_value) in p {
206 match a.get(key) {
207 Some(actual_value) => {
208 if !assert_json_matches(actual_value, expected_value) {
209 return false;
210 }
211 }
212 None => return false,
213 }
214 }
215 true
216 }
217 (serde_json::Value::Array(a), serde_json::Value::Array(p)) => {
218 if a.len() != p.len() {
219 return false;
220 }
221 a.iter()
222 .zip(p.iter())
223 .all(|(a, p)| assert_json_matches(a, p))
224 }
225 (a, p) => a == p,
226 }
227}
228
229pub fn assert_contains<T, F>(items: &[T], predicate: F) -> bool
231where
232 F: Fn(&T) -> bool,
233{
234 items.iter().any(predicate)
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use crate::error::ForgeError;
241
242 #[test]
243 fn test_assert_ok_macro() {
244 let result: Result<i32, String> = Ok(42);
245 assert_ok!(result);
246 }
247
248 #[test]
249 #[should_panic(expected = "expected Ok")]
250 fn test_assert_ok_macro_fails() {
251 let result: Result<i32, String> = Err("error".to_string());
252 assert_ok!(result);
253 }
254
255 #[test]
256 fn test_assert_err_macro() {
257 let result: Result<i32, String> = Err("error".to_string());
258 assert_err!(result);
259 }
260
261 #[test]
262 #[should_panic(expected = "expected Err")]
263 fn test_assert_err_macro_fails() {
264 let result: Result<i32, String> = Ok(42);
265 assert_err!(result);
266 }
267
268 #[test]
269 fn test_error_contains() {
270 let error = ForgeError::Validation("email is required".to_string());
271 assert!(error_contains(&error, "email"));
272 assert!(error_contains(&error, "required"));
273 assert!(!error_contains(&error, "password"));
274 }
275
276 #[test]
277 fn test_validation_error_for_field() {
278 let error = ForgeError::Validation("email: is invalid".to_string());
279 assert!(validation_error_for_field(&error, "email"));
280 assert!(!validation_error_for_field(&error, "password"));
281
282 let other_error = ForgeError::Internal("internal error".to_string());
283 assert!(!validation_error_for_field(&other_error, "email"));
284 }
285
286 #[test]
287 fn test_assert_json_matches() {
288 let actual = serde_json::json!({
289 "id": 123,
290 "name": "Test",
291 "nested": {
292 "foo": "bar"
293 }
294 });
295
296 assert!(assert_json_matches(
298 &actual,
299 &serde_json::json!({"id": 123})
300 ));
301 assert!(assert_json_matches(
302 &actual,
303 &serde_json::json!({"name": "Test"})
304 ));
305 assert!(assert_json_matches(
306 &actual,
307 &serde_json::json!({"nested": {"foo": "bar"}})
308 ));
309
310 assert!(!assert_json_matches(
312 &actual,
313 &serde_json::json!({"id": 456})
314 ));
315 assert!(!assert_json_matches(
316 &actual,
317 &serde_json::json!({"missing": true})
318 ));
319 }
320
321 #[test]
322 fn test_assert_json_matches_arrays() {
323 let actual = serde_json::json!([1, 2, 3]);
324 assert!(assert_json_matches(&actual, &serde_json::json!([1, 2, 3])));
325 assert!(!assert_json_matches(&actual, &serde_json::json!([1, 2])));
326 assert!(!assert_json_matches(&actual, &serde_json::json!([1, 2, 4])));
327 }
328
329 #[test]
330 fn test_assert_contains() {
331 let items = vec![1, 2, 3, 4, 5];
332 assert!(assert_contains(&items, |x| *x == 3));
333 assert!(!assert_contains(&items, |x| *x == 6));
334 }
335}