use std::collections::HashMap;
use super::matcher::UrlMatcher;
use super::state::RecorderHandle;
#[derive(Debug, Clone)]
pub struct RecordedRequest {
pub url: String,
pub method: String,
pub headers: HashMap<String, String>,
pub body: Option<String>,
pub timestamp: f64,
}
pub(crate) struct RequestRecorder {
calls: Vec<RecordedRequest>,
}
impl RequestRecorder {
pub(crate) fn new() -> Self {
Self { calls: Vec::new() }
}
pub(crate) fn record(&mut self, request: RecordedRequest) {
self.calls.push(request);
}
pub(crate) fn all(&self) -> &[RecordedRequest] {
&self.calls
}
pub(crate) fn clear(&mut self) {
self.calls.clear();
}
}
pub struct CallQuery {
recorder: RecorderHandle,
matcher: UrlMatcher,
}
impl CallQuery {
pub(crate) fn new(recorder: &RecorderHandle, pattern: impl Into<UrlMatcher>) -> Self {
Self {
recorder: recorder.clone(),
matcher: pattern.into(),
}
}
fn filtered(&self) -> Vec<RecordedRequest> {
self.recorder
.all()
.into_iter()
.filter(|r| self.matcher.matches(&r.url))
.collect()
}
pub fn count(&self) -> usize {
self.filtered().len()
}
pub fn first(&self) -> Option<RecordedRequest> {
self.filtered().into_iter().next()
}
pub fn last(&self) -> Option<RecordedRequest> {
self.filtered().into_iter().last()
}
pub fn all(&self) -> Vec<RecordedRequest> {
self.filtered()
}
pub fn nth(&self, n: usize) -> Option<RecordedRequest> {
self.filtered().into_iter().nth(n)
}
pub fn assert_called(&self) {
assert!(
self.count() > 0,
"Expected at least one call matching {:?}, but found none",
self.matcher
);
}
pub fn assert_not_called(&self) {
let count = self.count();
assert!(
count == 0,
"Expected no calls matching {:?}, but found {}",
self.matcher,
count
);
}
pub fn assert_count(&self, expected: usize) {
let actual = self.count();
assert_eq!(
actual, expected,
"Expected {} calls matching {:?}, but found {}",
expected, self.matcher, actual
);
}
}
pub struct ServerFnCallQuery<S> {
pub(crate) inner: CallQuery,
pub(crate) _marker: std::marker::PhantomData<S>,
}
use reinhardt_pages::server_fn::MockableServerFn;
impl<S: MockableServerFn> ServerFnCallQuery<S> {
pub fn count(&self) -> usize {
self.inner.count()
}
pub fn assert_called(&self) {
self.inner.assert_called();
}
pub fn assert_not_called(&self) {
self.inner.assert_not_called();
}
pub fn assert_count(&self, expected: usize) {
self.inner.assert_count(expected);
}
pub fn last_args(&self) -> Option<S::Args> {
let last = self.inner.last()?;
let body = last.body.as_deref().unwrap_or("{}");
serde_json::from_str(body).ok()
}
pub fn assert_called_with(&self, expected: &S::Args)
where
S::Args: PartialEq + std::fmt::Debug,
{
let actual = self
.last_args()
.expect("assert_called_with: no calls recorded");
assert_eq!(actual, *expected, "assert_called_with: args do not match");
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::*;
fn make_request(url: &str, method: &str) -> RecordedRequest {
RecordedRequest {
url: url.to_string(),
method: method.to_string(),
headers: HashMap::new(),
body: None,
timestamp: 0.0,
}
}
#[rstest]
fn recorder_records_and_counts() {
let recorder = RecorderHandle::new();
recorder.record(make_request("/api/users", "GET"));
recorder.record(make_request("/api/users", "POST"));
recorder.record(make_request("/api/posts", "GET"));
let query = CallQuery::new(&recorder, "/api/users");
assert_eq!(query.count(), 2);
}
#[rstest]
fn call_query_first_and_last() {
let recorder = RecorderHandle::new();
recorder.record(make_request("/api/a", "GET"));
recorder.record(make_request("/api/a", "POST"));
let query = CallQuery::new(&recorder, "/api/a");
assert_eq!(query.first().unwrap().method, "GET");
assert_eq!(query.last().unwrap().method, "POST");
}
#[rstest]
fn assert_called_succeeds() {
let recorder = RecorderHandle::new();
recorder.record(make_request("/api/x", "GET"));
CallQuery::new(&recorder, "/api/x").assert_called();
}
#[rstest]
#[should_panic(expected = "Expected at least one call")]
fn assert_called_fails() {
let recorder = RecorderHandle::new();
CallQuery::new(&recorder, "/api/x").assert_called();
}
#[rstest]
fn assert_not_called_succeeds() {
let recorder = RecorderHandle::new();
CallQuery::new(&recorder, "/api/x").assert_not_called();
}
#[rstest]
fn assert_count_succeeds() {
let recorder = RecorderHandle::new();
recorder.record(make_request("/api/x", "GET"));
recorder.record(make_request("/api/x", "GET"));
CallQuery::new(&recorder, "/api/x").assert_count(2);
}
#[rstest]
fn recorder_clear() {
let recorder = RecorderHandle::new();
recorder.record(make_request("/api/x", "GET"));
recorder.clear();
assert!(recorder.all().is_empty());
}
}