crates/monetize-server/src/callback/tests.rs
//! The webhook's bounds (review #13) and its status words (review #14), called
//! through [`super::handle`] itself so the extractor-level answer is what is held.
use std::sync::Arc;
use axum::body::Bytes;
use axum::extract::{Path, State};
use axum::http::{HeaderMap, HeaderName, HeaderValue, StatusCode};
use super::*;
use crate::registry::Registry;
use crate::testing::CallbackPay;
const SECRET: &str = "mockpay-shared-secret";
fn rig() -> (CallbackPay, Arc<AppState>) {
let pay = CallbackPay::new(SECRET);
let state = Arc::new(AppState::in_memory(Registry::new().vendor(pay.clone())));
(pay, state)
}
fn headers(pairs: &[(String, String)]) -> HeaderMap {
pairs.iter().map(|(k, v)| (HeaderName::try_from(k.as_str()).unwrap(), HeaderValue::try_from(v.as_str()).unwrap())).collect()
}
async fn post(state: &Arc<AppState>, headers: HeaderMap, body: Vec<u8>) -> (StatusCode, String) {
let r = handle(State(Arc::clone(state)), Path("mockpay".into()), headers, Bytes::from(body)).await;
let code = r.status();
let body = axum::body::to_bytes(r.into_body(), usize::MAX).await.unwrap();
(code, String::from_utf8_lossy(&body).into_owned())
}
/// ★ #13: every delivery instantiates the vendor's parser before anything is
/// verified. With every slot taken, a stranger's delivery is answered 503 at
/// once and the parser is never run for it — no refusal row says it was read.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn with_every_slot_taken_a_delivery_is_answered_503_and_never_parsed() {
let (_pay, state) = rig();
let held: Vec<Slot> = std::iter::from_fn(|| state.callback_slots.take()).take(CALLBACK_SLOTS + 1).collect();
assert_eq!(held.len(), CALLBACK_SLOTS, "the pool is bounded");
let (code, _) = post(&state, HeaderMap::new(), CallbackPay::callback_body("gunnar/bob/1", "res-1", "captured")).await;
assert_eq!(code, StatusCode::SERVICE_UNAVAILABLE, "a full pool answers 503 so the vendor retries");
assert_eq!(state.refusals.stats().total, 0, "the vendor's parser was never run for it");
drop(held);
let (code, _) = post(&state, HeaderMap::new(), CallbackPay::callback_body("gunnar/bob/1", "res-1", "captured")).await;
assert_eq!(code, StatusCode::UNAUTHORIZED, "a slot given back is a delivery parsed again");
}
/// ★ #13: a body no vendor sends is refused before the parser is instantiated.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_body_larger_than_any_event_is_refused_before_it_is_parsed() {
let (pay, state) = rig();
let (code, _) = post(&state, headers(&pay.signed_headers()), vec![b' '; MAX_CALLBACK_BYTES + 1]).await;
assert_eq!(code, StatusCode::PAYLOAD_TOO_LARGE);
assert_eq!(state.refusals.stats().total, 0, "the vendor's parser was never run for it");
}
/// ★ #14: a verified delivery the vendor could not be asked about is OUR
/// outage, not a bad payload: 503 so the vendor retries it, no
/// "not understood" row, and none of the parser's words in the body.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_vendor_that_could_not_be_asked_is_503_not_400() {
let (pay, state) = rig();
let (code, body) = post(&state, headers(&pay.signed_headers()), b"{\"thin\":\"evt_1\"}".to_vec()).await;
assert_eq!(code, StatusCode::SERVICE_UNAVAILABLE, "{body}");
assert_eq!(state.refusals.stats().total, 0, "an outage of ours is not a refused delivery");
assert!(!body.contains("mockpay:"), "the body leaks the parser's own words: {body}");
}
/// ★ #14: a 500 says that something failed here, never what: the store's
/// error text is for the journal, not for whoever posted.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_store_error_is_a_500_whose_body_says_nothing_internal() {
let (pay, state) = rig();
let signup = crate::signup::tests::a_signup("cs_broken", "alice");
state.signups.break_row(&signup.vendor, &signup.session);
let (code, body) = post(&state, headers(&pay.signed_headers()), CallbackPay::signup_body(&signup)).await;
assert_eq!(code, StatusCode::INTERNAL_SERVER_ERROR, "{body}");
for word in ["signup store", "parse", "cs_broken", "Corrupted"] {
assert!(!body.contains(word), "the body leaks {word:?}: {body}");
}
}