OctoWiki

Test Infrastructure

How the codebase is tested — the test/ tree, repo integration tests with fault injection, the octopusfake in-process upstream, the mocky-balboa-driven vendor orchestration suites, concurrency/goleak, CI, and how to add a new vendor suite.

Testing spans five layers: co-located unit tests, real-Postgres repository tests, the test/clientapi/ client-API + orchestration suites, load tests, and fuzz targets. Two purpose-built harnesses carry the heavy work — octopusfake (in-process Octopus upstream) and mocky-balboa (a Cloudflare-worker mock vendor with a chaos API).

Two referenced-but-absent tooling paths

CLAUDE.md points at automation/scripts/run-go-tests.sh and the pre-push hook mentions make setupneither exists (there is no automation/ dir and no Makefile). Use the raw go test invocations below. The pre-push hook itself (.githooks/pre-push) is real and runs golangci-lint on changed Go files; enable it with git config core.hooksPath .githooks.

Layers & how to run

LayerLocationRun
Unitco-located *_test.go (cache, utils, models, services, handlers)go test ./cache/... ./utils/... ./database/models/...
Repository integrationdatabase/repo/*_test.go (150 files)needs Postgres: go test ./database/repo/... (CI adds -race -timeout=15m)
Client-API + money/deduptest/clientapi/*_test.gogo test ./test/clientapi/...
Vendor orchestrationtest/clientapi/*_orchestration_test.gobuild-tagged: go test -tags orchestration ./test/clientapi/... (needs mocky-balboa up)
Fuzz*_fuzz_test.go (auth, esim/topup/voucher dedup & validation, G2A)go test -run=xxx -fuzz=FuzzX ./...
Loadtest/load/ (e.g. test/load/g2a/g2a_load.js)k6 / JS runner
goleak soakservices/external_vendors/{direct_topup,esim}/octopus/*_soak_test.gogo test ./services/external_vendors/.../octopus/...

Race detector (-race) is used throughout CI. testhelpers.SkipIfShort(t) (test/testhelpers/db.go:53) lets long-running/DB tests be skipped with -short.

Repository test patterns

Two complementary styles:

  • Real-Postgres integration — the bulk of database/repo/*_test.go. CI spins up a postgres service, runs migrations + seeders, then go test ./database/repo/.... Isolation is per-test (transaction/cleanup helpers in test/testhelpers/db.go).
  • Fault injection (database/repo/fault_injection_internal_test.go) — a faultDB wraps a real DatabaseClient and forwards everything until a fail-flag is flipped, then returns a configured error. This unlocks the error branches integration tests can't reach (connection failures, scan errors, malformed JSON). newFaultRepo builds a Repository around it. Combined with cache-fault helpers, this is how the repo layer hits the "every function needs happy + error + cache-path tests" bar.

database/repo/TESTING.md is referenced in CLAUDE.md but does not exist — the patterns live in the *_internal_test.go files and test/testhelpers/ instead.

testhelpers

test/testhelpers/ — shared infrastructure imported by the suites:

FileProvides
db.goDB setup/teardown, SkipIfShort
factory.go, seeddata.gofixture builders + seed data
http.go, config.go, assertions.gorequest helpers, config, custom asserts
wallet.gowallet/balance test helpers
mocky.goMockyBaseURL() ($MOCKY_BASE_URL, default http://localhost:8788), MockyVendorURL(), MockyChaosURL()
vendor_binding.goBindVendorToMocky — points a vendor's host attribute at mocky's per-vendor sub-app, sets is_active, and restores both on cleanup; per-vendor locks so parallel tests don't clobber each other
mockychaos/client.gotyped client for mocky's /_chaos API — arm/clear failure rules; NewForTest resets to a clean baseline and restores the seeded library on teardown
cronrunner/runner.goone-shot cron/retry job invokers (e.g. PendingVoucherOrderRetryOnce) to step a PENDING order through its lifecycle without sleeping. Separate subpackage because it imports jobs (which pulls in every vendor adapter) — keeping it out of testhelpers avoids import cycles

test/shared/ adds concurrency.go (RunConcurrentN — fires N goroutines released simultaneously for race/idempotency tests, CountSuccess), isolation.go, suites.go, config_groups.go, fuzz.go, auth.go.

octopusfake — in-process Octopus upstream

test/octopusfake/octopusfake.go is a programmable net/http/httptest fake of the Octopus public /api/v1 used to test the federated Octopus vendor adapters (OCTO / OCTO_TOPUP / OCTO_ESIM) deterministically — including chaos, concurrency, fuzz and load that can't run against the live sandbox. It is not mocky-balboa: no separate service, just a server you start and program from a Go test.

  • Core: auth (/auth/login, /auth/refresh), /api/v1/wallets, a chaos-rule engine (Arm(Rule) — first armed rule matching method + path-contains applies; malformed-body kinds, consumption modes), a request recorder, and HMAC webhook signing.
  • Order engines (opt-in): EnableTopupOrderEngine() / EnableEsimOrderEngine() give fake orders a real create → status-poll → deliver lifecycle; SetTopupBehavior(createStatus, pollStatus, failureCode) models async PENDING→DELIVERED and failure paths. The eSIM engine renders activation_code/iccid deliverables.
  • SetHandler(method, path, h) overrides any exact route for bespoke scenarios.
  • Drives the goleak soak tests in services/external_vendors/{direct_topup,esim}/octopus/*_soak_test.go (the OCTO family uses octopusfake instead of a mocky route — see Octopus adapters).

Vendor orchestration harness

test/clientapi/<vendor>_orchestration_test.go (build tag orchestration) drives real orders end-to-end through a vendor pointed at mocky-balboa, exercising chaos, malformed responses, async polling, signed webhooks, idempotency, retry caps, the link path, validation, and vendor logging. Runa is the ~28-subtest reference.

Ten suites exist today:

runa · wupex · neo · irewardify        (voucher)
seagm_topup · dtone_topup              (top-up)
dtone_esim · octopus_esim · octopus_topup   (esim / octo)
g2a                                    (inbound channel)

Coverage gaps

No orchestration suite for TRS, EpinForce, or Grasshopper on the voucher side. TRS is the least-covered adapter overall (no tests, no mock route — see TRS). G2A and Runa are the best-covered. The money-leak suites do cover all three resources ({voucher,topup,esim}_money_leak_test.go).

Adding a new vendor suite (checklist)

  1. Add a per-vendor sub-app + routes to mocky-balboa (frontend/mocky-balboa/src/index.ts) that mimic the real vendor's contract.
  2. Seed chaos rules for that route (4xx/5xx/timeout/malformed/drop) in mocky's chaos_rules.
  3. In the test, BindVendorToMocky("CODE", "mocky-path") (from testhelpers) to point the vendor's host at mocky and restore on cleanup.
  4. Use mockychaos.NewForTest(t) to reset to a clean baseline, then Arm specific rules per subtest.
  5. Drive orders through the real client API; step retries with cronrunner one-shot helpers instead of sleeping.
  6. Assert idempotency with shared.RunConcurrentN (N concurrent same-client_reference requests → exactly one durable effect).
  7. Gate the file with //go:build orchestration and mirror the Runa suite's structure (chaos, malformed, async, webhook, idempotency, retry-cap, validation, logging).

See the vendor chaos + tests rule — every new adapter ships with a mocky route (incl. chaos hooks) plus unit + contract + service tests in the same PR.

mocky-balboa

frontend/mocky-balboa/ — a Cloudflare Worker (Hono + D1) mock vendor server with a /_chaos management API. Adapters are pointed at it by overwriting their host vendor-attribute (via BindVendorToMocky); tests arm failure scenarios through the mockychaos client. Base URL resolves from $MOCKY_BASE_URL (default http://localhost:8788; CI sets https://mocky-balboa.octopuscards.io). Deploys via GitHub Actions (deploy-mocky-balboa.yml) — see Cloudflare. Uses bun, not npm.

CI

.github/workflows/test-backend-api.yml — parallel jobs:

JobRuns
Lint & Static Checkgolangci-lint
Cache Testinggo test -race -count=1 ./cache/...
Utils Testinggo test -race -coverprofile ./utils/...
Models Testinggo test -race -coverprofile ./database/models/...
Build Binarygo build -o octopus . → uploads artifact
Database TestingPostgres service → download binary → migrate + seeders → go test -race -timeout=15m -coverprofile ./database/repo/...

Frontend/worker deploys are separate workflows (grasshopper, mocky-balboa, docs, website — see Deploy & Release). The orchestration-tagged suites are not obviously wired into this CI file (they need mocky-balboa reachable) — verify whether they run in CI or only locally/against the deployed mock.

Findings

FindingWhere
automation/scripts/run-go-tests.sh (CLAUDE.md) and make setup (pre-push) are referenced but absent
database/repo/TESTING.md referenced but absentCLAUDE.md
No orchestration suite for TRS / EpinForce / Grasshopper; TRS wholly untestedtest/clientapi/
Orchestration suites may not run in the backend CI workflow (mocky dependency)test-backend-api.yml

Key files

  • Harnesses: test/octopusfake/octopusfake.go, frontend/mocky-balboa/src/
  • Helpers: test/testhelpers/{db,factory,mocky,vendor_binding}.go, test/testhelpers/mockychaos/client.go, test/testhelpers/cronrunner/runner.go
  • Shared: test/shared/{concurrency,isolation,suites}.go
  • Fault injection: database/repo/fault_injection_internal_test.go
  • Reference suite: test/clientapi/runa_orchestration_test.go
  • CI: .github/workflows/test-backend-api.yml; hook .githooks/pre-push

On this page