Skip to content

Investigate scheduled mutation testing for the backend suite #169

Description

@simonvanlierde

Why

An ad-hoc sweep this week found tests that passed for the wrong reason: where several code paths
converge on one response, a test asserting only the status keeps passing after the guard it was
written for is deleted. Four such gaps were fixed in 033734f1, and the pitfall is recorded in
backend/tests/README.md.

That sweep was ad hoc and partial by construction. It matched literal
HTTPException(status_code=..., detail=...) constructs — 53 raise sites — while the codebase raises
through 113 custom exception classes (38 of them: BadRequestError, ConflictError,
DependentModelOwnershipError, ...) that the scan never saw. So roughly two thirds of the error
surface has not been checked.

A better regex is the wrong fix. This wants a real mutation-testing tool on a schedule, where a long
runtime does not matter.

What to investigate

  • mutmut vs cosmic-ray against backend/, run nightly or weekly rather than per-PR — the suite
    is ~16s but a full mutation run is hours.
  • Scope it to app/api/ and app/core/ first; skip generated code and migrations.
  • Decide how results surface: a failing scheduled job is noisy for a metric that will never reach
    zero. An artifact plus a tracked survivor count is probably better than a red build.
  • Baseline the survivor list so only new survivors need triage.

Prior findings to carry over

The last sweep left three survivors that are deliberate, not gaps — they should be baselined,
not "fixed":

  • images.py re-checks for empty bytes that validate_upload_size has already rejected. Kept as
    defence in depth on the untrusted device-upload path.
  • images.py raises when a stored preview thumbnail's URL cannot be computed.
  • account_security.py raises when the request context is missing.

Each guards a state that cannot currently occur, so a test for one would only pin a mock.

Reproducing the ad-hoc version

Replace a guard with if False: and run tests/unit tests/integration. A guard whose removal
breaks nothing is either untested, or covered only by an assertion another path also satisfies. Run
both tiers — several guards in routers and dependencies are only reachable from integration.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions