96 lines
6.3 KiB
YAML
96 lines
6.3 KiB
YAML
work_item: ORCH-095
|
||
stage: analysis
|
||
author_agent: analyst
|
||
status: ready-for-review
|
||
created_at: 2026-06-09
|
||
model_used: claude-opus-4-8
|
||
title: "HTML-безопасность динамических полей render_task_tracker (фикс инъекции «<1м»)"
|
||
framework: pytest
|
||
scope: >
|
||
Покрывается: HTML-безопасность всех подставляемых данных в render_task_tracker
|
||
(длительности < 1 мин, токены/стоимость, имя модели/эффорт, статус-лейбл, заголовок со
|
||
спецсимволами), сохранность намеренной разметки (<a href> номер задачи, _done_link),
|
||
возобновление обновлений застрявшей карточки, never-raise. Вне покрытия: реальная сеть к
|
||
Telegram Bot API (мокируется httpx), изменения STAGE_TRANSITIONS/QG_CHECKS/схемы БД (не
|
||
трогаются).
|
||
notes: >
|
||
Тесты — изоляция от сети: httpx.post/get мокируются; БД — временная SQLite-фикстура с
|
||
задачей и agent_runs (стадия < 60 с). Полный регресс pytest tests/ -q должен оставаться
|
||
зелёным, включая существующие test_telegram_tracker.py / test_tracker_*.py /
|
||
test_notifications_orphans.py / test_notify_issue_links.py. Регрессом считается: красный
|
||
любой существующий тест трекера, заэкранированная намеренная разметка, двойное
|
||
экранирование, непойманное исключение в пути рендера.
|
||
|
||
tests:
|
||
- id: TC-01
|
||
type: unit
|
||
description: "_fmt_minutes для длительности < 60 с (напр. 30) не возвращает сырой '<1м': результат HTML-безопасен (<1м либо переформулированный '~0м'/'< 1 мин' без сырого '<')."
|
||
module: tests/test_tracker_html_escape.py
|
||
expected: PASS
|
||
|
||
- id: TC-02
|
||
type: unit
|
||
description: "_fmt_minutes для граничных входов (0, None, нечисловое, ровно 60, большое значение) — never-raise и HTML-безопасный вывод во всех ветках."
|
||
module: tests/test_tracker_html_escape.py
|
||
expected: PASS
|
||
|
||
- id: TC-03
|
||
type: unit
|
||
description: "render_task_tracker для задачи со стадией < 1 мин: в выходном тексте нет неэкранированного '<' из данных длительности; подстрока длительности безопасна для parse_mode=HTML."
|
||
module: tests/test_tracker_html_escape.py
|
||
expected: PASS
|
||
|
||
- id: TC-04
|
||
type: unit
|
||
description: "render_task_tracker с заголовком, содержащим спецсимволы '<', '>', '&' (напр. 'A <b>x</b> & <1'): спецсимволы данных присутствуют только экранированными (</>/&), не как сырые теги; двойного экранирования (&lt;) нет."
|
||
module: tests/test_tracker_html_escape.py
|
||
expected: PASS
|
||
|
||
- id: TC-05
|
||
type: unit
|
||
description: "Статус-лейбл (_card_status_label) и имя модели/эффорт, попадающие в текст карточки, экранированы (defence-in-depth): спецсимволы в них не ломают HTML."
|
||
module: tests/test_tracker_html_escape.py
|
||
expected: PASS
|
||
|
||
- id: TC-06
|
||
type: unit
|
||
description: "Метрики токенов/стоимости (fmt_tokens/fmt_cost) в карточке HTML-безопасны: '$' и числовой формат не порождают сырых тегов."
|
||
module: tests/test_tracker_html_escape.py
|
||
expected: PASS
|
||
|
||
- id: TC-07
|
||
type: unit
|
||
description: "Регресс намеренной разметки: кликабельный номер задачи (plane_issue_link -> <a href>) присутствует в выводе как валидный незаэкранированный <a>-тег; href/label не задвоены экранированием."
|
||
module: tests/test_tracker_html_escape.py
|
||
expected: PASS
|
||
|
||
- id: TC-08
|
||
type: unit
|
||
description: "Регресс _done_link: для завершённой задачи строка '🔗 PR #n · 📦 Внедрено' рендерится валидной (ссылочная разметка не экранирована)."
|
||
module: tests/test_tracker_html_escape.py
|
||
expected: PASS
|
||
|
||
- id: TC-09
|
||
type: integration
|
||
description: "update_task_tracker (edit-режим) с замоканным editMessageText: текст карточки со стадией < 1 мин принимается (мок ассертит отсутствие 'can't parse entities'-триггера, т.е. нет сырого '<1м' в payload text)."
|
||
module: tests/test_tracker_html_escape.py
|
||
expected: PASS
|
||
|
||
- id: TC-10
|
||
type: integration
|
||
description: "Возобновление застрявшей карточки (AC-4): после фикса валидный рендер проходит edit-путь без EDIT_FAILED из-за parse-ошибки; защита от дублей сохранена — транзиентный (network) фейл по-прежнему НЕ плодит новую карточку."
|
||
module: tests/test_tracker_html_escape.py
|
||
expected: PASS
|
||
|
||
- id: TC-11
|
||
type: unit
|
||
description: "never-raise: render_task_tracker на 'битых' входах (отсутствует задача, None-заголовок, нечисловые длительности) возвращает fallback-строку, не выбрасывает исключение."
|
||
module: tests/test_tracker_html_escape.py
|
||
expected: PASS
|
||
|
||
- id: TC-12
|
||
type: integration
|
||
description: "Полный регресс существующих тестов трекера (test_telegram_tracker.py, test_tracker_issue_link.py, test_tracker_status_line.py, test_notifications_orphans.py, test_notify_issue_links.py) остаётся зелёным после фикса."
|
||
module: tests/test_telegram_tracker.py
|
||
expected: PASS
|