> ## Documentation Index
> Fetch the complete documentation index at: https://engineering.unkey.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Integration tests

> Testing components with real dependencies

## When to use integration tests

Use integration tests to cover behavior across service boundaries, real databases, caches, and storage. These tests catch failures that mocks miss.

Integration tests must document the cross-boundary guarantee they protect. Make it clear which production contract would be broken if the test failed, such as transaction rollback, idempotency, permission propagation, cache invalidation, or persistence after restart.

```go theme={"theme":"kanagawa-wave"}
// TestCreateKeyRollsBackAuditLogOnDatabaseFailure guarantees that key creation
// and audit logging remain atomic. Operators must not see an audit log for a
// key that was never committed.
func TestCreateKeyRollsBackAuditLogOnDatabaseFailure(t *testing.T) {
    // ...
}
```

## Container patterns

Use `pkg/testutil/containers` for shared test containers. Helpers lazily start
containers through `pkg/testutil/docker-compose.test.yaml` and reuse one Docker
Compose project per worktree, so tests only start the services they need.
`mise run test` removes the shared containers for that worktree after the test
suite exits.

Restate is the exception to container reuse. `containers.Restate` starts a
server per test and removes it afterwards, because Restate identifies services
by name and those names come from protobuf packages. Two tests registering
their own workers on one server would overwrite each other's routing and share
virtual object state.

A private Restate costs about a second to start. Since Go runs a package's
tests sequentially, at most one such container per test binary is alive at a
time.

To inspect a failure, set `UNKEY_TEST_KEEP_RESTATE=1`. The container of a
failed test is left running and its admin URL is logged, so the invocation
journal and state stay queryable:

```bash theme={"theme":"kanagawa-wave"}
UNKEY_TEST_KEEP_RESTATE=1 mise exec -- rask ./svc/ctrl/worker/cron
```

Containers left behind by a test process that was killed are removed by the
next run.

For full-suite runs, use `mise run test`. Direct `rask` does not run the
cleanup trap from the mise task.

```go theme={"theme":"kanagawa-wave"}
redisURL := containers.Redis(t)
```

## Test harness

Use `pkg/testutil` when you need a full service graph and seeded data:

```go theme={"theme":"kanagawa-wave"}
h := testutil.NewHarness(t)
workspace := h.CreateWorkspace()
```

Keep harness setup explicit when it affects the guarantee. Do not hide permissions, feature flags, tenants, or seeded records behind generic helpers unless the helper name or docstring explains the resulting system state.

## Suite cost

Integration tests that start containers must use the shared container helpers.
Keep expensive setup explicit so readers can see why the test belongs in the
integration suite.

## Debugging failures

Use verbose output for failing tests:

```bash theme={"theme":"kanagawa-wave"}
mise exec -- rask -v ./pkg/vault/integration
```
