User journeys
Read this when you add or change a journey step in services/tadoku-api/e2e/
or its fixtures under services/tadoku-api/e2e/testdata/journeys/. The general
test principles are in Testing, and single-request golden cases
are in HTTP end-to-end tests.
A user journey chains requests against one reset, so every state after its first step comes from the API itself: write and read paths must agree without a handwritten seed between them, business time can move between steps, and one user can observe another's writes. Journeys keep the functionality users expect working, so cover every important user journey in the application.
Journeys are grouped by domain, one explicit Go step table per journey passed
to runJourney or runScenarioJourney:
| File | Journeys |
|---|---|
services/tadoku-api/e2e/user_journeys_test.go | Tenant isolation, languages, contests, registrations and scoring rule sets |
services/tadoku-api/e2e/log_journeys_test.go | Logs and leaderboard jobs |
services/tadoku-api/e2e/access_journeys_test.go | Roles, bans, the user list and account deletion |
services/tadoku-api/e2e/content_journeys_test.go | Announcements, pages and posts |
Journeys keep dependent steps in one scenario and run the production API router
and, where needed, the worker application in process. Fixtures live under
e2e/testdata/journeys/:
e2e/testdata/journeys/
cast.json # cast member -> bearer token
relationships.json # cast roles, seeded for every journey
<Journey>/
setup.sql # optional, journey-specific
relationships.json # optional, journey-specific
01_<step>/
request.http # no Authorization header
golden.http
02_<step>/
verify.sql # one ordered query over deterministic columns
verify.json
Steps
- Steps are numbered by their position in the table. Each step is exactly one of a request, verify, Valkey snapshot or job step.
- A request step names the cast member that sends
request.http; the runner injects that member's token, andnonesends no credentials. Do not embed tokens in journeyrequest.httpfiles. - An identity step that needs a signed-in account sends a synthetic
Cookie: tadoku_session=…inrequest.http. The journey'ssetup.sqlseeds the session with the cookie value's SHA-256 hex digest and a-- Test safety:comment naming the raw value. - A request step's optional
othersmap replays the same request as other members before the primary request and checks only their status. Add replays only where they add information: identities that must be rejected on mutating steps,bannedincluded, and a second user only to observe limited visibility of a resource. - Replays must not legitimately mutate state, so an identity that is supposed to succeed at a write gets its own step.
- A verify step runs
verify.sql, aggregates the rows into one JSON array in query order and compares the indented result withverify.json. Reserve verify steps for effects no endpoint exposes, such as soft deletes, job rows and audit entries; API-observable persistence belongs in the next request or a repository test. - Verify queries end with
order byand select only application-supplied columns; database-defaulted IDs andnow()timestamps are not deterministic. - A
run_workerjob step has no fixture directory. It starts the worker against the journey's disposable database and Valkey fixture, waits until every job eligible for the base worker has completed, including jobs those jobs enqueue, and stops it before the next step. The worker runs with the journey's context, so its jobs read the journey's clock and UUID stream. Tenants overridden fortadoku-workerremain pending. Arun_branch_workerjob step requiresforTenantwith a parsed non-production key and completes only that tenant's jobs. - A Valkey snapshot step uses
valkey: "<name>"and a directory containing onlyverify.json. It checks ownership of the suite's leased Valkey database, scans leaderboard keys, removes duplicate scan results and compares the sorted names with the golden. Snapshots include tenant prefixes and cache markers; they exclude the lease key.
Reset and time
- A journey passed to
runScenarioJourneyseeds the same files into its own tenants instead of a reset and runs in parallel with other scenarios. Its requests, verify queries, Valkey snapshots and worker steps are confined to those tenants, and goldens name them by role; Scenario fixtures describes the contract. The rest of this section is the serial runner. - The runner resets PostgreSQL and Valkey once per journey: cleanup, then the
optional shared
journeys/setup.sql, the journey's ownsetup.sqland the account cast, then the shared and the journey's ownrelationships.json, as Relationship files describes. There are no per-step seeds and no resets between steps. A journey may seed an external cache or install a fault, such as a failing trigger, after reset; it removes a fault in cleanup. - The JWT clock stays at the suite's fixture instant. Each journey owns one
timex.Clockand one UUID stream, attached to the context of every request, replay and worker step. A step'sat, by default the fixture instant, sets the clock before the step runs. - The stream follows a fixed seed. A journey whose goldens name specific
generated IDs passes its own bytes to
runJourneyWithSetup. - The runner stops at the first failing step. Unknown entries in a journey or step directory fail the journey.
Cast
- Cast members are
guest,user,user2,adminandbanned, plus the implicitnone.adminandbannedhold their roles ontadoku/prodthrough the sharedrelationships.json. Tenant variants use the same subjects in signed tokens with a different tenant claim, and hold a role there only through a relationship for that tenant. - To add a member, sign a token with the
fixture recipe, append its public key when you
use a new signing key, and add the token to
cast.jsonand any relationship torelationships.json.
Tenant isolation
TestTenantIsolationJourney uses the restricted PostgreSQL application role
and real Valkey. Its requests create one log in each tenant, then check
separate leaderboards, cache keys, scoped worker outcomes and bans.
Flipt evaluation and management use the suite's tenant-aware protocol fixture. This journey therefore verifies application routing and independent flag state, not the Flipt server's storage or SDK snapshot behavior. Those boundaries require separate real-provider verification.
Regenerating journey goldens
The same -update-goldens command and source root as the golden-case tables
also cover journey golden.http and verify.json files. It never creates a
missing file, so add empty placeholders before recording a new journey, and
review the complete diff.