# 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_` — 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*