# Task 04: CI и проверяемость тестов (hermes-android)

**Repo:** `ochenstarik-ui/hermes-android`
**Assigned to:** Antigravity (режим оркестратора, два кодера)
**Priority:** HIGH (без этого ни одно предыдущее задание не защищено от регресса)
**Date:** 2026-08-24
**Base SHA:** результат задания 03 — указать фактический SHA при выдаче
**Зависимость:** задания 01–03 приняты. Их тесты — материал, на котором проверяется работоспособность CI.

## Роли и протокол

| Роль | Модель | Что делает |
|---|---|---|
| Оркестратор | Antigravity | Разбивает работу, маршрутизирует, принимает результат |
| Кодер 1 | Gemini Flash 3.7 high | Реализация §Scope целиком |
| Кодер 2 | Gemini Pro high | Независимая проверка работы кодера 1 и доработка |

**Раунд 1 (кодер 1).** Пункты §Scope по порядку. Для этого задания тесты-первыми неприменимы к пунктам 1–2 (инфраструктура) — вместо этого требуется демонстрация: намеренно сломанный коммит в черновой ветке валит пайплайн, вывод приложен к отчёту. Нерешённое — в `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

- Продуктовый код заданий 01–03: 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`.
