Test Conventions¶
Running tests¶
uv run pytest # all tests
uv run pytest tests/test_db.py # single file
uv run pytest -k test_supersession_links # single test
LLM mocking¶
All tests mock LLM calls via unittest.mock.patch. Never make real LLM calls in tests.
Ingest tests — patch at lattice.ingest.complete:
from unittest.mock import patch
with patch("lattice.ingest.complete") as mock_complete:
mock_complete.side_effect = [
'{"atoms": [{"kind": "fact", "subject": "test", "content": "A fact."}]}',
'{"superseded_atom_ids": []}',
]
result = ingest("A fact.", cfg)
Every atom requires two LLM responses: (1) the extraction JSON, (2) the supersession reply. Supersession reply format: '{"superseded_atom_ids": []}' or '{"superseded_atom_ids": ["<id>"]}'.
Conversation tests (classify_intent, reformulate_capture) — patch at lattice.conversation.complete:
with patch("lattice.conversation.complete") as mock_complete:
mock_complete.return_value = "recall"
result = classify_intent("what do I like?", cfg)
Synthesis tests — patch lattice.synthesis.make_llm_client (returns a mock OpenAI client):
from unittest.mock import MagicMock, patch
mock_client = MagicMock()
mock_client.chat.completions.create.return_value = ...
with patch("lattice.synthesis.make_llm_client", return_value=mock_client):
result = synthesize(atoms, "question", cfg)
Claude model paths — patch lattice.llm._anthropic_complete for Anthropic SDK dispatch.
Config in tests¶
Use Config(lattice_dir=tmp_path) directly — no monkeypatch.setenv needed:
def test_something(tmp_path):
cfg = Config(
lattice_dir=tmp_path,
llm_provider="ollama",
llm_model="test-model"
)
db = LatticeDB(cfg)
...
Fixtures¶
tmp_path— pytest built-in; provides a fresh temp directory per test- Use function-level isolation — don't share
LatticeDBinstances between tests
What not to test¶
- Don't test LLM output quality — mock the LLM, test the pipeline around it
- Don't test that
complete()retries on 429 — that's tested intest_llm.py - Don't write tests that require real filesystem paths outside
tmp_path