# Testing architecture

LuckyStack separates fast unit checks of package logic from integration checks of actual API and sync routes. The generated-route sweep tests cross-cutting contracts automatically; per-route tests cover business effects the sweep cannot infer. Neither layer replaces browser checks for pages or a real credentials/OAuth round-trip.

## What each layer proves

- Unit tests isolate small decisions behind replaceable dependencies; they do not prove Redis, database and socket wiring together. Live route tests exercise that wiring but require a healthy server and can modify real state.
- The sweep checks route contracts, authentication enforcement, rate limits and malformed input without hand-adding each route. A green sweep does **not** prove that the correct business record changed or another user's data stayed private; write a per-route test for those assertions.
- Negative tests pass when the expected rejection occurs. An expected-failure marker is only for an acknowledged defect, not a way to make a security rejection or failing regression look green. A skipped test is not evidence that the behavior passed.
- The test runner is a separate process from the server. Its secret resolution and client setup must be independent; a server that resolved a database pointer cannot resolve it for the runner. See [secret manager](./ARCHITECTURE_SECRET_MANAGER.md).
- Probes follow the project's own invocation transport. A socket-mode project's sync routes run through a separate server pipeline, so testing them over HTTP proved nothing about the path users hit. Sync sweeps and per-route sync calls therefore use Socket.io for socket projects and HTTP for routed projects. Sync probes join a throwaway room, so membership checks see what a real member sees. CSRF is the exception: it is an HTTP-only guard and is always probed over HTTP. Sweeping sync routes requires the generated sync metadata; without it, the runner warns and sweeps API routes only. See ADR 0064.
- The runner resets only rate limits between layers through the reset endpoint's scoped selector. The unscoped reset also deletes every Redis session, including the session an authenticated sweep runs on. That reset keeps its old meaning because CI setups rely on it to log users out between suites. A new reset target must be added to the shared target list in `@luckystack/core` so server and runner cannot drift. An authenticated sweep calls every route with its session, so exclude logout-type routes from the sweep (`TEST_SKIP`), or they end the session for the rest of the run.
- Integration tests can write to Prisma and Redis and do not auto-rollback. Select an isolated test environment, avoid shared or production state, and clean up test data where possible. Never run a live suite merely to validate documentation.

Use [the test-runner guide](https://github.com/ItsLucky23/LuckyStack-v2/blob/main/packages/test-runner/README.md) and the repository scripts for current test names, discovery conventions and invocation. When proving cross-instance Redis fan-out, require Redis explicitly so an unavailable backend cannot turn an integration check into a misleading skip; see [multi-instance architecture](./ARCHITECTURE_MULTI_INSTANCE.md).
