| name | td-integration-test |
| description | Write integration tests for the td-sync admin API using the TestHarness in internal/api/testharness_test.go. Use when asked to write, add, or fix integration tests for admin API endpoints (server, users, projects, events, snapshots, CORS, auth). The harness provides a real HTTP server, fluent state builder, and assertion helpers. Tests go in internal/api/admin_integration_test.go. |
td-sync Admin API Integration Tests
Write integration tests in internal/api/admin_integration_test.go using the harness in internal/api/testharness_test.go. Tests run against a real HTTP server on a random port with isolated temp databases.
Quick Start Pattern
func TestIntegration_DescriptiveName(t *testing.T) {
t.Parallel()
h := newTestHarness(t)
state := h.Build().
WithUser("user@test.com").
WithAdmin("admin@test.com", "admin:read:server,sync").
WithProject("proj1", "user@test.com").
WithEvents("proj1", "user@test.com", 5).
Done()
token := state.AdminToken("admin@test.com")
pid := state.ProjectID("proj1")
var resp adminEventsResponse
h.DoJSON("GET", fmt.Sprintf("/v1/admin/projects/%s/events", pid), token, nil, &resp)
if len(resp.Data) != 5 {
t.Fatalf("expected 5 events, got %d", len(resp.Data))
}
}
Harness API
See references/harness-api.md for the complete API reference with all method signatures and detailed usage notes.
Core
newTestHarness(t, ...func(*Config)) *TestHarness -- real HTTP server, isolated DB, auto-cleanup
h.Do(method, path, token, body) *http.Response -- real HTTP request (caller closes body)
h.DoJSON(method, path, token, body, &out) *http.Response -- request + JSON decode (fatals on 4xx/5xx)
State Builder
h.Build().
WithUser(email).
WithAdmin(email, scopes).
WithProject(name, ownerEmail).
WithMember(projectName, email, role).
WithEvents(projectName, userEmail, count).
WithSnapshot(projectName).
WithAuthEvents(count).
WithRateLimitEvents(count).
Done()
Ordering matters: create users before projects, projects before members/events/snapshots.
State Accessors
state.UserToken(email), state.UserID(email), state.AdminToken(email), state.ProjectID(name), state.Harness()
Assertions
AssertStatus(t, resp, 200) -- checks status, prints body on failure
AssertErrorResponse(t, resp, 403, "insufficient_admin_scope") -- checks status + error code
ReadJSON[T](t, resp) T -- generic JSON decode
AssertPaginated[T](t, resp, count, hasMore) PaginatedResponse[T] -- checks paginated list
AssertCORSHeaders(t, resp, origin) / AssertNoCORSHeaders(t, resp)
h.AssertRequiresAdminScope(t, method, path, wrongToken) -- 403 + error code check
Admin Scopes
| Scope | Endpoints |
|---|
admin:read:server | server/overview, server/config, rate-limit-violations, users, users/{id}, users/{id}/keys, auth/events |
admin:read:projects | projects, projects/{id}, projects/{id}/members, sync/status, sync/cursors |
admin:read:events | projects/{id}/events, projects/{id}/events/{seq}, entity-types |
admin:read:snapshots | projects/{id}/snapshot/meta, projects/{id}/snapshot/query |
admin:export | projects/{id}/events/export |
Response Types
Internal types accessible from test files in package api:
serverOverviewResponse -- server overview
serverConfigResponse -- server config
adminEventsResponse -- {Data []adminEvent, HasMore bool}
adminEvent -- single event: ServerSeq, EntityType, EntityID, ActionType, Payload
adminSyncStatusResponse -- {EventCount, LastServerSeq, LastEventTime}
adminCursorEntry -- {ClientID, LastEventID, LastSyncAt, DistanceFromHead}
serverdb.AdminProject -- project: ID, Name, MemberCount, EventCount
serverdb.AdminUser -- user: ID, Email, IsAdmin, ProjectCount
serverdb.AdminProjectMember -- {UserID, Email, Role}
Rules
- Always
t.Parallel() -- each harness is isolated
- Test name prefix:
TestIntegration_
- Config overrides via opts:
newTestHarness(t, func(cfg *Config) { cfg.CORSAllowedOrigins = []string{"https://x.com"} })
- First user created is auto-admin; consume with
h.CreateUser("first@test.com") when testing non-admin denial
- CORS tests need manual
http.NewRequest since Do doesn't support custom headers
- Run tests:
go test -v -run TestIntegration ./internal/api/