Skip to main content

Testing

Which lane to run when, and the environment-shaped failures that are worth recognizing on sight rather than debugging from scratch.

Lanes

CommandRuns
make testBackend pytest with coverage + frontend Vitest. The everyday loop.
make test-smokeFast smoke tests — app boot, health, core API paths.
make test-backendAll backend pytest tests.
make test-frontendAll frontend Vitest tests.
make test-e2ePlaywright end-to-end specs.
make test-a11yAccessibility checks (axe-core, serious/critical).
make test-nightlyFull suite with coverage — what nightly CI runs.
make checkThe complete gate: lint, typecheck, test, build.

make check is what CI runs on a pull request. Run it before pushing.

Static analysis

make lint and make typecheck run more tools than most projects:

ToolChecks
ruffPython lint + format.
mypy, ty, pyreflyThree Python type checkers, all of which must pass.
vultureDead Python code (confidence ≥ 80).
eslint, prettierFrontend lint and formatting.
knipDead TypeScript exports and unused dependencies.
tscTypeScript types.

Dead-code detection is part of the gate, not advisory. If vulture flags a symbol you deliberately keep, add it to backend/vulture_whitelist.py rather than disabling the check.

Markers

Select or exclude backend tests by marker (--strict-markers is on, so unknown markers are an error, not a typo that silently passes):

MarkerMeaning
smokeFast, high-signal: app boot, health, core API paths. No external services.
dbRequires a live PostgreSQL instance.
postgresAlias for db.
perf_serialTiming-sensitive benchmarks; should run without xdist load.
slowExpensive; reserved for the nightly suite.
pytest -m smoke -v
pytest -m "not slow"

Tests run under pytest-xdist (-n auto) by default.

Environment-shaped failures

These three account for most "everything is broken" reports. Each has a distinctive signature.

Every DB test fails with InsufficientPrivilege

The tests are connected to the wrong PostgreSQL instance — typically a native server on 5432 instead of the dedicated nd-test-pg container on 55432. The connection succeeds, so this does not look like a configuration error; it fails on ownership instead.

Check DATABASE_URL and TEST_DATABASE_URL in .env. See Environment setup.

DiskFull errors, or Postgres drops into recovery

Leaked shared-memory segments in the test container's /dev/shm, caused by parallel query workers. This can crash PostgreSQL into recovery and fail 100+ unrelated tests, which makes it look like a code regression.

Disable parallel query workers before a full run:

export PGOPTIONS='-c max_parallel_workers_per_gather=0'

Every DB test fails with UndefinedTable in the truncate fixture

Orphaned tables from an abandoned branch are stuck in the shared test container. They are empty — drop them, or reset the container.

Route assertions

Feature-module routers mount lazily as _IncludedRouter objects. Assert against resolved OpenAPI paths rather than app.routes:

paths = app.openapi()["paths"]
assert "/api/sources/{slug}/enabled" in paths

Optional integrations

Neo4j graph features activate only when NEO4J_URI, NEO4J_USER, and NEO4J_PASSWORD are configured. Unit tests fake the graph boundary — do not make the normal suite require a live Neo4j.

When changing graph behavior, add tests around graph_store.py, entities.py, or the relevant frontend component, then run the targeted backend and Vitest suites.

Packaging tests

scripts/test_*.py assert on workflows, the Helm chart, and repository metadata — release sync, Postgres backup templates, CI deploy namespace, Trivy workflows, agent-skill sync, and others. Changing a workflow or chart template often means updating one of these.

Validate the chart directly with:

make helm-validate

which lints and renders it under default, production-like, and external-database value sets.