test: prepare resource-limited isolated VPS PostgreSQL stand
CI / test (pull_request) Successful in 9m10s
CI / deploy (pull_request) Has been skipped

This commit is contained in:
codex committed 2026-10-05 12:48:06 +03:00
1 parent 8adaa4d6ea
commit 4c609cc7ea
9 files changed
+241 -8

No files matched your search

+3 -1
View File
@@ -141,10 +141,12 @@ node --check app/static/admin.js
Тесты с данными используют временную SQLite; отдельные API smoke-тесты запускают приложение с его текущей конфигурацией. Браузерная проверка: `pip install playwright`, `python -m playwright install chromium`, затем `python tests/browser_admin.py`. Она проверяет реальный рендеринг страниц, клавиатуру, диалог, фильтры и восстановление прогресса после ошибки. Скриншоты сохраняются во временную папку; путь выводится в конце.
Версия сервиса: `0.10.3`. Админка всегда показывает версии backend и frontend в footer.
Версия сервиса: `0.10.4`. Админка всегда показывает версии backend и frontend в footer.
## Разовый импорт публикаций
Инструкция, минимальные права временной роли и ограниченный SSH-запуск: [docs/publication-import.md](docs/publication-import.md). По умолчанию импортер выполняет dry-run; production-применение требует отдельного решения владельца.
Интеграционная проверка импорта на отдельном PostgreSQL: [docs/postgresql-import-verification.md](docs/postgresql-import-verification.md).
Отдельный тестовый стенд на VPS и read-only проверка ресурсов: [docs/postgresql-import-vps.md](docs/postgresql-import-vps.md). Стенд запускает только владелец после оценки ресурсов.
+3 -3
View File
@@ -1,3 +1,3 @@
APP_VERSION = "0.10.3"
FRONTEND_VERSION = "0.10.3"
BACKEND_VERSION = "0.10.3"
APP_VERSION = "0.10.4"
FRONTEND_VERSION = "0.10.4"
BACKEND_VERSION = "0.10.4"
+5 -2
View File
@@ -52,7 +52,9 @@ HTTP заблокирован test fixture; входные профили пол
Выполняйте на локальной машине с Docker Engine/Compose v2 из корня репозитория
после независимого ревью. Команды явно выбирают локальный Unix socket и очищают
наследуемое окружение. Для Docker Desktop/Windows используйте Linux/WSL с
доступным локальным `/var/run/docker.sock`; не подменяйте его удалённым VPS.
доступным локальным `/var/run/docker.sock`. Для отдельного стенда на VPS используйте
[отдельную инструкцию](postgresql-import-vps.md) и `compose.vps.yml`: этот локальный
Compose не предназначен для запуска на сервере с работающими сервисами.
Первый шаг:
```sh
@@ -61,7 +63,8 @@ env -i PATH="$PATH" docker --host unix:///var/run/docker.sock version
Ожидается exit code 0 и секции `Client` и `Server`. `command not found`,
`permission denied` или только Client с ошибкой подключения означает, что
до запуска тестов нужно восстановить локальный Docker; не подключайтесь к VPS.
до запуска этого варианта нужен локальный Docker. При его отсутствии сначала
выполните только read-only диагностику из инструкции VPS.
Дальнейшие точные команды сохранены здесь для воспроизводимого запуска:
+153
View File
@@ -0,0 +1,153 @@
# PostgreSQL-проверка на отдельном стенде VPS
Команды выполняет владелец под root. Подготовка не подключается к VPS и не
запускает стенд. Не используйте production Compose/`.env`, Docker context,
volumes, сети, mounts или production credentials. Никаких production-ролей БД,
SSH-ключей и правил firewall эти команды не создают и не изменяют.
## Сначала только read-only проверка
Следующий блок не создаёт контейнеров, не скачивает образов и не меняет данные.
Docker CLI использует локальный Unix socket и очищенное окружение; конфигурация
CLI указывает на отдельный каталог, который здесь не создаётся.
```sh
free -h
nproc
uptime
df -h / /var/lib/docker
env -i PATH="$PATH" docker --config /root/miem-publication-integration/docker-config --host unix:///var/run/docker.sock info --format 'CPUs={{.NCPU}} RAM_bytes={{.MemTotal}} cgroup={{.CgroupVersion}} DockerRootDir={{.DockerRootDir}} warnings={{json .Warnings}}'
env -i PATH="$PATH" docker --config /root/miem-publication-integration/docker-config --host unix:///var/run/docker.sock compose version
env -i PATH="$PATH" docker --config /root/miem-publication-integration/docker-config --host unix:///var/run/docker.sock stats --no-stream --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.PIDs}}'
env -i PATH="$PATH" docker --config /root/miem-publication-integration/docker-config --host unix:///var/run/docker.sock image inspect postgres:16-alpine python:3.12-slim miem-publication-integration-postgres:pr55 miem-publication-integration-runner:pr55 --format '{{.RepoTags}} id={{.Id}} revision={{index .Config.Labels "org.opencontainers.image.revision"}}'
```
Как читать результат:
- `free`: нужна колонка **available**, а не только free. Предварительный запас —
не менее 2 GiB: стенд ограничен суммарно 1 GiB, ещё минимум 1 GiB остаётся
системе/сервисам. Это ориентир, решение зависит от их нагрузки и пиков.
- `nproc` и `uptime`: сравните число CPU и load average. Постоянный load на уровне
числа CPU или выше — основание отложить тест, а не останавливать сервисы.
- `df`: желательно не менее 4 GiB свободно для загрузки образов и запаса работы
Docker. Если DockerRootDir отличается от `/var/lib/docker`, отдельно выполните
`df -h` для показанного каталога. Не используйте prune для освобождения места.
- `stats`: показывает потребление уже работающих контейнеров, но не их пиковые
требования. Docker warnings о недоступных memory/swap/cgroup limits нужно
устранить до запуска; без действующих лимитов этот вариант не запускать.
- `image inspect`: существующие образы показывают tags, ID и revision.
`No such image` означает отсутствие образа, **не** разрешение на pull/build.
Для запуска нужны два образа `miem-publication-integration-*:pr55` с revision
проверенного commit PR #55. Base-образы нужны только для сборки, не для самого
запуска уже подготовленного стенда.
Пришлите только этот диагностический вывод. Не присылайте `docker inspect`
production-контейнеров целиком, env, конфигурации подключения или секреты.
До оценки ресурсов и завершения независимого review остальные действия ниже
не выполнять.
## Изоляция и лимиты подготовленного стенда
Используется только `tests/postgres/compose.vps.yml`, не override production или
локального Compose. Project name фиксирован:
`miem-publication-integration-vps`.
| Ресурс | Лимит / область |
|---|---|
| PostgreSQL | Отдельный контейнер `postgres-test`, 0.5 CPU, 512 MiB RAM, без дополнительного swap, 128 процессов |
| PostgreSQL настройки | shared_buffers 64 MiB, max_connections 12 |
| Test runner | 0.5 CPU, 512 MiB RAM, без дополнительного swap, 128 процессов; UID 65534, read-only rootfs, no-new-privileges, без capabilities |
| Данные PostgreSQL | Только named volume `miem-publication-integration-vps_pgdata`; local driver tmpfs до 256 MiB |
| Временные файлы runner | tmpfs `/tmp` до 128 MiB; входят в memory limit контейнера |
| Сеть | Только `miem-publication-integration-vps_isolated`, internal; опубликованных портов нет |
| Логи | json-file, до 5 MiB на контейнер, один файл |
Volume с tmpfs выбран намеренно: отдельное имя для адресной очистки и предел
размера синтетических данных. Данные не сохраняются после остановки/размонтирования.
Образы содержат только код и синтетический init.sql. Host bind mounts,
Docker socket, production networks и env_file отсутствуют. Docker socket
доступен только CLI владельца на хосте, ни одному контейнеру он не передаётся.
Нет restart policy. Pull запрещён; ресурсы не создаются автоматически при
отсутствии заранее подготовленного образа.
Лимиты процесса сборки/загрузки образов отличаются от runtime: Compose CPU/RAM
limits **не ограничивают Docker build**. Поэтому на VPS с рабочими сервисами
не выполняйте незапланированную сборку. Если готовых образов нет, отдельно
согласуйте место сборки и окно загрузки после read-only диагностики.
## Подготовка образов после review (не запуск стенда)
На отдельной согласованной машине сборки с Docker, из проверенной копии PR #55,
укажите полный SHA проверенного commit в переменной `TEST_COMMIT` (не секрет).
Сначала `git status --short` должен быть пустым, `git rev-parse HEAD` — совпадать
с TEST_COMMIT. Это не команды для немедленного выполнения на VPS.
```sh
git status --short
git rev-parse HEAD
env -i PATH="$PATH" docker --host unix:///var/run/docker.sock build --label org.opencontainers.image.revision="$TEST_COMMIT" -t miem-publication-integration-runner:pr55 -f tests/postgres/Dockerfile .
env -i PATH="$PATH" docker --host unix:///var/run/docker.sock build --label org.opencontainers.image.revision="$TEST_COMMIT" -t miem-publication-integration-postgres:pr55 -f tests/postgres/Dockerfile.postgres tests/postgres
env -i PATH="$PATH" docker --host unix:///var/run/docker.sock image save --output miem-publication-integration-images.tar miem-publication-integration-postgres:pr55 miem-publication-integration-runner:pr55
sha256sum miem-publication-integration-images.tar tests/postgres/compose.vps.yml
```
Dockerfile-specific ignore-файлы исключают секреты, реальные CSV и остальные
файлы workspace из обоих контекстов сборки. Передайте владельцу только архив
этих двух образов и `compose.vps.yml` с проверенными SHA256, без полного workspace
и `.env`. Транспорт выбирает владелец; инструкция не получает SSH/production
credentials. Base-образы могут скачиваться только на согласованной машине сборки.
На VPS после оценки ресурсов/отдельного разрешения владельца: файлы размещаются
в `/root/miem-publication-integration`, Compose переименован в `compose.vps.yml`,
архив — в `images.tar`. Проверка checksum должна совпасть с источником.
```sh
install -d -m 700 /root/miem-publication-integration /root/miem-publication-integration/docker-config
sha256sum /root/miem-publication-integration/images.tar /root/miem-publication-integration/compose.vps.yml
env -i PATH="$PATH" docker --config /root/miem-publication-integration/docker-config --host unix:///var/run/docker.sock image load --input /root/miem-publication-integration/images.tar
env -i PATH="$PATH" docker --config /root/miem-publication-integration/docker-config --host unix:///var/run/docker.sock image inspect miem-publication-integration-postgres:pr55 miem-publication-integration-runner:pr55 --format '{{.RepoTags}} id={{.Id}} revision={{index .Config.Labels "org.opencontainers.image.revision"}}'
env -i PATH="$PATH" docker --config /root/miem-publication-integration/docker-config --host unix:///var/run/docker.sock compose --env-file /dev/null --project-name miem-publication-integration-vps -f /root/miem-publication-integration/compose.vps.yml config --quiet
```
Проверьте отсутствие уже существующих ресурсов с этим project name. Если они
есть, выясните их назначение; не запускайте второй стенд и не удаляйте чужие
ресурсы. Следующие команды только читают labels Docker Compose:
```sh
env -i PATH="$PATH" docker --config /root/miem-publication-integration/docker-config --host unix:///var/run/docker.sock ps -a --filter label=com.docker.compose.project=miem-publication-integration-vps
env -i PATH="$PATH" docker --config /root/miem-publication-integration/docker-config --host unix:///var/run/docker.sock network ls --filter label=com.docker.compose.project=miem-publication-integration-vps
env -i PATH="$PATH" docker --config /root/miem-publication-integration/docker-config --host unix:///var/run/docker.sock volume ls --filter label=com.docker.compose.project=miem-publication-integration-vps
```
## Запуск и адресная очистка (пока не выполнять)
Только после согласования владельцем ресурсов и проверенного состава стенда:
```sh
env -i PATH="$PATH" docker --config /root/miem-publication-integration/docker-config --host unix:///var/run/docker.sock compose --env-file /dev/null --project-name miem-publication-integration-vps -f /root/miem-publication-integration/compose.vps.yml up --no-build --pull never --abort-on-container-exit --exit-code-from tests
```
Ожидаемый успех — `10 passed`, exit code 0. Сохраните вывод/код выхода и логи:
```sh
env -i PATH="$PATH" docker --config /root/miem-publication-integration/docker-config --host unix:///var/run/docker.sock compose --env-file /dev/null --project-name miem-publication-integration-vps -f /root/miem-publication-integration/compose.vps.yml logs --no-color > /root/miem-publication-integration/test.log
```
**Следующая команда удаляет контейнеры, внутреннюю сеть и named volume именно
этого теста; все его синтетические данные будут потеряны.** Перед ней сохраните
отчёт. Она не затрагивает ресурсы других Compose-проектов:
```sh
env -i PATH="$PATH" docker --config /root/miem-publication-integration/docker-config --host unix:///var/run/docker.sock compose --env-file /dev/null --project-name miem-publication-integration-vps -f /root/miem-publication-integration/compose.vps.yml down --volumes
```
Не используйте `--remove-orphans`, `docker system prune`, общие rm/rmi/volume prune
или down рабочего проекта. Base-образы и образы теста оставьте: они могут быть
общими для других проверок. Архив и журнал также не удаляются этой командой.
Для подтверждения очистки повторите три read-only команды с project label:
в выводе должны остаться только заголовки.
## Статус
VPS-стенд не запускался, Docker/Compose-проверка не выполнялась в среде
подготовки из-за отсутствия Docker. PostgreSQL-интеграция ещё не подтверждена.
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "miem-workers"
version = "0.10.3"
version = "0.10.4"
description = "MIEM employees parser, admin API, and web admin"
requires-python = ">=3.11"
dependencies = [
+2
View File
@@ -0,0 +1,2 @@
FROM postgres:16-alpine
COPY init.sql /docker-entrypoint-initdb.d/10-test.sql
@@ -0,0 +1,4 @@
**
!init.sql
!Dockerfile.postgres
!Dockerfile.postgres.dockerignore
+69
View File
@@ -0,0 +1,69 @@
name: miem-publication-integration-vps
services:
postgres-test:
image: miem-publication-integration-postgres:pr55
pull_policy: never
environment:
POSTGRES_USER: test_admin
POSTGRES_PASSWORD: isolated_test_admin
POSTGRES_DB: miem_import_test
command: ["postgres", "-c", "shared_buffers=64MB", "-c", "max_connections=12"]
cpus: 0.5
mem_limit: 512m
memswap_limit: 512m
pids_limit: 128
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-h", "127.0.0.1", "-U", "test_admin", "-d", "miem_import_test"]
interval: 2s
timeout: 5s
retries: 30
logging:
driver: json-file
options:
max-size: "5m"
max-file: "1"
networks: [isolated]
tests:
image: miem-publication-integration-runner:pr55
pull_policy: never
user: "65534:65534"
read_only: true
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
cpus: 0.5
mem_limit: 512m
memswap_limit: 512m
pids_limit: 128
tmpfs:
- /tmp:size=128m,mode=1777
environment:
DATABASE_URL: postgresql+psycopg://test_admin:isolated_test_admin@postgres-test:5432/miem_import_test
MIEM_POSTGRES_INTEGRATION: "1"
PYTHONDONTWRITEBYTECODE: "1"
PYTEST_ADDOPTS: "-p no:cacheprovider"
command: ["python", "-m", "pytest", "-q", "tests/test_publication_import_postgres.py"]
depends_on:
postgres-test:
condition: service_healthy
logging:
driver: json-file
options:
max-size: "5m"
max-file: "1"
networks: [isolated]
networks:
isolated:
internal: true
volumes:
pgdata:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
o: size=256m
+1 -1
View File
@@ -18,7 +18,7 @@ def test_health_returns_versions():
response = TestClient(app).get("/api/health")
assert response.status_code == 200
assert response.json()["backend_version"] == "0.10.3"
assert response.json()["backend_version"] == "0.10.4"
def test_api_employees_and_stats_require_admin_session():