Files
tutortool/backend/src/test_helpers.rs

131 lines
4.9 KiB
Rust

// cfg(test) only — this whole module is test-only
use sqlx::SqlitePool;
use axum::Router;
use tower::ServiceExt;
use axum::http::{Request, StatusCode};
use http_body_util::BodyExt;
/// Insert a test tutor (if not exists), return a valid JWT for that tutor.
pub async fn make_token(pool: &SqlitePool, email: &str, is_superadmin: bool) -> String {
let hash = bcrypt::hash("testpass", 4).unwrap();
sqlx::query("INSERT OR IGNORE INTO tutors (name,email,password_hash,is_superadmin) VALUES (?,?,?,?)")
.bind("Test Tutor").bind(email).bind(&hash).bind(is_superadmin)
.execute(pool).await.unwrap();
// Ensure the superadmin flag is correct even if it existed
sqlx::query("UPDATE tutors SET is_superadmin = ? WHERE email = ?")
.bind(is_superadmin).bind(email).execute(pool).await.unwrap();
let row: (i64, bool) = sqlx::query_as("SELECT id, is_superadmin FROM tutors WHERE email = ?")
.bind(email).fetch_one(pool).await.unwrap();
unsafe { std::env::set_var("JWT_SECRET", "testsecret"); }
crate::auth::encode_jwt(row.0, email, row.1).unwrap()
}
/// Build the full Axum app wired with the given pool, plus a Bearer auth header value.
pub async fn build_test_app(pool: SqlitePool) -> (Router, String) {
unsafe { std::env::set_var("JWT_SECRET", "testsecret"); }
let token = make_token(&pool, "tutor@test.com", false).await;
let app = crate::routes::build(pool);
(app, format!("Bearer {token}"))
}
/// Build the full Axum app wired with a superadmin Bearer auth header.
pub async fn build_test_admin_app(pool: SqlitePool) -> (Router, String) {
unsafe { std::env::set_var("JWT_SECRET", "testsecret"); }
let token = make_token(&pool, "admin@test.com", true).await;
let app = crate::routes::build(pool);
(app, format!("Bearer {token}"))
}
/// POST JSON body to the app (one-shot), returns (StatusCode, response body bytes).
pub async fn post_json(app: Router, path: &str, auth: &str, body: serde_json::Value)
-> (StatusCode, bytes::Bytes)
{
let mut builder = Request::builder()
.method("POST").uri(path)
.header("Content-Type", "application/json");
if !auth.is_empty() {
builder = builder.header("Authorization", auth);
}
let req = builder
.body(axum::body::Body::from(body.to_string()))
.unwrap();
let res = app.oneshot(req).await.unwrap();
let status = res.status();
let body = res.into_body().collect().await.unwrap().to_bytes();
(status, body)
}
/// PUT JSON body to the app (one-shot), returns (StatusCode, response body bytes).
pub async fn put_json(app: Router, uri: &str, auth: &str, body: serde_json::Value)
-> (StatusCode, bytes::Bytes)
{
let mut req = Request::builder()
.method("PUT")
.uri(uri)
.header("Content-Type", "application/json");
if !auth.is_empty() {
req = req.header("Authorization", auth);
}
let req = req
.body(axum::body::Body::from(body.to_string()))
.unwrap();
let res = app.oneshot(req).await.unwrap();
let status = res.status();
let body = res.into_body().collect().await.unwrap().to_bytes();
(status, body)
}
/// PATCH JSON body to the app (one-shot), returns (StatusCode, response body bytes).
pub async fn patch_json(
app: Router,
uri: &str,
auth: &str,
body: serde_json::Value,
) -> (StatusCode, bytes::Bytes) {
let mut req = Request::builder()
.method("PATCH")
.uri(uri)
.header("Content-Type", "application/json");
if !auth.is_empty() {
req = req.header("Authorization", auth);
}
let req = req
.body(axum::body::Body::from(body.to_string()))
.unwrap();
let res = app.oneshot(req).await.unwrap();
let status = res.status();
let body = res.into_body().collect().await.unwrap().to_bytes();
(status, body)
}
/// GET from the app (one-shot), returns (StatusCode, response body bytes).
pub async fn get(app: Router, path: &str, auth: &str) -> (StatusCode, bytes::Bytes) {
let mut builder = Request::builder()
.method("GET").uri(path);
if !auth.is_empty() {
builder = builder.header("Authorization", auth);
}
let req = builder
.body(axum::body::Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
let status = res.status();
let body = res.into_body().collect().await.unwrap().to_bytes();
(status, body)
}
/// DELETE from the app (one-shot), returns (StatusCode, response body bytes).
pub async fn delete(app: Router, path: &str, auth: &str) -> (StatusCode, bytes::Bytes) {
let mut builder = Request::builder().method("DELETE").uri(path);
if !auth.is_empty() {
builder = builder.header("Authorization", auth);
}
let req = builder.body(axum::body::Body::empty()).unwrap();
let res = app.oneshot(req).await.unwrap();
let status = res.status();
let body = res.into_body().collect().await.unwrap().to_bytes();
(status, body)
}