Skip to main content

reinhardt_testkit/
assertions.rs

1//! Additional assertion helpers for testing
2
3use http::StatusCode;
4use serde_json::Value;
5/// Assert that JSON contains a field with a specific value
6///
7/// # Examples
8///
9/// ```
10/// use reinhardt_testkit::assertions::assert_json_field_eq;
11/// use serde_json::json;
12///
13/// let data = json!({"name": "John", "age": 30});
14/// assert_json_field_eq(&data, "name", &json!("John"));
15/// ```
16pub fn assert_json_field_eq(json: &Value, field: &str, expected: &Value) {
17	let actual = json.get(field);
18	assert_eq!(
19		actual,
20		Some(expected),
21		"Expected field '{}' to equal {:?}, got {:?}",
22		field,
23		expected,
24		actual
25	);
26}
27/// Assert that JSON contains a field
28///
29/// # Examples
30///
31/// ```
32/// use reinhardt_testkit::assertions::assert_json_has_field;
33/// use serde_json::json;
34///
35/// let data = json!({"name": "John", "age": 30});
36/// assert_json_has_field(&data, "name");
37/// ```
38pub fn assert_json_has_field(json: &Value, field: &str) {
39	assert!(
40		json.get(field).is_some(),
41		"Expected JSON to have field '{}'",
42		field
43	);
44}
45/// Assert that JSON does not contain a field
46///
47/// # Examples
48///
49/// ```
50/// use reinhardt_testkit::assertions::assert_json_missing_field;
51/// use serde_json::json;
52///
53/// let data = json!({"name": "John"});
54/// assert_json_missing_field(&data, "age");
55/// ```
56pub fn assert_json_missing_field(json: &Value, field: &str) {
57	assert!(
58		json.get(field).is_none(),
59		"Expected JSON to not have field '{}'",
60		field
61	);
62}
63/// Assert that JSON array has a specific length
64///
65/// # Examples
66///
67/// ```
68/// use reinhardt_testkit::assertions::assert_json_array_len;
69/// use serde_json::json;
70///
71/// let data = json!([1, 2, 3]);
72/// assert_json_array_len(&data, 3);
73/// ```
74pub fn assert_json_array_len(json: &Value, expected_len: usize) {
75	if let Value::Array(arr) = json {
76		assert_eq!(
77			arr.len(),
78			expected_len,
79			"Expected array length {}, got {}",
80			expected_len,
81			arr.len()
82		);
83	} else {
84		panic!("Expected JSON array, got {:?}", json);
85	}
86}
87/// Assert that JSON array is empty
88///
89/// # Examples
90///
91/// ```
92/// use reinhardt_testkit::assertions::assert_json_array_empty;
93/// use serde_json::json;
94///
95/// let data = json!([]);
96/// assert_json_array_empty(&data);
97/// ```
98pub fn assert_json_array_empty(json: &Value) {
99	assert_json_array_len(json, 0);
100}
101/// Assert that JSON array is not empty
102///
103/// # Examples
104///
105/// ```
106/// use reinhardt_testkit::assertions::assert_json_array_not_empty;
107/// use serde_json::json;
108///
109/// let data = json!([1, 2, 3]);
110/// assert_json_array_not_empty(&data);
111/// ```
112pub fn assert_json_array_not_empty(json: &Value) {
113	if let Value::Array(arr) = json {
114		assert!(!arr.is_empty(), "Expected non-empty array");
115	} else {
116		panic!("Expected JSON array, got {:?}", json);
117	}
118}
119/// Assert that JSON array contains a value
120///
121/// # Examples
122///
123/// ```
124/// use reinhardt_testkit::assertions::assert_json_array_contains;
125/// use serde_json::json;
126///
127/// let data = json!([1, 2, 3]);
128/// assert_json_array_contains(&data, &json!(2));
129/// ```
130pub fn assert_json_array_contains(json: &Value, expected: &Value) {
131	if let Value::Array(arr) = json {
132		assert!(
133			arr.contains(expected),
134			"Expected array to contain {:?}, got {:?}",
135			expected,
136			arr
137		);
138	} else {
139		panic!("Expected JSON array, got {:?}", json);
140	}
141}
142/// Assert that JSON matches a pattern (subset matching)
143///
144/// # Examples
145///
146/// ```
147/// use reinhardt_testkit::assertions::assert_json_matches;
148/// use serde_json::json;
149///
150/// let actual = json!({"name": "John", "age": 30, "city": "NYC"});
151/// let pattern = json!({"name": "John", "age": 30});
152/// assert_json_matches(&actual, &pattern);
153/// ```
154pub fn assert_json_matches(actual: &Value, pattern: &Value) {
155	match (actual, pattern) {
156		(Value::Object(actual_map), Value::Object(pattern_map)) => {
157			for (key, pattern_value) in pattern_map {
158				let actual_value = actual_map.get(key);
159				assert!(
160					actual_value.is_some(),
161					"Expected field '{}' in {:?}",
162					key,
163					actual_map
164				);
165				assert_json_matches(actual_value.unwrap(), pattern_value);
166			}
167		}
168		(Value::Array(actual_arr), Value::Array(pattern_arr)) => {
169			assert_eq!(actual_arr.len(), pattern_arr.len(), "Array length mismatch");
170			for (actual_item, pattern_item) in actual_arr.iter().zip(pattern_arr.iter()) {
171				assert_json_matches(actual_item, pattern_item);
172			}
173		}
174		_ => {
175			assert_eq!(actual, pattern, "Value mismatch");
176		}
177	}
178}
179/// Assert that response body contains text
180///
181/// # Examples
182///
183/// ```
184/// use reinhardt_testkit::assertions::assert_contains;
185///
186/// let text = "Hello, World!";
187/// assert_contains(text, "World");
188/// ```
189pub fn assert_contains(text: &str, substring: &str) {
190	assert!(
191		text.contains(substring),
192		"Expected text to contain '{}', got: {}",
193		substring,
194		text
195	);
196}
197/// Assert that response body does not contain text
198///
199/// # Examples
200///
201/// ```
202/// use reinhardt_testkit::assertions::assert_not_contains;
203///
204/// let text = "Hello, World!";
205/// assert_not_contains(text, "Goodbye");
206/// ```
207pub fn assert_not_contains(text: &str, substring: &str) {
208	assert!(
209		!text.contains(substring),
210		"Expected text to not contain '{}', got: {}",
211		substring,
212		text
213	);
214}
215/// Assert that two status codes are equal
216///
217/// # Examples
218///
219/// ```
220/// use reinhardt_testkit::assertions::assert_status_eq;
221/// use http::StatusCode;
222///
223/// let status = StatusCode::OK;
224/// assert_status_eq(status, StatusCode::OK);
225/// ```
226pub fn assert_status_eq(actual: StatusCode, expected: StatusCode) {
227	assert_eq!(
228		actual, expected,
229		"Expected status {}, got {}",
230		expected, actual
231	);
232}
233/// Assert that status is in 2xx range
234///
235/// # Examples
236///
237/// ```
238/// use reinhardt_testkit::assertions::assert_status_success;
239/// use http::StatusCode;
240///
241/// let status = StatusCode::OK;
242/// assert_status_success(status);
243/// ```
244pub fn assert_status_success(status: StatusCode) {
245	assert!(
246		status.is_success(),
247		"Expected success status (2xx), got {}",
248		status
249	);
250}
251/// Assert that status is in 4xx range
252///
253/// # Examples
254///
255/// ```
256/// use reinhardt_testkit::assertions::assert_status_client_error;
257/// use http::StatusCode;
258///
259/// let status = StatusCode::BAD_REQUEST;
260/// assert_status_client_error(status);
261/// ```
262pub fn assert_status_client_error(status: StatusCode) {
263	assert!(
264		status.is_client_error(),
265		"Expected client error status (4xx), got {}",
266		status
267	);
268}
269/// Assert that status is in 5xx range
270///
271/// # Examples
272///
273/// ```
274/// use reinhardt_testkit::assertions::assert_status_server_error;
275/// use http::StatusCode;
276///
277/// let status = StatusCode::INTERNAL_SERVER_ERROR;
278/// assert_status_server_error(status);
279/// ```
280pub fn assert_status_server_error(status: StatusCode) {
281	assert!(
282		status.is_server_error(),
283		"Expected server error status (5xx), got {}",
284		status
285	);
286}
287/// Assert that status is an error (4xx or 5xx)
288///
289/// # Examples
290///
291/// ```
292/// use reinhardt_testkit::assertions::assert_status_error;
293/// use http::StatusCode;
294///
295/// let status = StatusCode::NOT_FOUND;
296/// assert_status_error(status);
297/// ```
298pub fn assert_status_error(status: StatusCode) {
299	assert!(
300		status.is_client_error() || status.is_server_error(),
301		"Expected error status (4xx or 5xx), got {}",
302		status
303	);
304}
305/// Assert that status is in 3xx range
306///
307/// # Examples
308///
309/// ```
310/// use reinhardt_testkit::assertions::assert_status_redirect;
311/// use http::StatusCode;
312///
313/// let status = StatusCode::FOUND;
314/// assert_status_redirect(status);
315/// ```
316pub fn assert_status_redirect(status: StatusCode) {
317	assert!(
318		status.is_redirection(),
319		"Expected redirect status (3xx), got {}",
320		status
321	);
322}
323
324// ========== HTTP Response Assertions ==========
325
326/// Assert that response has expected status code
327///
328/// This is a unified function combining `assert_status()` from micro
329/// and `assert_response_status()` from views.
330///
331/// # Examples
332///
333/// ```
334/// use reinhardt_testkit::assertions::assert_status;
335/// use reinhardt_http::Response;
336/// use http::StatusCode;
337///
338/// let response = Response::ok();
339/// assert_status(&response, StatusCode::OK);
340/// ```
341///
342/// # Panics
343///
344/// Panics if status codes don't match.
345pub fn assert_status(response: &reinhardt_http::Response, expected: StatusCode) {
346	assert_eq!(
347		response.status, expected,
348		"Expected status {}, got {}",
349		expected, response.status
350	);
351}
352
353// ========== Response Body Assertions ==========
354
355/// Assert that response body contains expected text
356///
357/// # Examples
358///
359/// ```
360/// use reinhardt_testkit::assertions::assert_response_body_contains;
361/// use reinhardt_http::Response;
362///
363/// let response = Response::ok().with_body(b"Hello, World!".to_vec());
364/// assert_response_body_contains(&response, "World");
365/// ```
366///
367/// # Panics
368///
369/// Panics if body doesn't contain the expected text.
370pub fn assert_response_body_contains(response: &reinhardt_http::Response, expected: &str) {
371	let body_str = String::from_utf8_lossy(&response.body);
372	assert!(
373		body_str.contains(expected),
374		"Expected body to contain '{}', got '{}'",
375		expected,
376		body_str
377	);
378}
379
380/// Assert that response body equals expected bytes
381///
382/// # Examples
383///
384/// ```
385/// use reinhardt_testkit::assertions::assert_response_body_equals;
386/// use reinhardt_http::Response;
387///
388/// let expected = b"exact content";
389/// let response = Response::ok().with_body(expected.to_vec());
390/// assert_response_body_equals(&response, expected);
391/// ```
392///
393/// # Panics
394///
395/// Panics if body doesn't match expected bytes.
396pub fn assert_response_body_equals(response: &reinhardt_http::Response, expected: &[u8]) {
397	assert_eq!(
398		response.body, expected,
399		"Expected body {:?}, got {:?}",
400		expected, response.body
401	);
402}
403
404// ========== JSON Response Assertions ==========
405
406/// Assert that response contains expected JSON data (exact match)
407///
408/// This function deserializes the response body and compares it with the expected value.
409/// For subset matching, use `assert_json_response_contains` instead.
410///
411/// # Examples
412///
413/// ```
414/// use reinhardt_testkit::assertions::assert_json_response;
415/// use reinhardt_http::Response;
416/// use serde::{Deserialize, Serialize};
417///
418/// #[derive(Serialize, Deserialize, PartialEq, Debug)]
419/// struct User {
420///     id: i64,
421///     name: String,
422/// }
423///
424/// let user = User { id: 1, name: "Alice".to_string() };
425/// let json = serde_json::to_vec(&user).unwrap();
426/// let response = Response::ok()
427///     .with_header("Content-Type", "application/json")
428///     .with_body(json);
429///
430/// let expected = User { id: 1, name: "Alice".to_string() };
431/// assert_json_response(response, expected);
432/// ```
433///
434/// # Panics
435///
436/// Panics if:
437/// - Response body is not valid JSON
438/// - Deserialized value doesn't match expected
439pub fn assert_json_response<T>(response: reinhardt_http::Response, expected: T)
440where
441	T: serde::de::DeserializeOwned + PartialEq + std::fmt::Debug,
442{
443	let actual: T = serde_json::from_slice(&response.body)
444		.expect("Failed to deserialize response body as JSON");
445
446	assert_eq!(
447		actual, expected,
448		"Response body mismatch: expected {:?}, got {:?}",
449		expected, actual
450	);
451}
452
453/// Assert that response is JSON and contains expected field with value
454///
455/// # Examples
456///
457/// ```
458/// use reinhardt_testkit::assertions::assert_json_response_contains;
459/// use reinhardt_http::Response;
460/// use serde_json::json;
461///
462/// let json = json!({"name": "Alice", "age": 30, "city": "NYC"});
463/// let response = Response::ok()
464///     .with_header("Content-Type", "application/json")
465///     .with_body(serde_json::to_vec(&json).unwrap());
466///
467/// assert_json_response_contains(&response, "name", &json!("Alice"));
468/// ```
469///
470/// # Panics
471///
472/// Panics if:
473/// - Response body is not valid JSON
474/// - JSON doesn't contain the expected field
475/// - Field value doesn't match expected
476pub fn assert_json_response_contains(
477	response: &reinhardt_http::Response,
478	expected_key: &str,
479	expected_value: &serde_json::Value,
480) {
481	let body_str = String::from_utf8_lossy(&response.body);
482	let json: serde_json::Value =
483		serde_json::from_str(&body_str).expect("Response body should be valid JSON");
484
485	assert!(
486		json.get(expected_key).is_some(),
487		"JSON should contain key '{}'",
488		expected_key
489	);
490	assert_eq!(
491		json.get(expected_key).unwrap(),
492		expected_value,
493		"Expected field '{}' to equal {:?}, got {:?}",
494		expected_key,
495		expected_value,
496		json.get(expected_key).unwrap()
497	);
498}
499
500// ========== Error Type Assertions ==========
501
502/// Assert that a result is an error (generic error assertion)
503///
504/// This function checks if the result is an error without checking the specific error type.
505/// Use specific error assertions (like `assert_not_found_error`) for type-specific checks.
506///
507/// # Examples
508///
509/// ```
510/// use reinhardt_testkit::assertions::assert_error;
511/// use reinhardt_http::{Error, Result};
512///
513/// let result: Result<()> = Err(Error::NotFound("Item not found".to_string()));
514/// assert_error(result);
515/// ```
516///
517/// # Panics
518///
519/// Panics if result is `Ok`.
520pub fn assert_error<T>(result: reinhardt_http::Result<T>) {
521	if result.is_ok() {
522		panic!("Expected error, got Ok");
523	}
524	// Any error is acceptable
525}
526
527/// Assert that a result is a NotFound error
528///
529/// # Examples
530///
531/// ```
532/// use reinhardt_testkit::assertions::assert_not_found_error;
533/// use reinhardt_http::{Error, Result};
534///
535/// let result: Result<()> = Err(Error::NotFound("User not found".to_string()));
536/// assert_not_found_error(result);
537/// ```
538///
539/// # Panics
540///
541/// Panics if result is `Ok` or a different error type.
542pub fn assert_not_found_error<T>(result: reinhardt_http::Result<T>) {
543	match result {
544		Ok(_) => panic!("Expected NotFound error, got Ok"),
545		Err(reinhardt_http::Error::NotFound(_)) => {}
546		Err(error) => panic!("Expected NotFound error, got {:?}", error),
547	}
548}
549
550/// Assert that a result is a Validation error
551///
552/// # Examples
553///
554/// ```
555/// use reinhardt_testkit::assertions::assert_validation_error;
556/// use reinhardt_http::{Error, Result};
557///
558/// let result: Result<()> = Err(Error::Validation("Invalid email".to_string()));
559/// assert_validation_error(result);
560/// ```
561///
562/// # Panics
563///
564/// Panics if result is `Ok` or a different error type.
565pub fn assert_validation_error<T>(result: reinhardt_http::Result<T>) {
566	match result {
567		Ok(_) => panic!("Expected Validation error, got Ok"),
568		Err(reinhardt_http::Error::Validation(_)) => {}
569		Err(error) => panic!("Expected Validation error, got {:?}", error),
570	}
571}
572
573/// Assert that a result is an Internal error
574///
575/// # Examples
576///
577/// ```
578/// use reinhardt_testkit::assertions::assert_internal_error;
579/// use reinhardt_http::{Error, Result};
580///
581/// let result: Result<()> = Err(Error::Internal("Database connection failed".to_string()));
582/// assert_internal_error(result);
583/// ```
584///
585/// # Panics
586///
587/// Panics if result is `Ok` or a different error type.
588pub fn assert_internal_error<T>(result: reinhardt_http::Result<T>) {
589	match result {
590		Ok(_) => panic!("Expected Internal error, got Ok"),
591		Err(reinhardt_http::Error::Internal(_)) => {}
592		Err(error) => panic!("Expected Internal error, got {:?}", error),
593	}
594}
595
596#[cfg(test)]
597mod tests {
598	use super::*;
599	use serde_json::json;
600
601	#[test]
602	fn test_assert_json_field_eq() {
603		let data = json!({"name": "Alice", "age": 30});
604		assert_json_field_eq(&data, "name", &json!("Alice"));
605		assert_json_field_eq(&data, "age", &json!(30));
606	}
607
608	#[test]
609	fn test_assert_json_has_field() {
610		let data = json!({"name": "Alice"});
611		assert_json_has_field(&data, "name");
612	}
613
614	#[test]
615	fn test_assert_json_missing_field() {
616		let data = json!({"name": "Alice"});
617		assert_json_missing_field(&data, "age");
618	}
619
620	#[test]
621	fn test_assert_json_array_len() {
622		let data = json!([1, 2, 3]);
623		assert_json_array_len(&data, 3);
624	}
625
626	#[test]
627	fn test_assert_json_array_contains() {
628		let data = json!([1, 2, 3]);
629		assert_json_array_contains(&data, &json!(2));
630	}
631
632	#[test]
633	fn test_assert_json_matches() {
634		let actual = json!({
635			"name": "Alice",
636			"age": 30,
637			"email": "alice@example.com"
638		});
639		let pattern = json!({
640			"name": "Alice",
641			"age": 30
642		});
643		assert_json_matches(&actual, &pattern);
644	}
645
646	#[test]
647	fn test_assert_contains() {
648		let text = "Hello, world!";
649		assert_contains(text, "world");
650	}
651
652	#[test]
653	fn test_assert_not_contains() {
654		let text = "Hello, world!";
655		assert_not_contains(text, "foo");
656	}
657
658	#[test]
659	fn test_assert_status() {
660		let response = reinhardt_http::Response::ok();
661		assert_status(&response, StatusCode::OK);
662	}
663
664	#[test]
665	fn test_assert_response_body_contains() {
666		let response = reinhardt_http::Response::ok().with_body(b"Hello, World!".to_vec());
667		assert_response_body_contains(&response, "World");
668	}
669
670	#[test]
671	fn test_assert_response_body_equals() {
672		let expected = b"exact content";
673		let response = reinhardt_http::Response::ok().with_body(expected.to_vec());
674		assert_response_body_equals(&response, expected);
675	}
676
677	#[test]
678	fn test_assert_json_response() {
679		use serde::{Deserialize, Serialize};
680
681		#[derive(Serialize, Deserialize, PartialEq, Debug)]
682		struct TestData {
683			id: i64,
684			name: String,
685		}
686
687		let data = TestData {
688			id: 1,
689			name: "test".to_string(),
690		};
691		let json = serde_json::to_vec(&data).unwrap();
692		let response = reinhardt_http::Response::ok()
693			.with_header("Content-Type", "application/json")
694			.with_body(json);
695
696		let expected = TestData {
697			id: 1,
698			name: "test".to_string(),
699		};
700		assert_json_response(response, expected);
701	}
702
703	#[test]
704	fn test_assert_json_response_contains() {
705		let json = json!({"name": "Alice", "age": 30});
706		let response = reinhardt_http::Response::ok()
707			.with_header("Content-Type", "application/json")
708			.with_body(serde_json::to_vec(&json).unwrap());
709
710		assert_json_response_contains(&response, "name", &json!("Alice"));
711		assert_json_response_contains(&response, "age", &json!(30));
712	}
713
714	#[test]
715	fn test_assert_error() {
716		let result: reinhardt_http::Result<()> =
717			Err(reinhardt_http::Error::NotFound("Not found".to_string()));
718		assert_error(result);
719	}
720
721	#[test]
722	fn test_assert_not_found_error() {
723		let result: reinhardt_http::Result<()> = Err(reinhardt_http::Error::NotFound(
724			"User not found".to_string(),
725		));
726		assert_not_found_error(result);
727	}
728
729	#[test]
730	fn test_assert_validation_error() {
731		let result: reinhardt_http::Result<()> = Err(reinhardt_http::Error::Validation(
732			"Invalid input".to_string(),
733		));
734		assert_validation_error(result);
735	}
736
737	#[test]
738	fn test_assert_internal_error() {
739		let result: reinhardt_http::Result<()> = Err(reinhardt_http::Error::Internal(
740			"Database error".to_string(),
741		));
742		assert_internal_error(result);
743	}
744}
745
746/// Task execution assertion utilities
747///
748/// Provides assertion helpers for testing task execution, completion, and failure scenarios.
749pub mod tasks {
750	use std::time::Duration;
751	use tokio::time::timeout;
752
753	/// Task status for assertion checks
754	#[derive(Debug, Clone, PartialEq, Eq)]
755	pub enum TaskStatus {
756		/// Task is pending execution
757		Pending,
758		/// Task is currently running
759		Running,
760		/// Task completed successfully
761		Completed,
762		/// Task failed with error
763		Failed,
764		/// Task was cancelled
765		Cancelled,
766	}
767
768	/// Assert that a task completes successfully within the given timeout
769	///
770	/// # Example
771	///
772	/// ```rust,no_run
773	/// use reinhardt_testkit::assertions::tasks::{assert_task_completed, TaskStatus};
774	/// use std::time::Duration;
775	/// use std::sync::Arc;
776	///
777	/// #[tokio::test]
778	/// async fn test_task_execution() {
779	///     // When using reinhardt-tasks, you would typically:
780	///     // 1. Get a ResultBackend instance from your test fixtures
781	///     // 2. Create a closure that queries the backend for task status
782	///     //
783	///     // Example with reinhardt-tasks (requires "tasks" feature):
784	///     // ```
785	///     // use reinhardt_tasks::{ResultBackend, TaskId, TaskStatus as TasksStatus};
786	///     //
787	///     // let backend: Arc<dyn ResultBackend> = // ... from test setup
788	///     // let task_id = TaskId::new();
789	///     //
790	///     // let check_status = || {
791	///     //     let backend = backend.clone();
792	///     //     let task_id = task_id;
793	///     //     async move {
794	///     //         match backend.get_result(task_id).await {
795	///     //             Ok(Some(metadata)) => match metadata.status() {
796	///     //                 TasksStatus::Success => TaskStatus::Completed,
797	///     //                 TasksStatus::Failure => TaskStatus::Failed,
798	///     //                 TasksStatus::Pending => TaskStatus::Pending,
799	///     //                 TasksStatus::Running => TaskStatus::Running,
800	///     //                 TasksStatus::Retry => TaskStatus::Running,
801	///     //             },
802	///     //             Ok(None) => TaskStatus::Pending,
803	///     //             Err(_) => TaskStatus::Failed,
804	///     //         }
805	///     //     }
806	///     // };
807	///     // ```
808	///
809	///     // Simple example with mock status check:
810	///     let check_status = || async { TaskStatus::Completed };
811	///
812	///     assert_task_completed("task-123", check_status, Duration::from_secs(5))
813	///         .await
814	///         .unwrap();
815	/// }
816	/// ```
817	pub async fn assert_task_completed<F, Fut>(
818		task_id: &str,
819		status_check: F,
820		timeout_duration: Duration,
821	) -> Result<(), String>
822	where
823		F: Fn() -> Fut,
824		Fut: std::future::Future<Output = TaskStatus>,
825	{
826		match timeout(timeout_duration, async {
827			loop {
828				let status = status_check().await;
829				match status {
830					TaskStatus::Completed => return Ok(()),
831					TaskStatus::Failed => {
832						return Err(format!("Task {} failed during execution", task_id));
833					}
834					TaskStatus::Cancelled => return Err(format!("Task {} was cancelled", task_id)),
835					TaskStatus::Pending | TaskStatus::Running => {
836						tokio::time::sleep(Duration::from_millis(100)).await;
837					}
838				}
839			}
840		})
841		.await
842		{
843			Ok(result) => result,
844			Err(_) => Err(format!(
845				"Task {} did not complete within {:?}",
846				task_id, timeout_duration
847			)),
848		}
849	}
850
851	/// Assert that a task fails with expected status
852	pub async fn assert_task_failed<F, Fut>(
853		task_id: &str,
854		status_check: F,
855		timeout_duration: Duration,
856	) -> Result<(), String>
857	where
858		F: Fn() -> Fut,
859		Fut: std::future::Future<Output = TaskStatus>,
860	{
861		match timeout(timeout_duration, async {
862			loop {
863				let status = status_check().await;
864				match status {
865					TaskStatus::Failed => return Ok(()),
866					TaskStatus::Completed => {
867						return Err(format!(
868							"Task {} completed successfully, expected failure",
869							task_id
870						));
871					}
872					TaskStatus::Cancelled => {
873						return Err(format!("Task {} was cancelled, expected failure", task_id));
874					}
875					TaskStatus::Pending | TaskStatus::Running => {
876						tokio::time::sleep(Duration::from_millis(100)).await;
877					}
878				}
879			}
880		})
881		.await
882		{
883			Ok(result) => result,
884			Err(_) => Err(format!(
885				"Task {} did not fail within {:?}",
886				task_id, timeout_duration
887			)),
888		}
889	}
890
891	/// Assert that a task is in specific status
892	pub fn assert_task_status(actual: &TaskStatus, expected: &TaskStatus, task_id: &str) {
893		assert_eq!(
894			actual, expected,
895			"Task {} status mismatch: expected {:?}, got {:?}",
896			task_id, expected, actual
897		);
898	}
899
900	#[cfg(test)]
901	mod tests {
902		use super::*;
903
904		#[tokio::test]
905		async fn test_assert_task_completed_success() {
906			let task_id = "test-task-1";
907			let status_check = || async { TaskStatus::Completed };
908
909			let result = assert_task_completed(task_id, status_check, Duration::from_secs(1)).await;
910			assert!(result.is_ok());
911		}
912
913		#[tokio::test]
914		async fn test_assert_task_failed_success() {
915			let task_id = "test-task-2";
916			let status_check = || async { TaskStatus::Failed };
917
918			let result = assert_task_failed(task_id, status_check, Duration::from_secs(1)).await;
919			assert!(result.is_ok());
920		}
921
922		#[test]
923		fn test_assert_task_status() {
924			let status = TaskStatus::Completed;
925			assert_task_status(&status, &TaskStatus::Completed, "test-task");
926		}
927	}
928}