OpenParable
Skip to content
← Back to skills

td-integration-test

Testing & Quality

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.

View on GitHub ↗
QUICK START

How to use this skill

Bring this guide into your coding agent with a prompt tailored to the tool you use.

  1. Open your project in Codex.
  2. Copy and paste the prompt below.
  3. Review files and risks before approving any action.
Prompt to paste
Inspect the Agent Skill at https://github.com/marcus/td/blob/HEAD/.claude/skills/td-integration-test/SKILL.md and all supporting files. Report provenance, permissions, scripts, dependencies, and security risks. Propose an exact installation into .agents/skills/td-integration-test, but wait for my approval before writing files or running scripts.

Copying this prompt does not install or run the skill. Review third-party files before use.

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("[email protected]").
        WithAdmin("[email protected]", "admin:read:server,sync").
        WithProject("proj1", "[email protected]").
        WithEvents("proj1", "[email protected]", 5).
        Done()

    token := state.AdminToken("[email protected]")
    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

ScopeEndpoints
admin:read:serverserver/overview, server/config, rate-limit-violations, users, users/{id}, users/{id}/keys, auth/events
admin:read:projectsprojects, projects/{id}, projects/{id}/members, sync/status, sync/cursors
admin:read:eventsprojects/{id}/events, projects/{id}/events/{seq}, entity-types
admin:read:snapshotsprojects/{id}/snapshot/meta, projects/{id}/snapshot/query
admin:exportprojects/{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("[email protected]") 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/