Содержание

Разработка и тесты

Локальный стенд без 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-триггере, иначе гейт узнает о правке через минуту, а не сразу.
  • Единицы одни от начала до конца. Цены задаются, расход копится и бюджеты сравниваются в долларах; валюта отображения применяется на пути к странице и никогда не записывается.
  • Неизвестное лучше выдуманного. Длительность аудио, число токенов у провайдера без такого эндпоинта, тело потокового ответа — там, где честного ответа нет, возвращается отказ или ноль, а не оценка. Оценка превращается в счёт.