> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Стратегия тестирования

> Что выполняется по умолчанию, чему нужен Docker и что обязано принести каждое финансовое изменение.

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

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

Рядом с ним CI прогоняет проверку типов TypeScript, сборки фронтенда, Biome, валидацию Atlas, валидацию Mintlify, сканирование уязвимостей и сборку контейнеров.

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

Тесты в `internal/integrationtest` несут build-тег `integration` и поднимают PostgreSQL через Testcontainers:

```bash theme={null}
go test -tags=integration -race -timeout=20m ./internal/integrationtest/...
```

Обвязка применяет закоммиченные файлы миграций по порядку, а не дамп схемы, поэтому набор является заслоном не только для репозиториев, но и для миграций. Он покрывает:

* применение миграций с пустой базы данных и перевод существующих клиентов на подписки при обновлении с 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
согласуются: без него расхождение валило бы каждый прогон и его «починили» бы,
ослабив ту самую проверку подписи, ради которой набор и существует.

> [!NOTE]
>
> Ничего за правом доступа Remnawave не покрыто. Клиент, которого заводит набор,
> вошёл и ничего не купил, поэтому экраны, которым нужна подписка, отрисовывают
> пустое состояние, а не заполненное. Это отслеживается в дорожной карте как долг
> по верификации для v0.10.

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

`bun --filter @omniflow/web perf:budget` запускается против завершённой сборки
`next build` и падает, когда JavaScript первой загрузки маршрута, вес стилей или
собственные шрифты превышают свои потолки. CI выполняет его в задании `web`
сразу после сборки, которую он и измеряет.

Размеры несжатые: именно их сообщает `route-bundle-stats.json` самого Next, и
именно на них честнее ставить заслон — сжатие прячет регресс, который всё равно
стоит телефону времени на разбор и исполнение.

Изображения, время ответа сервера и полевые Core Web Vitals намеренно не
измеряются. Им нужна работающая установка с реальным содержимым и реальными
клиентами, а число, выдуманное из локальной сборки, читалось бы как пройденный
заслон.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.