# 🧹 Разобрать кандидатов по тестам и записать конвенцию - **Тип:** chore - **Категория:** Инфраструктура - **Зачем:** как пишем тесты, не записано нигде: пункт «Тесты» в convention-candidates не пересматривали, он обещает фикстуры в testdata/, которых в проекте нет — а трение накопилось (четыре внешних клиента, fakeStore с инъекцией ошибок, env-гейты, флаки-прогон, diff-coverage) - **Теги:** sprint:2026-08-06 В `docs/conventions/` пять записей, и ни одна не про тесты. Знание о том, как они здесь устроены, живёт в головах и в самих файлах: фикстуры чужих форматов константами (`testdata` не заводился сознательно), интеграционные за env-гейтами в `*_integration_test.go`, инъекция ошибок хранилища через `fakeStore`, архитектурные правила тестом-сканером `internal/archrules` с русскими именами, покрытие изменённых строк меряет гейт. Пункт «Тесты» в [convention-candidates](convention-candidates.md) с тех пор не пересматривали, и он уже расходится с проектом: обещает фикстуры в `testdata/`, которого нет и который запрещён `CLAUDE.md`. Часть остальных кандидатов проверяется замером, а не мнением: table-driven используется в 18 пакетах из 19, `t.Parallel()` — ни разу, `testify` в `go.mod` не подключён. Правило каталога конвенций — «пишем по мере реального трения, а не вперёд». Трение здесь и есть: тема `autotests` идёт первым проходом на любой метке ревью, а дома у неё нет — источником ей служит только семантика гейта из `CLAUDE.md`, то есть «что краснеет», а не «что стоит проверять». **Два кандидата пришли из ревью `tvdb-title-locale`** (2026-08-07, [отчёт триажа](../../../openspec/changes/archive/2026-08-07-tvdb-title-locale/review/report.md) → «Promote candidates»), оба с провенансом прохода, а не из головы: - **один стенд чужого API на пакет.** В `tvdb_test.go` завелись два фейка одного и того же API. Когда форма ответа поменяется по факту разведки, забытый `fakeTVDB` останется зелёным и продолжит подтверждать опровергнутое предположение; - **интеграционный тест обязан утверждать, а не печатать.** `TestIntegration_TVDB` печатает результат и ничего не проверяет: `PASS` с пустым выводом неотличим от подтверждения. Тест, который нельзя провалить, оракулом не является. ## Затрагивает - `docs/conventions/tests.md` — новый файл записи конвенции; - `docs/conventions/README.md` — строка в индексе «Записи» и, возможно, строки в таблице «Механизировано»; - `docs/tasks/items/convention-candidates.md` — пункт «Тесты» уходит из списка кандидатов; - `.golangci.yml` и `internal/archrules` — только если что-то из решённого выражается правилом, а не прозой. ## Критерии приёмки - Каждый кандидат пункта «Тесты» получил исход поимённо: в прозу, в правило или выброшен как выдуманный вперёд. **Оракул:** в `convention-candidates.md` пункта «Тесты» больше нет, а судьба каждого его подпункта названа — в новой конвенции, в таблице «Механизировано» или в `REJECTED`-строке причины. - `docs/conventions/tests.md` заведён и назван в индексе. **Оракул:** `task gate`, шаг `canon` — файл вне канона и битая ссылка краснят безусловно. - Ни одно утверждение конвенции не расходится с тем, как код устроен сегодня: фикстуры описаны как лежат, `testdata` не обещан, число пакетов и приёмов не выдумано. **Оракул:** `grep -r testdata` по репозиторию пуст, и агент `doc-code-drift` на ближайшей сессии не даёт находки по этому файлу. - Выражаемое правилом не осталось прозой. **Оракул:** каждое утверждение новой конвенции либо отсутствует в таблице «Механизировано», либо стоит там с адресом механизации — конфигом линтера или тестом `internal/archrules`. ## Рамки Существующие тесты под новую конвенцию не переписываются — она применяется к тому, что пишется дальше. `testdata/` не заводится: отказ от него записан в `CLAUDE.md`, и его пересмотр — отдельное решение, а не побочный эффект этой задачи.