Development
This guide covers setting up a development environment and running tests. For how to report issues and open pull requests, see CONTRIBUTING.md.
Setup
Clone the repository, install dependencies and the pre-commit hooks:
Running Tests
Test Markers
Tests use pytest markers to categorize them:
@pytest.mark.integration- Tests requiring local services (Docling models, etc.) that aren't available in CI@pytest.mark.asyncio- Async tests (applied automatically via pytest-asyncio)@pytest.mark.vcr()- Tests with HTTP call recording
CI runs pytest -m "not integration" to skip integration tests.
HTTP Recording with VCR
Tests use pytest-recording (VCR.py) to record and replay HTTP calls. This allows tests to run without external services like Ollama or API providers.
How It Works
- Tests marked with
@pytest.mark.vcr()record HTTP interactions to YAML cassettes - On subsequent runs, HTTP calls are replayed from cassettes instead of hitting real services
- Cassettes are committed to the repository so CI can run tests without external dependencies
Recording New Cassettes
When adding a new test that makes HTTP calls:
- Add the
@pytest.mark.vcr()decorator to your test - Run the test with the required services available (e.g., Ollama running)
- The cassette is automatically created on first run
Re-recording Cassettes
To update an existing cassette, delete it and re-run the test, or use --record-mode=rewrite.
Running Without Cassettes (Live Mode)
To run tests against real services instead of recorded cassettes:
Writing Tests
Common Fixtures
Available fixtures from tests/conftest.py:
temp_db_path- Isolated temporary databasetemp_yaml_config- Temporary config fileallow_model_requests- Enables pydantic-ai model calls
Example: Adding a New Test with VCR
import pytest
from haiku.rag.client import HaikuRAG
@pytest.mark.asyncio
@pytest.mark.vcr()
async def test_my_feature(temp_db_path):
async with HaikuRAG(temp_db_path, create=True) as client:
doc = await client.create_document("Test content", uri="test://doc")
assert doc.id is not None
Integration Tests
For tests requiring local services that can't be mocked via VCR:
@pytest.mark.integration
@pytest.mark.asyncio
async def test_pdf_visualization(temp_db_path):
# Test code that needs local PDF processing
pass
Integration tests are skipped in CI but run locally when you have the required services.
Linting and Formatting
Mock API Keys
Tests automatically set mock API keys for providers that require them during client initialization. When running with VCR playback, these mock keys are sufficient since no real API calls are made.
Recording reaches the real service, so the recording command needs network
access and the keys that service reads. Name the exact test and pass -n0:
a module-wide --record-mode=rewrite re-records every cassette in it,
including ones whose service you do not have running.
# Ollama-backed cassettes need no key, only a running Ollama
uv run pytest tests/test_embedder.py::test_ollama_embedder -n0 --record-mode=rewrite
# A keyed provider reads its own variable. Cohere's SDK reads CO_API_KEY
CO_API_KEY=... uv run pytest tests/test_reranker.py::test_cohere_reranker -n0 --record-mode=rewrite