mocktail/
mock.rs

1//! Mock
2use std::sync::{
3    atomic::{AtomicUsize, Ordering},
4    Arc,
5};
6
7use uuid::Uuid;
8
9use crate::{
10    matchers::Matcher,
11    mock_builder::{Then, When},
12    request::Request,
13    response::Response,
14};
15
16const DEFAULT_PRIORITY: u8 = 5;
17
18/// A mock.
19#[derive(Debug)]
20pub struct Mock {
21    /// Mock ID.
22    pub id: Uuid,
23    /// A set of request match conditions.
24    pub matchers: Vec<Arc<dyn Matcher>>,
25    /// A mock response.
26    pub response: Response,
27    /// Priority.
28    pub priority: u8,
29    /// Match counter.
30    pub match_count: AtomicUsize,
31    /// Limit on how many times this mock can be matched.
32    pub limit: Option<usize>,
33}
34
35impl Mock {
36    /// Builds a mock.
37    pub fn new<F>(f: F) -> Self
38    where
39        F: FnOnce(When, Then),
40    {
41        let id = Uuid::now_v7();
42        let when = When::new();
43        let then = Then::new();
44        f(when.clone(), then.clone());
45        Self {
46            id,
47            matchers: when.into_inner(),
48            response: then.into_inner(),
49            priority: DEFAULT_PRIORITY,
50            match_count: AtomicUsize::new(0),
51            limit: None,
52        }
53    }
54
55    /// Sets the mock priority.
56    pub fn with_priority(mut self, priority: u8) -> Self {
57        self.priority = priority;
58        self
59    }
60
61    /// Sets the mock limit.
62    pub fn with_limit(mut self, limit: usize) -> Self {
63        self.limit = Some(limit);
64        self
65    }
66
67    /// Returns the mock ID.
68    pub fn id(&self) -> &Uuid {
69        &self.id
70    }
71
72    /// Returns the mock response.
73    pub fn response(&self) -> &Response {
74        &self.response
75    }
76
77    /// Returns the mock priority.
78    pub fn priority(&self) -> u8 {
79        self.priority
80    }
81
82    /// Returns the match count.
83    pub fn match_count(&self) -> usize {
84        self.match_count.load(Ordering::Relaxed)
85    }
86
87    /// Evaluates a request against match conditions.
88    pub fn matches(&self, req: &Request) -> bool {
89        if let Some(limit) = self.limit {
90            if self.match_count.load(Ordering::Relaxed) >= limit {
91                return false;
92            }
93        }
94        let matched = self.matchers.iter().all(|matcher| matcher.matches(req));
95        if matched {
96            self.match_count.fetch_add(1, Ordering::Relaxed);
97        }
98        matched
99    }
100
101    /// Resets the match counter.
102    pub fn reset(&self) {
103        self.match_count.store(0, Ordering::Relaxed);
104    }
105}
106
107impl PartialEq for Mock {
108    fn eq(&self, other: &Self) -> bool {
109        self.id == other.id
110            && self.matchers == other.matchers
111            && self.response == other.response
112            && self.priority == other.priority
113            && self.match_count.load(Ordering::Relaxed) == other.match_count.load(Ordering::Relaxed)
114            && self.limit == other.limit
115    }
116}
117
118impl Clone for Mock {
119    fn clone(&self) -> Self {
120        Self {
121            id: self.id,
122            matchers: self.matchers.clone(),
123            response: self.response.clone(),
124            priority: self.priority,
125            match_count: AtomicUsize::new(self.match_count.load(Ordering::Relaxed)),
126            limit: self.limit,
127        }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use crate::Method;
135
136    #[test]
137    fn test_match_counter() {
138        let mock = Mock::new(|when, then| {
139            when.get();
140            then.ok();
141        });
142        let request = Request::new(Method::GET, "http://localhost/".parse().unwrap());
143        mock.matches(&request);
144        assert_eq!(mock.match_count(), 1);
145        mock.matches(&request);
146        assert_eq!(mock.match_count(), 2);
147        mock.reset();
148        assert_eq!(mock.match_count(), 0);
149    }
150
151    #[test]
152    fn test_limit() {
153        let mock = Mock::new(|when, then| {
154            when.get();
155            then.ok();
156        })
157        .with_limit(2);
158        let request = Request::new(Method::GET, "http://localhost/".parse().unwrap());
159        assert!(mock.matches(&request));
160        assert!(mock.matches(&request));
161        assert!(!mock.matches(&request));
162    }
163}