Files
Mangalord/backend/tests/api_admin_overview.rs
fabi cde4aca98b
All checks were successful
deploy / test-backend (push) Successful in 25m9s
deploy / test-frontend (push) Successful in 10m13s
deploy / build-and-push (push) Successful in 10m25s
deploy / deploy (push) Successful in 12s
feat(admin): overview dashboard sections + system temperature/load sensors (0.85.0) (#11)
2026-06-16 18:46:28 +00:00

126 lines
4.2 KiB
Rust

//! Integration tests for the admin overview aggregates endpoint
//! (`GET /api/v1/admin/overview`). Auth gating mirrors the system tab;
//! the aggregate counts are asserted against seeded data.
mod common;
use axum::http::StatusCode;
use axum::Router;
use serde_json::json;
use sqlx::PgPool;
use tower::ServiceExt;
use mangalord::repo;
async fn seed_admin(pool: &PgPool, app: &Router) -> String {
let (username, cookie) = common::register_user(app).await;
let u = repo::user::find_by_username(pool, &username)
.await
.unwrap()
.unwrap();
repo::user::set_is_admin_unchecked(pool, u.id, true).await.unwrap();
cookie
}
/// Seed a manga → chapter with `n` pages; mark the first `analyzed` of
/// them as a `done` page_analysis. Returns the page ids.
async fn seed_manga_pages(pool: &PgPool, title: &str, n: i32, analyzed: i32) -> Vec<uuid::Uuid> {
let manga_id: uuid::Uuid =
sqlx::query_scalar("INSERT INTO mangas (title) VALUES ($1) RETURNING id")
.bind(title)
.fetch_one(pool)
.await
.unwrap();
let chapter_id: uuid::Uuid =
sqlx::query_scalar("INSERT INTO chapters (manga_id, number) VALUES ($1, 1) RETURNING id")
.bind(manga_id)
.fetch_one(pool)
.await
.unwrap();
let mut ids = Vec::new();
for i in 1..=n {
let id: uuid::Uuid = sqlx::query_scalar(
"INSERT INTO pages (chapter_id, page_number, storage_key, content_type) \
VALUES ($1, $2, $3, 'image/png') RETURNING id",
)
.bind(chapter_id)
.bind(i)
.bind(format!("k/{manga_id}/{i}.png"))
.fetch_one(pool)
.await
.unwrap();
if i <= analyzed {
sqlx::query("INSERT INTO page_analysis (page_id, status) VALUES ($1, 'done')")
.bind(id)
.execute(pool)
.await
.unwrap();
}
ids.push(id);
}
ids
}
#[sqlx::test(migrations = "./migrations")]
async fn requires_admin(pool: PgPool) {
let h = common::harness(pool);
let (_u, cookie) = common::register_user(&h.app).await;
let resp = h
.app
.oneshot(common::get_with_cookie("/api/v1/admin/overview", &cookie))
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::FORBIDDEN);
}
#[sqlx::test(migrations = "./migrations")]
async fn unauthenticated_request_is_rejected(pool: PgPool) {
let h = common::harness(pool);
let resp = h
.app
.oneshot(common::get("/api/v1/admin/overview"))
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
#[sqlx::test(migrations = "./migrations")]
async fn aggregates_users_mangas_and_coverage(pool: PgPool) {
let h = common::harness(pool.clone());
// seed_admin registers one user and promotes it; register a second
// (non-admin) user so the counts have a known split.
let cookie = seed_admin(&pool, &h.app).await;
let _ = common::register_user(&h.app).await;
// Two mangas: 3 pages with 2 analyzed, and 2 pages with 0 analyzed.
seed_manga_pages(&pool, "Alpha", 3, 2).await;
seed_manga_pages(&pool, "Beta", 2, 0).await;
let resp = h
.app
.oneshot(common::get_with_cookie("/api/v1/admin/overview", &cookie))
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let body = common::body_json(resp).await;
let users = body.get("users").expect("users key");
assert_eq!(users["total"], json!(2));
assert_eq!(users["admins"], json!(1));
assert!(users["newest_username"].is_string());
let mangas = body.get("mangas").expect("mangas key");
assert_eq!(mangas["total"], json!(2));
// Uploaded mangas (no manga_sources) classify as synced.
assert_eq!(mangas["synced"], json!(2));
assert_eq!(mangas["in_progress"], json!(0));
assert_eq!(mangas["dropped"], json!(0));
assert_eq!(mangas["total_chapters"], json!(2));
assert_eq!(mangas["total_pages"], json!(5));
assert!(mangas["newest_title"].is_string());
let analysis = body.get("analysis").expect("analysis key");
assert_eq!(analysis["total_pages"], json!(5));
assert_eq!(analysis["analyzed_pages"], json!(2));
}