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>
90 lines
2.5 KiB
Rust
90 lines
2.5 KiB
Rust
//! 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())
|
|
}
|