# -*- coding: utf-8 -*- """t-261013131228: резолв цели реплая без текста (документ/фото). Кейс 13.10: Alex в личке реплайнул на присланный ранее документ (концепт панельной сессии для ИИШНИЦЫ) словами «вот этого мероприятия». Telegram не отдаёт содержимое документа в reply_to_message → reply_to_text пустой → мин-триаж видел «[reply на сообщение без текста]» без контекста и анонсировал не то событие (перформанс 8 сентября вместо ИИШНИЦЫ 20.10). Фикс: receive_message резолвит цель по инбоксу (chat_id, reply_to_message_id) и пишет reply_to_desc; build_context показывает desc, когда текста нет. Запуск: ./venv/bin/python tests/test_reply_target_resolve.py """ import os import sys _DST = "/tmp/simbiot-test-reply-target.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, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from harness import es, tg_inbox fails = [] def check(name, cond, extra=""): print(("PASS" if cond else "FAIL") + " " + name + ("" if cond else " | " + extra)) if not cond: fails.append(name) def _recv(update_id, message_id, chat_id, text=None, document=None, reply_to=None, chat_type="private", date=1780000000): msg = { "message_id": message_id, "date": date, "chat": {"id": chat_id, "type": chat_type}, "from": {"id": 1, "first_name": "Alex"}, } if text is not None: msg["text"] = text if document is not None: msg["document"] = document if reply_to is not None: msg["reply_to_message"] = reply_to tg_inbox.receive_message({"update_id": update_id, "message": msg}) DOC_CAPTION = "Концепт панельной сессии «Общее видение» для ИИШНИЦЫ (HTML): миссия 42 как рамка" es.init_db() # ── 1. Реплай на документ: цель резолвится из инбокса ────────────────────── _recv(1001, 4060, 7569223131, text=DOC_CAPTION, document={"file_id": "f1", "file_unique_id": "u1", "file_name": "panel-concept.html", "mime_type": "text/html"}) _recv(1002, 4076, 7569223131, text="вот этого мероприятия", reply_to={"message_id": 4060}) # Telegram текст цели не отдал model = es.run_projections() m = model["inbox"]["m-1002"] check("реплай на документ: reply_to_message_id зафиксирован", m["reply_to_message_id"] == 4060) check("реплай на документ: reply_to_text пустой (Telegram не отдал)", not (m.get("reply_to_text") or "").strip()) desc = m.get("reply_to_desc") or "" check("реплай на документ: reply_to_desc резолвнулся из инбокса", bool(desc), repr(desc)) check("реплай на документ: desc несёт содержимое (caption документа)", ("Концепт панельной сессии" in desc) or ("документ" in desc.lower()), repr(desc)) # ── 2. Реплай на текстовое: desc не нужен, текст из Telegram ─────────────── _recv(2001, 5001, 7569223131, text="старое текстовое сообщение") _recv(2002, 5002, 7569223131, text="принято", reply_to={"message_id": 5001, "text": "старое текстовое сообщение"}) model = es.run_projections() m = model["inbox"]["m-2002"] check("реплай на текст: reply_to_text из Telegram", (m.get("reply_to_text") or "").strip() == "старое текстовое сообщение") check("реплай на текст: reply_to_desc не пишется (не нужен)", not (m.get("reply_to_desc") or "").strip()) # ── 3. Цель вне инбокса: не выдумываем desc ──────────────────────────────── _recv(3001, 6002, 7569223131, text="вот этого мероприятия", reply_to={"message_id": 999999}) model = es.run_projections() m = model["inbox"]["m-3001"] check("цель вне инбокса: reply_to_desc не выдумывается", not (m.get("reply_to_desc") or "").strip()) # ── 4. build_context: якорь реплая несёт контекст документа ──────────────── _recv(4001, 7000, 7569223131, text=DOC_CAPTION, document={"file_id": "f2", "file_unique_id": "u2", "file_name": "panel-concept.html", "mime_type": "text/html"}) _recv(4002, 7001, 7569223131, text="вот этого мероприятия", reply_to={"message_id": 7000}) # закрываем ранние входящие, чтобы в контекст попал только свежий батч for mid in ("m-1001", "m-1002", "m-2001", "m-2002", "m-3001"): mm = model["inbox"].get(mid) if mm is not None: es.append_event("inbox", mid, "MessageIgnored", {"reason": "test"}) from harness.scheduler import build_context from harness.levels import LEVELS ctx = build_context(LEVELS["min"], es.run_projections(), batch=[]) check("build_context: якорь реплая показывает контекст документа", "в ответ на" in ctx and "[reply на сообщение без текста]" not in ctx and "Концепт панельной сессии" in ctx) print() if fails: print("FAILS: " + ", ".join(fails)) sys.exit(1) print("ALL PASS")