use crate::{
mounted_mock::MountedMock,
verification::{VerificationOutcome, VerificationReport},
};
use crate::{Mock, Request, ResponseTemplate};
use futures_timer::Delay;
use http_types::{Response, StatusCode};
use log::debug;
use std::{
ops::{Index, IndexMut},
sync::{atomic::AtomicBool, Arc},
};
use tokio::sync::Notify;
pub(crate) struct MountedMockSet {
mocks: Vec<(MountedMock, MountedMockState)>,
generation: u16,
}
#[derive(Copy, Clone)]
pub(crate) struct MockId {
index: usize,
generation: u16,
}
impl MountedMockSet {
pub(crate) fn new() -> MountedMockSet {
MountedMockSet {
mocks: vec![],
generation: 0,
}
}
pub(crate) async fn handle_request(&mut self, request: Request) -> (Response, Option<Delay>) {
debug!("Handling request.");
let mut response_template: Option<ResponseTemplate> = None;
self.mocks.sort_by_key(|(m, _)| m.specification.priority);
for (mock, mock_state) in &mut self.mocks {
if *mock_state == MountedMockState::OutOfScope {
continue;
}
if mock.matches(&request) {
response_template = Some(mock.response_template(&request));
break;
}
}
if let Some(response_template) = response_template {
let delay = response_template.delay().map(|d| Delay::new(d.to_owned()));
(response_template.generate_response(), delay)
} else {
debug!("Got unexpected request:\n{}", request);
(Response::new(StatusCode::NotFound), None)
}
}
pub(crate) fn register(&mut self, mock: Mock) -> (Arc<(Notify, AtomicBool)>, MockId) {
let n_registered_mocks = self.mocks.len();
let active_mock = MountedMock::new(mock, n_registered_mocks);
let notify = active_mock.notify();
self.mocks.push((active_mock, MountedMockState::InScope));
(
notify,
MockId {
index: self.mocks.len() - 1,
generation: self.generation,
},
)
}
pub(crate) fn reset(&mut self) {
self.mocks = vec![];
self.generation += 1;
}
pub(crate) fn deactivate(&mut self, mock_id: MockId) {
let mock = &mut self[mock_id];
mock.1 = MountedMockState::OutOfScope;
}
pub(crate) fn verify_all(&self) -> VerificationOutcome {
let failed_verifications: Vec<VerificationReport> = self
.mocks
.iter()
.filter(|(_, state)| *state == MountedMockState::InScope)
.map(|(m, _)| m.verify())
.filter(|verification_report| !verification_report.is_satisfied())
.collect();
if failed_verifications.is_empty() {
VerificationOutcome::Success
} else {
VerificationOutcome::Failure(failed_verifications)
}
}
pub(crate) fn verify(&self, mock_id: MockId) -> VerificationReport {
let (mock, _) = &self[mock_id];
mock.verify()
}
}
impl IndexMut<MockId> for MountedMockSet {
fn index_mut(&mut self, index: MockId) -> &mut Self::Output {
if index.generation != self.generation {
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`!.")
}
&mut self.mocks[index.index]
}
}
impl Index<MockId> for MountedMockSet {
type Output = (MountedMock, MountedMockState);
fn index(&self, index: MockId) -> &Self::Output {
if index.generation != self.generation {
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`!.")
}
&self.mocks[index.index]
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub(crate) enum MountedMockState {
InScope,
OutOfScope,
}
#[cfg(test)]
mod tests {
use crate::matchers::path;
use crate::mock_set::{MountedMockSet, MountedMockState};
use crate::{Mock, ResponseTemplate};
#[test]
fn generation_is_incremented_for_every_reset() {
let mut set = MountedMockSet::new();
assert_eq!(set.generation, 0);
for i in 1..10 {
set.reset();
assert_eq!(set.generation, i);
}
}
#[test]
#[should_panic]
fn accessing_a_mock_id_after_a_reset_triggers_a_panic() {
let mut set = MountedMockSet::new();
let mock = Mock::given(path("/")).respond_with(ResponseTemplate::new(200));
let (_, mock_id) = set.register(mock);
set.reset();
let _ = &set[mock_id];
}
#[test]
fn deactivating_a_mock_does_not_invalidate_other_ids() {
let mut set = MountedMockSet::new();
let first_mock = Mock::given(path("/")).respond_with(ResponseTemplate::new(200));
let second_mock = Mock::given(path("/hello")).respond_with(ResponseTemplate::new(500));
let (_, first_mock_id) = set.register(first_mock);
let (_, second_mock_id) = set.register(second_mock);
set.deactivate(first_mock_id);
let first_mock = &set[first_mock_id];
assert_eq!(first_mock.1, MountedMockState::OutOfScope);
let second_mock = &set[second_mock_id];
assert_eq!(second_mock.1, MountedMockState::InScope);
}
}