--- name: pytest description: Instructions for running, writing, and maintaining tests in this project --- ## Running tests ```bash uv run pytest --no-selenium # all tests with coverage (slow) uv run pytest --no-selenium --no-cov # skip coverage (much faster for local work) uv run pytest ca/django_ca/tests/test_models.py # single file uv run pytest ca/django_ca/tests/test_models.py::Cls::method # single test uv run pytest -k "test_something" # keyword filter ``` When writing/updating tests, it is sufficient to test the added/updated tests. When updating fixtures, you can also only run the affected module. ## Critical test quirks - `--cov-fail-under=100` is in `addopts` — 100% coverage is enforced by default. New code must be covered or use `# pragma: no cover` (version-conditional variants like `# pragma: only cryptography>46` also exist). - Tests run in **random order** by default (`pytest-random-order`). - `DJANGO_SETTINGS_MODULE=ca.test_settings` is set automatically by pytest-env. - `ca/` and `docs/source/` are on `pythonpath` via pytest config — no need to set `PYTHONPATH` manually when running pytest. - `cert-data.json` is loaded at `test_settings.py` import time; it must exist before any test run. Regenerate with `uv run python dev.py recreate-fixtures`. - Selenium tests auto-skip unless on newest Python + cryptography + pydantic combo. Require Firefox + `contrib/selenium/geckodriver`. - HSM tests require `softhsm2` (`apt-get install softhsm2`). Skip locally: `--no-hsm`. - Crypto-related tests use pre-generated fixtures in `ca/django_ca/tests/` - Do not regenerate fixtures unless explicitly asked. ## Test environment conventions - **`COLUMNS=80`** is set in pytest env to make argparse output deterministic. - **Pydantic models pre-imported** in `pytest_sessionstart` to avoid freezegun/Pydantic metaclass conflicts — do not move that import. - **`CA_DIR = "/non/existent"`** in test settings — tests needing a real CA dir must use `override_settings` or a temp-dir fixture. ## Writing tests - Tests are defined in `ca/django_ca/tests/`. - Tests are grouped into submodules, e.g. `ca/django_ca/tests/models/test_.py` for Django models. - New tests are implemented as pytest-style functions, not `unittest.TestCase` classes. - Class based unit tests are allowed for grouping, but pytest fixtures should be used instead of `setUp`/ `tearDown`. ### Fixtures This section provides generic instructions how to use fixtures. - Always use pytest fixtures instead of `setUp`/`tearDown`-style functions. - Globally available fixtures are defined in `ca/django_ca/tests/base/fixtures.py`. - Fixtures required only by special cases (e.g. a special HTTP client used in many admin tests), define it in a `conftest.py` in the test submodule. If only a single `test_*.py file` needs it, define it in that file. - A constant instead of a fixture can be used for trivial values, never change and are not stored in the database. Store them in `ca/django_ca/tests/base/constants.py` in this case. - When using fixtures in the function definition, please use the following order: 1. Standard pytest fixtures (e.g. `tmpdir`). 2. Fixtures defined by plugins (e.g. `db`, `settings`, `client`). 2. Fixtures defined in `conftest.py` (e.g. `db`, `settings`, `client`). - If a fixture value only needs to be requested, use `request.getfixturevalue("fixture_name")` instead of adding it to the function signature to avoid linter warnings. - The project defines a few dynamically created fixtures, they are documented in `/home/mertl/git/mati/django-ca/docs/source/dev/testing.rst`.