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

8.5 KiB

Testing Patterns

Analysis Date: 2026-04-01

Test Framework

Runner: pytest 8.x Config: pyproject.toml [tool.pytest.ini_options]

[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:

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:

@pytest.fixture
def mgr():
    return AuthManager(MockPlatformClient(), InMemoryStore())

@pytest.fixture
def store() -> InMemoryStore:
    return InMemoryStore()

Async tests require no decorator (asyncio_mode = "auto"):

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):

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:

@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):

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:

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:

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:

@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 handlersadapter/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 converteradapter/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.pyresolve_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 streamingstream_message is not tested; only send_message is covered.

Error pathsChatManager.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