# Testing overview How DevPlace verifies itself: an end-to-end browser suite, in-process unit tests, and a load-testing rig, all driven through the project `Makefile`, measured for coverage, and promoted through automated DTAP streets. See also [Test framework and rules](/docs/testing-framework.html), [Load testing with Locust](/docs/testing-locust.html), [Running tests via make](/docs/testing-make.html), and [Coverage and CI/CD](/docs/testing-cicd.html). ## Three tiers, a directory tree that mirrors the path The `tests/` directory holds the correctness suite: roughly 1,959 tests in three category directories by *what each test exercises*. Within each tier the **directory structure mirrors the path** - one segment per directory, the last segment is the file: - **`tests/api/` - HTTP integration tests.** `requests`/`httpx` drive a real Uvicorn server (`app_server`) and assert on status, JSON, and headers without a browser. **Organised by endpoint path.** - **`tests/e2e/` - end-to-end (Playwright).** A real Chromium browser drives the same Uvicorn server, covering anything a user sees or clicks. **Organised by endpoint path.** - **`tests/unit/` - in-process unit tests.** Pure tests over functions and data helpers (the `local_db` fixture or none), no running server. **Organised by the source module path** of the code under test. A test's tier is chosen by its fixtures: a browser fixture (`page`/`alice`/`bob`) makes it e2e; `app_server`/`seeded_db` or any HTTP call makes it api; `local_db`-only or no fixture makes it unit. **api and e2e tests follow the endpoint's path:** each route segment becomes a directory and the final segment becomes the file, with `{param}` segments dropped and each segment lowercased + stripped of non-alphanumerics. So `GET /admin/ai-usage` becomes `tests/e2e/admin/aiusage.py`, `POST /auth/login` becomes `tests/api/auth/login.py`, and `GET /projects/{slug}/files/lines` becomes `tests/api/projects/files/lines.py`. A bare collection path that is also a parent of deeper paths uses `index.py` inside its own directory (`GET /posts` → `tests/e2e/posts/index.py`, beside `POST /posts/create` → `tests/e2e/posts/create.py`); `GET /` becomes `tests//root.py`. Multiple HTTP verbs for one path share a single file. **unit tests mirror the source module path:** tests for `devplacepy/utils.py` live in `tests/unit/utils.py`, and tests for `devplacepy/services/audit/store.py` live in `tests/unit/services/audit/store.py`. Every directory is a Python package (`__init__.py`). Files are not `test_`-prefixed (the discovery glob is widened to `*.py`); test *functions* still start with `test_`. All three tiers share one fixture stack (the root `tests/conftest.py`) and one ephemeral SQLite database, so a single `make test` runs everything, while `make test-unit`, `make test-api`, and `make test-e2e` run a single tier. ## The load layer `locustfile.py` does not assert correctness; it measures throughput and latency under concurrent traffic. It spins up its own server on its own port against a throwaway database, then floods it with simulated users. Run it from `make locust` (interactive web UI) or `make locust-headless` (CI-style, writes an HTML report). See [Load testing with Locust](/docs/testing-locust.html). ## Coverage and continuous delivery Every run is measured for coverage (`coverage.py` over the `devplacepy` package, including the Uvicorn subprocess that the browser tests drive), and the suite is wired into automated DTAP streets. `.gitea/workflows/test.yaml` runs the full suite under coverage on every push and pull request to `master`, publishes the coverage HTML as an artifact, and uploads failure screenshots; only commits that pass that gate are promoted from `master` to the `production` branch. The full coverage configuration, the Gitea pipeline steps, and the Development, Test, Acceptance, and Production streets are documented in [Coverage and CI/CD](/docs/testing-cicd.html). ## The cardinal rule **Every user-facing feature requires an end-to-end Playwright test.** A JSON endpoint or data helper may be covered by an in-process test alone, but anything rendered in the browser must be exercised through the browser. A feature is not complete until its E2E test passes. The framework conventions behind reliable tests are documented in [Test framework and rules](/docs/testing-framework.html).