204 lines
8.1 KiB
Rust
204 lines
8.1 KiB
Rust
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);
|
|
}
|