package server_test import ( "bytes" "encoding/json" "net/http" "strconv" "testing" "chat-agent-system/internal/auth" ) func loginTS(t *testing.T, baseURL, username, password string) (token string, userID int64) { t.Helper() body, _ := json.Marshal(map[string]string{"username": username, "password": password}) resp, err := http.Post(baseURL+"/api/v1/auth/login", "application/json", bytes.NewReader(body)) if err != nil { t.Fatalf("login: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("login %s: %d", username, resp.StatusCode) } var session auth.SessionInfo if err := json.NewDecoder(resp.Body).Decode(&session); err != nil { t.Fatalf("decode login: %v", err) } if session.Token == "" || session.User == nil { t.Fatal("empty session") } return session.Token, session.User.ID } func TestUserDirectory_RequiresAuthAndReturnsActiveNames(t *testing.T) { ts, _ := setupTestServer(t) unauth, err := http.Get(ts.URL + "/api/v1/users/directory") if err != nil { t.Fatal(err) } defer unauth.Body.Close() if unauth.StatusCode != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", unauth.StatusCode) } token, _ := loginTS(t, ts.URL, "zhanggong", "123456") req, _ := http.NewRequest(http.MethodGet, ts.URL+"/api/v1/users/directory", nil) req.Header.Set("Authorization", "Bearer "+token) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected 200, got %d", resp.StatusCode) } var payload struct { Users []auth.UserDirectoryEntry `json:"users"` } if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { t.Fatal(err) } if len(payload.Users) == 0 { t.Fatal("expected non-empty directory") } foundZhang := false for _, u := range payload.Users { if u.Name == "" { t.Fatalf("directory entry missing name: %+v", u) } if u.Username == "zhanggong" { foundZhang = true if u.Name != "张工" { t.Fatalf("expected 张工, got %q", u.Name) } } } if !foundZhang { t.Fatal("expected zhanggong in directory") } } func TestMeProfile_PatchDepartmentAndClearAdminHint(t *testing.T) { ts, _ := setupTestServer(t) adminToken, adminID := loginTS(t, ts.URL, "admin", "admin123") userToken, userID := loginTS(t, ts.URL, "zhanggong", "123456") // Admin edits target profile → hint set patchBody, _ := json.Marshal(map[string]string{ "name": "张工-管理员改名", "department": "骨科耗材研发组", "email": "zhang@example.com", "employee_id": "RD-001", }) adminReq, _ := http.NewRequest( http.MethodPatch, ts.URL+"/api/v1/admin/users/"+strconv.FormatInt(userID, 10)+"/profile", bytes.NewReader(patchBody), ) adminReq.Header.Set("Authorization", "Bearer "+adminToken) adminReq.Header.Set("Content-Type", "application/json") adminResp, err := http.DefaultClient.Do(adminReq) if err != nil { t.Fatal(err) } defer adminResp.Body.Close() if adminResp.StatusCode != http.StatusOK { t.Fatalf("admin profile patch: %d %s", adminResp.StatusCode, adminResp.Body) } getReq, _ := http.NewRequest(http.MethodGet, ts.URL+"/api/v1/me/profile", nil) getReq.Header.Set("Authorization", "Bearer "+userToken) getResp, err := http.DefaultClient.Do(getReq) if err != nil { t.Fatal(err) } defer getResp.Body.Close() if getResp.StatusCode != http.StatusOK { t.Fatalf("me profile get: %d", getResp.StatusCode) } var profile map[string]any if err := json.NewDecoder(getResp.Body).Decode(&profile); err != nil { t.Fatal(err) } if profile["name"] != "张工-管理员改名" { t.Fatalf("name after admin edit: %+v", profile["name"]) } if profile["profile_updated_by_name"] == nil || profile["profile_updated_by_name"] == "" { t.Fatalf("expected admin hint name, got %+v", profile) } if by, ok := profile["profile_updated_by"].(float64); !ok || int64(by) != adminID { t.Fatalf("expected profile_updated_by=%d, got %+v", adminID, profile["profile_updated_by"]) } // Self patch clears hint and updates department selfBody, _ := json.Marshal(map[string]string{ "name": "张工", "department": "骨科研发一部", "email": "zhang.self@example.com", }) selfReq, _ := http.NewRequest(http.MethodPatch, ts.URL+"/api/v1/me/profile", bytes.NewReader(selfBody)) selfReq.Header.Set("Authorization", "Bearer "+userToken) selfReq.Header.Set("Content-Type", "application/json") selfResp, err := http.DefaultClient.Do(selfReq) if err != nil { t.Fatal(err) } defer selfResp.Body.Close() if selfResp.StatusCode != http.StatusOK { t.Fatalf("self profile patch: %d", selfResp.StatusCode) } getReq2, _ := http.NewRequest(http.MethodGet, ts.URL+"/api/v1/me/profile", nil) getReq2.Header.Set("Authorization", "Bearer "+userToken) getResp2, err := http.DefaultClient.Do(getReq2) if err != nil { t.Fatal(err) } defer getResp2.Body.Close() var profile2 map[string]any if err := json.NewDecoder(getResp2.Body).Decode(&profile2); err != nil { t.Fatal(err) } if profile2["name"] != "张工" { t.Fatalf("name after self save: %+v", profile2["name"]) } if profile2["department"] != "骨科研发一部" { t.Fatalf("department after self save: %+v", profile2["department"]) } if profile2["profile_updated_by_name"] != nil { t.Fatalf("expected admin hint cleared, got %+v", profile2["profile_updated_by_name"]) } } func TestAdminUpdateProfile_ForbiddenForNonAdmin(t *testing.T) { ts, _ := setupTestServer(t) userToken, userID := loginTS(t, ts.URL, "zhanggong", "123456") body, _ := json.Marshal(map[string]string{"name": "黑客改名"}) req, _ := http.NewRequest( http.MethodPatch, ts.URL+"/api/v1/admin/users/"+strconv.FormatInt(userID, 10)+"/profile", bytes.NewReader(body), ) req.Header.Set("Authorization", "Bearer "+userToken) req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusForbidden { t.Fatalf("expected 403, got %d", resp.StatusCode) } }