mod common; use axum::http::StatusCode; use common::*; // ═══════════════════════════════════════════════════════════════════ // Account listing // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn list_accounts_forbidden_for_regular_user() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "useracct").await; let (status, _) = send(&app, get("/api/v1/accounts", &token)).await; assert_eq!(status, StatusCode::FORBIDDEN); } #[tokio::test] async fn list_accounts_success_as_admin() { let (app, pool) = build_test_app().await; let admin = admin_token(&app, &pool, "adminacct").await; let (status, body) = send(&app, get("/api/v1/accounts", &admin)).await; assert_eq!(status, StatusCode::OK); // Should include at least the admin + the auto-seeded testadmin assert!(body["items"].is_array() || body.is_array()); } #[tokio::test] async fn get_own_account() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "ownacct").await; // First get own account info from /me let (status, me) = send(&app, get("/api/v1/auth/me", &token)).await; assert_eq!(status, StatusCode::OK, "get /me: {me}"); let account_id = me["id"].as_str().unwrap(); eprintln!("DEBUG account_id = {account_id}"); let url = format!("/api/v1/accounts/{}", account_id); eprintln!("DEBUG url = {url}"); let (status, body) = send(&app, get(&url, &token)).await; eprintln!("DEBUG status = {status}, body = {body}"); assert_eq!(status, StatusCode::OK, "get own account: {body}"); assert_eq!(body["username"], "ownacct"); } #[tokio::test] async fn update_own_account_display_name() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "updateacct").await; // Get account ID from /me let (_, me) = send(&app, get("/api/v1/auth/me", &token)).await; let account_id = me["id"].as_str().unwrap(); let (status, body) = send( &app, patch( &format!("/api/v1/accounts/{account_id}"), &token, serde_json::json!({ "display_name": "New Display Name" }), ), ).await; assert_eq!(status, StatusCode::OK, "update account: {body}"); assert_eq!(body["display_name"], "New Display Name"); } // ═══════════════════════════════════════════════════════════════════ // API Token lifecycle // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn api_token_create_list_revoke() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "tokenuser").await; // Create let (status, body) = send( &app, post( "/api/v1/tokens", &token, serde_json::json!({ "name": "test-token", "permissions": ["model:read", "relay:use"] }), ), ).await; assert_eq!(status, StatusCode::OK, "create token: {body}"); let raw_token = body["token"].as_str().unwrap(); assert!(raw_token.starts_with("zclaw_")); let token_id = body["id"].as_str().unwrap(); // List (paginated response: {items, total, page, page_size}) let (status, list) = send(&app, get("/api/v1/tokens", &token)).await; assert_eq!(status, StatusCode::OK, "list tokens: {list}"); assert!(list["items"].is_array(), "tokens list should have items field: {list}"); assert_eq!(list["items"].as_array().unwrap().len(), 1); // Use the API token to authenticate let (status, _) = send(&app, get("/api/v1/auth/me", raw_token)).await; assert_eq!(status, StatusCode::OK); // Revoke let (status, _) = send(&app, delete(&format!("/api/v1/tokens/{token_id}"), &token)).await; assert_eq!(status, StatusCode::OK); // After revoke, API token no longer works let (status, _) = send(&app, get("/api/v1/auth/me", raw_token)).await; assert_eq!(status, StatusCode::UNAUTHORIZED); } // ═══════════════════════════════════════════════════════════════════ // Device registration // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn device_register_and_list() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "deviceuser").await; let (status, _) = send( &app, post( "/api/v1/devices/register", &token, serde_json::json!({ "device_id": "test-device-001", "device_name": "Test Desktop", "platform": "windows", "app_version": "0.1.0" }), ), ).await; assert_eq!(status, StatusCode::OK); let (status, body) = send(&app, get("/api/v1/devices", &token)).await; assert_eq!(status, StatusCode::OK, "list devices: {body}"); let devices = body["items"].as_array().expect("devices should be paginated {items}"); assert_eq!(devices.len(), 1); assert_eq!(devices[0]["device_id"], "test-device-001"); } #[tokio::test] async fn device_upsert_on_reregister() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "upsertdev").await; send(&app, post("/api/v1/devices/register", &token, serde_json::json!({ "device_id": "dev-upsert", "device_name": "Old Name" }))).await; send(&app, post("/api/v1/devices/register", &token, serde_json::json!({ "device_id": "dev-upsert", "device_name": "New Name" }))).await; let (_, body) = send(&app, get("/api/v1/devices", &token)).await; let devs = body["items"].as_array().expect("devices should be paginated {items}"); assert_eq!(devs.len(), 1); assert_eq!(devs[0]["device_name"], "New Name"); } #[tokio::test] async fn device_heartbeat() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "hbuser").await; // Register first send(&app, post("/api/v1/devices/register", &token, serde_json::json!({ "device_id": "hb-dev" }))).await; // Heartbeat let (status, _) = send( &app, post("/api/v1/devices/heartbeat", &token, serde_json::json!({ "device_id": "hb-dev" })), ).await; assert_eq!(status, StatusCode::OK); // Heartbeat nonexistent → 404 let (status, _) = send( &app, post("/api/v1/devices/heartbeat", &token, serde_json::json!({ "device_id": "ghost" })), ).await; assert_eq!(status, StatusCode::NOT_FOUND); } // ═══════════════════════════════════════════════════════════════════ // Operation logs (admin only) // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn operation_logs_forbidden_for_user() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "loguser").await; let (status, _) = send(&app, get("/api/v1/logs/operations", &token)).await; assert_eq!(status, StatusCode::FORBIDDEN); } #[tokio::test] async fn dashboard_stats_admin() { let (app, pool) = build_test_app().await; let admin = admin_token(&app, &pool, "statsadmin").await; let (status, _) = send(&app, get("/api/v1/stats/dashboard", &admin)).await; assert_eq!(status, StatusCode::OK); }