td-integration-test

Installation
SKILL.md

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).                          // sync-scoped key
    WithAdmin(email, scopes).                 // admin key with scopes
    WithProject(name, ownerEmail).            // via API (owner must exist)
    WithMember(projectName, email, role).     // "owner"/"writer"/"reader"
    WithEvents(projectName, userEmail, count).// cycles issues/logs/comments
    WithSnapshot(projectName).                // triggers snapshot build
    WithAuthEvents(count).                    // inserts directly to DB
    WithRateLimitEvents(count).               // inserts directly to DB
    Done() // -> *TestState

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

  1. Always t.Parallel() -- each harness is isolated
  2. Test name prefix: TestIntegration_
  3. Config overrides via opts: newTestHarness(t, func(cfg *Config) { cfg.CORSAllowedOrigins = []string{"https://x.com"} })
  4. First user created is auto-admin; consume with h.CreateUser("first@test.com") when testing non-admin denial
  5. CORS tests need manual http.NewRequest since Do doesn't support custom headers
  6. Run tests: go test -v -run TestIntegration ./internal/api/
Related skills
Installs
3
Repository
marcus/td
GitHub Stars
207
First Seen
9 days ago