1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
use std::sync::{atomic::AtomicBool, Arc};
use tokio::sync::Notify;
use crate::{verification::VerificationReport, Match, Mock, Request, ResponseTemplate};
/// Given the behaviour specification as a [`Mock`](crate::Mock), keep track of runtime information
/// concerning this mock - e.g. how many times it matched on a incoming request.
pub(crate) struct MountedMock {
pub(crate) specification: Mock,
n_matched_requests: u64,
/// The position occupied by this mock within the parent [`MountedMockSet`](crate::mock_set::MountedMockSet)
/// collection of `MountedMock`s.
///
/// E.g. `0` if this is the first mock that we try to match against an incoming request, `1`
/// if it is the second, etc.
position_in_set: usize,
// matched requests:
matched_requests: Vec<crate::Request>,
notify: Arc<(Notify, AtomicBool)>,
}
impl MountedMock {
pub(crate) fn new(specification: Mock, position_in_set: usize) -> Self {
Self {
specification,
n_matched_requests: 0,
position_in_set,
matched_requests: Vec::new(),
notify: Arc::new((Notify::new(), AtomicBool::new(false))),
}
}
/// This is NOT the same of `matches` from the `Match` trait!
/// Key difference: we are talking a mutable reference to `self` in order to capture
/// additional information (e.g. how many requests we matched so far) or change behaviour
/// after a certain threshold has been crossed (e.g. start returning `false` for all requests
/// once enough requests have been matched according to `max_n_matches`).
pub(crate) fn matches(&mut self, request: &Request) -> bool {
if Some(self.n_matched_requests) == self.specification.max_n_matches {
// Skip the actual check if we are already at our maximum of matched requests.
false
} else {
let matched = self
.specification
.matchers
.iter()
.all(|matcher| matcher.matches(request));
if matched {
// Increase match count
self.n_matched_requests += 1;
// Keep track of request
self.matched_requests.push(request.clone());
// notification of satisfaction
if self.verify().is_satisfied() {
// always set the satisfaction flag **before** raising the event
self.notify
.1
.store(true, std::sync::atomic::Ordering::Release);
self.notify.0.notify_waiters();
}
}
matched
}
}
/// Verify if this mock has verified the expectations set at creation time
/// over the number of invocations.
pub(crate) fn verify(&self) -> VerificationReport {
VerificationReport {
mock_name: self.specification.name.clone(),
n_matched_requests: self.n_matched_requests,
expectation_range: self.specification.expectation_range.clone(),
position_in_set: self.position_in_set,
}
}
pub(crate) fn response_template(&self, request: &Request) -> ResponseTemplate {
self.specification.response_template(request)
}
pub(crate) fn received_requests(&self) -> Vec<crate::Request> {
self.matched_requests.clone()
}
pub(crate) fn notify(&self) -> Arc<(Notify, AtomicBool)> {
self.notify.clone()
}
}