Vendor Adapter Tests
The build-tagged test layer colocated with the vendor adapters (services/external_vendors/**) — the unit/contract/soak/sandbox/fuzz matrix, the per-vendor coverage table, how contract tests bind to mocky-balboa and drive chaos, the two goleak soak tests, and the concrete gaps (TRS/GH/iRewardify untested, SEAGM money-mapping fixed-but-unverified, the triple FailureInfo declaration, the untested Svix verifier).
The test layer below the orchestration suites — 26 build-tagged files colocated with the vendor adapters under services/external_vendors/ (~7,100 LOC, 8 packages). These exercise a single adapter method (CreateOrder, GetOrderStatus, mappers) rather than a full DB-backed order. Companion to the Test Catalog; the mock they hit is mocky-balboa.
Build-tag matrix
| Kind | Build tag | Runs against | In CI? |
|---|---|---|---|
| Unit / mapper | none | pure in-process / httptest — no network, no DB | ✅ default matrix |
| Contract | //go:build contract | real HTTP to mocky-balboa (seeded) | ✅ vendor-contract-tests job |
| Soak | //go:build !sandbox (+ goleak TestMain) | octopusfake, concurrent load + goleak | ✅ default build (-short skips body) |
| Sandbox | //go:build sandbox | the real Octopus sandbox | ❌ manual/opt-in only |
| Fuzz | none (FuzzXxx) | pure parser fuzzing | corpus in matrix; -fuzz manual |
CI's vendor-contract-tests job runs go test -race -tags=contract -timeout=10m ./services/external_vendors/... with MOCKY_BASE_URL=https://mocky-balboa.octopuscards.io, parsing per-vendor --- PASS: Test<V>_Contract lines. Sandbox tags are never run in CI. The !sandbox soak gate is deliberate — the live-sandbox test opens a keep-alive connection the test can't close, which would trip goleak.VerifyTestMain; scoping goleak to !sandbox keeps the leak check on the deterministic in-process tests only.
Per-vendor coverage
| Vendor | Adapter | Test files | Kinds |
|---|---|---|---|
| Runa | vouchers/runa_vendor.go | runa_contract_test.go | contract |
| NeoCurrency | vouchers/neo_vendor.go | neo_contract_test.go, neo_chaos_contract_test.go | contract (deepest suite) |
| Wupex (voucher) | vouchers/wupex_vendor.go | wupex_vendor_test.go, wupex_contract_test.go | unit + contract |
| OCTO (self-voucher) | vouchers/octopus_vendor.go | octopus_vendor_test.go, octopus_sandbox_test.go | unit + sandbox |
| EpinForce | vouchers/epinforce_vendor.go | epinforce_contract_test.go | contract |
| iRewardify | vouchers/irewardify_vendor.go (1101 L) | none | — |
| TRS | vouchers/trs_vendor.go (879 L) | none | — |
| Grasshopper (GH) | vouchers/grasshopper_vendor.go (616 L) | none | — |
| DTOne (topup) | direct_topup/dtone/ | dtone_contract_test.go, error_messages_test.go | contract + mapper |
| SEAGM | direct_topup/seagm/ | seagm_contract_test.go, error_messages_test.go | contract + mapper |
| Wupex (topup) | direct_topup/wupex/ | error_messages_test.go | mapper only — no contract |
| OCTO_TOPUP (self) | direct_topup/octopus/ | vendor + orders + soak + sandbox + fuzz | richest self-vendor |
| DTOne eSIM | esim/dtone/ | dtone_esim_vendor_test.go, error_messages_test.go | fixture-unit + mapper — no contract |
| OCTO_ESIM (self) | esim/octopus/ | vendor + soak + sandbox + fuzz | 30-test vendor file (largest) |
| octopuscommon | shared self-vendor | token_manager_test.go, guard_test.go | unit + fuzz |
Highlights. Neo is the reference: neo_chaos_contract_test.go runs the full failure matrix and asserts the classification (retryable-PENDING vs terminal-FAILED via expectRetryablePending/expectTerminalFailure), plus 401 auto-refresh and the _R1 retry-suffix-stripping idempotency hash. The OCTO self-vendors carry the deepest unit coverage — self-loop guards, token cache + 401-reauth + refresh-on-expiry, composite-id variant mapping, ErrDuplicateMerchantRef, recover-by-reference → ErrOrderNotFound, HMAC webhook verification, and concurrent-idempotent create (-race, exactly one wins). octopuscommon/token_manager_test.go proves single-flight login (50 concurrent callers → 1 login).
Contract tests, specifically
Each contract file is gated //go:build contract and points the adapter at a per-vendor mock URL via testhelpers.MockyVendorURL(vendor) (resolves $MOCKY_BASE_URL, default localhost:8788). Credentials/SKUs are hardcoded constants that must stay in lockstep with frontend/mocky-balboa/seed.sql. There's no shared per-package harness — each file has its own newXxxContractVendor(t) + a referenceCode(t) uniqueness helper (nanos suffix so reruns don't collide on the idempotency cache).
The pattern is auth → catalog → create → status/vouchers at the adapter-method level. EpinForce is the most elaborate, driving mocky's /_mock/complete|fail|auto-fulfill seams to make create→poll→complete deterministic and asserting ErrShouldReCreate on Failed/404. Chaos here uses the mockychaos client (ResetForTest then ArmForTest one-shots keyed by vendor+method+path); delay_then_fail tests assert elapsed ≥1.5s to prove the rule fired. Only the Neo suite asserts the full classification — the thinner Runa/Wupex/SEAGM/DTOne contract files mostly assert "error propagated."
Soak tests
Two goleak soak tests, self-vendor only: direct_topup/octopus/octopus_soak_test.go and esim/octopus/octopus_esim_soak_test.go — 32 workers each doing create+status over one shared client/TokenManager against octopusfake, arming one-shot 401s to force re-auth under concurrency, asserting every created order eventually delivers (created == delivered). Each TestMain calls goleak.VerifyTestMain(m) so a surviving goroutine (e.g. a TokenManager refresh timer that never stops) fails the package. They skip under -short. No third-party adapter has a soak test.
Gaps
Third-party voucher adapters with zero adapter-level tests
iRewardify (1101 L — its source even carries a // You'll need to add this to types package TODO), TRS (879 L), and Grasshopper/GH (616 L) have no unit, contract, sandbox, or mapper test. GH is exercised only indirectly as the mint stub inside the G2A orchestration suite.
- Missing contract tests: Wupex topup (mapper only — no request-building/parsing test at all) and DTOne eSIM (fixture-unit + mapper, but no
contracttest — unlike DTOne topup). - SEAGM money-mapping — fixed in source, untested. The adapter now populates
MinAmount/MaxAmount/VendorDiscountfromunitPrice(seagm_vendor.go:837, with a comment explaining the earlier ~60× overcharge from usingpar_value). But no test asserts it — the contractGetProductssubtest only checkslen(Products) != 0. The exact regression that caused the overcharge is uncovered at the adapter level. - The
FailureInfotype is declared three times —types/failure_info.go:13(canonical) plus independent copies indirect_topup/dtone/error_messages.go:26andesim/dtone/error_messages.go:23. A change to the canonical contract won't be caught by the two DTOne copies' compilers (see error-codes). - The
types/package has no test of its own —failure_codes/failure_info/commonand the sentinels (ErrShouldReCreate,ErrDuplicateMerchantRef,ErrOrderNotFound) are exercised only indirectly through the per-vendorerror_messages_test.gofiles. manager.goand the three factories have no unit tests — the factory is exercised only via the manualsandbox-tagged OCTO test.payouts/,webhooks/(incl.svix_verifier.go), andvendorhttp/have no test files at all — the Svix webhook verifier is untested; only the OCTO self-vendors' inline HMAC-SHA256 verification is covered.- No
testdata/anywhere underexternal_vendors/— all fixtures are inline string literals; contract fixtures live remotely in mocky'sseed.sql.
Key files
- Contract:
services/external_vendors/{vouchers,direct_topup/{dtone,seagm},esim/dtone}/*_contract_test.go - Self-vendor:
services/external_vendors/{direct_topup,esim}/octopus/*_test.go,octopuscommon/*_test.go - Mappers:
.../{dtone,seagm,wupex}/error_messages_test.go; typesservices/external_vendors/types/ - Harness:
test/testhelpers/mocky.go,test/testhelpers/mockychaos/client.go
Test Catalog
The complete inventory of test cases across every system — the 291 Go suites (150 repo with the three-branch sqlmock/faultDB/cachedCache harness, the 10 vendor orchestration suites / ~226 subtests, unit/utils/email), the 25 Grasshopper vitest files (~330 cases), what CI actually runs vs skips, the notable security assertions, and the concrete coverage gaps.
Mocky-Balboa — Vendor Mock
The Cloudflare Worker (Hono + D1) that impersonates all seven third-party vendor APIs so the orchestration and contract tests run without touching live vendors — its per-vendor emulation, async-delivery modes (Svix/signed/unsigned webhooks vs poll), the chaos engine and its MustHaveFired self-deletion contract, the D1 schema and seeded credentials, deploy, and the shared-state fragility.