From f69003be75a69056c5dd9e010d51c189d1a6e78f Mon Sep 17 00:00:00 2001 From: Vladislav Kopnin Date: Mon, 28 Sep 2026 13:37:23 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=20=D1=81=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=BA=D0=B8:=20api-=D0=BE=D0=B1=D1=80=D0=B0=D0=B7=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=BA=D0=BE=D0=BF=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BB=20admin.py=20(=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=B9=D0=BD?= =?UTF-8?q?=D0=B5=D1=80=20=D0=BF=D0=B0=D0=B4=D0=B0=D0=BB=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=B8=D0=BC=D0=BF=D0=BE=D1=80=D1=82=D0=B5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dockerfile для stage api копировал только main.py и schedule/, поэтому в контейнере 'from admin import ...' давал ModuleNotFoundError — api стартовал и сразу падал, compose останавливался на 'dependency failed to start'. Добавлен COPY admin.py и тест tests/test_deploy.py: проверяет, что в образ api попадают все модули backend, в frontend — страница админки и статика, а compose передаёт ADMIN_* и подключает том настроек. --- Dockerfile | 1 + tests/test_deploy.py | 47 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 tests/test_deploy.py diff --git a/Dockerfile b/Dockerfile index 8356767..c5d189f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ RUN pip install --no-cache-dir -r requirements.txt \ FROM base AS api COPY --chown=app:app src/backend/main.py ./ +COPY --chown=app:app src/backend/admin.py ./ COPY --chown=app:app src/backend/schedule ./schedule USER app EXPOSE 8000 diff --git a/tests/test_deploy.py b/tests/test_deploy.py new file mode 100644 index 0000000..9e294d5 --- /dev/null +++ b/tests/test_deploy.py @@ -0,0 +1,47 @@ +"""Проверки сборки образов: все нужные файлы должны копироваться. + +Ошибка «в Dockerfile забыли COPY admin.py» роняет контейнер на импорте, а +локальные тесты её не видят (они запускаются из исходников). Ловим статически. +""" + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +DOCKERFILE = ROOT / "Dockerfile" +COMPOSE = ROOT / "docker-compose.yml" + + +def _stage(name: str) -> str: + dockerfile = DOCKERFILE.read_text(encoding="utf-8") + part = dockerfile.split(f"FROM base AS {name}", 1)[1] + for other in ("api", "frontend"): + if other != name: + part = part.split(f"FROM base AS {other}", 1)[0] + return part + + +def test_api_image_copies_every_backend_module(): + stage = _stage("api") + for module in sorted((ROOT / "src" / "backend").glob("*.py")): + assert module.name in stage, f"{module.name} не копируется в образ api" + assert "src/backend/schedule" in stage, "пакет schedule не копируется в образ api" + + +def test_frontend_image_copies_page_and_static(): + stage = _stage("frontend") + for path in ("src/frontend/main.py", "src/frontend/admin.html", "src/frontend/static"): + assert path in stage, f"{path} не копируется в образ frontend" + + +def test_compose_passes_admin_settings(): + compose = COMPOSE.read_text(encoding="utf-8") + for var in ( + "ADMIN_PATH", + "ADMIN_LOGIN", + "ADMIN_PASSWORD", + "SCHEDULE_INTERVAL_MINUTES", + "SETTINGS_FILE", + "SCHEDULE_API", + ): + assert var in compose, f"{var} не передаётся контейнерам" + assert "schedule_data:/data" in compose, "том для настроек админки не подключён"