hermes-android/agy-work/TASK-2026-08-24-04-ci-and-test-harness.md

113 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Task 04: CI и проверяемость тестов (hermes-android)
**Repo:** `ochenstarik-ui/hermes-android`
**Assigned to:** Antigravity (режим оркестратора, два кодера)
**Priority:** HIGH (без этого ни одно предыдущее задание не защищено от регресса)
**Date:** 2026-08-24
**Base SHA:** результат задания 03 — указать фактический SHA при выдаче
**Зависимость:** задания 0103 приняты. Их тесты — материал, на котором проверяется работоспособность CI.
## Роли и протокол
| Роль | Модель | Что делает |
|---|---|---|
| Оркестратор | Antigravity | Разбивает работу, маршрутизирует, принимает результат |
| Кодер 1 | Gemini Flash 3.7 high | Реализация §Scope целиком |
| Кодер 2 | Gemini Pro high | Независимая проверка работы кодера 1 и доработка |
**Раунд 1 (кодер 1).** Пункты §Scope по порядку. Для этого задания тесты-первыми неприменимы к пунктам 12 (инфраструктура) — вместо этого требуется демонстрация: намеренно сломанный коммит в черновой ветке валит пайплайн, вывод приложен к отчёту. Нерешённое — в `OPEN QUESTIONS`.
**Раунд 2 (кодер 2).** Независимо запускает пайплайн своим прогоном, проходит §Anti-checklist с явной отметкой каждого пункта, доводит минимальным дифом, даёт findings по шкале.
**Раунд 3 (оркестратор).** Приёмка только при ссылке на фактический прогон CI (job URL или лог), а не на конфиг.
## Проблема
**1. CI отсутствует полностью (`BUILD-03`).** Каталога `.github` в репозитории нет, хотя README документирует `gradlew test` и `gradlew lint`. 75 юнит-тестов и тесты `hermes-pair` никем не запускаются при пуше — все коммиты с пометками «fix P0/P1» проверялись только локально и на слово.
**2. Объявлен runner несуществующих тестов (`BUILD-05`).** `app/build.gradle.kts:19` задаёт `testInstrumentationRunner`, но исходного набора `androidTest` нет, а из зависимостей подключён только `androidTestImplementation(composeBom)` — без `androidx.test.ext:junit`, `espresso`, `compose-ui-test-junit4`. Инструментальные тесты, требуемые заданиями 02 и 03, запускать нечем.
**3. Фейки скрывают дефекты (`TEST-01`).** `FakeDaos.kt:70-76` возвращает сообщения в порядке вставки — маскирует `DATA-02`. Внутри фейка обычные `mutableMap` без синхронизации, при этом часть тестов гоняется на `runBlocking(Dispatchers.Default)`: сами тесты могут падать с `ConcurrentModificationException`.
**4. Нет инструментальных и UI-тестов (`TEST-02`).** 75 тестов — все JVM-only. Не покрыты миграции Room, реальное поведение `EncryptedSharedPreferences`, экран чата, сканер, навигация, поворот экрана — то есть ровно то, что чинили задания 02 и 03.
**5. Ожидания через `delay(50)` (`TEST-03`).** `JsonRpcGatewayClientTest.kt:100`, `EndToEndContractScenarioTest.kt:173,183,199`, `ApprovalRoutingTest.kt:209` — пять мест синхронизируются реальными задержками. На CI под нагрузкой такие тесты мигают, и первым делом их начнут отключать.
**6. Тест покрывает несуществующий код (`TEST-04`).** `EndToEndContractScenarioTest.kt:45` проверяет `HermesGatewayRepository` — класс прошлой архитектуры, недостижимый из `MainActivity`. Тест зелёный и ничего не гарантирует.
## Scope
**1. Пайплайн (`BUILD-03`).**
`.github/workflows/ci.yml`: на `push` и `pull_request`.
Джоб `android`: JDK 17, Android SDK, кэш Gradle, шаги `testDebugUnitTest`, `lint`, `assembleDebug`. Отчёты тестов и lint — артефактами.
Джоб `rust`: `cargo test --all-targets` и `cargo clippy -- -D warnings` в `hermes-pair/`, кэш cargo.
Джоб `instrumented`: `connectedDebugAndroidTest` на эмуляторе (`reactivecircus/android-emulator-runner` или эквивалент). Если эмулятор на раннере недоступен — джоб объявляется обязательным, но помечается известным ограничением в отчёте; молча выключать его нельзя.
Настроить branch protection на `main`: merge блокируется при падении обязательных джобов. Если прав на настройку у исполнителя нет — зафиксировать в отчёте как действие для владельца, а не пропустить.
**2. Инфраструктура инструментальных тестов (`BUILD-05`).**
Добавить `androidx.test.ext:junit`, `espresso-core`, `androidx.compose.ui:ui-test-junit4`, `androidx.room:room-testing`. Создать `app/src/androidTest/…` и убедиться, что тесты заданий 02 и 03 действительно запускаются.
**3. Ревизия фейков (`TEST-01`).**
`FakeUnifiedSessionDao` и `FakeHostDao` — на потокобезопасные структуры. Порядок возврата сообщений — перемешанный (см. задание 02 §Scope 2), чтобы фейк не был мягче реального Room.
Пройти по всем существующим тестам и отметить те, что зелены только благодаря удобному фейку. Список — в отчёт, с пометкой, какие из них требуют переписывания (сами переписывания — по пункту 4, если укладываются, иначе отдельным заданием).
**4. Детерминированные ожидания (`TEST-03`).**
Пять мест с `delay(50)` перевести на `runTest` с виртуальным временем и `Turbine` (зависимость `app.cash.turbine:turbine:1.2.0` уже подключена). Семантику проверок не менять — только способ ожидания.
**5. Тест на актуальный путь (`TEST-04`).**
`EndToEndContractScenarioTest` переписать на `UnifiedSessionRepository`, сохранив покрываемые сценарии (полный жизненный цикл контракта, обновление токена). Старую версию удалить только после того, как новая проходит и покрывает те же сценарии — соответствие перечислить построчно в отчёте.
Сам класс `HermesGatewayRepository` **не удалять** — это задание 10.
## Do not change
- Продуктовый код заданий 0103: CI не повод «попутно поправить».
- Версии AGP, Kotlin, Compose BOM — задание 05.
- Логику существующих тестов: меняется способ ожидания, не ассерты.
- `hermes-pair/**` исходники — здесь только запуск его тестов.
## Anti-checklist
1. Workflow добавлен, но ни разу не запускался — «должно работать». Требование: приложить ссылку на фактический прогон и на прогон с намеренно сломанным тестом.
2. Джоб `instrumented` добавлен с `continue-on-error: true` — падение не блокирует merge, то есть джоба нет. Проверить.
3. `cargo clippy` без `-D warnings` — предупреждения не валят сборку, смысл потерян.
4. Кэш Gradle настроен так, что валидный прогон переиспользует результаты предыдущего и тесты фактически не выполняются. Проверить по времени и логу, что тесты реально шли.
5. Фейки объявлены потокобезопасными заменой `mutableMapOf` на `ConcurrentHashMap`, но итерация по ним осталась небезопасной в другом методе. Проверить все методы фейка.
6. Порядок в фейке перемешан, но тест задания 02 после этого стал падать — и его «поправили» вместо кода. Это ослабление, запрещено.
7. `delay(50)` заменён на `advanceUntilIdle()`, но ассерт при этом ослаблен или перенесён. Сверить ассерты до и после построчно.
8. Новый `EndToEndContractScenarioTest` покрывает меньше сценариев, чем старый, а старый уже удалён. Требование: удаление только после построчного соответствия.
9. В отчёте `green` для незапущенной команды (`AGENTS.md §3`).
## Definition of Done
- Пуш с намеренно сломанным юнит-тестом валит пайплайн — приложен лог.
- Пуш с намеренно сломанным `cargo clippy` валит пайплайн — приложен лог.
- Инструментальные тесты заданий 02 и 03 запускаются в CI и проходят.
- Ни один тест не запускается с `delay`-ожиданием; все пять мест переведены.
- Ни один тест не удалён без построчно обоснованной замены.
- Фейки потокобезопасны, порядок сообщений в них не мягче реального Room.
- `main` защищён от merge при падении обязательных джобов, либо это зафиксировано как требующее действия владельца.
## Required tests
Отдельных продуктовых тестов задание не добавляет. Доказательства — прогоны:
1. прогон на текущем `HEAD`: всё зелёное;
2. прогон с намеренно сломанным юнит-тестом: красный, указано какой шаг;
3. прогон с намеренно сломанным clippy-предупреждением: красный;
4. прогон инструментальных тестов: список выполненных, не «0 tests».
Все четыре — с ссылками или полными логами, обоими кодерами независимо.
## Required verification
```text
./gradlew --no-daemon testDebugUnitTest
./gradlew --no-daemon connectedDebugAndroidTest
./gradlew --no-daemon lint
./gradlew --no-daemon assembleDebug
cd hermes-pair && cargo test --all-targets && cargo clippy -- -D warnings
```
Локально и в CI. Расхождение локального и CI-результата — само по себе находка, фиксируется в отчёте.
## Result
`agents/antigravity/done/TASK-2026-08-24-04-ci-and-test-harness.md` — разделы `## Кодер 1`, `## Кодер 2 (review + доработка)`, `## Вердикт оркестратора`. Содержимое по `AGENTS.md §4`.