Разработка и тесты
Локальный стенд без Docker¶
Порядок важен: схему ставит панель, поэтому она первая.
# Postgres
docker run -d --name zbgate-pg -e POSTGRES_USER=zbgate -e POSTGRES_PASSWORD=zbgate \
-e POSTGRES_DB=zbgate -p 5432:5432 postgres:17-alpine
# Панель
cd zbgate-admin
uv venv --python 3.12 .venv && uv pip install -e '.[dev]' psycopg2-binary
export PGUSER=zbgate PGPASSWORD=zbgate
.venv/bin/alembic upgrade head
.venv/bin/uvicorn app.main:app --reload --port 8081
# Гейт
cd ../zbgate
go build -o zbgate ./cmd/zbgate && ./zbgate -config config.yaml
Если Docker недоступен, Postgres можно взять пакетом pgserver — он приносит
бинарники PostgreSQL колесом. Его супервизор поднимает сервер только на unix-сокете
(-h "" в командной строке перебивает postgresql.conf), поэтому для TCP
сервер запускается своими руками через pg_ctl -o "-h 127.0.0.1 -p ПОРТ". Путь к
сокету при этом должен быть коротким: лимит 107 байт, и каталог глубоко в /tmp
его уже превышает.
Проверки панели¶
.venv/bin/ruff check app/
.venv/bin/alembic upgrade head && .venv/bin/alembic downgrade base
Прогон миграций вниз до нуля — часть проверки, а не формальность.
Тесты гейта¶
go test ./... # без базы
Это уже покрывает: конвертеры форматов в обе стороны, маршрутизацию и регистр имён, пул ключей и cooldown, тарифную карту со ступенями, окнами и аудио, измерение длительности всех поддерживаемых контейнеров, разбор каталога моделей и сквозные тесты через настоящие HTTP-хендлеры против мок-апстримов на настоящих wire-форматах.
Сквозные проверяют то, чего юнит-тест одной стороны не увидит: кросс-форматную трансляцию в обе стороны, стриминг в обе стороны, фолбэк и неутечку клиентского ключа в апстрим.
Тесты с базой¶
# Учётные данные — в переменных окружения, а не в строке подключения: DSN с
# логином и паролем внутри рано или поздно оказывается в чьей-то истории команд.
export PGUSER=zbgate PGPASSWORD="$(grep ^POSTGRES_PASSWORD .env | cut -d= -f2)"
docker compose exec postgres createdb -U "$PGUSER" zbgate_test
docker run --rm --network host -e PGUSER -e PGPASSWORD \
-e ZBGATE_DATABASE_URL=postgresql+asyncpg://localhost:5432/zbgate_test \
-e ZBGATE_SECRET_KEY=любой-32-байтный -e ZBGATE_SESSION_SECRET=любой \
--entrypoint alembic zbgate-admin:local upgrade head
ZBGATE_TEST_DATABASE_URL="postgres://localhost:5432/zbgate_test?sslmode=disable" \
go test -race ./...
Схему ставит образ панели — она ей владеет. Без ZBGATE_TEST_DATABASE_URL
БД-тесты скипаются, а не падают.
Они делают TRUNCATE контрол-плейна, а строка подключения отличается от
боевой только хостом — база в обоих случаях называется zbgate. Поэтому они
отказываются работать, если в базе уже что-то есть: одна проверка стоимостью в
один запрос превращает «уничтожил боевые данные без предупреждения» в упавший
тест. Снять — ZBGATE_TEST_DESTRUCTIVE=1.
Что проверяют: расшифровку секретов, авторизацию по ключу, отключённые и истёкшие ключи, ограничение по моделям, лимит rpm, запись логов со стоимостью, наценку владельца на всех его ключах, каталог моделей под ограничениями, сохранность имени запрошенной модели при большом теле и горячий релоад.
Прогон интерфейса браузером¶
Панель без Node и сборки фронта, поэтому проверять её удобно Playwright-ом против живого стенда:
uv pip install playwright && .venv/bin/playwright install chromium
Что стоит проходить после правок в шаблонах: первый вход, создание провайдера и
ключа, кнопка «Спросить /v1/models», создание учётной записи с наценкой и
метками, цены со ступенью и аудио, выдача ключа пользователю, страница «Мои
ключи» под не-администратором.
Так был найден баг, которого не видит ни один серверный тест: у поля порога
ступени стояли min="1" step="1000", из-за чего браузер считал допустимыми
только 1, 1001, 2001… и молча отказывался отправлять форму на любом круглом
пороге. Сервер об этом запросе не узнавал вовсе. Если добавляете
<input type="number"> с min и step, проверьте, что min делится на step.
Что стоит помнить, меняя код¶
- Схема — контракт. Меняя её, прогоняйте БД-тесты гейта (Схема и миграции).
- Новая таблица конфигурации нуждается в notify-триггере, иначе гейт узнает о правке через минуту, а не сразу.
- Единицы одни от начала до конца. Цены задаются, расход копится и бюджеты сравниваются в долларах; валюта отображения применяется на пути к странице и никогда не записывается.
- Неизвестное лучше выдуманного. Длительность аудио, число токенов у провайдера без такого эндпоинта, тело потокового ответа — там, где честного ответа нет, возвращается отказ или ноль, а не оценка. Оценка превращается в счёт.