Files
Agent-System/internal/server/user_profile_directory_test.go
weichen 3e01f18756 merge: integrate final-artifact-vault into local_dev (authorized exception)
User authorized lifting CURRENT FAV whole-branch merge protection on
2026-09-23. Keep FAV_DEGRADED_READ/FAV_PUBLISHER_ENABLED default false.
Drop duplicate goose 00034 (local_dev already has 00036). Preserve
agentx workspace admin routes and pin-export wiring.
2026-09-23 13:57:08 +00:00

195 lines
6.0 KiB
Go

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)
}
}