Domain entities/ports, service stubs, 18 application unit tests with in-memory fakes, API integration tests for /api/auth and /api/users, Vitest specs for the auth store, login page and user form, Playwright auth/user-management flow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
115 lines
3.3 KiB
Rust
115 lines
3.3 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())
|
|
}
|
|
|
|
/// App with a bootstrapped admin `admin@example.com` / `admin-password-123`.
|
|
pub async fn test_app_with_admin() -> Router {
|
|
api::test_support::build_test_app_with_admin("admin@example.com", "admin-password-123").await
|
|
}
|
|
|
|
pub struct Login {
|
|
pub access: String,
|
|
pub user_id: String,
|
|
}
|
|
|
|
pub async fn login(app: &Router, email: &str, password: &str) -> Login {
|
|
let res = post(
|
|
app,
|
|
"/api/auth/login",
|
|
serde_json::json!({"email": email, "password": password}),
|
|
None,
|
|
)
|
|
.await;
|
|
assert_eq!(res.status, StatusCode::OK, "login failed: {}", res.json);
|
|
Login {
|
|
access: res.json["access_token"].as_str().unwrap().to_string(),
|
|
user_id: res.json["user"]["id"].as_str().unwrap().to_string(),
|
|
}
|
|
}
|