Add disabled-user 401, admin roles/sessions handler tests, optional PG store integration, cross-project 403 fixture, and update the Phase 1 test report to reflect completion status. Co-authored-by: Cursor <cursoragent@cursor.com>
144 lines
4.3 KiB
Go
144 lines
4.3 KiB
Go
package server_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"chat-agent-system/internal/auth"
|
|
)
|
|
|
|
func adminToken(t *testing.T, baseURL string) string {
|
|
t.Helper()
|
|
body, _ := json.Marshal(map[string]string{"username": "admin", "password": "admin123"})
|
|
resp, err := http.Post(baseURL+"/api/v1/auth/login", "application/json", bytes.NewReader(body))
|
|
if err != nil {
|
|
t.Fatalf("admin login: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
var session auth.SessionInfo
|
|
if err := json.NewDecoder(resp.Body).Decode(&session); err != nil {
|
|
t.Fatalf("decode admin session: %v", err)
|
|
}
|
|
return session.Token
|
|
}
|
|
|
|
func TestAuthHandlers_DisabledUserTokenReturns401(t *testing.T) {
|
|
ts, authSvc := setupTestServer()
|
|
defer ts.Close()
|
|
|
|
loginBody, _ := json.Marshal(map[string]string{"username": "zhanggong", "password": "123456"})
|
|
resp, err := http.Post(ts.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(loginBody))
|
|
if err != nil {
|
|
t.Fatalf("user login: %v", err)
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("expected engineer login 200, got %d", resp.StatusCode)
|
|
}
|
|
var userSession auth.SessionInfo
|
|
_ = json.NewDecoder(resp.Body).Decode(&userSession)
|
|
|
|
adminTok := adminToken(t, ts.URL)
|
|
disableBody, _ := json.Marshal(map[string]string{"status": string(auth.StatusDisabled)})
|
|
req, _ := http.NewRequest(http.MethodPut, ts.URL+"/api/v1/admin/users/2/status", bytes.NewReader(disableBody))
|
|
req.Header.Set("Authorization", "Bearer "+adminTok)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
client := &http.Client{}
|
|
disableResp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("disable user: %v", err)
|
|
}
|
|
disableResp.Body.Close()
|
|
if disableResp.StatusCode != http.StatusOK {
|
|
t.Fatalf("expected disable 200, got %d", disableResp.StatusCode)
|
|
}
|
|
|
|
meReq, _ := http.NewRequest(http.MethodGet, ts.URL+"/api/v1/auth/me", nil)
|
|
meReq.Header.Set("Authorization", "Bearer "+userSession.Token)
|
|
meResp, err := client.Do(meReq)
|
|
if err != nil {
|
|
t.Fatalf("me after disable: %v", err)
|
|
}
|
|
defer meResp.Body.Close()
|
|
if meResp.StatusCode != http.StatusUnauthorized {
|
|
t.Fatalf("expected 401 after disable, got %d", meResp.StatusCode)
|
|
}
|
|
|
|
u, err := authSvc.GetUserByID(t.Context(), 2)
|
|
if err != nil || u.Status != auth.StatusDisabled {
|
|
t.Fatalf("user should remain disabled in store")
|
|
}
|
|
}
|
|
|
|
func TestAuthHandlers_AdminListRoles(t *testing.T) {
|
|
ts, _ := setupTestServer()
|
|
defer ts.Close()
|
|
|
|
req, _ := http.NewRequest(http.MethodGet, ts.URL+"/api/v1/admin/roles", nil)
|
|
req.Header.Set("Authorization", "Bearer "+adminToken(t, ts.URL))
|
|
resp, err := (&http.Client{}).Do(req)
|
|
if err != nil {
|
|
t.Fatalf("list roles: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
|
}
|
|
var body map[string]any
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
roles, ok := body["roles"].([]any)
|
|
if !ok || len(roles) == 0 {
|
|
t.Fatalf("expected roles array, got %v", body["roles"])
|
|
}
|
|
}
|
|
|
|
func TestAuthHandlers_ListSessions(t *testing.T) {
|
|
ts, _ := setupTestServer()
|
|
defer ts.Close()
|
|
|
|
tok := adminToken(t, ts.URL)
|
|
req, _ := http.NewRequest(http.MethodGet, ts.URL+"/api/v1/auth/sessions", nil)
|
|
req.Header.Set("Authorization", "Bearer "+tok)
|
|
resp, err := (&http.Client{}).Do(req)
|
|
if err != nil {
|
|
t.Fatalf("list sessions: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
|
}
|
|
var body map[string]any
|
|
_ = json.NewDecoder(resp.Body).Decode(&body)
|
|
sessions, ok := body["sessions"].([]any)
|
|
if !ok {
|
|
t.Fatalf("expected sessions array")
|
|
}
|
|
if len(sessions) == 0 {
|
|
t.Fatal("expected at least one active session")
|
|
}
|
|
}
|
|
|
|
func TestAuthHandlers_LoginReturnsCapabilitiesFallback(t *testing.T) {
|
|
ts, _ := setupTestServer()
|
|
defer ts.Close()
|
|
|
|
req, _ := http.NewRequest(http.MethodGet, ts.URL+"/api/v1/auth/me", nil)
|
|
req.Header.Set("Authorization", "Bearer "+adminToken(t, ts.URL))
|
|
resp, err := (&http.Client{}).Do(req)
|
|
if err != nil {
|
|
t.Fatalf("me: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
var body map[string]any
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
caps, ok := body["capabilities"].([]any)
|
|
if !ok || len(caps) == 0 {
|
|
t.Fatalf("expected non-empty capabilities fallback for admin, got %v", body["capabilities"])
|
|
}
|
|
}
|