254 lines
13 KiB
YAML
254 lines
13 KiB
YAML
work_item_id: ET-015
|
||
title: "Multi-repo support для оркестратора"
|
||
target_repo: orchestrator
|
||
notes: >
|
||
Тесты пишутся и запускаются в репо `orchestrator` (pytest).
|
||
Поскольку фича чисто бэкендовая (нет UI-изменений в enduro-trails),
|
||
UI test cases НЕ создаются.
|
||
|
||
unit:
|
||
- id: UT-01
|
||
name: get_next_work_item_id — per-prefix numbering
|
||
file: tests/test_db_multi_repo.py::test_next_id_per_prefix
|
||
given: "tasks table has ET-001..ET-015 (repo=enduro-trails) and no ORCH-* rows"
|
||
when: "вызывается get_next_work_item_id для проекта с prefix='ET'"
|
||
expect: "возвращает 'ET-016'"
|
||
and_when: "вызывается get_next_work_item_id для проекта с prefix='ORCH'"
|
||
expect_2: "возвращает 'ORCH-001'"
|
||
covers_ac: [AC-4]
|
||
|
||
- id: UT-02
|
||
name: get_next_work_item_id — нумерация ORCH идёт независимо от ET
|
||
file: tests/test_db_multi_repo.py::test_orch_numbering_independent
|
||
given: "в tasks есть ET-001..ET-015 и ORCH-001..ORCH-003"
|
||
when: "запрашивается следующий ID для ORCH"
|
||
expect: "ORCH-004 (не ORCH-016)"
|
||
covers_ac: [AC-4]
|
||
|
||
- id: UT-03
|
||
name: ProjectRegistry — load from config
|
||
file: tests/test_project_registry.py::test_load_two_projects
|
||
given: "конфиг с двумя проектами"
|
||
when: "registry загружается на старте"
|
||
expect: "len(registry.projects) == 2; lookup по plane_project_id возвращает правильный key"
|
||
covers_ac: [AC-1, AC-2]
|
||
|
||
- id: UT-04
|
||
name: ProjectRegistry — invalid config
|
||
file: tests/test_project_registry.py::test_invalid_config_logged
|
||
given: "конфиг с проектом без plane_project_id"
|
||
when: "registry загружается"
|
||
expect: "запись пропускается с WARNING, остальные грузятся"
|
||
covers_ac: [AC-1]
|
||
|
||
- id: UT-05
|
||
name: ProjectRegistry — lookup by plane_project_id
|
||
file: tests/test_project_registry.py::test_lookup_by_plane_project_id
|
||
given: "registry с тремя проектами"
|
||
when: "lookup по plane_project_id='unknown-uuid'"
|
||
expect: "возвращает None"
|
||
covers_ac: [AC-12]
|
||
|
||
- id: UT-06
|
||
name: ProjectRegistry — lookup by repo name
|
||
file: tests/test_project_registry.py::test_lookup_by_repo
|
||
given: "registry с проектом repo='orchestrator'"
|
||
when: "lookup('orchestrator')"
|
||
expect: "возвращает конфиг проекта"
|
||
covers_ac: [AC-3]
|
||
|
||
- id: UT-07
|
||
name: plane_sync — workspace/project определяется по work_item_id
|
||
file: tests/test_plane_sync_multi.py::test_workspace_per_project
|
||
given: "tasks содержит ORCH-001 (repo=orchestrator); registry имеет настройки orchestrator"
|
||
when: "вызывается update_issue_state('ORCH-001', 'in_progress')"
|
||
expect: "httpx.patch вызывается с URL содержащим workspace проекта orchestrator и state-id из его plane_states"
|
||
covers_ac: [AC-5, AC-6]
|
||
|
||
- id: UT-08
|
||
name: verify_plane_signature — per-project secret
|
||
file: tests/test_webhooks_multi.py::test_hmac_per_project
|
||
given: "проект orchestrator имеет secret S2"
|
||
when: "вебхук подписан S1"
|
||
expect: "verify возвращает False"
|
||
and_when: "вебхук подписан S2"
|
||
expect_2: "verify возвращает True"
|
||
covers_ac: [AC-14]
|
||
|
||
- id: UT-09
|
||
name: get_repo_path_or_fail — отсутствие main-clone
|
||
file: tests/test_launcher_multi.py::test_missing_main_clone
|
||
given: "проект foo в реестре, /repos/foo не существует"
|
||
when: "launcher.launch('analyst', 'foo', ...)"
|
||
expect: "raises FileNotFoundError И логируется ERROR с текстом 'Main repo not found'"
|
||
covers_ac: [AC-13]
|
||
|
||
- id: UT-10
|
||
name: has_deploy_stage=false — skip deployer
|
||
file: tests/test_launcher_multi.py::test_skip_deploy_stage
|
||
given: "проект orchestrator с has_deploy_stage=false; задача в testing с PASS test report"
|
||
when: "_try_advance_stage вызывается"
|
||
expect: "task.stage становится done; deployer.launch НЕ вызывается"
|
||
covers_ac: [AC-15]
|
||
|
||
- id: UT-11
|
||
name: STAGE_TO_STATE — стейт берётся из конфига проекта
|
||
file: tests/test_plane_sync_multi.py::test_stage_to_state_per_project
|
||
given: "два проекта с разными UUID для in_progress"
|
||
when: "оба переходят в стадию development"
|
||
expect: "patch с разными UUID — каждый со своим"
|
||
covers_ac: [AC-5]
|
||
|
||
- id: UT-12
|
||
name: handle_work_item_created — branch создаётся в нужном репо
|
||
file: tests/test_webhooks_multi.py::test_branch_in_correct_repo
|
||
given: "вебхук с project_id orchestrator"
|
||
when: "обрабатывается work_item.created"
|
||
expect: "POST /api/v1/repos/admin/orchestrator/branches; НЕ /enduro-trails/branches"
|
||
covers_ac: [AC-2]
|
||
|
||
integration:
|
||
- id: IT-01
|
||
name: End-to-end создание задачи в проекте enduro-trails
|
||
file: tests/integration/test_e2e_enduro.py::test_create_et_task
|
||
given: "оркестратор запущен, registry с обоими проектами; mock Plane + mock Gitea"
|
||
when: "POST /webhook/plane с work_item.created для enduro-trails"
|
||
expect:
|
||
- "Plane mock получил PATCH issue.state = in_progress"
|
||
- "Gitea mock получил POST /branches"
|
||
- "tasks содержит запись (repo=enduro-trails, work_item_id=ET-NNN, stage=analysis)"
|
||
- "Был вызван launcher.launch('analyst', 'enduro-trails', ...)"
|
||
covers_ac: [AC-2, AC-9]
|
||
|
||
- id: IT-02
|
||
name: End-to-end создание задачи в проекте orchestrator
|
||
file: tests/integration/test_e2e_orch.py::test_create_orch_task
|
||
given: "то же, что IT-01"
|
||
when: "POST /webhook/plane с work_item.created для orchestrator (другой project_id)"
|
||
expect:
|
||
- "Gitea mock получил POST /repos/admin/orchestrator/branches"
|
||
- "tasks содержит (repo=orchestrator, work_item_id=ORCH-001)"
|
||
- "launcher.launch('analyst', 'orchestrator', ...) — НЕ enduro-trails"
|
||
covers_ac: [AC-2, AC-4]
|
||
|
||
- id: IT-03
|
||
name: Параллельные задачи в разных репо не пересекаются
|
||
file: tests/integration/test_concurrent_repos.py::test_two_repos
|
||
given: "две задачи: ET-099 в enduro-trails и ORCH-099 в orchestrator"
|
||
when: "обе на стадии analysis с активными worktree"
|
||
expect:
|
||
- "/repos/_wt/enduro-trails/feature_ET-099-... существует"
|
||
- "/repos/_wt/orchestrator/feature_ORCH-099-... существует"
|
||
- "артефакты ET записаны в enduro-trails worktree, ORCH — в orchestrator worktree"
|
||
covers_ac: [AC-7]
|
||
|
||
- id: IT-04
|
||
name: Gitea push для одного репо не задевает задачи в другом
|
||
file: tests/integration/test_gitea_routing.py::test_push_isolation
|
||
given: "активны ET-099 и ORCH-099"
|
||
when: "приходит push на ветку ORCH-099 в репо orchestrator"
|
||
expect:
|
||
- "tasks.stage для ORCH-099 обновляется при наличии валидных файлов"
|
||
- "tasks.stage для ET-099 НЕ меняется"
|
||
covers_ac: [AC-3]
|
||
|
||
- id: IT-05
|
||
name: Plane comment :approved: для orchestrator advance'ит правильную задачу
|
||
file: tests/integration/test_plane_comment_routing.py::test_approved_routing
|
||
given: "ET-099 stage=analysis, ORCH-099 stage=analysis"
|
||
when: "comment.created с :approved: на issue ORCH-099"
|
||
expect:
|
||
- "tasks для ORCH-099 advance в architecture"
|
||
- "tasks для ET-099 не меняется"
|
||
covers_ac: [AC-6]
|
||
|
||
- id: IT-06
|
||
name: Неизвестный project_id игнорируется без 500
|
||
file: tests/integration/test_unknown_project.py::test_unknown_returns_202
|
||
given: "registry с одним проектом enduro-trails"
|
||
when: "приходит вебхук с project_id='unknown'"
|
||
expect:
|
||
- "HTTP 202"
|
||
- "WARNING в логах с текстом 'Unknown project_id'"
|
||
- "tasks без новых записей"
|
||
covers_ac: [AC-12]
|
||
|
||
- id: IT-07
|
||
name: Отсутствие main-clone — Telegram + 202
|
||
file: tests/integration/test_missing_clone.py::test_no_main_clone
|
||
given: "registry с проектом foo, /repos/foo отсутствует; Telegram mock"
|
||
when: "приходит work_item.created для foo"
|
||
expect:
|
||
- "HTTP 202"
|
||
- "ERROR в логах 'Main repo not found: /repos/foo'"
|
||
- "Telegram mock получил одно сообщение"
|
||
covers_ac: [AC-13]
|
||
|
||
e2e:
|
||
- id: E2E-01
|
||
name: Smoke: подключение нового проекта на тестовом стенде
|
||
type: manual
|
||
where: "test stand (mva154)"
|
||
steps:
|
||
- "Подготовить /repos/orchestrator на хосте (git clone, если ещё нет)"
|
||
- "Создать в Plane проект 'Orchestrator-smoke', получить state UUID'ы"
|
||
- "Добавить запись в конфиг проектов оркестратора (см. ADR)"
|
||
- "Перезапустить контейнер оркестратора: docker compose restart"
|
||
- "Проверить логи: 'Loaded 2 projects: enduro-trails, orchestrator-smoke'"
|
||
- "Зарегистрировать webhook Plane (target orchestrator) для Orchestrator-smoke"
|
||
- "Создать тестовый work item 'Smoke ORCH-1' в Plane"
|
||
- "Дождаться появления ветки в gitea-репо orchestrator (~30s)"
|
||
- "Проверить tasks в БД оркестратора: repo=orchestrator, work_item_id=ORCH-001"
|
||
- "Удалить тестовую запись Plane и проверить, что enduro-trails не пострадал"
|
||
expect: "Все шаги без ошибок, регрессий на enduro-trails нет"
|
||
covers_ac: [AC-2, AC-4, AC-7, AC-9, AC-17]
|
||
|
||
- id: E2E-02
|
||
name: Smoke: создание следующей задачи в enduro-trails после релиза
|
||
type: manual
|
||
where: "test stand"
|
||
steps:
|
||
- "После раскатки фичи создать в Plane-проекте enduro-trails новый WI"
|
||
- "Дождаться появления ветки feature/ET-016-..."
|
||
- "Проверить artifact'ы в docs/work-items/ET-016/"
|
||
expect: "ID=ET-016 (следующий после ET-015), всё как раньше"
|
||
covers_ac: [AC-9, AC-4]
|
||
|
||
regression:
|
||
- id: REG-01
|
||
name: Активные задачи продолжают работу после рестарта
|
||
type: manual
|
||
where: "test stand"
|
||
steps:
|
||
- "Перед раскаткой запомнить snapshot tasks (id, stage, repo, branch)"
|
||
- "Раскатать фичу"
|
||
- "Перезапустить orchestrator container"
|
||
- "Проверить tasks snapshot после рестарта"
|
||
expect: "Записи идентичны (stage не сбился, repo/branch не изменились)"
|
||
covers_ac: [AC-10]
|
||
|
||
- id: REG-02
|
||
name: Test report check_tests_local продолжает работать
|
||
given: "enduro-trails branch с зелёными тестами"
|
||
when: "agent developer завершается"
|
||
expect: "QG check_tests_local вызывает make test в worktree и проходит"
|
||
covers_ac: [AC-9]
|
||
|
||
coverage:
|
||
required_min_percent: 75
|
||
applies_to:
|
||
- src/config.py
|
||
- src/db.py
|
||
- src/plane_sync.py
|
||
- src/webhooks/plane.py
|
||
- src/webhooks/gitea.py
|
||
- src/agents/launcher.py
|
||
new_modules_expected:
|
||
- src/project_registry.py # имя ориентировочное, точное название — на усмотрение архитектора
|
||
|
||
notes_for_tester:
|
||
- "В существующих тестах могут быть моки на settings.default_repo — их нужно расширить под новый registry"
|
||
- "find_issue_id в plane_sync.py делает DB lookup — учесть это при моках"
|
||
- "Telegram-нотификации логировать через send_telegram моки, чтобы избежать реальных запросов"
|
||
- "Не нужно тестировать сами Plane/Gitea API — только что оркестратор формирует правильные запросы"
|