reinhardt_testkit/server_fn/
assertions.rs1#![cfg(not(target_arch = "wasm32"))]
22
23use std::fmt::Debug;
24
25use http::StatusCode;
26
27pub trait ServerFnResultAssertions<T, E> {
32 fn should_be_ok(&self) -> &T;
34
35 fn should_be_err(&self) -> &E;
37
38 fn should_have_value(&self, expected: &T)
40 where
41 T: PartialEq + Debug;
42
43 fn should_satisfy<F>(&self, predicate: F)
45 where
46 F: FnOnce(&T) -> bool;
47}
48
49impl<T: Debug, E: Debug> ServerFnResultAssertions<T, E> for Result<T, E> {
50 fn should_be_ok(&self) -> &T {
51 match self {
52 Ok(value) => value,
53 Err(e) => panic!("Expected Ok result, but got Err: {:?}", e),
54 }
55 }
56
57 fn should_be_err(&self) -> &E {
58 match self {
59 Ok(value) => panic!("Expected Err result, but got Ok: {:?}", value),
60 Err(e) => e,
61 }
62 }
63
64 fn should_have_value(&self, expected: &T)
65 where
66 T: PartialEq + Debug,
67 {
68 let actual = self.should_be_ok();
69 assert_eq!(
70 actual, expected,
71 "Expected value {:?}, but got {:?}",
72 expected, actual
73 );
74 }
75
76 fn should_satisfy<F>(&self, predicate: F)
77 where
78 F: FnOnce(&T) -> bool,
79 {
80 let value = self.should_be_ok();
81 assert!(
82 predicate(value),
83 "Value {:?} did not satisfy the predicate",
84 value
85 );
86 }
87}
88
89pub trait ServerFnErrorAssertions<E> {
91 fn should_contain_message(&self, expected: &str)
93 where
94 E: std::fmt::Display;
95
96 fn should_have_message(&self, expected: &str)
98 where
99 E: std::fmt::Display;
100}
101
102impl<E: Debug> ServerFnErrorAssertions<E> for E {
103 fn should_contain_message(&self, expected: &str)
104 where
105 E: std::fmt::Display,
106 {
107 let message = self.to_string();
108 assert!(
109 message.contains(expected),
110 "Expected error message to contain '{}', but got '{}'",
111 expected,
112 message
113 );
114 }
115
116 fn should_have_message(&self, expected: &str)
117 where
118 E: std::fmt::Display,
119 {
120 let message = self.to_string();
121 assert_eq!(
122 message, expected,
123 "Expected error message '{}', but got '{}'",
124 expected, message
125 );
126 }
127}
128
129pub fn assert_server_fn_returns<T, E>(result: &Result<T, E>, expected: &T)
131where
132 T: PartialEq + Debug,
133 E: Debug,
134{
135 result.should_have_value(expected);
136}
137
138pub fn assert_server_fn_error<T, E>(result: &Result<T, E>)
140where
141 T: Debug,
142 E: Debug,
143{
144 let _ = result.should_be_err();
145}
146
147pub fn assert_server_fn_error_contains<T, E>(result: &Result<T, E>, message: &str)
149where
150 T: Debug,
151 E: Debug + std::fmt::Display,
152{
153 let error = result.should_be_err();
154 error.should_contain_message(message);
155}
156
157pub fn assert_validation_error<T, E>(result: &Result<T, E>, field: &str)
162where
163 T: Debug,
164 E: Debug + std::fmt::Display,
165{
166 let error = result.should_be_err();
167 let message = error.to_string();
168 assert!(
169 message.to_lowercase().contains(&field.to_lowercase()),
170 "Expected validation error for field '{}', but got: {}",
171 field,
172 message
173 );
174}
175
176pub fn assert_validation_errors<T, E>(result: &Result<T, E>, fields: &[&str])
178where
179 T: Debug,
180 E: Debug + std::fmt::Display,
181{
182 let error = result.should_be_err();
183 let message = error.to_string().to_lowercase();
184
185 for field in fields {
186 assert!(
187 message.contains(&field.to_lowercase()),
188 "Expected validation error for field '{}', but it was not found in: {}",
189 field,
190 error
191 );
192 }
193}
194
195#[derive(Debug)]
197pub struct ResponseAssertion<T> {
198 value: T,
199 status: Option<StatusCode>,
200}
201
202impl<T> ResponseAssertion<T> {
203 pub fn new(value: T) -> Self {
205 Self {
206 value,
207 status: None,
208 }
209 }
210
211 pub fn with_status(mut self, status: StatusCode) -> Self {
213 self.status = Some(status);
214 self
215 }
216
217 pub fn value(&self) -> &T {
219 &self.value
220 }
221
222 pub fn into_value(self) -> T {
224 self.value
225 }
226}
227
228pub trait HasStatusCode {
230 fn status_code(&self) -> StatusCode;
232}
233
234impl<T: HasStatusCode> ResponseAssertion<T> {
235 pub fn should_have_status(&self, expected: StatusCode) {
237 let actual = self.value.status_code();
238 assert_eq!(
239 actual, expected,
240 "Expected status code {:?}, but got {:?}",
241 expected, actual
242 );
243 }
244
245 pub fn should_be_success(&self) {
247 let status = self.value.status_code();
248 assert!(
249 status.is_success(),
250 "Expected successful response, but got {:?}",
251 status
252 );
253 }
254
255 pub fn should_be_client_error(&self) {
257 let status = self.value.status_code();
258 assert!(
259 status.is_client_error(),
260 "Expected client error response, but got {:?}",
261 status
262 );
263 }
264
265 pub fn should_be_server_error(&self) {
267 let status = self.value.status_code();
268 assert!(
269 status.is_server_error(),
270 "Expected server error response, but got {:?}",
271 status
272 );
273 }
274}
275
276#[macro_export]
292macro_rules! server_fn_test_cases {
293 ($fn_name:path, $($test_name:ident: ($input:expr, $expected:pat)),* $(,)?) => {
294 $(
295 #[rstest::rstest]
296 #[tokio::test]
297 async fn $test_name(
298 #[allow(unused_variables)]
300 server_fn_context: $crate::server_fn::ServerFnTestEnv,
301 ) {
302 let result = $fn_name($input, &server_fn_context).await;
303 assert!(
304 matches!(result, $expected),
305 "Expected {:?} to match pattern {}",
306 result,
307 stringify!($expected)
308 );
309 }
310 )*
311 };
312}
313
314pub mod assert_status {
316 use super::*;
317
318 pub fn ok<T: HasStatusCode>(response: &T) {
320 let status = response.status_code();
321 assert_eq!(
322 status,
323 StatusCode::OK,
324 "Expected 200 OK, but got {:?}",
325 status
326 );
327 }
328
329 pub fn created<T: HasStatusCode>(response: &T) {
331 let status = response.status_code();
332 assert_eq!(
333 status,
334 StatusCode::CREATED,
335 "Expected 201 Created, but got {:?}",
336 status
337 );
338 }
339
340 pub fn no_content<T: HasStatusCode>(response: &T) {
342 let status = response.status_code();
343 assert_eq!(
344 status,
345 StatusCode::NO_CONTENT,
346 "Expected 204 No Content, but got {:?}",
347 status
348 );
349 }
350
351 pub fn bad_request<T: HasStatusCode>(response: &T) {
353 let status = response.status_code();
354 assert_eq!(
355 status,
356 StatusCode::BAD_REQUEST,
357 "Expected 400 Bad Request, but got {:?}",
358 status
359 );
360 }
361
362 pub fn unauthorized<T: HasStatusCode>(response: &T) {
364 let status = response.status_code();
365 assert_eq!(
366 status,
367 StatusCode::UNAUTHORIZED,
368 "Expected 401 Unauthorized, but got {:?}",
369 status
370 );
371 }
372
373 pub fn forbidden<T: HasStatusCode>(response: &T) {
375 let status = response.status_code();
376 assert_eq!(
377 status,
378 StatusCode::FORBIDDEN,
379 "Expected 403 Forbidden, but got {:?}",
380 status
381 );
382 }
383
384 pub fn not_found<T: HasStatusCode>(response: &T) {
386 let status = response.status_code();
387 assert_eq!(
388 status,
389 StatusCode::NOT_FOUND,
390 "Expected 404 Not Found, but got {:?}",
391 status
392 );
393 }
394
395 pub fn conflict<T: HasStatusCode>(response: &T) {
397 let status = response.status_code();
398 assert_eq!(
399 status,
400 StatusCode::CONFLICT,
401 "Expected 409 Conflict, but got {:?}",
402 status
403 );
404 }
405
406 pub fn unprocessable_entity<T: HasStatusCode>(response: &T) {
408 let status = response.status_code();
409 assert_eq!(
410 status,
411 StatusCode::UNPROCESSABLE_ENTITY,
412 "Expected 422 Unprocessable Entity, but got {:?}",
413 status
414 );
415 }
416
417 pub fn internal_error<T: HasStatusCode>(response: &T) {
419 let status = response.status_code();
420 assert_eq!(
421 status,
422 StatusCode::INTERNAL_SERVER_ERROR,
423 "Expected 500 Internal Server Error, but got {:?}",
424 status
425 );
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432
433 #[test]
434 fn test_should_be_ok() {
435 let result: Result<i32, &str> = Ok(42);
436 assert_eq!(*result.should_be_ok(), 42);
437 }
438
439 #[test]
440 #[should_panic(expected = "Expected Ok result")]
441 fn test_should_be_ok_panics_on_err() {
442 let result: Result<i32, &str> = Err("error");
443 result.should_be_ok();
444 }
445
446 #[test]
447 fn test_should_be_err() {
448 let result: Result<i32, &str> = Err("error");
449 assert_eq!(*result.should_be_err(), "error");
450 }
451
452 #[test]
453 #[should_panic(expected = "Expected Err result")]
454 fn test_should_be_err_panics_on_ok() {
455 let result: Result<i32, &str> = Ok(42);
456 result.should_be_err();
457 }
458
459 #[test]
460 fn test_should_have_value() {
461 let result: Result<i32, &str> = Ok(42);
462 result.should_have_value(&42);
463 }
464
465 #[test]
466 fn test_should_satisfy() {
467 let result: Result<i32, &str> = Ok(42);
468 result.should_satisfy(|v| *v > 0);
469 }
470
471 #[test]
472 fn test_assert_server_fn_returns() {
473 let result: Result<i32, &str> = Ok(42);
474 assert_server_fn_returns(&result, &42);
475 }
476
477 #[test]
478 fn test_assert_server_fn_error_contains() {
479 let result: Result<i32, String> = Err("Validation failed: email is required".to_string());
480 assert_server_fn_error_contains(&result, "email");
481 }
482
483 #[test]
484 fn test_assert_validation_error() {
485 let result: Result<i32, String> = Err("Field 'username' is required".to_string());
486 assert_validation_error(&result, "username");
487 }
488
489 #[test]
490 fn test_assert_validation_errors() {
491 let result: Result<i32, String> =
492 Err("Validation errors: username is required, email is invalid".to_string());
493 assert_validation_errors(&result, &["username", "email"]);
494 }
495}