From bfb0c9e7bbe37f3cb856163cd3e7c077df271ba4 Mon Sep 17 00:00:00 2001 From: Lacky1234union Date: Mon, 5 Oct 2026 12:28:03 +0300 Subject: [PATCH] feat(bot): /start without a code on the demo stand opens a private copy of veteran Andrey Refs #108 Co-Authored-By: Claude Opus 5.5 --- app/channels/telegram.py | 17 +++++++++++++++++ tests/test_telegram.py | 18 +++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/app/channels/telegram.py b/app/channels/telegram.py index 755dbcc..66cda0f 100644 --- a/app/channels/telegram.py +++ b/app/channels/telegram.py @@ -194,11 +194,28 @@ class TelegramChannel: await self._dispatch(chat_id, person.token, Event(type="start", params={"code": code})) return token = await self.directory.token_for(chat_id) + if token is None and get_settings().demo_clock: + token = await self._demo_copy(chat_id) if token is None: await self._say(chat_id, texts.not_connected) return await self._dispatch(chat_id, token, Event(type="start")) + async def _demo_copy(self, chat_id: int) -> str | None: + """Demo stand (DEMO_CLOCK): `/start` without a code gets a private copy of veteran Andrey (synthetic), + starting from consent and the intake — the same entry as the ЕМИАС demo button of the web app.""" + from app.api.patient_web import _fresh_demo_copy + + try: + token = (await _fresh_demo_copy(start_in_chat=False)).token + except Exception: + log.exception("start: demo copy failed") + return None + if not await self.directory.bind(chat_id, token): + return None + log.info("start: demo copy bound tok=%s", _short(token)) + return token + async def on_text(self, chat_id: int, text: str | None) -> None: texts = load_texts() token = await self.directory.token_for(chat_id) diff --git a/tests/test_telegram.py b/tests/test_telegram.py index 64ccd88..bbcb30f 100644 --- a/tests/test_telegram.py +++ b/tests/test_telegram.py @@ -22,6 +22,7 @@ from app.channels.telegram import ( render_markup, split_text, ) +from app.config import get_settings from app.db import identity_repo, session from app.db.bootstrap import init_db from app.dialog.contracts import Button, Event, Reply @@ -163,7 +164,10 @@ async def test_verified_token_without_identity_record(dbs: None, engine: Recordi assert sender.texts == [load_texts().code_invalid] -async def test_unbound_chat_gets_not_connected(dbs: None, engine: RecordingEngine) -> None: +async def test_unbound_chat_gets_not_connected( + dbs: None, engine: RecordingEngine, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setattr(get_settings(), "demo_clock", False) sender = FakeSender() channel = TelegramChannel(sender) await channel.on_start(CHAT, None) @@ -251,6 +255,18 @@ async def test_outbox_pushes_to_bound_chat_and_falls_back(dbs: None, engine: Rec assert [r.text for r in fallback.drain(TOKEN2)] == ["Как вы сегодня?"] +async def test_start_without_code_on_demo_stand_binds_a_demo_copy( + dbs: None, engine: RecordingEngine, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setattr(get_settings(), "demo_clock", True) + sender = FakeSender() + channel = TelegramChannel(sender) + await channel.on_start(CHAT, None) + token = await channel.directory.token_for(CHAT) + assert token is not None and token.startswith("tok-andrey-") + assert [c[1].type for c in engine.calls] == ["start"] + + async def test_start_is_noop_without_token(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(telegram, "get_settings", lambda: type("S", (), {"tg_bot_token": ""})()) before = integrations.outbox