Skip to main content

Набор по умолчанию

go test ./... выполняется где угодно без демона Docker и без сети. Он покрывает доменные пакеты, рендеринг представлений, разбор вебхуков провайдеров, шифрование резервных копий и вычищение секретов из логов. Так и держите: набор, которому для запуска нужна инфраструктура, — это набор, который перестают запускать. Рядом с ним CI прогоняет проверку типов TypeScript, сборки фронтенда, Biome, валидацию Atlas, валидацию Mintlify, сканирование уязвимостей и сборку контейнеров.

Интеграционный набор

Тесты в internal/integrationtest несут build-тег integration и поднимают PostgreSQL через Testcontainers:
Обвязка применяет закоммиченные файлы миграций по порядку, а не дамп схемы, поэтому набор является заслоном не только для репозиториев, но и для миграций. Он покрывает:
  • применение миграций с пустой базы данных и перевод существующих клиентов на подписки при обновлении с v0.4 на v0.5;
  • пополнение, зачисляемое на кошелёк ровно один раз при повторно проигранном вебхуке, втором событии и сверке;
  • переплату и недоплату, которые разрешаются в книге проводок, притом что заказ всё равно рассчитывается;
  • сохранённую корзину, которая остаётся сохранённой, пока средств не хватает, и списывается один раз, когда они появляются;
  • ограничения одновременности, которые выдерживают одновременные покупки;
  • outbox и уведомление оператору, которые записываются по одному разу на расчёт;
  • режим обслуживания, который блокирует новые заказы, притом что оплаченный заказ всё равно рассчитывается;
  • устойчивую задачу выполнения заказа, которая фиксируется в транзакции расчёта;
  • ретенцию, которая удаляет расходуемые строки и никогда не удаляет событие аудита;
  • первичную настройку оператора, создающую ровно одного владельца, и одноразовость токена настройки;
  • вход, который отвечает одинаково на неизвестный адрес и на неверный пароль, блокировку после повторных неудач, а также жизненные циклы сессии, TOTP и кодов восстановления;
  • последнего активного владельца, который переживает и отзыв роли, и блокировку, и то, что роль владельца выдаёт только владелец;
  • блокировку, отзывающую живые сессии, смену пароля, щадящую только ту сессию, из которой она сделана, и ссылку сброса, срабатывающую один раз;
  • классификацию аудита, оба направления сортировки и keyset-страницы, которые никогда не повторяют и не пропускают строку.
load_test.go — часть того же набора. Это не бенчмарк пропускной способности: он гонит те же расчёт, покупку корзины и создание подписки из множества горутин одновременно, потому что задвоенное списание проявляется только тогда, когда одна и та же работа приходит с нескольких сторон сразу. Прогон набора — самый дешёвый способ поймать класс дефектов, которые компилируются и проходят юнит-тесты: перечисление Go, расширенное без своего CHECK в базе данных, срок истечения, вычисленный по часам приложения, тогда как created_at его строки берётся из часов базы, или ограничение, чьё производное имя угадали неверно. Каждый из них падает только против настоящего PostgreSQL.

Заслон обновления миграций

Задача CI migrations выполняется по одному разу на каждую поддерживаемую базовую точку — пустая база данных плюс каждый префикс истории миграций, — применяет остаток, а затем применяет head второй раз, чтобы доказать, что это no-op. Миграция, которая не может выполниться поверх выпущенной версии, падает там.

Что обязано принести финансовое изменение

Новое поведение платежей, кошелька, аутентификации, RBAC, импорта и выполнения заказов через Remnawave обязано с первой же реализации включать точечные тесты путей отказа и идемпотентности. На практике это означает доказать для нового пути:
  1. Один и тот же вход, поданный дважды, даёт один эффект.
  2. Частичный отказ не оставляет наполовину применённого состояния.
  3. Каждый отказ несёт устойчивую машинную причину, а не отформатированное сообщение.

Фикстуры провайдеров

internal/payments/testdata содержит записанные формы вебхуков, в которых заменены все настоящие идентификаторы. Их повторное проигрывание держит разбор и проверку подписи честными без песочного аккаунта. Никогда не коммитьте настоящий платёжный payload, токен или подпись — синтезируйте их.

Браузерное покрытие

apps/web/e2e содержит наборы Playwright, запускаемые командой bun run e2e из apps/web. Они выполняются против продакшен-сборки, а не сервера разработки: Next.js ведёт себя в разработке иначе, а заслон, который проходит против сборки, которую никто не выкатывает, — не заслон. Наборы покрывают то, чего не видит тест компонента, потому что это свойство отрисованной страницы, а не компонента, — заслоны доступности, вёрстки и локализации, — плюс границу доступа обеих панелей: каждый операторский и клиентский маршрут отказывает анонимному посетителю, и ничто за сессионным заслоном не отрисовывается раньше, чем сессия известна. Операторский сценарий за сессионным заслоном покрыт. CI применяет миграции, поднимает настоящий API, погашает одноразовый токен установки, чтобы засеять владельца, а operator-journey.spec.ts по-настоящему входит, проверяет, что сессия переживает переход, и обходит все страницы панели. Локально задайте PANEL_EMAIL и PANEL_PASSWORD против запущенного стека; без них этот набор пропускается и прямо об этом сообщает, а не притворяется пройденным. Отсутствующий перевод отрисовывается маркером, а не собственным ключом — см. apps/web/i18n/missing-message.ts, — и обход ищет именно его. Прежняя проверка искала ключ с точками, а так нельзя отличить отсутствующее сообщение от данных: журнал аудита хранит имена действий в такой же форме, и проверка провалилась на полностью переведённой странице в первый же раз, когда её запустили против API с записями. Клиентский сценарий тоже покрыт, и засевать для него ничего не нужно: панель заводит аккаунт при первом входе, поэтому customer-journey.spec.ts подписывает полезную нагрузку виджета входа Telegram тем же токеном бота, которым настроен API, и отправляет её через прокси /v1 самого веб-сервера. Чтобы запустить локально, задайте CUSTOMER_BOT_TOKEN с этим токеном. В internal/customerauth есть тест совместимости, проверяющий, что подпись на TypeScript и проверка на Go согласуются: без него расхождение валило бы каждый прогон и его «починили» бы, ослабив ту самую проверку подписи, ради которой набор и существует.
Ничего за правом доступа Remnawave не покрыто. Клиент, которого заводит набор, вошёл и ничего не купил, поэтому экраны, которым нужна подписка, отрисовывают пустое состояние, а не заполненное. Это отслеживается в дорожной карте как долг по верификации для v0.10.

Бюджеты производительности

bun --filter @omniflow/web perf:budget запускается против завершённой сборки next build и падает, когда JavaScript первой загрузки маршрута, вес стилей или собственные шрифты превышают свои потолки. CI выполняет его в задании web сразу после сборки, которую он и измеряет. Размеры несжатые: именно их сообщает route-bundle-stats.json самого Next, и именно на них честнее ставить заслон — сжатие прячет регресс, который всё равно стоит телефону времени на разбор и исполнение. Изображения, время ответа сервера и полевые Core Web Vitals намеренно не измеряются. Им нужна работающая установка с реальным содержимым и реальными клиентами, а число, выдуманное из локальной сборки, читалось бы как пройденный заслон.