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(®Resp) 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") } }