# -*- coding: utf-8 -*- """t-261013113629: маршрутизация ответов TG — ответ уходит в чат-источник. Проверяет: ответ на сообщение из ГРУППЫ уходит в группу, из ЛИЧКИ — в личку; send_batch_reply разбивает смешанный батч по чатам (не смешивает разговоры); notify_chat/_default_chat_id выбирает личку Alex, даже если последнее активное сообщение — из группы (старые записи без chat_type → chat_id<0). Запуск: ./venv/bin/python tests/test_reply_routing.py """ import os import sys _DST = "/tmp/simbiot-test-reply-routing.db" for p in (_DST, _DST + "-wal", _DST + "-shm"): if os.path.exists(p): os.unlink(p) os.environ["SIMBIOT_DB"] = _DST sys.path.insert(0, "/root/simbiot-harness") from harness import es, tg_inbox fails = [] def check(name, cond, extra=""): print(("PASS" if cond else "FAIL") + " " + name + (" | " + extra if not cond else "")) if not cond: fails.append(name) def _recv(update_id, text, chat_id, chat_type, chat_title=None, date=1780000000): chat = {"id": chat_id, "type": chat_type} if chat_title: chat["title"] = chat_title tg_inbox.receive_message({"update_id": update_id, "message": { "message_id": update_id % 100000, "date": date, "chat": chat, "from": {"id": 1, "username": "alex", "first_name": "Alex"}, "text": text}}) es.init_db() PRIVATE_ID = 424242 # личка Alex (>0) GROUP_ID = -100999 # группа (<0) # ── 1. send_reply: ответ в чат-источник (группа → группа, личка → личка) ── _recv(900, "вопрос в личке", PRIVATE_ID, "private", date=1780000000) _recv(901, "вопрос в группе", GROUP_ID, "supergroup", "Кооп 42", date=1780000001) sends, actions = [], [] tg_inbox.send_message = lambda chat_id, text, reply_to=None, timeout=35: ( sends.append((chat_id, text)) or {"ok": True}) tg_inbox._api = lambda method, params, timeout=35: ( actions.append((method, params.get("chat_id"))) or {"ok": True}) tg_inbox.send_reply("m-900", "ответ в личку") check("send_reply: личка → личка", sends and sends[-1][0] == PRIVATE_ID, str(sends)) tg_inbox.send_reply("m-901", "ответ в группу") check("send_reply: группа → группа", sends and sends[-1][0] == GROUP_ID, str(sends)) model = es.run_projections() check("send_reply: оба закрыты (replied)", model["inbox"]["m-900"]["replied"] and model["inbox"]["m-901"]["replied"]) check("send_reply: chat_type сохранён", model["inbox"]["m-901"]["chat_type"] == "supergroup" and model["inbox"]["m-901"]["chat_title"] == "Кооп 42") # реплай ушёл именно в чат-источник — проверено по sends выше; здесь — что # ReplySent-событие тоже адресовано группе (to_chat) _last = [e for e in es.get_events() if e.get("event_type") == "ReplySent" and e.get("aggregate_id") == "m-901"] check("send_reply: ReplySent-событие адресовано группе", bool(_last) and str(GROUP_ID) in _last[-1]["data"], str(_last[-1:])) # ── 2. send_batch_reply: смешанный батч НЕ смешивает чаты ── _recv(910, "группа: первое", GROUP_ID, "group", "Кооп 42", date=1780000010) _recv(911, "группа: второе", GROUP_ID, "group", "Кооп 42", date=1780000011) _recv(912, "личка: первое", PRIVATE_ID, "private", date=1780000012) sends.clear() head = tg_inbox.send_batch_reply(["m-910", "m-911", "m-912"], "Сводный ответ") sent_chats = sorted(c for c, _ in sends) check("send_batch_reply: ДВЕ отправки (по числу чатов)", len(sends) == 2, str(sends)) check("send_batch_reply: ответ в группу ушёл в группу", sends and any(c == GROUP_ID for c, _ in sends), str(sends)) check("send_batch_reply: ответ в личку ушёл в личку", sends and any(c == PRIVATE_ID for c, _ in sends), str(sends)) model = es.run_projections() check("send_batch_reply: все 3 закрыты", all(model["inbox"][f"m-91{i}"]["replied"] for i in (0, 1, 2))) # ── 3. _default_chat_id: дефолт — личка, не последняя группа ── # последнее сообщение по ts — из группы (m-912 личка раньше m-911? нет: # m-912 новее). Добавим ещё более новую группу, чтобы проверить приоритет. _recv(920, "самое свежее — из группы", GROUP_ID, "group", "Кооп 42", date=1780000020) default = tg_inbox._default_chat_id() check("_default_chat_id: личка Alex, а не свежая группа", default == PRIVATE_ID, str(default)) # старые записи без chat_type: группа = chat_id < 0 es.append_event("inbox", "m-930", "MessageReceived", { "update_id": 930, "chat_id": PRIVATE_ID, "text": "старая личка без типа", "kind": "text", "ts": "2026-10-13T10:00:00+00:00"}) es.append_event("inbox", "m-931", "MessageReceived", { "update_id": 931, "chat_id": GROUP_ID, "text": "старая группа без типа", "kind": "text", "ts": "2026-10-13T10:01:00+00:00"}) default = tg_inbox._default_chat_id() check("_default_chat_id: без chat_type — тоже личка (chat_id>0)", default == PRIVATE_ID, str(default)) # только группы — фолбэк на группу (лучше, чем None) print() print("FAILS:", fails if fails else "нет") sys.exit(1 if fails else 0)