> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing strategy

> What runs by default, what needs Docker, and what every financial change owes.

## The default suite

`go test ./...` runs everywhere with no Docker daemon and no network. It covers the domain packages, view rendering, provider webhook parsing, backup encryption, and log redaction. Keep it that way: a suite that needs infrastructure to run is a suite that stops being run.

Alongside it CI runs TypeScript checking, frontend builds, Biome, Atlas validation, Mintlify validation, vulnerability scanning, and container builds.

## The integration suite

Tests under `internal/integrationtest` carry the `integration` build tag and start PostgreSQL through Testcontainers:

```bash theme={null}
go test -tags=integration -race -timeout=20m ./internal/integrationtest/...
```

The harness applies the committed migration files in order rather than a schema dump, so the suite is a migration gate as well as a repository gate. It covers:

* migrations applying from a bare database, and the v0.4 → v0.5 adoption of existing customers onto subscriptions;
* top-up crediting the wallet exactly once across a replayed webhook, a second event, and a reconciliation;
* overpayment and underpayment resolving in the ledger while the order still settles;
* a saved cart staying saved while short and being charged once when funded;
* concurrency limits holding under simultaneous purchases;
* the outbox and the operator notice being written once per settlement;
* maintenance blocking new orders while a paid order still settles;
* the durable fulfillment job committing in the settlement transaction;
* retention removing disposable rows and never an audit event;
* operator bootstrap creating exactly one owner, and the setup token being single-use;
* sign-in answering identically for an unknown address and a wrong password, lockout after repeated failures, and the session, TOTP, and recovery-code lifecycles;
* the last active owner surviving both role revocation and suspension, and only an owner granting the owner role;
* suspension revoking live sessions, a password change sparing only the session that made it, and a reset link working once;
* audit classification, both sort directions, and keyset pages that never repeat or skip a row.

`load_test.go` is part of the same suite. It is not a throughput benchmark: it drives the same settlement, cart purchase, and subscription creation from many goroutines at once, because a duplicated charge only appears when the same work arrives from several directions simultaneously.

Running the suite is the cheapest way to catch a class of defect that compiles
and unit-tests cleanly: a Go enum widened without its database `CHECK`, an expiry
computed from the application clock while its row's `created_at` comes from the
database's, or a constraint whose derived name was guessed wrong. Each of those
only fails against a real PostgreSQL.

## Migration upgrade gate

The `migrations` CI job runs once per supported baseline — the empty database plus each prefix of the migration history — applies the rest, then applies head a second time to prove it is a no-op. A migration that cannot run on top of a released version fails there.

## What a financial change owes

New payment, wallet, authentication, RBAC, import, and Remnawave fulfillment behaviour must include focused failure-path and idempotency tests from its first implementation. In practice that means proving, for the new path:

1. The same input applied twice produces one effect.
2. A partial failure leaves no half-applied state.
3. Every rejection carries a stable machine reason, not a formatted message.

## Provider fixtures

`internal/payments/testdata` holds recorded webhook shapes with every real identifier replaced. Replaying them keeps parsing and signature verification honest without a sandbox account. Never commit a real payment payload, token, or signature — synthesise one instead.

## Browser coverage

`apps/web/e2e` holds the Playwright suites, run with `bun run e2e` from `apps/web`. They run against a production build rather than the dev server: Next.js behaves differently in development, and a gate that passes against a build nobody ships is not a gate.

The suites cover what a component test cannot see, because it is a property of a rendered page rather than of a component — the accessibility, layout, and localisation gates — plus the access boundary of both panels: every operator and customer route refuses an anonymous visitor, and nothing behind a session gate paints before the session is known.

The operator journey behind the session gate is covered. CI applies the
migrations, starts a real API, redeems the one-time setup token to seed an
owner, and `operator-journey.spec.ts` signs in for real, proves the session
survives a navigation, and sweeps every panel page. Locally, set `PANEL_EMAIL`
and `PANEL_PASSWORD` against a running stack; without them that spec skips and
says so rather than pretending to pass.

A missing translation renders as a marker rather than as its own key — see
`apps/web/i18n/missing-message.ts` — and the sweep looks for exactly that. The
previous check searched for a dotted key, which cannot tell a missing message
from data: the audit log stores action names in the same shape, and the check
failed on a fully translated page the first time it ran against an API with rows
in it.

The customer journey is covered too, and needs no seeding at all: the panel
provisions an account on first sign-in, so `customer-journey.spec.ts` signs a
Telegram login-widget payload with the same bot token the API is configured with
and posts it through the web server's `/v1` proxy. Set `CUSTOMER_BOT_TOKEN` to
that token to run it locally. `internal/customerauth` carries an interop test
asserting the TypeScript signing and the Go verification agree — without it, a
mismatch would fail every run and get "fixed" by weakening the signature check
the suite exists to exercise.

> [!NOTE]
>
> Nothing behind a Remnawave entitlement is covered. The customer the suite
> creates has signed in and bought nothing, so the screens that need a
> subscription render their empty state rather than their populated one. That is
> tracked in the roadmap's verification debt for v0.10.

## Performance budgets

`bun --filter @omniflow/web perf:budget` runs against a finished `next build`
and fails when a route's first-load JavaScript, the stylesheet payload, or the
self-hosted fonts exceed their ceilings. CI runs it in the `web` job, straight
after the build it measures.

Sizes are uncompressed, which is what Next's own `route-bundle-stats.json`
reports and the more honest figure to gate on: compression hides a regression
that still costs a phone its parse and execute time.

Images, server response time, and field core web vitals are deliberately not
measured. They need a running installation with real content and real clients,
and a number invented from a local build would read as a gate that had passed.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.