Framework

  • pytest + pytest-asyncio (asyncio_mode = "auto" — no @pytest.mark.asyncio needed)
  • Test root: backend/tests/
  • Shared fixtures: backend/tests/conftest.py

File Naming

One test file per module: test_{module_name}.py

test_agents.py          # Agent output validation
test_config.py          # BrainConfig field validation
test_ingest.py          # VaultIngester classify/parse/ingest
test_mcp_server.py      # MCP tool input validation + routing
test_schemas.py         # Pydantic schema instantiation
test_services.py        # Service layer unit tests
test_parsers.py         # Parser functions

Test Patterns

# Unit test — mock services, test logic in isolation
async def test_classify_file_client_transcript(tmp_path):
    config = BrainConfig(...)
    ingester = VaultIngester(config)
    filepath = tmp_path / "clients" / "acme" / "transcripts" / "call.md"
    meta = ingester.classify_file(filepath)
    assert meta.category == "transcript"
    assert meta.client == "acme"
 
# Config validation test
def test_config_rejects_invalid_transport():
    with pytest.raises(ValidationError):
        BrainConfig(mcp_transport="invalid", ...)

Running Tests

# All tests
pytest backend/tests/
 
# Verbose with output
pytest backend/tests/ -v -s
 
# Single file
pytest backend/tests/test_ingest.py
 
# Single test
pytest backend/tests/test_config.py::test_validate_graph_config

What to Test

  • Config: field defaults, validators, env var loading
  • Schemas: model instantiation with required fields
  • Services: core logic with mocked external clients
  • Agents: output_validator logic, tool error handling
  • MCP tools: input validation (_validate_mcp_input), timeout handling