Перейти к содержанию

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.tstest/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.