This document defines *how we write and refactor tests* in Odysseus. It is the
standard that the incremental test-suite refactor (issue #2523) works toward,
and it applies to both human contributors and coding agents.
It is intentionally split from [`tests/README.md`](./README.md):
- **`README.md`** - the concrete, current helper reference: what each helper in
`tests/helpers/` does and how to call it.
- **`TESTING_STANDARD.md`** (this file) - the rules and taxonomy: what a good
test looks like, where it belongs, and the policy refactor PRs must follow.
When the two ever disagree, this file states the *intent* and `README.md` states
the *current mechanics*; fix whichever is stale.
This document changes no test behavior. It is guidance only.
## What the test suite is for
The goal is not only to reorganize `tests/`. The goal is for the suite to be a
reliable foundation for future development: deterministic, modular, informative,
behavior-focused, and complete enough to replace manual QA wherever practical.
Run tests with the project virtualenv interpreter (`.venv/bin/python -m pytest`).
The system `python3` may be missing pinned dependencies (e.g. `nh3`), which
shows up as import/collection errors that are environmental, not real failures.
## What "done" means for a single test
Every new or refactored test should be:
- **Deterministic** - same result every run, no reliance on wall-clock, network,
RNG seeds, or collection order.
- **Behavior-first** - asserts on observable behavior, not on the source text or
AST of the code under test (see [Behavioral-first policy](#behavioral-first-policy)).
- **Explicit** - setup and expected result are visible in the test, not hidden in
broad fixtures.
- **Isolated from global process state** - no leaked `sys.modules`, `os.environ`,
CWD, or package parent-attribute mutation (see [Determinism & isolation](#determinism--isolation-rules)).
- **Order-independent** - passes regardless of which tests ran before it.
- **Environment-independent** - does not assume a venv layout, a developer's home
directory, an existing `./data` dir, or optional packages that may be absent.
- **Informative on failure** - the assertion message or structure makes the cause
obvious without a debugger.
- **Small** - understandable quickly; one behavior per test where practical.
- **Backed by shared helpers only when duplication is proven** - not abstracted
preemptively.
## Test taxonomy
Tests are classified by the categories below. Today the suite is flat under
`tests/`; the **Target dir** column is the phased layout from #2523 that we move
toward *after* helpers and determinism are stable. Until a category is moved,
new tests in that category stay in flat `tests/` but should still follow this
standard.
| Category | What it covers | Examples today | Target dir |
|---|---|---|---|
| **Route / API integration** | Real ASGI request/response, auth gates, admin gates, owner isolation through the app | files using `TestClient` | `tests/routes/` |
| **CLI / script** | `scripts/` entry points and dev tooling | `tests.helpers.cli_loader.load_script` users, `test_pr_blocker_audit.py` | `tests/cli/` |
| **Frontend / JS** | Browser-coupled JS run via Node subprocess; streaming-render invariants | `*_js.py` wrappers, `tests/streaming/*.test.mjs` | `tests/js/` |
| **Session / history / DB** | Session lifecycle, history, schema, ownership at the data layer | `test_session_*`, `test_sqlite_foreign_keys.py` | `tests/services/` or `tests/unit/` |