Skip to content

Testing Guide

Valter uses pytest with pytest-asyncio for async test support. The test suite spans 64 files with approximately 12,200 lines of test code, covering unit, integration, regression, and MCP tests.

All test commands use Make targets:

Terminal window
make test # Run the full test suite (660+ tests)
make test-cov # Run with coverage report (term-missing format)
make test-neo4j-live # Run Neo4j integration tests (requires Aura credentials)
make lint # Check code style (ruff check + format verification)
make quality # Run lint + mypy (scoped) + tests in sequence

To run a specific test file or test function:

Terminal window
pytest tests/unit/test_graph_routes.py -v
pytest tests/unit/test_features_search.py::test_empty_results -v

Test configuration lives in pyproject.toml:

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"

Key settings:

  • testpaths: pytest only discovers tests under tests/
  • asyncio_mode = "auto": all async def test_* functions run as async tests automatically, without needing the @pytest.mark.asyncio decorator
  • Global fixtures: defined in tests/conftest.py, including settings (test-safe Settings instance) and live_graph_store (Neo4j Aura connection, skipped if credentials are missing)

Unit tests cover core logic, route handlers, stores (mocked), authentication, and models. They run without any external service dependencies.

Key test files:

FileTestsCoverage
test_graph_routes.py~162Graph API endpoints (divergences, optimal argument, etc.)
test_features_search.py~40Hybrid search, filtering, ranking
test_retriever.py~27Retriever logic, KG boost, query expansion
test_mcp_tools.py~28MCP tool registration and validation

Pattern: all external dependencies (stores, Redis, Neo4j, Qdrant) are mocked using unittest.mock.AsyncMock or MagicMock. No live service connections in unit tests.

Integration tests run against a live Neo4j Aura instance and verify end-to-end graph query behavior.

FilePurpose
test_kg_live_graph.pyKnowledge graph query correctness
test_retriever_kg_live.pyRetriever with real KG boost
test_graph_store_live.pyGraph store methods against real data

These tests skip automatically if the Neo4j environment variables (VALTER_NEO4J_URI, VALTER_NEO4J_USERNAME, VALTER_NEO4J_PASSWORD) are not set. To run them:

Terminal window
export VALTER_NEO4J_URI=neo4j+s://xxxxx.databases.neo4j.io
export VALTER_NEO4J_USERNAME=neo4j
export VALTER_NEO4J_PASSWORD=your_password
make test-neo4j-live

Regression tests guard against quality regressions in search and graph results:

  • Golden questions: known-good search queries with expected result characteristics
  • KG quality CI: knowledge graph consistency checks (node/relationship counts, expected patterns)
  • Parity tests: verify compatibility between Valter’s API responses and the Juca frontend’s expectations

MCP tests cover all 28 MCP tools, verifying:

  • Tool registration and metadata
  • Parameter validation (required fields, types, constraints)
  • Response format compliance with the MCP protocol
  • Error handling for invalid inputs

Follow the Arrange-Act-Assert pattern:

async def test_divergences_returns_empty_for_unknown_ministro():
# Arrange
mock_store = AsyncMock()
mock_store.find_divergences.return_value = []
# Act
result = await get_divergences(
request=DivergenceRequest(ministro="Unknown", tema="civil"),
graph_store=mock_store,
)
# Assert
assert result == []
mock_store.find_divergences.assert_called_once_with("Unknown", "civil")

Tests must verify behavior, not just schema structure:

  • Test the actual logic and post-processing, not only that the response has the correct fields
  • Test edge cases: empty results, filters that exclude everything, boundary values
  • Test error paths: what happens with invalid inputs, missing data, service failures
  • Test inputs with accents, mixed case, and special characters (relevant for Brazilian legal data)

AsyncMock for async store methods:

from unittest.mock import AsyncMock, MagicMock
# Mock an entire store
mock_doc_store = AsyncMock()
mock_doc_store.get_by_id.return_value = sample_document
# Mock a specific method with side effects
mock_graph_store = AsyncMock()
mock_graph_store.find_divergences.side_effect = ConnectionError("Neo4j unavailable")

Dependency injection override in route tests:

from fastapi.testclient import TestClient
from valter.api.deps import get_graph_store
app.dependency_overrides[get_graph_store] = lambda: mock_graph_store
client = TestClient(app)
response = client.post("/v1/graph/divergences", json={"ministro": "Test"})

Settings fixture from conftest.py:

@pytest.fixture
def settings():
return Settings(
_env_file=None,
ENV="test",
DATABASE_URL="postgresql+asyncpg://test:test@localhost:5432/valter_test",
NEO4J_URI="bolt://localhost:7687",
NEO4J_USERNAME="neo4j",
NEO4J_PASSWORD="test",
REDIS_URL="redis://localhost:6379/1",
)

The _env_file=None parameter prevents the test Settings from loading .env values, ensuring test isolation.

  • Test files: test_<module_name>.py
  • Test functions: test_<behavior_description>
  • Place unit tests in tests/unit/, integration tests in tests/integration/, regression tests in tests/regression/

Current test coverage by category:

CategoryFilesLinesScope
Unit57~10,000Core logic, routes, stores (mocked), auth, models
Integration3~350Neo4j Aura live queries
Regression3~130Golden set, KG quality, Juca parity
MCP1~1,70028 tools + handlers + validation
Load00Placeholder (Locust available in dev deps)
Total64~12,200

Generate a coverage report:

Terminal window
make test-cov

This produces a terminal report with --cov-report=term-missing, showing which lines lack test coverage.

The following modules have limited or no direct test coverage:

ModuleStatusNotes
stores/document.py (PostgresDocStore)No direct testTested indirectly via route tests
stores/vector.py (QdrantVectorStore)No direct testTested indirectly via retriever tests
stores/cache.py (RedisCacheStore)No direct testTested indirectly via route tests
stores/artifact_storage.pyNo testR2/local artifact storage
api/middleware.pyNo direct testAuth, rate limiting, CORS middleware
tests/load/EmptyPlaceholder for Locust load tests

These gaps are known. Store modules are exercised indirectly through route and retriever tests that mock them, but dedicated unit tests for each store would improve confidence in edge case handling.