Skip to main content

mockeroo_mock_responses/
lib.rs

1//! Sarcastic HTTP status code responses for testing and development.
2//!
3//! Mirrors the behaviour of the `@mockeroo/mock-responses` npm package.
4//!
5//! # Example
6//!
7//! ```rust
8//! use mockeroo_mock_responses::{get_response, get_available_codes};
9//!
10//! if let Some(resp) = get_response(404) {
11//!     println!("{} — {}", resp.status, resp.message);
12//! }
13//!
14//! let codes = get_available_codes();
15//! println!("Supported codes: {:?}", codes);
16//! ```
17
18use rand::seq::SliceRandom;
19
20include!(concat!(env!("OUT_DIR"), "/responses_data.rs"));
21
22/// An HTTP status code paired with a randomly chosen sarcastic message.
23pub struct Response {
24    pub status: u16,
25    pub message: &'static str,
26}
27
28/// Returns a [`Response`] with a random sarcastic message for the given HTTP
29/// status code, or `None` if the code is not recognised.
30pub fn get_response(status_code: u16) -> Option<Response> {
31    let messages = RESPONSES
32        .iter()
33        .find(|(code, _)| *code == status_code)
34        .map(|(_, msgs)| *msgs)?;
35
36    let message = messages.choose(&mut rand::thread_rng())?;
37    Some(Response {
38        status: status_code,
39        message,
40    })
41}
42
43/// Returns all supported HTTP status codes in ascending order.
44pub fn get_available_codes() -> Vec<u16> {
45    RESPONSES.iter().map(|(code, _)| *code).collect()
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use std::collections::HashSet;
52
53    #[test]
54    fn known_code_returns_response() {
55        let resp = get_response(200).expect("expected response for 200");
56        assert_eq!(resp.status, 200);
57        assert!(!resp.message.is_empty());
58    }
59
60    #[test]
61    fn unknown_code_returns_none() {
62        assert!(get_response(999).is_none());
63    }
64
65    #[test]
66    fn all_available_codes_return_responses() {
67        for code in get_available_codes() {
68            let resp = get_response(code)
69                .unwrap_or_else(|| panic!("get_response({code}) returned None but code is listed as available"));
70            assert_eq!(resp.status, code);
71            assert!(!resp.message.is_empty(), "empty message for code {code}");
72        }
73    }
74
75    #[test]
76    fn available_codes_not_empty() {
77        assert!(!get_available_codes().is_empty());
78    }
79
80    #[test]
81    fn available_codes_are_sorted() {
82        let codes = get_available_codes();
83        for w in codes.windows(2) {
84            assert!(w[0] < w[1], "codes not sorted: {} followed by {}", w[0], w[1]);
85        }
86    }
87
88    #[test]
89    fn available_codes_contains_expected() {
90        let codes: HashSet<u16> = get_available_codes().into_iter().collect();
91        for expected in [200, 201, 204, 400, 401, 403, 404, 500] {
92            assert!(codes.contains(&expected), "expected code {expected} to be available");
93        }
94    }
95
96    #[test]
97    fn responses_are_varied() {
98        // 404 has 10+ messages; after 100 draws we expect more than one unique message.
99        let seen: HashSet<&str> = (0..100)
100            .filter_map(|_| get_response(404))
101            .map(|r| r.message)
102            .collect();
103        assert!(
104            seen.len() > 1,
105            "get_response(404) returned the same message 100 times — randomness may be broken"
106        );
107    }
108}