package api_test import ( "context" "net/http" "testing" ) // TestAPIStats 仪表盘统计:admin 登录后 /admin/stats 返回各状态数与待审批数。 func TestAPIStats(t *testing.T) { app := setupTestApp(t) w := app.doJSON(http.MethodGet, "/api/v1/admin/stats", nil) if w.Code != http.StatusUnauthorized { t.Fatalf("unauth stats status = %d, want 401", w.Code) } w = app.doJSON(http.MethodPost, "/api/v1/auth/admin/login", map[string]string{"username": "root", "password": "Passw0rd"}) ck := sessionCookie(t, w) // 建 1 个用户 + 1 个待审批申请,stats 应反映 w = app.doJSON(http.MethodPost, "/api/v1/users", map[string]any{ "username": "m5stats", "email": "m5stats@example.com", "supervisor": "profA", "purpose": "test", }, ck) if w.Code != http.StatusOK { t.Fatalf("create user status = %d, body=%s", w.Code, w.Body.String()) } w = app.doJSON(http.MethodPost, "/api/v1/approvals", map[string]any{ "username": "m5apply", "email": "m5apply@example.com", "supervisor": "profB", "purpose": "apply test", }) if w.Code != http.StatusOK { t.Fatalf("submit approval status = %d, body=%s", w.Code, w.Body.String()) } w = app.doJSON(http.MethodGet, "/api/v1/admin/stats", nil, ck) if w.Code != http.StatusOK { t.Fatalf("stats status = %d, body=%s", w.Code, w.Body.String()) } m := decodeBody(t, w) data := m["data"].(map[string]any) if data["total"].(float64) != 1 { t.Fatalf("stats total = %v, want 1", data["total"]) } if data["pending_approvals"].(float64) != 1 { t.Fatalf("pending_approvals = %v, want 1", data["pending_approvals"]) } if _, ok := data["expiring_soon"]; !ok { t.Fatalf("stats missing expiring_soon: %v", data) } } // TestAPIMeAudit 外部用户"我的审计":登录后可查自己的审计痕迹,管理端接口拒绝 user 会话。 func TestAPIMeAudit(t *testing.T) { app := setupTestApp(t) // 建用户 + 通过 OTP 登录(复用 m3 流程) adminW := app.doJSON(http.MethodPost, "/api/v1/auth/admin/login", map[string]string{"username": "root", "password": "Passw0rd"}) ack := sessionCookie(t, adminW) adminW = app.doJSON(http.MethodPost, "/api/v1/users", map[string]any{ "username": "meaudit", "email": "meaudit@example.com", }, ack) if adminW.Code != http.StatusOK { t.Fatalf("create user status = %d, body=%s", adminW.Code, adminW.Body.String()) } // OTP 双通道:经 store 取验证码(模拟用户看到图形验证码)+ 登录 // OTP 双通道:经 store 取验证码(模拟用户看到图形验证码)+ 登录 cap, err := app.captchas.New() if err != nil { t.Fatalf("captcha new: %v", err) } sendW := app.doJSON(http.MethodPost, "/api/v1/auth/otp/send", map[string]string{ "username": "meaudit", "captcha_id": cap.ID, "captcha_code": cap.Text, }) if sendW.Code != http.StatusOK { t.Fatalf("otp send status = %d, body=%s", sendW.Code, sendW.Body.String()) } code, err := app.otps.Current(context.Background(), "ext_meaudit") if err != nil { t.Fatalf("get otp: %v", err) } loginW := app.doJSON(http.MethodPost, "/api/v1/auth/otp/login", map[string]string{"username": "meaudit", "code": code}) if loginW.Code != http.StatusOK { t.Fatalf("otp login status = %d, body=%s", loginW.Code, loginW.Body.String()) } uck := sessionCookie(t, loginW) // 用户访问管理端审计 → 403 w := app.doJSON(http.MethodGet, "/api/v1/audit", nil, uck) if w.Code != http.StatusForbidden { t.Fatalf("user access admin audit status = %d, want 403", w.Code) } // 我的审计:登录成功等操作应可见 w = app.doJSON(http.MethodGet, "/api/v1/me/audit", nil, uck) if w.Code != http.StatusOK { t.Fatalf("me/audit status = %d, body=%s", w.Code, w.Body.String()) } m := decodeBody(t, w) data := m["data"].(map[string]any) if data["total"].(float64) < 1 { t.Fatalf("me/audit total = %v, want >= 1", data["total"]) } }