"""Сквозной тест без сети: поддельные страницы Outlook/Teams → watcher.js → pipeline → «Telegram».
Запуск: CHROMIUM_PATH=/usr/bin/google-chrome python -m tests.test_e2e
"""
import asyncio
import os
import shutil
import tempfile
TMP = tempfile.mkdtemp(prefix="uni-test-")
os.environ.update(TG_TOKEN="1:x", OWNER_ID="1", OPENROUTER_API_KEY="x", UNI_EMAIL="me@uni.edu",
DATA_DIR=TMP, HEADLESS="true", BATCH_WINDOW="1.5", VIP="dziekanat", MUTE="newsletter")
from app import config, pipeline as pl # noqa: E402
from app.browser import session as bs # noqa: E402
from app.db import DB # noqa: E402
pl.BASELINE_SECONDS = 3
MAIL_HTML = """
Mail - Outlook
"""
TEAMS_HTML = """Chat | Microsoft Teams
Recent
Group Projekt\\nAnia: ok see you tomorrow everyone\\n12:00
"""
async def main():
s = config.load()
db = DB(s.db_path)
sent = []
async def say(text, silent=False, photo=None):
sent.append((text, silent))
print("\n>>> TG", "(silent)" if silent else "", "\n" + text)
class FakeLLM:
calls = 0
async def triage(self, events, rules, my_name):
FakeLLM.calls += 1
out = []
for i, e in enumerate(events):
t = (e.get("body", "") + e.get("title", "") + e.get("sender", "")).lower()
lvl = 3 if "kolokwium" in t else 2 if "@me" in t else 1
out.append({"i": i, "level": lvl, "sender": e.get("sender", ""), "summary": f"[summary] {t[:60]}"})
return out
p = pl.Pipeline(s, db, FakeLLM(), say)
bm = bs.BrowserManager(s, p.submit, say)
orig_goto = bm._goto
async def goto_with_routes(name):
if not getattr(bm, "_routed", False):
bm._routed = True
await bm.ctx.route("https://outlook.office.com/**", lambda r: r.fulfill(body=MAIL_HTML, content_type="text/html; charset=utf-8"))
await bm.ctx.route("https://teams.microsoft.com/**", lambda r: r.fulfill(body=TEAMS_HTML, content_type="text/html; charset=utf-8"))
await orig_goto(name)
bm._goto = goto_with_routes
await bm.start()
runner = asyncio.create_task(p.run())
await asyncio.sleep(14)
print("\nstates:", bm.states())
print("debug mail:", await bm.debug("mail"))
rows = db.conn.execute("SELECT source, kind, status, level, sender FROM items ORDER BY id").fetchall()
print("\nDB:")
for r in rows:
print(" ", tuple(r))
texts = "\n".join(t for t, _ in sent)
assert bm.states() == {"mail": "ok", "teams": "ok"}, bm.states()
assert texts.count("Kolokwium z analizy") >= 1 and texts.lower().count("kolokwium przeniesione") <= 1, "kolokwium: ровно одно уведомление"
kolok_msgs = [t for t, _ in sent if "kolokwium" in t.lower()]
assert len(kolok_msgs) == 1, f"дубль уведомления: {len(kolok_msgs)}"
assert any("Dziekanat" in t and "🔴" in t for t, _ in sent), "VIP должен быть срочным"
assert not any("number 43" in t for t, _ in sent), "MUTE не должен приходить"
assert any("@me" in t.lower() for t, _ in sent), "Teams: упоминание должно прийти"
assert not any("Old subject" in t for t, _ in sent), "baseline не должен уведомлять"
statuses = {r["status"] for r in rows}
assert "baseline" in statuses
# тиканье времени не создало новое событие для c1
c1 = [r for r in rows if r["sender"] == "Old Sender"]
assert len(c1) == 1 and c1[0]["status"] == "baseline", c1
print(f"\n✅ ВСЁ ОК. Сообщений в TG: {len(sent)}, вызовов LLM: {FakeLLM.calls}")
runner.cancel()
await bm.close()
shutil.rmtree(TMP, ignore_errors=True)
if __name__ == "__main__":
asyncio.run(main())