7 documents covering stack, integrations, architecture, structure, conventions, testing, and concerns.
210 lines
8.5 KiB
Markdown
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*
|