# Testing Strategy

## Stack
- **pytest** + **pytest-asyncio** (`asyncio_mode = auto`)
- **pytest-mock** — `mocker` fixture
- **httpx** — `TestClient` for FastAPI routes
- **asgi-lifespan** — lifespan management in integration tests

## Directory layout

```
tests/
├── conftest.py              # Shared fixtures (settings override, event_loop policy)
├── conftest_factory.py      # Factories: FakeLLMBackend, FakeTool, make_profile, FakePool
├── unit/                    # No external deps (mocked DB / LLM)
│   ├── core/
│   │   ├── test_events.py         # 17 tests — wire serialization for all 15 event types
│   │   ├── test_context_builder.py # 9 tests — system prompt caching, persona, iteration budget
│   │   ├── test_compressor.py      # 14 tests — partition, format, compress_context
│   │   ├── test_registry.py        # 10 tests — Tool/Profile/Backend registries
│   │   └── test_planning.py
│   ├── memory/
│   │   ├── test_store.py           # 18 tests — upsert, search, delete, summary, session state
│   │   └── test_extractor.py      # 11 tests — JSON fact extraction, summary regeneration
│   ├── tools/
│   │   └── test_filesystem.py
│   ├── profiles/
│   │   └── test_base.py            # 9 tests — Pydantic coercion, defaults, extra fields
│   └── config/
│       └── test_settings.py
├── integration/             # FastAPI TestClient + mocked or real DB
│   ├── test_api_routes.py
│   ├── test_websocket.py
│   └── test_memory_store.py
└── e2e/
    └── test_chat_flow.py    # Critical path: message → tool call → response
```

## Mock strategy

### LLM — FakeLLMBackend
`FakeLLMBackend` cycles through a list of pre-defined responses and optionally emits `ToolCallRequest` objects. This lets us test the agent loop and planning without real Ollama.

```python
from tests.conftest_factory import FakeLLMBackend

backend = FakeLLMBackend(
    responses=["Hello", "DIRECT"],
    tool_calls=[None, None],
    thinking=["Hmm", None],
)
resp = await backend.complete([])  # → LLMResponse(content="Hello")
```

### PostgreSQL — FakePool / FakeConnection
Unit tests mock `asyncpg.Pool` via an in-memory `FakePool`/`FakeConnection`. Integration tests may use a real Postgres instance via `TEST_DATABASE_URL`.

```python
from tests.conftest_factory import FakeConnection, FakeRecord, make_store_with_pool

conn = FakeConnection()
conn.enqueue(42)  # fetchval result
conn.enqueue([FakeRecord(id="1", key="name", value="Eugene")])  # fetch result
store = make_store_with_pool(conn)
```

## Coverage status

| Phase | Module | Tests | Status |
|-------|--------|-------|--------|
| 1 | `navi.core.events` | 17 | ✅ Done |
| 1 | `navi.core.compressor` | 14 | ✅ Done |
| 1 | `navi.core.registry` | 10 | ✅ Done |
| 1 | `navi.core.context_builder` | 9 | ✅ Done |
| 1 | `navi.profiles.base` | 9 | ✅ Done |
| 2 | `navi.memory.store` | 18 | ✅ Done |
| 2 | `navi.memory.extractor` | 11 | ✅ Done |
| 3 | `navi.api.routes` | — | ⏳ Pending |
| 3 | `navi.api.websocket` | — | ⏳ Pending |
| 4 | `navi.core.agent` | — | ⏳ Pending |
| 4 | `navi.core.planning` | — | ⏳ Pending |
| 5 | `navi.tools.filesystem` | — | ⏳ Pending |
| 5 | `navi.tools.code_exec` | — | ⏳ Pending |
| 5 | `navi.tools.terminal` | — | ⏳ Pending |

## Running tests

```bash
# All tests
pytest

# Unit only
pytest tests/unit

# With verbose
pytest -v tests/unit/core

# Single file
pytest -v tests/unit/core/test_events.py

# Single test
pytest -v tests/unit/core/test_events.py::TestToolStarted::test_to_wire

# Integration (requires TEST_DATABASE_URL)
TEST_DATABASE_URL=postgresql://... pytest tests/integration
```

## Adding a new test

1. Create file in the appropriate `tests/unit/` or `tests/integration/` directory.
2. Use `async def` for async tests — `pytest-asyncio` handles the rest.
3. Import helpers from `tests.conftest_factory` for fakes.
4. Mutations to `navi.config.settings` are reset automatically by the autouse fixture in `conftest.py`.

## Guidelines

- **Mock at boundaries**: LLM calls → `FakeLLMBackend`, DB → `FakePool`, filesystem → `tmp_path`.
- **Avoid real network**: Never hit Ollama, OpenAI, or DuckDuckGo in unit tests.
- **Avoid real DB in unit tests**: Use in-memory mocks; real Postgres only in `tests/integration/`.
- **Keep tests deterministic**: No randomness, no time-dependent logic without monkeypatching `datetime.now`.
