This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/handlers/presence_test.go
Jeffrey Smith a8204859b5
Some checks failed
CI/CD / detect-changes (pull_request) Successful in 3s
CI/CD / test-frontend (pull_request) Successful in 6s
CI/CD / build-and-deploy (pull_request) Has been cancelled
CI/CD / test-go-pg (pull_request) Has been cancelled
CI/CD / test-sqlite (pull_request) Has been cancelled
Feat v0.6.15 user display audit
Add batch user resolve endpoint, sw.users SDK module, and migrate all
surfaces to show display_name instead of UUIDs. Canonical fallback chain:
display_name → username → "Unknown".

- GET /api/v1/users/resolve?ids=... (max 100, map response)
- sw.users.resolve(), resolveMany(), displayName() with 60s cache
- Chat participants resolved from users table, not snapshot
- Admin users/teams/groups/team-admin show display_name
- Chat-core participants.display_name column deprecated
- 5 new handler tests, OpenAPI spec updated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 14:19:03 +00:00

180 lines
4.8 KiB
Go

package handlers
import (
"encoding/json"
"net/http"
"testing"
"github.com/gin-gonic/gin"
"armature/config"
"armature/database"
"armature/middleware"
"armature/store"
postgres "armature/store/postgres"
sqlite "armature/store/sqlite"
)
// ── Presence/Resolve Test Harness ──────────────
type presenceHarness struct {
*testHarness
stores store.Stores
userToken string
userID string
}
func setupPresenceHarness(t *testing.T) *presenceHarness {
t.Helper()
database.RequireTestDB(t)
database.TruncateAll(t)
cfg := &config.Config{
JWTSecret: testJWTSecret,
BasePath: "",
}
var stores store.Stores
if database.IsSQLite() {
stores = sqlite.NewStores(database.TestDB)
} else {
stores = postgres.NewStores(database.TestDB)
}
userCache := middleware.NewUserStatusCache()
r := gin.New()
api := r.Group("/api/v1")
protected := api.Group("")
protected.Use(middleware.Auth(cfg, stores.Users, userCache))
presence := NewPresenceHandler(stores)
protected.GET("/users/resolve", presence.ResolveUsers)
protected.GET("/users/search", presence.SearchUsers)
// Seed a caller user
userID := database.SeedTestUser(t, "caller", "caller@test.com")
database.TestDB.Exec(dialectSQL("UPDATE users SET is_active = true WHERE id = $1"), userID)
userToken := makeToken(userID, "caller@test.com", "user")
return &presenceHarness{
testHarness: &testHarness{router: r, t: t},
stores: stores,
userToken: userToken,
userID: userID,
}
}
// ── Tests ──────────────────────────────────────
func TestResolveUsers_SingleID(t *testing.T) {
h := setupPresenceHarness(t)
// Seed a user with display_name
targetID := database.SeedTestUser(t, "alice", "alice@test.com")
database.TestDB.Exec(dialectSQL("UPDATE users SET is_active = true, display_name = $1 WHERE id = $2"), "Alice Smith", targetID)
w := h.request("GET", "/api/v1/users/resolve?ids="+targetID, h.userToken, nil)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
}
var resp struct {
Data map[string]store.UserSearchResult `json:"data"`
}
json.NewDecoder(w.Body).Decode(&resp)
u, ok := resp.Data[targetID]
if !ok {
t.Fatalf("expected user %s in response, got %v", targetID, resp.Data)
}
if u.Username != "alice" {
t.Errorf("expected username 'alice', got %q", u.Username)
}
if u.DisplayName != "Alice Smith" {
t.Errorf("expected display_name 'Alice Smith', got %q", u.DisplayName)
}
}
func TestResolveUsers_MultipleIDs(t *testing.T) {
h := setupPresenceHarness(t)
id1 := database.SeedTestUser(t, "bob", "bob@test.com")
id2 := database.SeedTestUser(t, "carol", "carol@test.com")
database.TestDB.Exec(dialectSQL("UPDATE users SET is_active = true WHERE id = $1"), id1)
database.TestDB.Exec(dialectSQL("UPDATE users SET is_active = true, display_name = $1 WHERE id = $2"), "Carol D", id2)
w := h.request("GET", "/api/v1/users/resolve?ids="+id1+","+id2, h.userToken, nil)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
var resp struct {
Data map[string]store.UserSearchResult `json:"data"`
}
json.NewDecoder(w.Body).Decode(&resp)
if len(resp.Data) != 2 {
t.Fatalf("expected 2 users, got %d", len(resp.Data))
}
if resp.Data[id1].Username != "bob" {
t.Errorf("expected bob, got %q", resp.Data[id1].Username)
}
if resp.Data[id2].DisplayName != "Carol D" {
t.Errorf("expected 'Carol D', got %q", resp.Data[id2].DisplayName)
}
}
func TestResolveUsers_MissingID(t *testing.T) {
h := setupPresenceHarness(t)
w := h.request("GET", "/api/v1/users/resolve?ids=00000000-0000-0000-0000-000000000099", h.userToken, nil)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
var resp struct {
Data map[string]store.UserSearchResult `json:"data"`
}
json.NewDecoder(w.Body).Decode(&resp)
if len(resp.Data) != 0 {
t.Errorf("expected empty map for missing ID, got %d entries", len(resp.Data))
}
}
func TestResolveUsers_EmptyParam(t *testing.T) {
h := setupPresenceHarness(t)
w := h.request("GET", "/api/v1/users/resolve?ids=", h.userToken, nil)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
var resp struct {
Data map[string]interface{} `json:"data"`
}
json.NewDecoder(w.Body).Decode(&resp)
if len(resp.Data) != 0 {
t.Errorf("expected empty map, got %d entries", len(resp.Data))
}
}
func TestResolveUsers_NoParam(t *testing.T) {
h := setupPresenceHarness(t)
w := h.request("GET", "/api/v1/users/resolve", h.userToken, nil)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
var resp struct {
Data map[string]interface{} `json:"data"`
}
json.NewDecoder(w.Body).Decode(&resp)
if len(resp.Data) != 0 {
t.Errorf("expected empty map, got %d entries", len(resp.Data))
}
}