work_item: ORCH-062
stage: analysis
author_agent: analyst
status: ready-for-review
created_at: 2026-06-09
model_used: claude-opus-4-8
title: "Авто-prune docker build cache на mva154 — план тестов"
framework: pytest
scope: >
  Покрывает code-путь (Вариант A — heartbeat-демон src/build_cache_pruner.py по образцу
  src/disk_watchdog.py): чистая decision-логика (надо ли убирать на этом тике), построение
  безопасной docker-команды с политикой удержания, never-raise на ошибках subprocess/таймаут/
  недоступность docker.sock, kill-switch (демон не стартует), наблюдаемость status()/GET /queue,
  интеграция в lifespan. ВНЕ покрытия pytest: реальный вызов docker (subprocess мокается — тесты
  не должны трогать настоящий docker daemon), реальное освобождение диска. Если архитектор выберет
  чистый инфра-путь (B daemon.json / C cron) без кода src/**, применимые TC сводятся к ручной
  host-верификации, описанной в 07-infra-requirements.md / INFRA.md (см. TC-10).
notes: >
  docker-вызовы изолируются моками (monkeypatch subprocess.run / docker-клиента) — НИ ОДИН тест не
  выполняет настоящий `docker builder prune`. Время/период инъектируются (now_provider), как в
  тестах disk_watchdog. Полный регресс `pytest tests/ -q` остаётся зелёным; STAGE_TRANSITIONS /
  QG_CHECKS / схема БД не затрагиваются — отдельных гейт-тестов фича не добавляет.

tests:
  - id: TC-01
    type: unit
    description: "decide-функция: при включённом pruner и истёкшем периоде с прошлой уборки решение = PRUNE"
    module: tests/test_build_cache_pruner.py
    expected: PASS

  - id: TC-02
    type: unit
    description: "decide-функция: период с прошлой уборки не истёк → решение = SKIP (анти-частота, NFR-4)"
    module: tests/test_build_cache_pruner.py
    expected: PASS

  - id: TC-03
    type: unit
    description: "Построение docker-команды несёт возрастной фильтр удержания (until=<retention>) и НЕ содержит image/system prune (FR-2/FR-3/AC-2/AC-3)"
    module: tests/test_build_cache_pruner.py
    expected: PASS

  - id: TC-04
    type: unit
    description: "never-raise: subprocess бросает исключение / возвращает ненулевой rc → тик не падает, ошибка залогирована (FR-6/AC-4)"
    module: tests/test_build_cache_pruner.py
    expected: PASS

  - id: TC-05
    type: unit
    description: "never-raise: недоступность docker.sock (FileNotFoundError/PermissionError) → тик пропускается, цикл жив (FR-6/AC-4)"
    module: tests/test_build_cache_pruner.py
    expected: PASS

  - id: TC-06
    type: unit
    description: "never-raise: таймаут docker-команды (TimeoutExpired) проглатывается, фоновый цикл продолжает работу (FR-6/AC-4)"
    module: tests/test_build_cache_pruner.py
    expected: PASS

  - id: TC-07
    type: unit
    description: "kill-switch: при *_enabled=False start() — no-op, фоновый поток не стартует (FR-5/AC-5/NFR-3)"
    module: tests/test_build_cache_pruner.py
    expected: PASS

  - id: TC-08
    type: unit
    description: "config: невалидный *_interval_s / retention → лог-warning + безопасный дефолт, старт не падает (FR-5/AC-6)"
    module: tests/test_build_cache_pruner.py
    expected: PASS

  - id: TC-09
    type: unit
    description: "status() never-raise и содержит enabled/interval_s/retention/last_run_ts + best-effort результат последней уборки (FR-4/AC-7)"
    module: tests/test_build_cache_pruner.py
    expected: PASS

  - id: TC-10
    type: unit
    description: "Изоляция от Quality Gate: модуль-pruner — leaf, не импортирует stage_engine/stages/qg; STAGE_TRANSITIONS и QG_CHECKS не изменены (NFR-2/AC-8)"
    module: tests/test_build_cache_pruner.py
    expected: PASS

  - id: TC-11
    type: integration
    description: "lifespan: при включённом флаге демон стартует в app-lifespan и корректно останавливается на shutdown (рядом с disk_watchdog), docker замокан (FR-1/AC-1)"
    module: tests/test_build_cache_pruner.py
    expected: PASS

  - id: TC-12
    type: integration
    description: "GET /queue содержит read-only блок авто-prune с состоянием (enabled/interval_s/retention/last_run_ts); при выключенном флаге enabled=false (FR-4/AC-5/AC-7)"
    module: tests/test_build_cache_pruner.py
    expected: PASS
