1use forge_core::error::ForgeError;
4use forge_core::job::JobStatus;
5use forge_core::workflow::WorkflowStatus;
6
7#[macro_export]
9macro_rules! assert_ok {
10 ($expr:expr) => {
11 match &$expr {
12 Ok(_) => (),
13 Err(e) => panic!("assertion failed: expected Ok, got Err({:?})", e),
14 }
15 };
16 ($expr:expr, $($arg:tt)+) => {
17 match &$expr {
18 Ok(_) => (),
19 Err(e) => panic!("assertion failed: {}: expected Ok, got Err({:?})", format_args!($($arg)+), e),
20 }
21 };
22}
23
24#[macro_export]
26macro_rules! assert_err {
27 ($expr:expr) => {
28 match &$expr {
29 Err(_) => (),
30 Ok(v) => panic!("assertion failed: expected Err, got Ok({:?})", v),
31 }
32 };
33 ($expr:expr, $($arg:tt)+) => {
34 match &$expr {
35 Err(_) => (),
36 Ok(v) => panic!("assertion failed: {}: expected Err, got Ok({:?})", format_args!($($arg)+), v),
37 }
38 };
39}
40
41#[macro_export]
43macro_rules! assert_err_variant {
44 ($expr:expr, $variant:pat) => {
45 match &$expr {
46 Err($variant) => (),
47 Err(e) => panic!(
48 "assertion failed: expected {}, got {:?}",
49 stringify!($variant),
50 e
51 ),
52 Ok(v) => panic!(
53 "assertion failed: expected Err({}), got Ok({:?})",
54 stringify!($variant),
55 v
56 ),
57 }
58 };
59}
60
61#[macro_export]
63macro_rules! assert_job_dispatched {
64 ($ctx:expr, $job_type:expr) => {
65 assert!(
66 $ctx.job_dispatched($job_type),
67 "assertion failed: job '{}' was not dispatched",
68 $job_type
69 );
70 };
71 ($ctx:expr, $job_type:expr, $predicate:expr) => {
72 let jobs = $ctx
73 .dispatched_jobs()
74 .iter()
75 .filter(|j| j.job_type == $job_type)
76 .collect::<Vec<_>>();
77 assert!(
78 jobs.iter().any(|j| $predicate(&j.input)),
79 "assertion failed: no job '{}' matching predicate was dispatched",
80 $job_type
81 );
82 };
83}
84
85#[macro_export]
87macro_rules! assert_workflow_started {
88 ($ctx:expr, $workflow_name:expr) => {
89 assert!(
90 $ctx.started_workflows()
91 .iter()
92 .any(|w| w.workflow_name == $workflow_name),
93 "assertion failed: workflow '{}' was not started",
94 $workflow_name
95 );
96 };
97}
98
99pub fn error_contains(error: &ForgeError, substring: &str) -> bool {
101 error.to_string().contains(substring)
102}
103
104pub fn validation_error_for_field(error: &ForgeError, field: &str) -> bool {
106 match error {
107 ForgeError::Validation(msg) => msg.contains(field),
108 _ => false,
109 }
110}
111
112#[allow(clippy::panic)]
114pub fn assert_job_status(actual: Option<JobStatus>, expected: JobStatus) {
115 match actual {
116 Some(status) => assert_eq!(
117 status, expected,
118 "expected job status {:?}, got {:?}",
119 expected, status
120 ),
121 None => panic!("expected job status {:?}, but job not found", expected),
122 }
123}
124
125#[allow(clippy::panic)]
127pub fn assert_workflow_status(actual: Option<WorkflowStatus>, expected: WorkflowStatus) {
128 match actual {
129 Some(status) => assert_eq!(
130 status, expected,
131 "expected workflow status {:?}, got {:?}",
132 expected, status
133 ),
134 None => panic!(
135 "expected workflow status {:?}, but workflow not found",
136 expected
137 ),
138 }
139}
140
141pub fn assert_json_matches(actual: &serde_json::Value, pattern: &serde_json::Value) -> bool {
143 match (actual, pattern) {
144 (serde_json::Value::Object(a), serde_json::Value::Object(p)) => {
145 for (key, expected_value) in p {
146 match a.get(key) {
147 Some(actual_value) => {
148 if !assert_json_matches(actual_value, expected_value) {
149 return false;
150 }
151 }
152 None => return false,
153 }
154 }
155 true
156 }
157 (serde_json::Value::Array(a), serde_json::Value::Array(p)) => {
158 if a.len() != p.len() {
159 return false;
160 }
161 a.iter()
162 .zip(p.iter())
163 .all(|(a, p)| assert_json_matches(a, p))
164 }
165 (a, p) => a == p,
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_assert_ok_macro() {
175 let result: Result<i32, String> = Ok(42);
176 assert_ok!(result);
177 }
178
179 #[test]
180 #[should_panic(expected = "expected Ok")]
181 fn test_assert_ok_macro_fails() {
182 let result: Result<i32, String> = Err("error".to_string());
183 assert_ok!(result);
184 }
185
186 #[test]
187 fn test_assert_err_macro() {
188 let result: Result<i32, String> = Err("error".to_string());
189 assert_err!(result);
190 }
191
192 #[test]
193 #[should_panic(expected = "expected Err")]
194 fn test_assert_err_macro_fails() {
195 let result: Result<i32, String> = Ok(42);
196 assert_err!(result);
197 }
198
199 #[test]
200 fn test_error_contains() {
201 let error = ForgeError::Validation("email is required".to_string());
202 assert!(error_contains(&error, "email"));
203 assert!(error_contains(&error, "required"));
204 assert!(!error_contains(&error, "password"));
205 }
206
207 #[test]
208 fn test_validation_error_for_field() {
209 let error = ForgeError::Validation("email: is invalid".to_string());
210 assert!(validation_error_for_field(&error, "email"));
211 assert!(!validation_error_for_field(&error, "password"));
212
213 let other_error = ForgeError::Internal("internal error".to_string());
214 assert!(!validation_error_for_field(&other_error, "email"));
215 }
216
217 #[test]
218 fn test_assert_job_status() {
219 assert_job_status(Some(JobStatus::Completed), JobStatus::Completed);
220 }
221
222 #[test]
223 #[should_panic(expected = "expected job status")]
224 fn test_assert_job_status_mismatch() {
225 assert_job_status(Some(JobStatus::Pending), JobStatus::Completed);
226 }
227
228 #[test]
229 #[should_panic(expected = "job not found")]
230 fn test_assert_job_status_not_found() {
231 assert_job_status(None, JobStatus::Completed);
232 }
233
234 #[test]
235 fn test_assert_job_status_cancelled() {
236 assert_job_status(Some(JobStatus::Cancelled), JobStatus::Cancelled);
237 }
238
239 #[test]
240 fn test_assert_json_matches() {
241 let actual = serde_json::json!({
242 "id": 123,
243 "name": "Test",
244 "nested": {
245 "foo": "bar"
246 }
247 });
248
249 assert!(assert_json_matches(
251 &actual,
252 &serde_json::json!({"id": 123})
253 ));
254 assert!(assert_json_matches(
255 &actual,
256 &serde_json::json!({"name": "Test"})
257 ));
258 assert!(assert_json_matches(
259 &actual,
260 &serde_json::json!({"nested": {"foo": "bar"}})
261 ));
262
263 assert!(!assert_json_matches(
265 &actual,
266 &serde_json::json!({"id": 456})
267 ));
268 assert!(!assert_json_matches(
269 &actual,
270 &serde_json::json!({"missing": true})
271 ));
272 }
273
274 #[test]
275 fn test_assert_json_matches_arrays() {
276 let actual = serde_json::json!([1, 2, 3]);
277 assert!(assert_json_matches(&actual, &serde_json::json!([1, 2, 3])));
278 assert!(!assert_json_matches(&actual, &serde_json::json!([1, 2])));
279 assert!(!assert_json_matches(&actual, &serde_json::json!([1, 2, 4])));
280 }
281}