WP-00: project skeleton with three-level test harness
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / ui (push) Has been cancelled

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:
Dennis Nemec
2026-09-02 21:28:21 +02:00
parent 741c5e35ad
commit 780c84098b
44 changed files with 8419 additions and 14 deletions

View 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())
}