[CL-COVTESTS] test(api): branch-покрытие financial/worker/AML путей (wash-trading, auto-reinvest, watchlist) #416

Merged
andrei merged 2 commits from feature/claude-cl-branch-coverage-20260607 into master 2026-06-07 13:55:25 +00:00
Owner

Что сделано

Подняты branch-ветки на трёх низко-покрытых financial/AML/worker модулях. Только тесты — production-код не тронут.

Файл branches до branches после
api/src/services/aml/rules/wash-trading.ts 14% (fn 0%) 96%
api/src/workers/auto-reinvest.worker.ts 50% 87%
api/src/services/watchlist.service.ts 65% 87%

Итого +60 branches на critical money/AML путях.

wash-trading.ts (был вообще без тестов)

cursor-пагинация + short-batch break, ?? [] на null findMany, skip system-seller/buyer/self-trade, COALESCE roomId из buyOrder (buy-direct flow), roomId-null continue, both-direction flag vs single-direction skip, alert на обоих участниках пары.

auto-reinvest.worker.ts

failed-handler (transient skip / DLQ+Sentry+alert / DLQ-throw / attempts не исчерпаны), object-path через batch-loaded room, maxSharesPerUser cap (remaining>0 и remaining<=0), balance<minAmount, maxShares<1, eligible-none, fee==0 ветка, per-user error catch (Sentry + AUTO_REINVEST_FAILED audit), execution time-cap, completed/error handlers.

watchlist.service.ts

resolveMatch (note<20 guard, CAS 404 vs 409 double-resolution, CLEARED vs CONFIRMED), screenHighRiskUsers (errors-accum, generic catch+Sentry, WatchlistApiKeyExpiredError rethrow), screenAllUsers (cursor-пагинация, high-error-rate alert, key-expired rethrow), listMatches (limit clamp + page offset), getMatchStats.

Зачем

Audit pre-launch выявил branch coverage 70.88% с дырами в error/race/AML путях. Названные в аудите воркеры (income-distribution) уже были покрыты — coverage-hunt по coverage-summary.json нашёл настоящие низко-покрытые financial-файлы. Непокрытые ветки = регрессии в idempotency/race/AML не ловятся до прода.

План тестирования

  • npx vitest run9838 passed, 0 регрессий (было 9805).
  • Scoped coverage подтверждает рост на каждом файле (см. таблицу).
  • npx tsc --noEmit — clean.
  • npx eslint src --max-warnings 0 на изменённых — clean.
  • pre-commit (i18n, migrations, audit-trail, mock-sync, security, eslint+prettier) + pre-push (tsc+build) — passed.

Где могу ошибаться

  • Непокрытыми осталось: wash-trading line 55 (OOM cap 50k — дорого тестить), watchlist line 105 (MAX_ITERATIONS 10k), несколько fire-and-forget .catch callbacks. Все — low-value safety-края.
  • Follow-up таргеты (отдельный PR): stripePayout.service.ts (50%), recurring-invest.worker.ts (35%), buyback.service.ts (69%), premiumSubscription.webhook (11%), p2p-trade/execute-trade (70%).
  • mock auto-reinvest.worker.test.ts: on-handler теперь захватывается в Map (остался spy — старые toHaveBeenCalledWith assertions работают).
## Что сделано Подняты branch-ветки на трёх низко-покрытых financial/AML/worker модулях. **Только тесты — production-код не тронут.** | Файл | branches до | branches после | |---|---|---| | `api/src/services/aml/rules/wash-trading.ts` | 14% (fn 0%) | **96%** | | `api/src/workers/auto-reinvest.worker.ts` | 50% | **87%** | | `api/src/services/watchlist.service.ts` | 65% | **87%** | Итого **+60 branches** на critical money/AML путях. ### wash-trading.ts (был вообще без тестов) cursor-пагинация + short-batch break, `?? []` на null findMany, skip system-seller/buyer/self-trade, COALESCE roomId из buyOrder (buy-direct flow), roomId-null continue, both-direction flag vs single-direction skip, alert на обоих участниках пары. ### auto-reinvest.worker.ts failed-handler (transient skip / DLQ+Sentry+alert / DLQ-throw / attempts не исчерпаны), object-path через batch-loaded room, maxSharesPerUser cap (remaining>0 и remaining<=0), balance<minAmount, maxShares<1, eligible-none, fee==0 ветка, per-user error catch (Sentry + AUTO_REINVEST_FAILED audit), execution time-cap, completed/error handlers. ### watchlist.service.ts resolveMatch (note<20 guard, CAS 404 vs 409 double-resolution, CLEARED vs CONFIRMED), screenHighRiskUsers (errors-accum, generic catch+Sentry, WatchlistApiKeyExpiredError rethrow), screenAllUsers (cursor-пагинация, high-error-rate alert, key-expired rethrow), listMatches (limit clamp + page offset), getMatchStats. ## Зачем Audit pre-launch выявил branch coverage 70.88% с дырами в error/race/AML путях. Названные в аудите воркеры (income-distribution) уже были покрыты — coverage-hunt по coverage-summary.json нашёл настоящие низко-покрытые financial-файлы. Непокрытые ветки = регрессии в idempotency/race/AML не ловятся до прода. ## План тестирования - `npx vitest run` — **9838 passed**, 0 регрессий (было 9805). - Scoped coverage подтверждает рост на каждом файле (см. таблицу). - `npx tsc --noEmit` — clean. - `npx eslint src --max-warnings 0` на изменённых — clean. - pre-commit (i18n, migrations, audit-trail, mock-sync, security, eslint+prettier) + pre-push (tsc+build) — passed. ## Где могу ошибаться - Непокрытыми осталось: wash-trading line 55 (OOM cap 50k — дорого тестить), watchlist line 105 (MAX_ITERATIONS 10k), несколько fire-and-forget `.catch` callbacks. Все — low-value safety-края. - Follow-up таргеты (отдельный PR): stripePayout.service.ts (50%), recurring-invest.worker.ts (35%), buyback.service.ts (69%), premiumSubscription.webhook (11%), p2p-trade/execute-trade (70%). - mock auto-reinvest.worker.test.ts: `on`-handler теперь захватывается в Map (остался spy — старые toHaveBeenCalledWith assertions работают).
[CL-COVTESTS] test(api): branch-покрытие financial/worker/AML путей
All checks were successful
CI / Contracts (pull_request) Successful in 1m52s
CI / Python SDK (pull_request) Successful in 27s
CI / Telegram Mini App (pull_request) Successful in 1m10s
CI / Secrets Scan (pull_request) Successful in 18s
CI / Prisma Migrate Gate (pull_request) Successful in 1m36s
CI / API (pull_request) Successful in 7m2s
CI / App (pull_request) Successful in 7m18s
cdf8027e32
Подняты ветки на трёх низко-покрытых модулях (только тесты, prod-код не тронут):

