# 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`.