surfaces/.planning/codebase/TESTING.md
Mikhail Putilovskij c9072d51ea docs: add codebase map to .planning/codebase/
7 documents covering stack, integrations, architecture, structure,
conventions, testing, and concerns.
2026-04-02 00:00:51 +03:00

210 lines
8.5 KiB
Markdown

# Testing Patterns
**Analysis Date:** 2026-04-01
## Test Framework
**Runner:** pytest 8.x
**Config:** `pyproject.toml` `[tool.pytest.ini_options]`
```toml
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
pythonpath = ["."]
```
**Async support:** pytest-asyncio with `asyncio_mode = "auto"` — all `async def` test functions run automatically without decorators.
**Coverage:** pytest-cov (available but no minimum threshold configured)
**Run commands:**
```bash
pytest tests/ -v # all tests
pytest tests/core/ -v # core layer only
pytest tests/adapter/telegram/ -v # telegram adapter only
pytest tests/adapter/matrix/ -v # matrix adapter only
pytest tests/ --cov=. --cov-report=term # with coverage report
```
## Test Directory Structure
```
tests/
├── __init__.py
├── core/
│ ├── test_auth.py — AuthManager unit tests
│ ├── test_chat.py — ChatManager unit tests
│ ├── test_dispatcher.py — EventDispatcher routing tests
│ ├── test_integration.py — full flow smoke tests (dispatcher + managers + mock)
│ ├── test_protocol.py — dataclass defaults and construction
│ ├── test_settings.py — SettingsManager unit tests
│ ├── test_store.py — InMemoryStore + SQLiteStore tests
│ └── test_voice_slot.py — handle_message() handler unit tests
├── adapter/
│ ├── __init__.py
│ ├── test_forum_db.py — Telegram SQLite DB helpers (untracked, new)
│ └── matrix/
│ ├── __init__.py
│ ├── test_converter.py — matrix-nio event → IncomingEvent converter
│ ├── test_dispatcher.py — full Matrix bot integration (build_runtime)
│ ├── test_reactions.py — reaction text builders and emoji mapping
│ └── test_store.py — Matrix store helper functions
└── platform/
└── test_mock.py — MockPlatformClient behavior
```
Tests mirror the source tree. New tests for `adapter/telegram/` go in `tests/adapter/telegram/` (directory exists in `.worktrees/telegram` branch, not yet merged to main).
## conftest.py
`conftest.py` at project root (`/Users/a/MAI/sem2/lambda/surfaces-bot/conftest.py`) handles a sys.path conflict: the project has a local `platform/` (now `sdk/`) package that shadows Python's stdlib `platform` module. It inserts the project root at `sys.path[0]` and removes the cached stdlib `platform` module.
No shared fixtures are defined in `conftest.py`. All fixtures are local to test files.
## Test Structure
**Fixture pattern — local to each test file:**
```python
@pytest.fixture
def mgr():
return AuthManager(MockPlatformClient(), InMemoryStore())
@pytest.fixture
def store() -> InMemoryStore:
return InMemoryStore()
```
**Async tests require no decorator** (asyncio_mode = "auto"):
```python
async def test_not_authenticated_initially(mgr):
assert await mgr.is_authenticated("u1") is False
```
**Sync tests** are used for pure-function tests (protocol dataclass construction, reaction text builders):
```python
def test_incoming_message_defaults():
msg = IncomingMessage(user_id="u1", platform="telegram", chat_id="C1", text="hi")
assert msg.attachments == []
```
**Integration fixture pattern** — builds full runtime in-process:
```python
@pytest.fixture
def dispatcher():
platform = MockPlatformClient()
store = InMemoryStore()
d = EventDispatcher(
platform=platform,
chat_mgr=ChatManager(platform, store),
auth_mgr=AuthManager(platform, store),
settings_mgr=SettingsManager(platform, store),
)
register_all(d)
return d
```
## Mocking Strategy
**Primary mock: `MockPlatformClient`** from `sdk/mock.py`
All tests use `MockPlatformClient()` directly — it is the real mock for the SDK layer. No unittest.mock patching of `MockPlatformClient` is needed.
**`unittest.mock.AsyncMock`** is used only when testing integration with external clients (matrix-nio `AsyncClient`):
```python
from unittest.mock import AsyncMock
client = SimpleNamespace(
room_create=AsyncMock(return_value=SimpleNamespace(room_id="!r2:example"))
)
```
**`types.SimpleNamespace`** is used to fabricate matrix-nio event objects without importing the full nio library:
```python
def text_event(body: str, sender: str = "@a:m.org", event_id: str = "$e1"):
return SimpleNamespace(
sender=sender, body=body, event_id=event_id, msgtype="m.text", replyto_event_id=None
)
```
This is the pattern for all matrix converter tests — define factory functions at module level that return `SimpleNamespace` objects.
**`tmp_path` pytest fixture** is used for SQLiteStore tests to get a throwaway database file:
```python
async def test_sqlite_set_and_get(tmp_path):
store = SQLiteStore(str(tmp_path / "test.db"))
```
**`monkeypatch.setenv`** is used in `tests/adapter/test_forum_db.py` to inject `DB_PATH` env var and reload the module with a fresh database:
```python
@pytest.fixture(autouse=True)
def fresh_db(tmp_path, monkeypatch):
db_file = str(tmp_path / "test.db")
monkeypatch.setenv("DB_PATH", db_file)
import importlib
import adapter.telegram.db as db_mod
importlib.reload(db_mod)
db_mod.init_db()
return db_mod
```
**What NOT to mock:**
- `InMemoryStore` — use it directly; it's a real in-memory implementation
- `MockPlatformClient` — use it directly; patching it defeats the purpose
- Core manager classes (`AuthManager`, `ChatManager`, `SettingsManager`) — always instantiate real ones
## Test Data Patterns
**User IDs:** short strings like `"u1"`, `"u2"`, `"tg_123"`, `"@alice:m.org"`
**Chat IDs:** `"C1"`, `"C2"`, `"C3"` — matches the workspace slot naming
**Platform strings:** literal `"telegram"` or `"matrix"`
**Room IDs:** `"!r:m.org"`, `"!dm:example.org"` — valid Matrix room ID format
No shared factories or fixtures files. Test data is constructed inline or via simple factory functions local to the test module.
## What Is Tested
| Area | Status |
|------|--------|
| `core/protocol.py` — dataclass defaults | Covered (`test_protocol.py`) |
| `core/store.py` — InMemoryStore + SQLiteStore | Covered (`test_store.py`) |
| `core/auth.py` — AuthManager | Covered (`test_auth.py`) |
| `core/chat.py` — ChatManager | Covered (`test_chat.py`) |
| `core/settings.py` — SettingsManager | Covered (`test_settings.py`) |
| `core/handler.py` — EventDispatcher routing | Covered (`test_dispatcher.py`) |
| `core/handlers/message.py` — handle_message | Covered (`test_voice_slot.py`) |
| Full dispatcher + all core handlers integration | Covered (`test_integration.py`) |
| `sdk/mock.py` — MockPlatformClient | Covered (`test_mock.py`) |
| `adapter/matrix/converter.py` — event parsing | Covered (`test_converter.py`) |
| `adapter/matrix/store.py` — store helpers | Covered (`test_store.py`) |
| `adapter/matrix/reactions.py` — text builders | Covered (`test_reactions.py`) |
| `adapter/matrix/bot.py` — MatrixBot + build_runtime | Covered (`test_dispatcher.py`) |
| `adapter/telegram/db.py` — SQLite helpers | Covered (`test_forum_db.py`, untracked) |
## Coverage Gaps
**Telegram adapter handlers**`adapter/telegram/handlers/` (`auth.py`, `chat.py`, `confirm.py`, `forum.py`, `settings.py`) have no tests in `main`. Tests exist only in `.worktrees/telegram` branch (not yet merged).
**Telegram converter**`adapter/telegram/converter.py` has no tests in `main`.
**`core/handlers/callback.py` and `core/handlers/settings.py`** — tested indirectly through integration tests but lack dedicated unit tests.
**`adapter/matrix/room_router.py`** — `resolve_chat_id` has no direct unit tests; exercised only through `MatrixBot.on_room_message` integration path.
**`adapter/matrix/handlers/`** — individual handler files (`auth.py`, `chat.py`, `confirm.py`, `settings.py`) are tested only via `test_dispatcher.py` integration; no isolated unit tests.
**`sdk/mock.py` streaming** — `stream_message` is not tested; only `send_message` is covered.
**Error paths**`ChatManager.rename` raises `ValueError` when chat not found; no test exercises this path. Same for `ChatManager.archive`.
## Naming Conventions
- Test functions: `test_<behavior_under_test>` — descriptive, no abbreviations
- Fixture names match the object they create: `mgr`, `store`, `dispatcher`, `deps`
- Factory functions in converter tests: `text_event()`, `file_event()`, `image_event()`, `reaction_event()`
---
*Testing analysis: 2026-04-01*