All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m10s
CI / backend-tests (pull_request) Successful in 17m21s
`_page_contains_table` документировалась как «hint-только режим (оглавление, перекрёстные ссылки) даёт false positive и подавляется». Кода подавления в функции никогда не было — это `bool(cap.search(text))`. Обещание защиты, которой нет, опаснее её отсутствия: читающий не станет её добавлять. Замер уточнил и сам пункт эпика. Приведённый в нём пример перекрёстной ссылки «показатели приведены в таблице 12» регекс НЕ ловит: он требует именительное «Таблица N», поэтому «в таблице 12», «см. Таблицу 12», «табл. 12» дают False. Опасно ровно ОГЛАВЛЕНИЕ — там падеж тот же именительный, и «Таблица 11 Баланс территории ..... 34» неотличима от подписи. Это зафиксировано характеризующим тестом, чтобы следующая попытка подавления целилась в признаки оглавления (точки-выноски, номер страницы в конце), а не в падежи. Подавление здесь не реализовано сознательно: `ekb_ppt_tep` на проде пуста (0 строк), URL в `_SEED_DOCS` — заглушка, живых PDF нет. Эвристику отсева не на чем откалибровать, а правило, придуманное без образцов, ловит ровно те случаи, которые придумали вместе с ним. Отдельно исправлен комментарий сида: хост `gisogd.ekburg.ru`, названный там местом, «где лежит реальный URL», НЕ СУЩЕСТВУЕТ — DNS не резолвит его ни с рабочей машины, ни с прод-хоста (20.08.2026). Комментарий отправлял искать документ на портале, которого нет. Назван живой портал `gisogd66.midural.ru` и способ перечислить его разделы. Двусторонне: против origin/main два гейта красные — «докстрока обещает подавление, а в теле только поиск подстроки» и «комментарий сида не предупреждает, что хост мёртв». Характеризующие тесты зелёные с обеих сторон: они фиксируют фактическое поведение, а контроль test_docstring_names_the_actual_behaviour ловит «починку» через вычёркивание неудобной фразы. Гейт ищет обещание по слову, поэтому старая формулировка в докстроке пересказана, а не процитирована — иначе он не отличил бы цитату от утверждения; это оговорено прямо в тексте. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
103 lines
6.9 KiB
Python
103 lines
6.9 KiB
Python
"""Докстрока `_page_contains_table` не обещает защиты, которой нет (#2464).
|
||
|
||
Прежняя редакция утверждала:
|
||
|
||
«Требуем явный caption «Таблица N» — hint-только режим (оглавление,
|
||
перекрёстные ссылки) даёт false positive и подавляется.»
|
||
|
||
Кода подавления в функции никогда не было — это `bool(cap.search(text))`, то есть
|
||
обычный поиск подстроки. Строка оглавления «Таблица 11 Баланс территории ..... 34» от
|
||
настоящей подписи не отличается: падеж тот же, именительный.
|
||
|
||
Замер 20.08.2026 уточнил и сам пункт эпика: приведённый в нём пример перекрёстной
|
||
ссылки «показатели приведены в таблице 12» регекс НЕ ловит — он требует именительное
|
||
«Таблица N». Опасно ровно оглавление, а не любое упоминание. Это зафиксировано
|
||
характеризующим тестом, чтобы следующая попытка подавления целилась в признаки
|
||
оглавления, а не в падежи.
|
||
|
||
Обещание защиты, которой нет, опаснее её отсутствия: читающий не станет её
|
||
добавлять. Тот же класс, что «объявлено ≠ исполняется».
|
||
|
||
Почему подавление не реализовано, а докстрока приведена к коду: таблица
|
||
`ekb_ppt_tep` на проде пуста (0 строк) — URL в `_SEED_DOCS` заглушка, живых PDF нет,
|
||
эвристику отсева оглавления не на чем откалибровать. Правило, придуманное без
|
||
образцов, ловит ровно те случаи, которые придумали вместе с ним.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import inspect
|
||
|
||
from app.services.scrapers import ekb_ppt_tep_parser as mod
|
||
|
||
|
||
def test_docstring_does_not_promise_suppression_that_is_absent() -> None:
|
||
"""Головной: докстрока не должна обещать подавление false positive.
|
||
|
||
На origin/main она содержит «даёт false positive и подавляется», а в теле
|
||
функции — один `cap.search(text)`. Красное значение конкретно: обещание есть,
|
||
реализации нет.
|
||
"""
|
||
doc = inspect.getdoc(mod._page_contains_table) or ""
|
||
src = inspect.getsource(mod._page_contains_table)
|
||
тело = src.split('"""')[-1]
|
||
# Ищем именно ОБЕЩАНИЕ («подавляется»), а не разговор о подавлении: новая
|
||
# редакция объясняет, почему подавление НЕ реализовано, и не должна краснеть.
|
||
обещает = "подавляется" in doc
|
||
реализует = any(kw in тело for kw in ("оглавлен", "leader", "count(", "finditer", "rsplit"))
|
||
assert not (обещает and not реализует), (
|
||
"докстрока обещает подавление false positive, а в теле только поиск "
|
||
f"подстроки:\n{тело.strip()}"
|
||
)
|
||
|
||
|
||
def test_docstring_names_the_actual_behaviour() -> None:
|
||
"""Докстрока обязана честно называть false positive, а не умалчивать о нём.
|
||
|
||
Просто убрать обещание было бы недостаточно: молчание читается как «всё
|
||
хорошо». Ловит «починку» через вычёркивание неудобной фразы.
|
||
"""
|
||
doc = (inspect.getdoc(mod._page_contains_table) or "").lower()
|
||
assert (
|
||
"false positive" in doc or "оглавлен" in doc
|
||
), "докстрока не предупреждает о ложных срабатываниях на оглавлении"
|
||
|
||
|
||
def test_toc_line_really_is_a_false_positive() -> None:
|
||
"""Характеризующий: строка оглавления действительно проходит как подпись.
|
||
|
||
Зелёный с обеих сторон — он фиксирует ФАКТИЧЕСКОЕ поведение, о котором теперь
|
||
говорит докстрока. Если кто-то реализует подавление, тест покраснеет и
|
||
заставит обновить и докстроку, и его.
|
||
"""
|
||
оглавление = "СОДЕРЖАНИЕ\nТаблица 11 Баланс территории .......... 34\nТаблица 12 ТЭП .... 36"
|
||
assert mod._page_contains_table(оглавление, 11) is True, "оглавление обязано срабатывать"
|
||
|
||
# А вот перекрёстные ссылки в косвенных падежах регекс НЕ ловит: он требует
|
||
# именительное «Таблица N». Пункт эпика приводил «приведены в таблице 12» как
|
||
# пример false positive — замер это опроверг, и докстрока теперь говорит точнее.
|
||
for мимо in (
|
||
"Основные показатели приведены в таблице 12 настоящей записки.",
|
||
"см. Таблицу 12 ниже",
|
||
"Данные в табл. 12",
|
||
):
|
||
assert mod._page_contains_table(мимо, 12) is False, f"неожиданно сработало: {мимо!r}"
|
||
|
||
assert mod._page_contains_table("Никаких таблиц тут нет", 11) is False
|
||
|
||
|
||
def test_seed_comment_does_not_send_to_a_dead_host() -> None:
|
||
"""Комментарий сида не должен отправлять искать документ на несуществующем хосте.
|
||
|
||
`gisogd.ekburg.ru` не резолвится ни с рабочей машины, ни с прод-хоста
|
||
(проверено 20.08.2026), а прежний комментарий называл его местом, где «лежит
|
||
реальный URL». Живой портал — gisogd66.midural.ru.
|
||
"""
|
||
from pathlib import Path
|
||
|
||
src = Path(inspect.getsourcefile(mod)).resolve().parents[3]
|
||
текст = (src / "app" / "workers" / "tasks" / "ekb_ppt_tep_sync.py").read_text(encoding="utf-8")
|
||
assert (
|
||
"НЕ СУЩЕСТВУЕТ" in текст or "не резолв" in текст.lower()
|
||
), "комментарий сида не предупреждает, что gisogd.ekburg.ru мёртв"
|
||
assert "gisogd66.midural.ru" in текст, "не назван живой портал ГИСОГД-СО"
|