- aml/rules/wash-trading.ts: 14% -> 96% branches (был fn 0% — вообще без тестов).
  Покрыто: cursor-пагинация + short-batch break, `?? []` на null findMany,
  skip system-seller/buyer/self-trade, COALESCE roomId из buyOrder (buy-direct),
  roomId-null continue, both-direction flag vs single-direction skip, alert на обоих users.

- workers/auto-reinvest.worker.ts: 50% -> 87% branches.
  Добавлено: failed-handler (transient skip / DLQ+Sentry+alert / DLQ-throw / не исчерпан),
  object-path через batch-loaded room, maxSharesPerUser cap (remaining>0 и <=0),
  balance<minAmount, maxShares<1, eligible-none, fee==0 ветка, per-user error catch
  (Sentry + AUTO_REINVEST_FAILED audit), execution time-cap, completed/error handlers.

- services/watchlist.service.ts: 65% -> 87% branches.
  Покрыто: resolveMatch (note<20, CAS 404 vs 409, CLEARED vs CONFIRMED),
  screenHighRiskUsers (errors-accum, generic catch+Sentry, ApiKeyExpired rethrow),
  screenAllUsers (cursor-пагинация, high-error-rate alert, key-expired rethrow),
  listMatches (limit clamp + page offset), getMatchStats.

+60 branches. Full suite: 9838 passed, 0 регрессий. tsc + eslint чисты.
[CL-COVTESTS] test(api): harden coverage tests per code review
All checks were successful
CI / Telegram Mini App (pull_request) Successful in 1m37s
CI / Secrets Scan (pull_request) Successful in 10s
CI / Python SDK (pull_request) Successful in 35s
CI / Contracts (pull_request) Successful in 2m15s
CI / Prisma Migrate Gate (pull_request) Successful in 1m42s
React Doctor / React Doctor / App (pull_request) Successful in 3m11s
CI / API (pull_request) Successful in 7m36s
CI / App (pull_request) Successful in 7m34s
dd8ded65a5
Адресованы замечания local code-reviewer (0 CRITICAL/HIGH, 2 MEDIUM + 4 LOW):

- watchlist test: WatchlistApiKeyExpiredError берётся через module-export (KeyExpiredError),
  а не raw hoisted var — instanceof-guard не рассинхронизируется при рефакторинге (MEDIUM).
- watchlist screenAllUsers: stub setTimeout → убран реальный 100ms/user sleep (MEDIUM);
  добавлен assert skip:1 на cursor-пагинацию (LOW).
- auto-reinvest fee==0: pin buyFromBalance args (u-ok, room1, 10) вместо toHaveBeenCalled (LOW);
  убрана избыточная Date.now=realNow после mockRestore (LOW).
- wash-trading pagination: добавлен assert results.toHaveLength(2) — детекция через batch-границу (LOW).

42 tests pass, tsc + eslint clean.
Author
Owner

admin-merge reason: solo-dev (Rule 103.1)
local-review: Agent(code-reviewer) — 0 CRITICAL/HIGH, 2 MEDIUM + 4 LOW, все findings addressed в dd8ded65a
ci-status: 8/8 green (API, App, Contracts, Telegram Mini App, Python SDK, Secrets Scan, Prisma Migrate Gate, React Doctor)
copilot-review: бот отсутствует на Forgejo → fallback на local code-reviewer (Rule 103.1)
scope: test-only, production-код не тронут

admin-merge reason: solo-dev (Rule 103.1) local-review: Agent(code-reviewer) — 0 CRITICAL/HIGH, 2 MEDIUM + 4 LOW, все findings addressed в dd8ded65a ci-status: 8/8 green (API, App, Contracts, Telegram Mini App, Python SDK, Secrets Scan, Prisma Migrate Gate, React Doctor) copilot-review: бот отсутствует на Forgejo → fallback на local code-reviewer (Rule 103.1) scope: test-only, production-код не тронут
andrei merged commit 277e4a903e into master 2026-06-07 13:55:25 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
europa-tech-srl/europatech!416
No description provided.