Files
Agent-System/internal/server/auth_handlers_test.go
T
weichenandCursor ad6e38d11a test(workflow): add governance regression and activation PG coverage
Cover public auth config, catalog policy, official main workflow activation on Postgres, and conn-busy metadata writeback fixes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 02:50:23 +00:00

241 lines
7.4 KiB
Go

package server_test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"chat-agent-system/internal/auth"
"chat-agent-system/internal/config"
"chat-agent-system/internal/server"
)
func setupTestServer() (*httptest.Server, *auth.Service) {
cfg := &config.Config{
Env: "dev",
AdminUsername: "admin",
AdminPassword: "admin123",
AuthRegistrationEnabled: true,
AuthRequireApproval: false,
}
srv := server.NewServer(cfg, nil)
ts := httptest.NewServer(srv.Routes())
return ts, srv.AuthService()
}
func TestAuthHandlers_PublicConfigUnauthenticated(t *testing.T) {
ts, authSvc := setupTestServer()
defer ts.Close()
_ = authSvc.UpdateConfig(t.Context(), auth.SystemAuthConfig{
AllowRegistration: true,
RequireApproval: true,
})
resp, err := http.Get(ts.URL + "/api/v1/auth/public-config")
if err != nil {
t.Fatalf("public-config request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("Expected 200 for unauthenticated /auth/public-config, got %d", resp.StatusCode)
}
var cfg auth.SystemAuthConfig
if err := json.NewDecoder(resp.Body).Decode(&cfg); err != nil {
t.Fatalf("decode public-config: %v", err)
}
if !cfg.AllowRegistration || !cfg.RequireApproval {
t.Fatalf("unexpected public-config: %+v", cfg)
}
adminResp, err := http.Get(ts.URL + "/api/v1/admin/config")
if err != nil {
t.Fatalf("admin config request failed: %v", err)
}
defer adminResp.Body.Close()
if adminResp.StatusCode != http.StatusUnauthorized {
t.Fatalf("Expected 401 for unauthenticated /admin/config, got %d", adminResp.StatusCode)
}
}
func TestAuthHandlers_UnauthenticatedMeReturns401(t *testing.T) {
ts, _ := setupTestServer()
defer ts.Close()
resp, err := http.Get(ts.URL + "/api/v1/auth/me")
if err != nil {
t.Fatalf("Me request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusUnauthorized {
t.Fatalf("Expected 401 for unauthenticated /auth/me, got %d", resp.StatusCode)
}
}
func TestAuthHandlers_LoginAndMe(t *testing.T) {
ts, _ := setupTestServer()
defer ts.Close()
// 1. Admin login with .env credentials
loginReq := map[string]string{
"username": "admin",
"password": "admin123",
}
body, _ := json.Marshal(loginReq)
resp, err := http.Post(ts.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(body))
if err != nil {
t.Fatalf("Login request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("Expected 200 OK, got %d", resp.StatusCode)
}
var session auth.SessionInfo
if err := json.NewDecoder(resp.Body).Decode(&session); err != nil {
t.Fatalf("Failed to decode session: %v", err)
}
if session.User == nil || session.User.RoleID != "role-admin" {
t.Errorf("Expected role-admin, got %+v", session.User)
}
// 2. Fetch /api/v1/auth/me with Authorization header
req, _ := http.NewRequest("GET", ts.URL+"/api/v1/auth/me", nil)
req.Header.Set("Authorization", "Bearer "+session.Token)
client := &http.Client{}
meResp, err := client.Do(req)
if err != nil {
t.Fatalf("Me request failed: %v", err)
}
defer meResp.Body.Close()
if meResp.StatusCode != http.StatusOK {
t.Fatalf("Expected 200 OK for /auth/me, got %d", meResp.StatusCode)
}
var meBody map[string]any
if err := json.NewDecoder(meResp.Body).Decode(&meBody); err != nil {
t.Fatalf("decode /auth/me: %v", err)
}
caps, ok := meBody["capabilities"].([]any)
if !ok {
t.Fatalf("expected capabilities array in /auth/me, got %T", meBody["capabilities"])
}
_ = caps // memory mode: empty slice is valid
}
func TestAuthHandlers_RegisterAndApproval(t *testing.T) {
ts, authSvc := setupTestServer()
defer ts.Close()
// 1. Turn on RequireApproval
_ = authSvc.UpdateConfig(t.Context(), auth.SystemAuthConfig{
AllowRegistration: true,
RequireApproval: true,
})
// 2. Register user
regReq := map[string]string{
"username": "new_doctor",
"password": "DoctorPass123",
"name": "王医生",
}
body, _ := json.Marshal(regReq)
resp, err := http.Post(ts.URL+"/api/v1/auth/register", "application/json", bytes.NewReader(body))
if err != nil {
t.Fatalf("Register request failed: %v", err)
}
defer resp.Body.Close()
var regResp map[string]any
_ = json.NewDecoder(resp.Body).Decode(&regResp)
if regResp["status"] != "pending_approval" {
t.Errorf("Expected pending_approval, got %v", regResp["status"])
}
// 3. Trying to login should fail because of pending approval
loginBody, _ := json.Marshal(map[string]string{
"username": "new_doctor",
"password": "DoctorPass123",
})
loginResp, _ := http.Post(ts.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(loginBody))
if loginResp.StatusCode != http.StatusForbidden {
t.Errorf("Expected 403 Forbidden for pending user, got %d", loginResp.StatusCode)
}
}
func TestAuthHandlers_AdminCreateUser_MustChangePassword(t *testing.T) {
ts, _ := setupTestServer()
defer ts.Close()
// 1. Admin login to obtain token
loginBody, _ := json.Marshal(map[string]string{
"username": "admin",
"password": "admin123",
})
resp, _ := http.Post(ts.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(loginBody))
var adminSession auth.SessionInfo
_ = json.NewDecoder(resp.Body).Decode(&adminSession)
// 2. Admin creates a new user
createUserReq := map[string]string{
"username": "qa_engineer",
"name": "周质量",
"initial_password": "TempPassword123",
"role_id": "role-director",
"position": "质量总监",
}
reqBody, _ := json.Marshal(createUserReq)
req, _ := http.NewRequest("POST", ts.URL+"/api/v1/admin/users", bytes.NewReader(reqBody))
req.Header.Set("Authorization", "Bearer "+adminSession.Token)
client := &http.Client{}
createResp, err := client.Do(req)
if err != nil {
t.Fatalf("Admin create user request failed: %v", err)
}
defer createResp.Body.Close()
if createResp.StatusCode != http.StatusOK {
t.Fatalf("Expected 200 OK, got %d", createResp.StatusCode)
}
// 3. New user logs in with initial password -> must_change_password must be true
userLoginBody, _ := json.Marshal(map[string]string{
"username": "qa_engineer",
"password": "TempPassword123",
})
uResp, _ := http.Post(ts.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(userLoginBody))
var uSession auth.SessionInfo
_ = json.NewDecoder(uResp.Body).Decode(&uSession)
if !uSession.MustChangePassword {
t.Errorf("Expected MustChangePassword to be true for admin created user")
}
// 4. Change password
changeBody, _ := json.Marshal(map[string]string{
"old_password": "TempPassword123",
"new_password": "NewPermanentPass888",
})
chReq, _ := http.NewRequest("POST", ts.URL+"/api/v1/auth/change-password", bytes.NewReader(changeBody))
chReq.Header.Set("Authorization", "Bearer "+uSession.Token)
chResp, err := client.Do(chReq)
if err != nil || chResp.StatusCode != http.StatusOK {
t.Fatalf("Change password failed: %v", err)
}
// 5. Login again with new password -> must_change_password must be false
newLoginBody, _ := json.Marshal(map[string]string{
"username": "qa_engineer",
"password": "NewPermanentPass888",
})
nResp, _ := http.Post(ts.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(newLoginBody))
var nSession auth.SessionInfo
_ = json.NewDecoder(nResp.Body).Decode(&nSession)
if nSession.MustChangePassword {
t.Errorf("Expected MustChangePassword to be false after changing password")
}
}