//! WP-40: GET /api/dashboard aggregates the state of all areas. mod common; use axum::http::StatusCode; use common::{get, post, test_app_with_admin}; use serde_json::json; const ADMIN: &str = "admin@example.com"; const PW: &str = "admin-password-123"; async fn wait(app: &axum::Router, token: &str, id: &str) { for _ in 0..100 { let r = get(app, &format!("/api/jobs/{id}"), Some(token)).await; if r.json["status"] != "running" { return; } tokio::time::sleep(std::time::Duration::from_millis(30)).await; } } #[tokio::test] async fn dashboard_reflects_inventory_vulnerabilities_cluster_backups_and_jobs() { let app = test_app_with_admin().await; let token = common::login(&app, ADMIN, PW).await.access; let d = get(&app, "/api/dashboard", Some(&token)).await; assert_eq!(d.status, StatusCode::OK, "{}", d.json); assert!(d.json["inventory"]["refreshed_at"].is_null()); assert_eq!(d.json["vulnerabilities"]["total"]["critical"], 0); assert_eq!(d.json["cluster"]["reachable"], true); assert_eq!(d.json["cluster"]["workloads"], 5); assert_eq!(d.json["cluster"]["unhealthy"], 0); assert_eq!(d.json["backups"].as_array().unwrap().len(), 0); assert_eq!(d.json["recent_jobs"].as_array().unwrap().len(), 0); for kind in ["package_refresh", "vulnerability_scan"] { let run = post(&app, "/api/jobs/run", json!({"kind": kind}), Some(&token)).await; wait(&app, &token, run.json["id"].as_str().unwrap()).await; } let t = post(&app, "/api/backups/targets", json!({"name": "NAS", "kind": "smb", "host": "nas", "share": "b", "username": "u", "password": "p"}), Some(&token)).await; let s = post(&app, "/api/backups/strategies", json!({"name": "DB", "source": {"type": "host_path", "path": "/tmp"}, "schedule": "0 0 2 * * *", "target_id": t.json["id"], "retention": 2}), Some(&token)).await; assert_eq!(s.status, StatusCode::CREATED, "{}", s.json); let d = get(&app, "/api/dashboard", Some(&token)).await; assert!(d.json["inventory"]["refreshed_at"].is_string()); assert_eq!( d.json["inventory"]["os"]["name"], "Debian GNU/Linux 12 (bookworm)" ); assert_eq!(d.json["inventory"]["upgradable"], 3); assert_eq!(d.json["inventory"]["security"], 2); assert_eq!(d.json["inventory"]["reboot_required"], true); assert!( d.json["vulnerabilities"]["total"]["critical"] .as_u64() .unwrap() >= 2 ); assert!(d.json["vulnerabilities"]["last_scan"].is_string()); let b = &d.json["backups"][0]; assert_eq!(b["name"], "DB"); assert!(b["last_backup"].is_null()); assert_eq!(b["enabled"], true); let jobs = d.json["recent_jobs"].as_array().unwrap(); assert_eq!(jobs.len(), 2); assert_eq!(jobs[0]["kind"], "vulnerability_scan", "newest first"); assert!( jobs[0].get("log").is_none(), "no logs in the dashboard payload" ); assert_eq!(d.json["failed_jobs_24h"], 0); assert_eq!( get(&app, "/api/dashboard", None).await.status, StatusCode::UNAUTHORIZED ); }