125 lines
4.3 KiB
Rust
125 lines
4.3 KiB
Rust
//! PR 4 (feat/admin-system-api) integration tests.
|
|
//!
|
|
//! Shape-only assertions — we don't mock the system, just call the
|
|
//! endpoint and check the response envelope. Threshold-triggering of
|
|
//! alerts would require faking statvfs / sysinfo, which is more
|
|
//! plumbing than the test gives back.
|
|
|
|
mod common;
|
|
|
|
use axum::http::StatusCode;
|
|
use axum::Router;
|
|
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
|
|
}
|
|
|
|
#[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/system", &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/system"))
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
|
|
}
|
|
|
|
#[sqlx::test(migrations = "./migrations")]
|
|
async fn returns_disk_memory_cpu_alerts_shape(pool: PgPool) {
|
|
let h = common::harness(pool.clone());
|
|
let cookie = seed_admin(&pool, &h.app).await;
|
|
|
|
let resp = h
|
|
.app
|
|
.oneshot(common::get_with_cookie("/api/v1/admin/system", &cookie))
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
|
let body = common::body_json(resp).await;
|
|
|
|
// Disk: harness uses LocalStorage on a tempdir, so disk SHOULD be
|
|
// populated. Validate the field shape and percent range.
|
|
let disk = body
|
|
.get("disk")
|
|
.expect("disk key present")
|
|
.as_object()
|
|
.expect("disk is an object (LocalStorage exposes a path)");
|
|
assert!(disk["total_bytes"].as_u64().unwrap() > 0);
|
|
let pct = disk["percent_used"].as_f64().unwrap();
|
|
assert!(
|
|
(0.0..=100.0).contains(&pct),
|
|
"percent_used outside [0,100]: {pct}"
|
|
);
|
|
|
|
let mem = body.get("memory").expect("memory key").as_object().unwrap();
|
|
assert!(mem["total_bytes"].as_u64().unwrap() > 0);
|
|
let mpct = mem["percent_used"].as_f64().unwrap();
|
|
assert!((0.0..=100.0).contains(&mpct));
|
|
|
|
let cpu = body.get("cpu").expect("cpu key").as_object().unwrap();
|
|
let cpu_pct = cpu["percent_used"].as_f64().unwrap();
|
|
assert!(
|
|
(0.0..=100.0).contains(&cpu_pct),
|
|
"cpu out of range: {cpu_pct}"
|
|
);
|
|
|
|
// Load average: three numeric keys. Values can legitimately be 0 on
|
|
// an idle box, so only assert presence + non-negativity.
|
|
let load = cpu["load_avg"].as_object().expect("cpu.load_avg object");
|
|
for k in ["one", "five", "fifteen"] {
|
|
let v = load[k].as_f64().unwrap_or_else(|| panic!("load_avg.{k} numeric"));
|
|
assert!(v >= 0.0, "load_avg.{k} negative: {v}");
|
|
}
|
|
|
|
// Per-core usage: an array of percentages in [0,100].
|
|
let cores = cpu["per_core"].as_array().expect("cpu.per_core array");
|
|
assert!(!cores.is_empty(), "expected at least one core");
|
|
for c in cores {
|
|
let p = c.as_f64().unwrap();
|
|
assert!((0.0..=100.0).contains(&p), "per_core out of range: {p}");
|
|
}
|
|
|
|
// Temperatures: an array (possibly empty in CI / containers without
|
|
// /sys sensors). Each present entry has a string label + numeric °C.
|
|
let temps = body
|
|
.get("temperatures")
|
|
.expect("temperatures key")
|
|
.as_array()
|
|
.expect("temperatures is an array");
|
|
for t in temps {
|
|
assert!(t["label"].is_string(), "temp label is string");
|
|
assert!(t["celsius"].as_f64().is_some(), "temp celsius is numeric");
|
|
}
|
|
|
|
let alerts = body.get("alerts").expect("alerts key").as_array().unwrap();
|
|
// Don't assert on length — the box may genuinely be >90% on memory
|
|
// when the test runs. Just confirm shape of any present entry.
|
|
for alert in alerts {
|
|
assert!(alert["level"].is_string());
|
|
assert!(alert["message"].is_string());
|
|
}
|
|
}
|