1use crate::error::{TestingError, TestingResult};
4use std::time::Duration;
5
6#[macro_export]
8macro_rules! assert_ok {
9 ($expr:expr) => {
10 match $expr {
11 Ok(v) => v,
12 Err(e) => panic!("Expected Ok, got Err: {:?}", e),
13 }
14 };
15 ($expr:expr, $msg:expr) => {
16 match $expr {
17 Ok(v) => v,
18 Err(e) => panic!("{}: Expected Ok, got Err: {:?}", $msg, e),
19 }
20 };
21}
22
23#[macro_export]
25macro_rules! assert_err {
26 ($expr:expr) => {
27 match $expr {
28 Err(e) => e,
29 Ok(v) => panic!("Expected Err, got Ok: {:?}", v),
30 }
31 };
32 ($expr:expr, $msg:expr) => {
33 match $expr {
34 Err(e) => e,
35 Ok(v) => panic!("{}: Expected Err, got Ok: {:?}", $msg, v),
36 }
37 };
38}
39
40#[macro_export]
42macro_rules! assert_some {
43 ($expr:expr) => {
44 match $expr {
45 Some(v) => v,
46 None => panic!("Expected Some, got None"),
47 }
48 };
49 ($expr:expr, $msg:expr) => {
50 match $expr {
51 Some(v) => v,
52 None => panic!("{}: Expected Some, got None", $msg),
53 }
54 };
55}
56
57#[macro_export]
59macro_rules! assert_none {
60 ($expr:expr) => {
61 match $expr {
62 None => (),
63 Some(v) => panic!("Expected None, got Some: {:?}", v),
64 }
65 };
66 ($expr:expr, $msg:expr) => {
67 match $expr {
68 None => (),
69 Some(v) => panic!("{}: Expected None, got Some: {:?}", $msg, v),
70 }
71 };
72}
73
74#[macro_export]
76macro_rules! assert_contains {
77 ($container:expr, $item:expr) => {
78 if !$container.contains(&$item) {
79 panic!("Expected container to contain item: {:?}", $item);
80 }
81 };
82 ($container:expr, $item:expr, $msg:expr) => {
83 if !$container.contains(&$item) {
84 panic!("{}: Expected container to contain item: {:?}", $msg, $item);
85 }
86 };
87}
88
89#[macro_export]
91macro_rules! assert_not_contains {
92 ($container:expr, $item:expr) => {
93 if $container.contains(&$item) {
94 panic!("Expected container to NOT contain item: {:?}", $item);
95 }
96 };
97 ($container:expr, $item:expr, $msg:expr) => {
98 if $container.contains(&$item) {
99 panic!("{}: Expected container to NOT contain item: {:?}", $msg, $item);
100 }
101 };
102}
103
104#[macro_export]
106macro_rules! assert_approx_eq {
107 ($left:expr, $right:expr, $epsilon:expr) => {
108 let left = $left;
109 let right = $right;
110 let diff = if left > right { left - right } else { right - left };
111 if diff > $epsilon {
112 panic!("Values are not approximately equal: left={}, right={}, diff={}", left, right, diff);
113 }
114 };
115}
116
117pub struct AsyncAssert;
119
120impl AsyncAssert {
121 pub async fn eventually<F, Fut>(condition: F, timeout_duration: Duration, check_interval: Duration) -> TestingResult<()>
123 where
124 F: Fn() -> Fut,
125 Fut: std::future::Future<Output = bool>,
126 {
127 let start = std::time::Instant::now();
128
129 while start.elapsed() < timeout_duration {
130 if condition().await {
131 return Ok(());
132 }
133 tokio::time::sleep(check_interval).await;
134 }
135
136 Err(TestingError::Timeout(format!("Condition not met within {:?}", timeout_duration)))
137 }
138
139 pub async fn eventually_with_value<F, Fut, T>(
141 condition: F,
142 timeout_duration: Duration,
143 check_interval: Duration,
144 ) -> TestingResult<T>
145 where
146 F: Fn() -> Fut,
147 Fut: std::future::Future<Output = Option<T>>,
148 {
149 let start = std::time::Instant::now();
150
151 while start.elapsed() < timeout_duration {
152 if let Some(value) = condition().await {
153 return Ok(value);
154 }
155 tokio::time::sleep(check_interval).await;
156 }
157
158 Err(TestingError::Timeout(format!("Condition not met within {:?}", timeout_duration)))
159 }
160}
161
162pub async fn assert_eventually<F, Fut>(condition: F, timeout_duration: Duration, check_interval: Duration) -> TestingResult<()>
164where
165 F: Fn() -> Fut,
166 Fut: std::future::Future<Output = bool>,
167{
168 AsyncAssert::eventually(condition, timeout_duration, check_interval).await
169}
170
171pub fn assert_matches_regex(text: &str, pattern: &str) -> TestingResult<()> {
173 let re = regex::Regex::new(pattern).map_err(|e| TestingError::AssertionFailed(format!("Invalid regex: {}", e)))?;
174
175 if !re.is_match(text) {
176 return Err(TestingError::AssertionFailed(format!("Text '{}' does not match pattern '{}'", text, pattern)));
177 }
178
179 Ok(())
180}
181
182pub fn assert_json_contains(json: &serde_json::Value, path: &str) -> TestingResult<()> {
184 let parts: Vec<&str> = path.split('.').collect();
185 let mut current = json;
186
187 for part in parts {
188 if let serde_json::Value::Object(map) = current {
189 current = map.get(part).ok_or_else(|| TestingError::AssertionFailed(format!("JSON path '{}' not found", path)))?;
190 }
191 else {
192 return Err(TestingError::AssertionFailed(format!("JSON path '{}' not found", path)));
193 }
194 }
195
196 Ok(())
197}