use std::cell::{Cell, RefCell};
use std::rc::Rc;
use js_sys::Promise;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::prelude::*;
use reinhardt_pages::server_fn::{MockableServerFn, ServerFnError};
use super::context::TestContext;
use super::handler::RestHandler;
use super::handler::{ErasedHandler, ServerFnContextHandler, ServerFnHandler};
use super::interceptor;
use super::matcher::UrlMatcher;
use super::recorder::{CallQuery, RecordedRequest, RequestRecorder, ServerFnCallQuery};
struct ActiveInterceptor {
owner_id: u64,
original_fetch: JsValue,
_closure: Closure<dyn FnMut(JsValue, JsValue) -> Promise>,
}
thread_local! {
static NEXT_WORKER_ID: Cell<u64> = const { Cell::new(1) };
static ACTIVE_INTERCEPTOR: RefCell<Option<ActiveInterceptor>> = const { RefCell::new(None) };
}
fn next_worker_id() -> u64 {
NEXT_WORKER_ID.with(|next| {
let id = next.get();
next.set(id.wrapping_add(1).max(1));
id
})
}
fn restore_active_interceptor() {
ACTIVE_INTERCEPTOR.with(|active| {
if let Some(active) = active.borrow_mut().take() {
interceptor::restore_fetch(&active.original_fetch);
}
});
}
fn restore_interceptor_if_owned(owner_id: u64) {
ACTIVE_INTERCEPTOR.with(|active| {
let should_restore = active
.borrow()
.as_ref()
.is_some_and(|active| active.owner_id == owner_id);
if should_restore && let Some(active) = active.borrow_mut().take() {
interceptor::restore_fetch(&active.original_fetch);
}
});
}
#[derive(Debug, Clone)]
pub enum UnhandledPolicy {
Error,
Passthrough,
Warn,
}
impl From<&UnhandledPolicy> for interceptor::UnhandledPolicy {
fn from(p: &UnhandledPolicy) -> Self {
match p {
UnhandledPolicy::Error => interceptor::UnhandledPolicy::Error,
UnhandledPolicy::Passthrough => interceptor::UnhandledPolicy::Passthrough,
UnhandledPolicy::Warn => interceptor::UnhandledPolicy::Warn,
}
}
}
pub struct MockServiceWorker {
worker_id: u64,
handlers: Rc<RefCell<Vec<Box<dyn ErasedHandler>>>>,
recorder: Rc<RefCell<RequestRecorder>>,
unhandled_policy: UnhandledPolicy,
active: Cell<bool>,
}
impl MockServiceWorker {
pub fn new() -> Self {
Self::with_policy(UnhandledPolicy::Error)
}
pub fn with_policy(policy: UnhandledPolicy) -> Self {
Self {
worker_id: next_worker_id(),
handlers: Rc::new(RefCell::new(Vec::new())),
recorder: Rc::new(RefCell::new(RequestRecorder::new())),
unhandled_policy: policy,
active: Cell::new(false),
}
}
pub async fn start(&self) {
assert!(
!self.active.get(),
"MockServiceWorker: already started. Call stop() before starting again."
);
restore_active_interceptor();
let original = interceptor::save_original_fetch();
let closure = interceptor::install_fetch_override(
self.handlers.clone(),
self.recorder.clone(),
(&self.unhandled_policy).into(),
original.clone(),
);
ACTIVE_INTERCEPTOR.with(|active| {
*active.borrow_mut() = Some(ActiveInterceptor {
owner_id: self.worker_id,
original_fetch: original,
_closure: closure,
});
});
self.active.set(true);
}
pub async fn stop(&self) {
if self.active.get() {
restore_interceptor_if_owned(self.worker_id);
self.active.set(false);
}
}
pub fn reset(&self) {
self.handlers.borrow_mut().clear();
self.recorder.borrow_mut().clear();
}
pub fn reset_handlers(&self) {
self.handlers.borrow_mut().clear();
}
pub fn handle(&self, handler: RestHandler) {
self.handlers.borrow_mut().push(Box::new(handler));
}
pub fn handle_server_fn<S: MockableServerFn>(
&self,
handler: impl Fn(S::Args) -> Result<S::Response, ServerFnError> + 'static,
) {
self.handlers
.borrow_mut()
.push(Box::new(ServerFnHandler::<S>::new(
Box::new(handler),
false,
None,
)));
}
pub fn handle_server_fn_with_context<S: MockableServerFn>(
&self,
context: TestContext,
handler: impl Fn(S::Args, &TestContext) -> Result<S::Response, ServerFnError> + 'static,
) {
self.handlers
.borrow_mut()
.push(Box::new(ServerFnContextHandler::<S>::new(
context,
Box::new(handler),
false,
None,
)));
}
pub fn calls_to(&self, pattern: impl Into<UrlMatcher>) -> CallQuery<'_> {
CallQuery::new(&self.recorder, pattern)
}
pub fn calls_to_server_fn<S: MockableServerFn>(&self) -> ServerFnCallQuery<'_, S> {
ServerFnCallQuery {
inner: CallQuery::new(&self.recorder, S::PATH),
_marker: std::marker::PhantomData,
}
}
pub fn all_calls(&self) -> Vec<RecordedRequest> {
self.recorder.borrow().all().to_vec()
}
}
impl Drop for MockServiceWorker {
fn drop(&mut self) {
if self.active.get() {
restore_interceptor_if_owned(self.worker_id);
}
}
}