stubr/wiremock/
mock_set.rs

1use crate::wiremock::{
2    mounted_mock::MountedMock,
3    verification::{VerificationOutcome, VerificationReport},
4};
5use crate::wiremock::{Mock, Request, ResponseTemplate};
6use futures_timer::Delay;
7use http_types::{Response, StatusCode};
8use log::debug;
9use std::ops::{Index, IndexMut};
10
11/// The collection of mocks used by a `MockServer` instance to match against
12/// incoming requests.
13///
14/// New mocks are added to `MountedMockSet` every time [`MockServer::register`](crate::MockServer::register),
15/// [`MockServer::register_as_scoped`](crate::MockServer::register_as_scoped) or
16/// [`Mock::mount`](crate::Mock::mount) are called.
17pub(crate) struct MountedMockSet {
18    pub(crate) mocks: Vec<(MountedMock, MountedMockState)>,
19    /// A counter that keeps track of how many times [`MountedMockSet::reset`] has been called.
20    /// It starts at `0` and gets incremented for each invocation.
21    ///
22    /// We need `generation` to know if a [`MockId`] points to an [`MountedMock`] that has been
23    /// removed via [`MountedMockSet::reset`].
24    generation: u16,
25}
26
27/// A `MockId` is an opaque index that uniquely identifies an [`MountedMock`] inside an [`MountedMockSet`].
28///
29/// The only way to create a `MockId` is calling [`MountedMockSet::register`].
30#[derive(Copy, Clone)]
31pub(crate) struct MockId {
32    index: usize,
33    /// The generation of [`MountedMockSet`] when [`MountedMockSet::register`] was called.
34    /// It allows [`MountedMockSet`] to check that the [`MountedMock`] our [`MockId`] points to is still in
35    /// the set (i.e. the set has not been wiped by a [`MountedMockSet::reset`] call).
36    generation: u16,
37}
38
39impl MountedMockSet {
40    /// Create a new instance of MockSet.
41    pub(crate) fn new() -> MountedMockSet {
42        MountedMockSet {
43            mocks: vec![],
44            generation: 0,
45        }
46    }
47
48    pub(crate) async fn handle_request(&mut self, request: Request) -> (Response, Option<Delay>) {
49        debug!("Handling request.");
50        let mut response_template: Option<ResponseTemplate> = None;
51        self.mocks.sort_by_key(|(m, _)| m.specification.priority);
52        for (mock, mock_state) in &mut self.mocks {
53            if *mock_state == MountedMockState::OutOfScope {
54                continue;
55            }
56            if mock.matches(&request) {
57                response_template = mock.response_template(&request).ok();
58                break;
59            }
60        }
61        if let Some(response_template) = response_template {
62            let delay = response_template.delay().map(|d| Delay::new(d.into_owned()));
63            (response_template.generate_response(), delay)
64        } else {
65            debug!("Got unexpected request:\n{}", request);
66            (Response::new(StatusCode::NotFound), None)
67        }
68    }
69
70    pub(crate) fn register(&mut self, mock: Mock) -> MockId {
71        let n_registered_mocks = self.mocks.len();
72        let active_mock = MountedMock::new(mock, n_registered_mocks);
73        self.mocks.push((active_mock, MountedMockState::InScope));
74
75        MockId {
76            index: self.mocks.len() - 1,
77            generation: self.generation,
78        }
79    }
80
81    pub(crate) fn reset(&mut self) {
82        self.mocks = vec![];
83        self.generation += 1;
84    }
85
86    /// Mark one of the mocks in the set as out of scope.
87    ///
88    /// It will stop matching against incoming requests, regardless of its specification.
89    pub(crate) fn deactivate(&mut self, mock_id: MockId) {
90        let mut mock = &mut self[mock_id];
91        mock.1 = MountedMockState::OutOfScope;
92    }
93
94    /// Verify that expectations have been met for **all** [`MountedMock`]s in the set.
95    pub(crate) fn verify_all(&self) -> VerificationOutcome {
96        let failed_verifications: Vec<VerificationReport> = self
97            .mocks
98            .iter()
99            .filter(|(_, state)| *state == MountedMockState::InScope)
100            .map(|(m, _)| m.verify())
101            .filter(|verification_report| !verification_report.is_satisfied())
102            .collect();
103        if failed_verifications.is_empty() {
104            VerificationOutcome::Success
105        } else {
106            VerificationOutcome::Failure(failed_verifications)
107        }
108    }
109
110    /// Verify that expectations have been met for the [`MountedMock`] corresponding to the specified [`MockId`].
111    pub(crate) fn verify(&self, mock_id: MockId) -> VerificationReport {
112        let (mock, _) = &self[mock_id];
113        mock.verify()
114    }
115}
116
117impl IndexMut<MockId> for MountedMockSet {
118    fn index_mut(&mut self, index: MockId) -> &mut Self::Output {
119        if index.generation != self.generation {
120            panic!("The mock you are trying to access is no longer active. It has been deleted from the active set via `reset` - you should not hold on to a `MockId` after you call `reset`!.")
121        }
122        &mut self.mocks[index.index]
123    }
124}
125
126impl Index<MockId> for MountedMockSet {
127    type Output = (MountedMock, MountedMockState);
128
129    fn index(&self, index: MockId) -> &Self::Output {
130        if index.generation != self.generation {
131            panic!("The mock you are trying to access is no longer active. It has been deleted from the active set via `reset` - you should not hold on to a `MockId` after you call `reset`!.")
132        }
133        &self.mocks[index.index]
134    }
135}
136
137/// A [`MountedMock`] can either be global (i.e. registered using [`crate::MockServer::register`]) or
138/// scoped (i.e. registered using [`crate::MockServer::register_as_scoped`]).
139///
140/// [`MountedMock`]s must currently be in scope to be matched against incoming requests.
141/// Out of scope [`MountedMock`]s are skipped when trying to match an incoming request.
142///
143/// # Implementation Rationale
144///
145/// An alternative approach would be removing a [`MountedMock`] from the [`MountedMockSet`] when it goes
146/// out of scope.
147/// This would create an issue for the stability of [`MockId`]s: removing an element from the vector
148/// of [`MountedMock`]s in [`MountedMockSet`] would invalidate the ids of all mocks registered after
149/// the removed one.
150///
151/// Attaching a state to the mocks in the vector, instead, allows us to ensure id stability while
152/// achieving the desired behaviour.
153#[derive(Debug, PartialEq, Eq, Copy, Clone)]
154pub(crate) enum MountedMockState {
155    InScope,
156    OutOfScope,
157}
158
159#[cfg(test)]
160mod tests {
161    use crate::wiremock::matchers::path;
162    use crate::wiremock::mock_set::{MountedMockSet, MountedMockState};
163    use crate::wiremock::{Mock, ResponseTemplate};
164
165    #[test]
166    fn generation_is_incremented_for_every_reset() {
167        let mut set = MountedMockSet::new();
168        assert_eq!(set.generation, 0);
169
170        for i in 1..10 {
171            set.reset();
172            assert_eq!(set.generation, i);
173        }
174    }
175
176    #[test]
177    #[should_panic]
178    fn accessing_a_mock_id_after_a_reset_triggers_a_panic() {
179        // Assert
180        let mut set = MountedMockSet::new();
181        let mock = Mock::given(path("/")).respond_with(ResponseTemplate::new(200));
182        let mock_id = set.register(mock);
183
184        // Act
185        set.reset();
186
187        // Assert
188        let _ = &set[mock_id];
189    }
190
191    #[test]
192    fn deactivating_a_mock_does_not_invalidate_other_ids() {
193        // Assert
194        let mut set = MountedMockSet::new();
195        let first_mock = Mock::given(path("/")).respond_with(ResponseTemplate::new(200));
196        let second_mock = Mock::given(path("/hello")).respond_with(ResponseTemplate::new(500));
197        let first_mock_id = set.register(first_mock);
198        let second_mock_id = set.register(second_mock);
199
200        // Act
201        set.deactivate(first_mock_id);
202
203        // Assert
204        let first_mock = &set[first_mock_id];
205        assert_eq!(first_mock.1, MountedMockState::OutOfScope);
206        let second_mock = &set[second_mock_id];
207        assert_eq!(second_mock.1, MountedMockState::InScope);
208    }
209}