Files
zclaw_openfang/crates/zclaw-saas/tests/account_test.rs
iven 5fdf96c3f5 chore: 提交所有工作进度 — SaaS 后端增强、Admin UI、桌面端集成
包含大量 SaaS 平台改进、Admin 管理后台更新、桌面端集成完善、
文档同步、测试文件重构等内容。为 QA 测试准备干净工作树。
2026-03-29 10:46:41 +08:00

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);
}