Testing Overview¶
Обзор тестовой инфраструктуры Payment Manager: какой runner используется, где лежит конфиг, как организован каталог test/, какие категории тестов поддерживаются и как их запускать.
Стек¶
- Runner: Vitest 4.x — единственный тестовый фреймворк PM. Поддерживает ESM, TypeScript «из коробки», использует Vite-конфиг.
- Coverage:
@vitest/coverage-v8(devDependency) — V8-провайдер покрытия, включается флагом--coverage. - HTTP-харнесс: Fastify
app.inject()— запускает приложение in-process без реального TCP-listener. Никаких внешних HTTP-клиентов. - Mock TB / IPPS: собственные моки в
test/mocks/(см. ниже) — реальный TigerBeetle и реальный PSP-эндпоинт в большинстве тестов не нужны.
Конфиг¶
Конфиг лежит в vitest.config.ts в корне проекта. Главная роль конфига — задать предсказуемые env-переменные для тестового процесса:
test: {
env: {
NODE_ENV: 'test',
PORT: '3001',
DATABASE_URL: 'postgres://test:test@localhost:5432/test',
REDIS_URL: 'redis://localhost:6379',
ADMIN_SECRET: 'test-admin-secret-32-characters!!',
TB_ADDRESS: '127.0.0.1:3000',
OUTBOX_INTERVAL_MS: '100',
SERVICE_SECRETS: JSON.stringify({ 'auth-center': '...', 'nginx-gateway': '...', 'admin-tool': '...' }),
INVOICE_QR_SECRET: 'test-invoice-qr-secret-32chars-x!',
},
}
Что важно:
SERVICE_SECRETSподменяется на детерминированный набор — тестыtest/helpers/app.tsподписывают запросы тем же секретом.OUTBOX_INTERVAL_MS=100ускоряет background-воркеры в integration-тестах.DATABASE_URLуказывает на локальный Postgres — нужен только для integration-тестов (test/integration/**).
Тестовый процесс не использует .env: все секреты берутся из vitest.config.ts. Это гарантирует, что unit-тесты воспроизводимы независимо от окружения разработчика.
Layout¶
Все тесты лежат в каталоге test/. Структура зеркалит src/ по модулям:
test/
├── adminPlugin.test.ts # глобальные плагины и health-роуты
├── config.test.ts
├── health.test.ts
├── hmac.test.ts
│
├── accounts/ # регистрация TB-аккаунтов (IPPS)
├── admin/ # admin-роуты (fee-rules, resolve-intent)
├── channels/ # внутренние каналы (INTERNAL P2P, IPPS, INVOICE)
├── intent/ # центральный flow: handler, saga, outbox, события
├── jobs/ # background-задачи (invoice-expiry)
├── ledger/ # TigerBeetle обёртка (accounts, transfers, id-gen)
├── limits/ # лимиты и step-up policies (evaluate-policy)
├── operation-types/ # реестры операций (P2P, NFC, INVOICE, IPPS, SERVICE)
├── policies/ # auth-policies роуты
├── psp/ # PSP-bootstrap, registry, IPPS-адаптер
├── rule-engine/ # evaluator + fee-calculator
├── shared/ # утилиты (currency, qr-signature)
├── workers/ # balance-monitor, psp-worker
│
├── helpers/ # фикстуры и общий boilerplate
│ ├── app.ts # buildTestApp() + signRequest() с HMAC
│ └── db.ts # resetDb(), seed* для integration-тестов
│
├── mocks/ # моки внешних систем
│ ├── ipps-mock-server.ts # HTTP-мок IPPS PSP
│ └── ipps-mock-server.test.ts # smoke-тест самого мока
│
└── integration/ # end-to-end сценарии с реальной БД
├── invoice-flow.test.ts
└── notifications.test.ts
Правила размещения файлов:
| Тип теста | Куда класть | Соглашение по имени |
|---|---|---|
| Unit-тест модуля | test/<module>/<file>.test.ts, зеркаля src/<module>/<file>.ts |
имя совпадает с тестируемым файлом |
| End-to-end сценарий | test/integration/<scenario>.test.ts |
по бизнес-сценарию (invoice-flow, notifications) |
| Фикстура / helper | test/helpers/<topic>.ts |
без суффикса .test.ts — иначе Vitest попробует их выполнить |
| Мок внешней системы | test/mocks/<system>.ts (+ smoke-тест в <system>.test.ts) |
имя по системе (ipps-mock-server) |
Категории тестов¶
PM-кодовая база различает три категории. Граница не формальная (тег/имя), а проявляется в том, какие внешние зависимости тест поднимает и что именно проверяет.
1. Unit¶
- Каталоги:
accounts/,admin/,channels/,intent/,jobs/,ledger/,limits/,operation-types/,policies/,psp/,rule-engine/,shared/,workers/, корневыеtest/*.test.ts. - Зависимости: только Fastify
app.inject()+ моки. Реальные TigerBeetle/Postgres/Redis не поднимаются — клиенты к ним замокированы или не вызываются. - Что проверяют:
- Чистая логика модулей (rule-engine, fee-calculator, currency, qr-signature).
- Контракты роутов и Zod-схемы (
intent/router.test.ts,admin/*.test.ts). - Поведение саги и step-registry на моках (
intent/saga-runner.test.ts,intent/step-registry.test.ts). - HMAC и authPolicies (
hmac.test.ts,policies/routes.test.ts). - Регистры операций (
operation-types/*.test.ts) — что для каждогоoperationTypeзарегистрированы все нужные шаги. - Куда складывать новые: рядом с тестируемым модулем (
src/intent/router.ts→test/intent/router.test.ts). Имя файла зеркалит исходник.
2. Integration¶
- Каталог:
test/integration/(invoice-flow.test.ts,notifications.test.ts). - Зависимости: реальный Postgres (
DATABASE_URL), реальный Redis, тестовый TB или мок. Запускаются только в окружении, где эти сервисы подняты (см.docker-compose.test.yml/INTEGRATION=1). - Что проверяют: end-to-end флоу через несколько модулей и БД. Пример:
invoice-flow.test.tsсоздаёт инвойс, оплачивает его, проверяетtx_history, settlement и запись в outbox. - Helpers:
test/helpers/db.tsэкспортируетresetDb(),seedMerchantAccount(),seedUserAccount(),seedAuthCenterServiceKey()— они работают только при поднятой БД. - Куда складывать новые:
test/integration/<сценарий>.test.ts. Не зеркалить структуруsrc/— группировка идёт по бизнес-флоу (invoice-flow,notifications,nfc-charge-flowи т. п.).
3. Regression¶
Отдельной директории regression/ нет — регрессии живут рядом с unit-тестами. Соглашение:
- Тест, фиксирующий конкретный баг/инцидент, кладётся в тот же модульный каталог, что и unit-тесты (
test/intent/,test/channels/и т. п.). - Имя файла или describe-блока содержит ссылку на issue/CHANGELOG-запись (
describe('regression: cancel returns channel/createdAt', ...)). - При снятии регрессии тест не удаляется — остаётся как страховка от повторного появления бага.
Примеры таких тестов: test/intent/cancel-handler.test.ts, test/intent/intent-events.test.ts, test/channels/merchant-invoice.test.ts — там есть кейсы, добавленные после конкретных hot-fix-ов (см. недавние коммиты fix(invoice):*, fix(cancel):*).
Сводная таблица¶
| Категория | Каталог | TB / Postgres / Redis | Скорость | Когда писать |
|---|---|---|---|---|
| Unit | по модулям | моки | мс | новая логика модуля, новая ветка в роуте/саге |
| Integration | test/integration/ |
реальные | секунды | новый бизнес-флоу, межмодульный сценарий |
| Regression | по модулям, с пометкой | по ситуации | мс–секунды | после фикса бага — закрепить корректное поведение |
Как запускать¶
Базовые команды объявлены в package.json:
npm test # vitest run — однократный прогон, используется в CI и при локальной проверке перед коммитом
npm run test:watch # vitest — watch-режим, перезапускает только затронутые файлы
Подробности — как запускать один файл, как фильтровать по имени теста, как генерить coverage, какие переменные нужны для integration-тестов и как поднять Postgres/Redis локально через docker-compose — описаны в ../cookbook/run-tests.md.
Что писать не нужно¶
Антипаттерны, которые в PM явно отвергаются:
- Тесты через реальный HTTP-сокет. Используйте
app.inject()из Fastify — он не открывает порт и работает быстрее. Тестовые env (PORT=3001) выставлены только для случаев, когдаbuildAppгде-то читает порт при инициализации. - Хардкод секретов. Подписывайте запросы через
signRequest()изtest/helpers/app.ts— он использует тот жеSERVICE_SECRETS, что иvitest.config.ts. - Кросс-тестовое состояние. Каждый тест должен сам создавать нужные ему фикстуры; в integration-тестах — звать
resetDb()вbeforeEach. - Прямой импорт
src/server.tsбезbuildTestApp(). Хелпер выключает логгер и убирает шум в выводе. - Тесты на
console.log/process.exit. Если нужно проверить лог — используйте Fastify-логгер с моком, а не глобальный stdout.
См. также¶
- ../cookbook/run-tests.md — рецепты запуска (один файл, coverage, integration).
- ./patterns.md — высокоуровневая стратегия и патерны.
- ../../../PASSPORT.md — канонический контракт PM; тесты ссылаются на него при изменениях DTO/парсеров.
- ../../AUTH-POLICIES.md — структура auth-policies; покрывается
test/policies/routes.test.tsиtest/limits/evaluate-policy.test.ts.