Tests
Summary
| Directory | Tests | What it covers |
|---|---|---|
tests/tools/ | 73 | OSINT tools mocked with respx |
tests/investigation/ | 77 | InvestigationDB, normalize, extract, chunk |
tests/integration/ | 17 | MCP, A2A, service facade |
tests/ (root) | 41 | Cache, dashboard, session, anomaly detector, market correlation, narrative detector |
| Total | 208 | — |
Requirements
pip install -e ".[dev]"Installs development dependencies: pytest, respx, pytest-asyncio, httpx, etc.
Running tests
# Complete suitepytest tests/ -v
# OSINT tools only (fast, no API keys needed)pytest tests/tools/ -v
# Investigation only (no API keys needed, SQLite in tmp_path)pytest tests/investigation/ -v
# Integration only (MCP, A2A)pytest tests/integration/ -v
# With coveragepytest tests/ --cov=src --cov-report=htmlTool tests (tests/tools/)
Each tool has its own test file. All HTTP calls are mocked with respx — no real API keys needed.
import pytestimport respxfrom httpx import Responsefrom src.tools.virustotal import virustotal_ip_lookup
@pytest.mark.asyncio@respx.mockasync def test_virustotal_ip_lookup(): respx.get("https://www.virustotal.com/api/v3/ip_addresses/8.8.8.8").mock( return_value=Response(200, json={ "data": { "attributes": { "last_analysis_stats": {"malicious": 0, "harmless": 89}, "reputation": 8, "country": "US", "asn": 15169, "as_owner": "GOOGLE" } } }) ) result = await virustotal_ip_lookup("8.8.8.8") assert result["malicious_count"] == 0 assert result["country"] == "US"Investigation tests (tests/investigation/)
Use pytest’s tmp_path to create temporary SQLite databases — completely isolated, no shared state:
import pytestfrom src.investigation.manager import InvestigationManager
@pytest.mark.asyncioasync def test_create_investigation(tmp_path): db_path = tmp_path / "test.db" manager = InvestigationManager(db_path=str(db_path))
investigation = await manager.create( name="Test Investigation", goal="Testing" ) assert investigation.slug == "test-investigation" assert investigation.status == "active"Integration tests (tests/integration/)
Verify that the MCP Server correctly registers all tools and that the A2A Server returns the correct agent card:
def test_mcp_tool_count(): from src.mcp_server.server import mcp tools = mcp.get_tools() assert len(tools) >= 29
def test_mcp_tool_names(): from src.mcp_server.server import mcp tool_names = [t.name for t in mcp.get_tools()] assert "virustotal_ip_lookup" in tool_names assert "investigate_ioc" in tool_names assert "create_investigation" in tool_namesLinting and formatting
# Lintingruff check src/ tests/
# Automatic formattingruff format src/ tests/
# Type checkingmypy src/Pre-commit hooks
The project includes pre-commit hooks:
# Install hookspre-commit install
# Run manuallypre-commit run --all-filesHooks configured in .pre-commit-config.yaml:
- ruff (lint + format)
- mypy (type check)
CI
GitHub Actions runs the complete suite on each push to main. See .github/workflows/ for the configuration.
Adding a new test
For a new tool in src/tools/my_tool.py:
- Create
tests/tools/test_my_tool.py - Use
@respx.mockto mock HTTP calls - Cover cases: normal response, 404 error, timeout, invalid key
- Run
pytest tests/tools/test_my_tool.py -vto verify