Разрешены конфликты: isolated_runner (тщательный upstream-вариант killpg-wait), cli.py task-диспетчеризация upstream + guard ValueError, aum.py paths-изоляция + STORE_DB fallback. Тесты после мерджа: 5 устарели к новой реальности — починены по интентам: - test_aum 9c: hermetic-мок OPENROUTER_KEY (ключ уехал со старого хоста) - test_router_env_fallback: патчит paths.env_path (runtime больше не читает _HARNESS_DIR) - test_submit_title_guard: контракт task list = показ доски (exit 0), а не ERROR - test_tg_commands: доска задач от тестовой ready-задачи (в прод-копии ready пусто) - test_instance_isolation: SKIP RAG-контура без вендора /root/rag (хост мигрировал) Итог: 19/19 зелёные
91 lines
4.3 KiB
Python
91 lines
4.3 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""e-261003094628 (повтор e-261001082241): guard мусорных заголовков задач (hermetic).
|
||
|
||
Запуск: ./venv/bin/python tests/test_submit_title_guard.py
|
||
|
||
Первопричина серии 'list': LLM-тик нижнего уровня звал
|
||
`cli.py task list` / `cli.py task <id>` (хотел доску/детали задачи), а
|
||
`task` принимает позиционный title → argparse молча подсунул слово
|
||
'list' / чужой id как заголовок и подал мусорную задачу. Min-уровень
|
||
закрыл их как no-op и эскалировал — дважды.
|
||
|
||
Проверяет: submit_task режет субкомандо-подобные заголовки и чужие id
|
||
задач БЕЗ деталей (ValueError), пропускает нормальные подачи, одно слово
|
||
с деталями, обычные слова не из стоп-листа; CLI `task list` → exit 1 с
|
||
ERROR, а не тихая подача.
|
||
"""
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
|
||
# ── изоляция: своя БД в /tmp (prod не трогаем) ──
|
||
_DST = "/tmp/simbiot-test-title-guard.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 commands
|
||
|
||
commands.es.init_db()
|
||
|
||
fails = []
|
||
|
||
|
||
def check(name, cond, extra=""):
|
||
print(("PASS" if cond else "FAIL"), name, ("| " + str(extra)) if not cond else "")
|
||
if not cond:
|
||
fails.append(name)
|
||
|
||
|
||
# ── 1. Субкомандо-подобные заголовки без деталей — режутся ──
|
||
for bad in ("list", "tasks", "status", "events", "help", "LIST", " Complete "):
|
||
try:
|
||
commands.submit_task(bad, "", minutes=5)
|
||
check(f"мусорный заголовок {bad!r} отвергнут", False, "подача прошла")
|
||
except ValueError:
|
||
check(f"мусорный заголовок {bad!r} отвергнут", True)
|
||
|
||
# ── 2. Чужой id задачи как заголовок без деталей — режется ──
|
||
try:
|
||
commands.submit_task("t-261003094008-ed226f", "", minutes=5)
|
||
check("id задачи как заголовок отвергнут", False, "подача прошла")
|
||
except ValueError:
|
||
check("id задачи как заголовок отвергнут", True)
|
||
|
||
# ── 3. Нормальные подачи — проходят ──
|
||
for title, details in (
|
||
("Разбор голосового m-973940607 + HTML-страница", "транскрипт в docs/"),
|
||
("list", "собрать список всех ключей в реестре"), # слово из стоп-листа, но с деталями
|
||
("фикс деплоя", ""),
|
||
):
|
||
try:
|
||
tid, size = commands.submit_task(title, details, minutes=5)
|
||
ok = bool(tid) and size == "S"
|
||
except ValueError as e:
|
||
ok, tid = False, str(e)
|
||
check(f"нормальная подача {title[:30]!r} прошла", ok, tid)
|
||
|
||
# ── 4. CLI: `cli.py task list` — показывает доску, а не плодит мусорную задачу ──
|
||
# Контракт изменён в origin/master (t-261003125513/e-261004134613): 'task list'
|
||
# и 'task <id>' показывают доску/задачу (exit 0). Интент guard'а прежний:
|
||
# НЕТ тихой подачи мусорной задачи с заголовком 'list'.
|
||
env = dict(os.environ, SIMBIOT_DB=_DST)
|
||
r = subprocess.run(
|
||
["./venv/bin/python", "cli.py", "task", "list"],
|
||
capture_output=True, text=True, cwd="/root/simbiot-harness", env=env,
|
||
)
|
||
out = (r.stdout + r.stderr).strip()
|
||
check("CLI task list → exit 0 (показ доски)", r.returncode == 0, f"rc={r.returncode} out={out!r}")
|
||
check("CLI task list → не ERROR-подача", "ERROR" not in out, out)
|
||
model = commands.es.run_projections()
|
||
check("мусорной задачи 'list' в сторе нет",
|
||
not any(t.get("title", "").strip().lower() == "list" and not t.get("details")
|
||
for t in model["tasks"].values()))
|
||
|
||
print()
|
||
if fails:
|
||
print(f"FAILED: {len(fails)}")
|
||
sys.exit(1)
|
||
print("OK: все проверки прошли")
|