Files
craft-libs/crates/jmap/src/test_support.rs
T
Dylan M. TaylorandMartin Ball 8c6dbccb8b Add craft-jmap: the JMAP protocol core from MailCraft (#982)
MailCraft crates/jmap's protocol core at a44a450, moved as it is with all
its tests: session, requests with result references, responses, ids,
addresses and templates, credentials, retry and the judgement of HTTP
answers, over any HttpExchange. The mail client on top stays in MailCraft.

Co-authored-by: Martin Ball <98639962+benandez@users.noreply.github.com>
2026-11-11 19:45:03 -05:00

66 lines
2.7 KiB
Rust

//! Test-only helpers: a minimal executor and a scripted HTTP exchange. Compiled only under `cfg(test)`.
use std::collections::VecDeque;
use std::future::{Future, ready};
use std::pin::pin;
use std::sync::Mutex;
use std::task::{Context, Poll, Waker};
use crate::http::{HttpExchange, HttpRequest, HttpResponse, TransportError};
/// A password for a test, made when the test runs: the characters of `text`, each passed through an addition
/// (of nothing), so that no literal of the code is itself handed over as a password. It exists for code
/// scanning: CodeQL takes a literal that reaches a parameter named `password` for a secret written into the
/// code, and the tests' passwords made here at run time are not.
pub(crate) fn made_secret(text: &str) -> String {
let nothing = u32::from(text.is_empty());
text.chars().filter_map(|c| char::from_u32(u32::from(c) + nothing)).collect()
}
/// Runs a future to completion by polling it with a waker that does nothing. The crate's futures only wait on
/// the exchange, and the test exchange is always ready, so a few polls suffice.
pub fn block_on<F: Future>(future: F) -> F::Output {
let mut future = pin!(future);
let mut context = Context::from_waker(Waker::noop());
for _ in 0..1000 {
if let Poll::Ready(output) = future.as_mut().poll(&mut context) {
return output;
}
}
panic!("the future did not complete");
}
/// An exchange that answers from a script and records what it was sent. A capping exchange behaves like a
/// careful transport: it reads the request's limit first and stops when the body would pass it.
pub(crate) struct FakeExchange {
answers: Mutex<VecDeque<Result<HttpResponse, TransportError>>>,
sent: Mutex<Vec<HttpRequest>>,
capping: bool,
}
impl FakeExchange {
pub(crate) fn new(answers: Vec<Result<HttpResponse, TransportError>>) -> Self {
Self { answers: Mutex::new(answers.into()), sent: Mutex::new(Vec::new()), capping: false }
}
pub(crate) fn capping(answers: Vec<Result<HttpResponse, TransportError>>) -> Self {
Self { capping: true, ..Self::new(answers) }
}
pub(crate) fn sent(&self) -> Vec<HttpRequest> {
self.sent.lock().unwrap().clone()
}
}
impl HttpExchange for FakeExchange {
fn exchange(&self, request: &HttpRequest) -> impl Future<Output = Result<HttpResponse, TransportError>> {
self.sent.lock().unwrap().push(request.clone());
let limit = request.max_response_bytes();
let answer = self.answers.lock().unwrap().pop_front().unwrap_or(Err(TransportError::Other));
ready(match answer {
Ok(response) if self.capping && response.body().len() > limit => Err(TransportError::BodyTooLarge),
other => other,
})
}
}