Testing
=======
The test suite is organized in tiers. Unit tests run everywhere with no
services; the integration tiers need the Docker Compose stack.
.. mermaid::
flowchart TB
subgraph tiers[Test tiers]
direction TB
U["Unit
pytest -m 'not integration'
mocked clients, fast"]
I["Integration
live ES / OpenSearch / MongoDB"]
F["Fault injection
real TCP faults via Toxiproxy"]
M["Mutation
mutmut, weekly + on demand"]
U --> I --> F
M -.->|hardens| U
end
Unit tests
----------
.. code-block:: bash
make test # pytest -m 'not integration'
Covers signature generation, record/word encoding, driver logic with mocked
backend clients, and negative paths — backend connection/write/delete
failures, malformed responses, worker and cursor errors, invalid inputs, and
migration failure modes (``tests/test_faults_unit.py``).
Integration tests
-----------------
.. code-block:: bash
make test-integration-local # starts services, runs tests, stops them
Runs against live Elasticsearch, OpenSearch 2.x/3.x and MongoDB. This
includes the fault-injection tier (``tests/test_faults_integration.py``),
which routes a driver through a `Toxiproxy `_
proxy and injects real transport faults — proxy kill, latency, connection
reset — that the mock layer cannot reproduce:
.. mermaid::
flowchart LR
T["pytest"] -->|search / insert| D["image-match driver"]
D -->|"HTTP :22300"| P["Toxiproxy proxy"]
P --> OS["OpenSearch :9201"]
A["Toxiproxy API :8475"] -.->|"toxics:
latency, reset_peer, down"| P
``make db-up`` starts toxiproxy alongside the databases (API on ``:8475``,
dynamic proxy listeners on ``:22300-22310``).
Mutation testing
----------------
`mutmut `_ generates mutants of
``src/image_match/`` and ``tools/migrate_to_knn.py`` and runs the unit suite
against each. It is deliberately not part of PR validation — a full run takes
a while — so it runs weekly and on demand via the ``mutation`` workflow:
.. code-block:: bash
make mutation # mutmut run + results
``mutmut run`` is incremental: re-running only re-tests mutants in functions
whose source changed. Lines that can never change behaviour (CLI help text,
``print`` reporting, the vestigial ``handle_mpo`` parameter, ``typing.cast``)
are excluded via ``do_not_mutate_patterns`` in ``pyproject.toml``.