Goals
Good tests in this repo should be:- Hermetic: no real network, Docker, user config, or external services.
- Behavioral: assert user-visible behavior or service contracts, not incidental internals.
- Small: one route group, command, service, or behavior area per file.
- Explicit: setup is visible in the test or in small local helpers.
- Fast: sleeps, external calls, and expensive setup are mocked at the boundary.
File layout
Use this structure for most new test files:- SPDX header
- Module docstring
from __future__ import annotationswhen useful- Standard library imports
- Test library imports
- App imports needed for the test harness
- Local helper factories
- Test classes or test functions
File scope
Prefer one file per behavior area:tests/test_agent_name_lookup.pyfor agent lookup behaviortests/test_component_versions_api.pyfor component version endpointsobserval_cli/tests/test_cmd_scan.pyfor thescancommandobserval-server/tests/test_jwt.pyfor JWT service behavior
Imports
Use top-level imports for the test harness:- Standard library modules
pytestunittest.mock- Test clients such as
AsyncClient,ASGITransport, andCliRunner - Models, schemas, routers, and dependency functions needed by local helpers
Helpers and fixtures
Prefer small helper functions for local setup:pytest.fixture when pytest lifecycle behavior is helpful:
- Temporary directories through
tmp_path - Environment isolation through
monkeypatch - Cleanup with
yield - Shared setup used by many files through
conftest.py
Test body shape
Use a simple arrange, act, assert flow with blank lines between phases:Naming
Use behavior names:Mocking
Mock boundaries, not the behavior under test. Good boundaries to mock:- HTTP clients and webhooks
- Database session methods
- Filesystem writes when the write itself is not under test
- Sleep and time delays
- Auth providers
- External CLIs and subprocesses
- Pydantic schemas
- Small pure functions
- The function being tested
- Several layers of internal implementation at once
AsyncMock for async methods and MagicMock for sync methods. Use spec when mocking model objects:
Async tests
Mark async tests explicitly:API route tests
Use a small FastAPI app with dependency overrides. Do not boot the full application unless the integration boundary is the thing being tested.CLI tests
CLI tests should not read or write the real home directory. Redirect home and current working directory to a temp path:result.output in exit code assertions so failures are easy to diagnose.
Parametrize
Use parametrization for the same behavior across multiple inputs:Property-based tests
hypothesis is available for invariants that should hold over many inputs. Use it for pure logic, parsers, serializers, redaction, and validation edge cases.
Fuzzing
Property tests cover invariants you can name. Fuzzing covers the ones you cannot: the Atheris targets live infuzz/, and the project is being submitted to Google OSS-Fuzz for continuous fuzzing.
Reach for a fuzz target instead of a property test when the boundary takes untrusted bytes and the interesting failures are crashes rather than wrong answers. Session transcripts, the ingest classifiers, and both redaction layers are covered today.
fuzz/README.md covers adding a target, seed corpora, dictionaries, and the OSS-Fuzz project configuration.
Assertions
Use plainassert. Assert the public result before internal interactions:
Test directories
Use the existing directories:tests/for cross-cutting backend, CLI, and integration-style unit testsobserval-server/tests/for server-focused tests that live with the server packageobserval_cli/tests/for CLI command and CLI package teststests/e2e/for Playwright tests that require the running stack
tests/conftest.pyadds the server source path for imports.observal-server/tests/conftest.pysets test environment variables and shared auth fixtures.
conftest.py only when setup is useful across several files.
Running tests
Use the make targets for normal workflows:observal-server when iterating on one file:
make lint and make format before pushing Python changes. Use make check when you need the full pre-commit suite.
Formatting and lint notes
The rootpyproject.toml relaxes a few lint rules for tests. Those ignores are for practical mock setup, not a reason to leave tests messy.
Current per-file ignores include:
- Unused mock variables are allowed in test files.
- Some server tests can use guarded imports after setup.
- Tests should import feature modules from their normal server package paths.
- New tests should still prefer clear imports, typed helpers, and small setup.
Review checklist
Before submitting a test change, check that:- The test fails without the product fix or covers a meaningful invariant.
- External services are mocked or replaced with local fakes.
- Real user config and real home directories are not touched.
- The test name states the behavior.
- Helpers are local unless they are reused across files.
- Async tests use
AsyncMockfor awaited methods. - CLI tests include command output in exit code assertions.
- API route tests override dependencies in a local FastAPI app.
- Focused tests pass locally.

