WP-00: project skeleton with three-level test harness
Cargo workspace (domain, application, infrastructure, api), axum health endpoint with SPA fallback, Vue 3 + Tailwind frontend, Vitest, Playwright, Makefile, Gitea Actions CI and README. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
89
backend/crates/api/tests/common/mod.rs
Normal file
89
backend/crates/api/tests/common/mod.rs
Normal file
@ -0,0 +1,89 @@
|
||||
//! Shared helpers for API integration tests: in-memory SQLite, request helpers.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::http::{header, Request, StatusCode};
|
||||
use axum::Router;
|
||||
use http_body_util::BodyExt;
|
||||
use tower::ServiceExt;
|
||||
|
||||
pub struct Res {
|
||||
pub status: StatusCode,
|
||||
pub json: serde_json::Value,
|
||||
pub headers: axum::http::HeaderMap,
|
||||
}
|
||||
|
||||
pub async fn test_app() -> Router {
|
||||
api::test_support::build_test_app().await
|
||||
}
|
||||
|
||||
async fn send(app: &Router, req: Request<Body>) -> Res {
|
||||
let res = app.clone().oneshot(req).await.unwrap();
|
||||
let status = res.status();
|
||||
let headers = res.headers().clone();
|
||||
let bytes = res.into_body().collect().await.unwrap().to_bytes();
|
||||
let json = serde_json::from_slice(&bytes).unwrap_or(serde_json::Value::Null);
|
||||
Res {
|
||||
status,
|
||||
json,
|
||||
headers,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_auth(
|
||||
mut b: axum::http::request::Builder,
|
||||
token: Option<&str>,
|
||||
) -> axum::http::request::Builder {
|
||||
if let Some(t) = token {
|
||||
b = b.header(header::AUTHORIZATION, format!("Bearer {t}"));
|
||||
}
|
||||
b
|
||||
}
|
||||
|
||||
pub async fn get(app: &Router, path: &str, token: Option<&str>) -> Res {
|
||||
let req = with_auth(Request::get(path), token)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
send(app, req).await
|
||||
}
|
||||
|
||||
pub async fn send_json(
|
||||
app: &Router,
|
||||
method: &str,
|
||||
path: &str,
|
||||
body: serde_json::Value,
|
||||
token: Option<&str>,
|
||||
) -> Res {
|
||||
let req = with_auth(Request::builder().method(method).uri(path), token)
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.body(Body::from(body.to_string()))
|
||||
.unwrap();
|
||||
send(app, req).await
|
||||
}
|
||||
|
||||
pub async fn post(app: &Router, path: &str, body: serde_json::Value, token: Option<&str>) -> Res {
|
||||
send_json(app, "POST", path, body, token).await
|
||||
}
|
||||
|
||||
pub async fn patch(app: &Router, path: &str, body: serde_json::Value, token: Option<&str>) -> Res {
|
||||
send_json(app, "PATCH", path, body, token).await
|
||||
}
|
||||
|
||||
/// POST with a `Cookie` header (used for refresh/logout which read the refresh cookie).
|
||||
pub async fn post_with_cookie(app: &Router, path: &str, cookie: &str) -> Res {
|
||||
let req = Request::post(path)
|
||||
.header(header::COOKIE, cookie)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
send(app, req).await
|
||||
}
|
||||
|
||||
/// Extract `name=value` of a Set-Cookie header.
|
||||
pub fn cookie_value(res: &Res, name: &str) -> Option<String> {
|
||||
res.headers
|
||||
.get_all(header::SET_COOKIE)
|
||||
.iter()
|
||||
.filter_map(|v| v.to_str().ok())
|
||||
.find(|c| c.starts_with(&format!("{name}=")))
|
||||
.map(|c| c.split(';').next().unwrap().to_string())
|
||||
}
|
||||
Reference in New Issue
Block a user