Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
441 lines
74 KiB
Markdown
441 lines
74 KiB
Markdown
# Аудит 09 — Test Architecture & Infrastructure
|
||
|
||
## 1. Audit metadata
|
||
|
||
**Дата:** 2026-10-11. **Режим:** read-only аудит тестовой инфраструктуры; записан только этот отчёт в родительском workspace.
|
||
|
||
| Область | Путь относительно workspace | Точный HEAD | Состояние при начале аудита |
|
||
| --- | --- | --- | --- |
|
||
| Workspace | `.` | `dc030f84c979cedb2628ef4bd97c62d6c63a9256` | Чистый, `release/0.2.1` |
|
||
| Frontend | `frontend/` | `c8821200ce699dab1ce65360d1968d5877a2e78b` | Чистый, `codex/fix-stock-screener-race` |
|
||
| Backend | `backend/` | `0ad84ff4f0b4712abc7e69dc299856f14af6eadd` | Чистый, `codex/add-stock-screener` |
|
||
| Specs | `specs/` | `fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121` | Чистый, `codex/add-stock-screener` |
|
||
| Docs | `docs/` | `e6fd4f64b81dd73b33959b845a1babcfac1891c8` | Чистый, `codex/add-stock-screener` |
|
||
|
||
Абсолютный корень: `/Users/ksv741/Project/vibe/WealthLedger`. Прочитаны локальные `AGENTS.md`, engineering/testing conventions и относящиеся к трассировке architecture/data источники. Conventions использованы для установления задуманного устройства, а не как доказательство полезности каждого правила.
|
||
|
||
**Skill:** установленный `ponytail-audit` загружен через Skill tool и применён к сложности test support. Его complexity-pass отделён от проверки isolation/lifecycle. Исправления не применялись. `technical-audit-2026-10-11.md` прочитан как список прежних гипотез; выводы ниже проверены по текущему checkout.
|
||
|
||
### Выполненные проверки
|
||
|
||
- В каждом репозитории: `git rev-parse HEAD`, `git status --porcelain=v1 --branch`.
|
||
- Статический inventory через Glob/Read/Grep и `rg`: runner configuration, imports test support, lifecycle hooks, mocks/timers, локальные factories, consumers fixtures и зависимости subprocess.
|
||
- Backend, после загрузки существующего NVM и активации местной `.nvmrc`:
|
||
|
||
```sh
|
||
source "/Users/ksv741/.nvm/nvm.sh" && nvm use --silent && npm test -- --runInBand --no-cache --runTestsByPath src/modules/banking/bank-cards/domain/rub-money.spec.ts
|
||
```
|
||
|
||
**PASS: 1 suite, 22 tests; Jest сообщил 0.379 s.** Это время одного локального запуска pure unit suite, не оценка CI или всего набора. Проверка подтверждает работоспособность дешёвой unit boundary без Nest bootstrap, SQLite и внешней сети. Coverage и Jest cache не создавались.
|
||
- Первая попытка той же команды остановилась на `nvm: command not found`: NVM не был загружен в tool shell, сам тест не запускался. После `source` проверка прошла. Это ограничение оболочки аудита, не finding проекта.
|
||
- Одна поисковая команда сначала включала отсутствующий frontend-каталог `test/`; inventory затем выполнен по фактическим `src/test/`, `src/`, `packages/`. Данных эта ошибка не меняла.
|
||
|
||
**Не запускались:** install, build, codegen, миграции, full-suite, coverage suite, `verify`, SQLite/HTTP suites, browser tests, performance/durability/container/regression runners. Реальные банковские и брокерские API не вызывались. Изменения исходников, тестов, конфигурации, lockfiles и Git history отсутствуют.
|
||
|
||
**Ограничение доказательств:** дефекты организации и возможные failure modes выведены из исходников. Частота flaky-сбоев, время bootstrap/migrations и экономия CI **не измерены**. Не воспроизведённые runtime-сценарии помечены отдельно; зелёный unit test не подтверждает исправность HTTP/SQLite инфраструктуры.
|
||
|
||
## 2. Executive verdict
|
||
|
||
1. **Основа достаточно проста и оправданна.** Backend разделяет colocated Jest unit, реальные SQLite suites и configured HTTP; frontend использует небольшие query/resource helpers, RTL/MSW и два browser layout-файла. Причин создавать общий test framework, builders DSL или cross-repo runtime не найдено.
|
||
2. **Подтверждена межтестовая зависимость банковской suite — TI-001, P2.** Данные одного owner живут между `it`; restart-case проверяет переименование категории, сделанное другим case. Это непосредственно мешает изолированному запуску и перестановке сценариев.
|
||
3. **Подтверждён неконтролируемый фоновой lifecycle — TI-002, P2.** В 10 функциональных HTTP suites зарегистрирован настоящий минутный scheduler. В части из них есть активные connection fixtures; длительный запуск способен начать работу вне Arrange/Act. Отдельный benchmark тоже запускает scheduler, что мешает изолированному измерению read budget.
|
||
4. **Синхронизация фоновых сценариев местами привязана к скорости машины — TI-003, P2.** Sleeps на 5–10 ms используются как признак начала работы; для portfolio unit tests уже существует `work.done`, но вместо него опрашиваются mock calls. Это подтверждённый механизм риска, а не измеренная flaky-rate.
|
||
5. **Targeted SQLite path допускает смешение source и build — TI-004, P2.** Crash producer загружается из `dist`, основной driver — через ts-jest из `src`; прямой runner не собирает build. Штатный `test:e2e` защищён предварительным build. Фактическое устаревание текущего `dist` — **UNVERIFIED**.
|
||
6. **Cleanup небольшой и необязательный.** Opt-in regression самого runner выполняет пять полных функциональных проходов — TI-005, P3; в bond setup есть один option без consumer — TI-006, P3. Ни одна находка не оправдывает массовую перестройку тестов.
|
||
|
||
Всего: **6 findings — 4 P2 и 2 P3. P0/P1 не установлены.** Самые важные изменения уменьшают скрытое состояние и делают выполнение управляемым, сохраняя реальные DB/HTTP guarantees.
|
||
|
||
## 3. Inventory & coverage
|
||
|
||
### 3.1. Фактический стек и размеры области
|
||
|
||
| Объект | Результат | Метод и граница подсчёта |
|
||
| --- | --- | --- |
|
||
| Frontend test files | 63 | `rg --files src packages -g '*.test.ts' -g '*.test.tsx' \| wc -l`; файлы, не expanded test cases |
|
||
| Frontend browser files | 2 | По найденным `*.browser.test.tsx`: `EntryCard` и `position-layout`; оба запускаются в двух viewport instances |
|
||
| Frontend JSDOM files | 61 | 63 минус 2 browser-файла; разграничение подтверждено `vitest.config.ts:26–65` |
|
||
| Consumers `@/test/query` | 27 test files | `rg -l 'from "@/test/query"' src packages` с теми же test globs; прямые imports, не число вызовов |
|
||
| Consumers `@/test/resource` | 20 test files | Аналогичный подсчёт прямых imports; дополнительно helper использует `src/test/handlers.ts:3` |
|
||
| Consumers `@/test/server` | 27 test files | Аналогичный подсчёт прямых imports; lifecycle setup рассматривается отдельно |
|
||
| Backend unit files | 39 | `rg --files src -g '*.spec.ts' \| wc -l` |
|
||
| Backend e2e/integration files | 20 | `rg --files test -g '*.e2e-spec.ts' \| wc -l` |
|
||
| Функциональные suites в `--all` | 19 | 20 файлов минус явно исключённый performance-file, `test/sqlite.cjs:7–17` |
|
||
| Suites с `AppModule` | 14, из них 13 функциональных | `rg -l 'imports: \[AppModule\]' test -g '*.e2e-spec.ts' \| wc -l`; просмотр multiline imports и consumer каждого результата |
|
||
| Overrides hourly scheduler | 3 suites | `portfolio-structure`, `tbank-value-history`, `upcoming-events`; остальные 10 функциональных AppModule suites запускают lifecycle настоящего provider |
|
||
| Backend локальные `deferred<T>` | 4 определения | По `function deferred` в `test/`: local-reads, operations-sync, metadata-persistence, catalog-discovery |
|
||
|
||
Frontend manifest: Vitest `5.0.0`, RTL `16.3.3`, MSW `2.15.0`, Playwright `1.63.0`, TanStack Query `5.102.8`, JSDOM `30.0.1` (`frontend/package.json:64–116`). Backend: Jest `30.5.1`, ts-jest `29.4.12`, Nest testing `12.0.1`, Supertest `7.2.2`, Prisma `7.10.0`, better-sqlite3 `13.0.3` (`backend/package.json:40–105`). Это manifest versions; переустановка зависимостей не выполнялась.
|
||
|
||
### 3.2. Test Support Inventory
|
||
|
||
| Support / owner | Consumer / выполняемый поток | Назначение и оценка |
|
||
| --- | --- | --- |
|
||
| `frontend/vitest.config.ts:26–65` | Unit project; browser project | Разные environments и взаимоисключающие globs. Browser geometry не дублируется в JSDOM — **KEEP** |
|
||
| `frontend/src/test/setup.ts:1–48` | Каждый JSDOM test file | jest-dom, i18n, strict MSW listener, RTL cleanup, auth reset, handler reset, browser API shims — **KEEP** |
|
||
| `frontend/src/test/server.ts:1–7`, `handlers.ts:10–82` | Setup и 27 напрямую импортирующих suites | Один process-local MSW server и synthetic defaults. Нет browser worker или backend dependency — **KEEP** |
|
||
| `frontend/src/test/query.tsx:5–24` | 27 files | Fresh QueryClient, query/mutation retries off, wrapper сохраняет тот же client при rerender — **KEEP** |
|
||
| `frontend/src/test/resource.ts:1–27` | 20 files + handlers | Resource envelope, typed Meta и explicit overrides. Не ходит в сеть, не нормализует деньги — **KEEP** |
|
||
| `frontend/src/pages/account/model/positions.fixtures.ts:1–73` | `positions.test.ts:6`, `position-groups.test.tsx:7`, `AccountPage.vm.test.tsx:14` | Три прямых consumers; typed data с null, mixed currencies и negative quantity — **KEEP** |
|
||
| Page-local `renderPage` / `setup` | Account, Card, OperationForm, Stock, Bond suites | Явная provider composition, локальные handlers/controls. Практически один suite-local уровень orchestration; не общий DSL — **KEEP**, TI-006 для одного flag |
|
||
| Auth test wrapper `features/auth/model/auth.test.tsx:20–28` | AuthProvider suite | QueryClientProvider + настоящий AuthProvider. Сценарии изменения identity живут у auth owner — **KEEP** |
|
||
| `frontend/src/test/storybook.test.tsx:19–57` | Composed stories из явно выбранного набора | Проверяет isolation от network/storage/cookies, а не работу factory как таковой — **KEEP** |
|
||
| Browser-local `afterEach(cleanup)` | Два browser-файла | Browser project не подключает JSDOM setup. Их cleanup обязателен, не дублирование global hook — **KEEP** |
|
||
| `backend/package.json:123–139` | `npm test`, watch/cov/debug | Unit Jest: `rootDir=src`, colocated specs, node environment — **KEEP** |
|
||
| `backend/test/jest-e2e.json:1–13` | SQLite runner → Jest | E2E config, isolated-env gate, ts-jest, `.js` import mapper — **KEEP** |
|
||
| `backend/test/sqlite.cjs:6–95` | `test:e2e`, `test:sqlite`, OpenAPI/performance entries | Suite selection, свежий temp DB, штатные migrations, отдельный Jest process, signals и cleanup — **KEEP**; TI-004 для build prerequisite |
|
||
| `backend/test/e2e-env.cjs:1–17` | Каждая e2e suite и crash subprocess | Проверка runner-owned URL/temp path до DB work; synthetic JWT/encryption config — **KEEP** |
|
||
| Local DB constructors | Пять service/repository suites: operations-persistence, operations-sync, metadata, catalog, candles | Реальная SQLite + конкретный service без Nest app и HTTP. Это уже реализованный меньший scope — **KEEP** |
|
||
| Suite-local DB seeds | Connections, memberships, portfolios, operations/coverage | Seed responsibility локальна; current revision, token и financial inputs доступны тесту. Глобального seed framework нет — **KEEP** |
|
||
| Local poll helpers | `state`, `settled`, `requestPortfolio`, `load` | Разные смыслы: конкретный work state, dependency group, await existing handle. Нельзя объединять исключительно по похожему loop; TI-003 касается readiness sleeps |
|
||
| `backend/test/fixtures/tbank-sync-crash.cjs:1–86` | `tbank-operations-sync.e2e-spec.ts:424–499` | Child process + IPC checkpoint + SIGKILL на durable boundaries. Обычный mock не заменяет crash — **KEEP**, TI-004 |
|
||
| `backend/test/sqlite-regression.cjs:20–96` | Явный opt-in command в `README.md:250–254` | Уникальные DB, внешний sentinel, migration failure, SIGINT/SIGTERM cleanup — **KEEP protections / SIMPLIFY workload**, TI-005 |
|
||
| `backend/test/bank-cards-durability.cjs:10–23,120–215` | `test:durability -- <baseline>` | Независимая historical build, migration prefix, restart, rollback, backup/restore. Explicit external artifact, не shared helper dependency — **KEEP** |
|
||
| `backend/test/container-smoke.cjs:19–73`, `runtime-check.cjs:1–8` | Container test / build-runtime проверка | Packaging/native modules/proto/read-only image boundaries; не дополнительный business test framework — **KEEP** |
|
||
| `backend/test/tbank-read-baseline.e2e-spec.ts` | Отдельный performance command | Большой synthetic dataset и read-budget measurement, исключён из `--all` — **KEEP separation**, TI-002 для scheduler |
|
||
| `backend/test/fixtures/tbank-read-baseline.json` | Benchmark, `tbank-read-baseline.e2e-spec.ts:522–529` | Historical RPC comparison input; не текущий CI timing — **KEEP** |
|
||
| `backend/test/fixtures/tbank-read-persistent.json:1–20` | Runtime consumer в просмотренных test/config/docs roots не найден | Historical measurement evidence, не executable factory. **KEEP как evidence**; отсутствие import не доказывает ненужность исторического результата |
|
||
|
||
Custom builders/classes, собственные matchers/`expect.extend`, snapshot serializers и concurrent-case режим в просмотренной test architecture не найдены. Policy/tooling tests `test/eslint-config.cjs`, `scripts/test-eslint.mjs`, `scripts/test-pre-commit.mjs` отмечены в inventory, но их содержательная целесообразность относится к аудитам 10/11.
|
||
|
||
### 3.3. Representative cases и границы чтения
|
||
|
||
Карта имён/config/support охватывает все найденные test files. Углублённая выборка risk-based, а не построчное чтение всех assertions.
|
||
|
||
| Выборка | Реально рассмотренные файлы / участок |
|
||
| --- | --- |
|
||
| 5 сложных frontend suites | `AccountPage.test.tsx` — оба render helpers, fixtures, state/route setup; `OperationForm.test.tsx` — весь файл; `CardPage.test.tsx` — setup, pending-page/spy teardown и consumers options; `StockPage.test.tsx`, `BondPage.test.tsx` — options, handlers, controls и все consumers |
|
||
| Дополнительные RTL/MSW flows | `StocksPage.test.tsx`, `resource-query.test.tsx`, `client.test.ts`, `auth.test.tsx`, `AccountPage.vm.test.tsx` — async barriers, retry/time policy и wrapper ownership |
|
||
| 5 простых frontend suites | `AuthForm.test.tsx`, `calendar.test.ts`, `period.test.ts`, `SourceSyncStatus.test.tsx`, `EntryCard.browser.test.tsx`; дополнительно `position-layout.browser.test.tsx` |
|
||
| 5 сложных backend suites | `bank-cards.e2e-spec.ts` — helper closure, seeds, global counts, category dependency, restart; `upcoming-events.e2e-spec.ts` — bootstrap, cleanup, dependency polling, revision-aware seed; `tbank-operations-sync.e2e-spec.ts` — весь файл; metadata/catalog persistence — construction, hooks, producer/observer races и deferred closure |
|
||
| 5 простых backend suites | `rub-money.spec.ts`, `bank-card.calculations.spec.ts`, `decimal.spec.ts`, `all-accounts.e2e-spec.ts`, `stocks.e2e-spec.ts` — unit/HTTP boundary и setup scope |
|
||
| Дополнительный backend infrastructure | `app.e2e-spec.ts`, `openapi-contract.e2e-spec.ts`, `portfolio.service.spec.ts`, candle/local-reads/connection/history bootstrap, все собственные test runners и crash fixture |
|
||
| Public intent / supporting docs | Местные testing conventions/README; `specs/.../frontend-ui-catalog/spec.md:40–88,97–105`; `docs/architecture/cross-cutting/data-freshness.md:63–83` |
|
||
|
||
**Вне scope:** правильность каждого assertion, полнота продуктовых regressions, общий coverage score и legacy сценарии как таковые — аудит 08; CI triggers — 11; production SRP — 01/06; non-test scripts — 10. Не исследовались реальные внешние API, performance profile всего приложения и архивная OpenSpec история.
|
||
|
||
### 3.4. Fixture Complexity Matrix
|
||
|
||
`Arrange` ниже — число явно видимых инфраструктурных операций в вызывающем тесте, не количество DB queries/framework internals. Глубина — последовательные project-owned helper calls; provider tree не считается цепочкой helper abstractions.
|
||
|
||
| Fixture/helper → consumers | Responsibility | Hidden side effects / сложность | Alternatives и решение |
|
||
| --- | --- | --- | --- |
|
||
| `createTestQueryClient` / `createQueryWrapper` → 27 files | Client lifecycle / wrapper | Один factory call; wrapper → factory — максимум два support calls. Fresh client, retries off; сетевых вызовов при construction нет | Сохранить; общий client вместо per-case создаст новую isolation проблему |
|
||
| `resource(data, meta)` → 20 files + handlers | Envelope только | Один call; generic data, fixed resource key/date/revision, shallow Meta override | Сохранить. Для stale/failure/connection-sensitive scenarios metadata задавать явно; не применять runtime validator ко всем fixtures |
|
||
| `positions.fixtures` → 3 files | Typed scenario data | Module-level objects, nested references при spread; consumers используют projections/overrides, общей мутации в рассмотренных путях не найдено | Сохранить; deep-freeze/clone framework без доказанной mutation не нужен |
|
||
| `OperationForm.setup` → собственная suite | Auth + categories handler + render | Три инфраструктурных шага; fresh client внутри render, close spy возвращается. Data-only operation передаётся отдельно; assertions в setup нет | Сохранить локально: category endpoint не перекрывает тестовые operation handlers |
|
||
| `CardPage.setup` → собственная suite | Auth + handler bundle + render + request capture | Четыре options, каждый имеет consumer; mutable request arrays принадлежат одному invocation. 1 helper, без builder layers | Сохранить; новые options добавлять по реальному сценарию |
|
||
| `StockPage.setup` → собственная suite | Несколько endpoint handlers + providers + failure controls | Девять options, examined consumers используют все; handlers содержат domain-specific payloads. Большой объём — данные/API sections, а не глубина DSL | Не объединять со BondPage: metric/rating/coupon contracts различаются |
|
||
| `BondPage.setup` → собственная suite | Аналогичная page composition | Семь options; шесть используются, `failBond` не имеет consumer; controls локальны | Удалить только unused option — TI-006; общий instrument test framework не нужен |
|
||
| Banking `createCard` → `auth` → `token`, `op`, `createOp` | Happy-path HTTP arrange / payload creation | Максимум три локальных helper calls; `createCard/createOp` делают HTTP/DB write и ожидают успешный status. Проверки ошибок используют `auth(...).expect(...)` напрямую | Сохранить короткие HTTP helpers: скрытая happy-path assertion здесь намеренная, negative path управляемый |
|
||
| Banking module-level owner/DB → все `it` | Shared suite state | До первого case users seed, после каждого case cleanup нет; category state пересекает границы сценариев | FIX TI-001: case-owned data и explicit prerequisites |
|
||
| Upcoming `portfolio` / `refresh` / `settled` → собственная suite | Seed / launch / await dependencies | Три разные обязанности уже разделены; `portfolio` явно повышает dataRevision и использует user-specific revision. `settled` включает quiet interval | KEEP разделение; не превращать в `givenPortfolioReadyAndAssert` DSL |
|
||
| Metadata/catalog `createService`, `seedConnection`, `deferred`, `settled` → по одной suite | Composition / seed / RPC gate / completion | Четыре небольших помощника с разными ролями, не четыре обязательных layers; RPC readiness иногда заменён sleep | FIX TI-003 для sequencing. Небольшие duplicated constructors не требуют общего DI container |
|
||
| Portfolio unit polling → два launch cases | Ожидание publish mock | Два одинаковых loop по десять `setImmediate`; тест игнорирует доступный `work.done` | Использовать existing completion promise — TI-003, без нового helper |
|
||
| Crash subprocess → три checkpoint cases | Настоящая потеря процесса | Контроль RPC/repository checkpoint, отдельный `dist` runtime, IPC, timeout и child cleanup | KEEP; согласовать freshness build — TI-004 |
|
||
| Runner regression → 3 sequential + 2 parallel full runs и failure/signal cases | Isolation/lifecycle infrastructure | Полный product набор является payload каждого успешного infrastructure check | SIMPLIFY TI-005: маленький existing workload с нужными suite/process boundaries |
|
||
|
||
### 3.5. Проверка SRP / DRY / KISS / YAGNI / ISP / DIP
|
||
|
||
- **SRP:** в изученной выборке нет общего helper, который одновременно скрыто генерирует финансовый набор, мутирует DB, делает HTTP и проверяет весь business outcome. Banking happy-path helpers объединяют HTTP action и success status; negative cases обходят их напрямую, поэтому разбиение ради SRP увеличило бы ceremony.
|
||
- **DRY:** семантически одинаковые freshness/reset patterns важнее одинаковых object literals. Provider composition повторяется, но QueryClient factory уже общая. Backend auth/JWT seed wrappers короткие и локальные; общего runtime между frontend/backend нет.
|
||
- **KISS:** максимальная установленная project-owned цепочка arrange в рассмотренных support paths — три коротких banking helper calls. Цепочек по 5–10 factory layers не найдено. Большие stock/bond setup — плоские bundles handlers, не builders framework.
|
||
- **YAGNI:** подтверждён один unused option TI-006 и избыточный объём product workload в opt-in infrastructure regression TI-005. Оснований удалять основные factories, MSW server, crash helper или DB runner нет.
|
||
- **ISP/DIP:** mocks в основном структурные, ограничены используемыми boundary methods; interfaces/fake hierarchies не вводятся. `as never` ослабляет compile-time проверку fake, но само по себе не доказывает runtime поломку и не оправдывает написание полного fake Prisma. DB guarantees проверяются на настоящей SQLite.
|
||
- **Lifecycle:** frontend global hook очищает DOM/auth/handlers; fake timers обычно восстанавливаются локально. Backend per-suite fresh DB не равен per-case isolation — TI-001; настоящий background provider мешает deterministic setup — TI-002; producer gates требуют explicit readiness/release — TI-003.
|
||
- **Financial defaults:** `resource` не округляет деньги; fixtures и сценарии явно показывают null, zero, negative/fractional quantities, UTF-8 names, разные revision/user, signed64 и >2^53. Доказательств, что builder скрывает финансовый corner case, нет. Fixed metadata не доказывает fresh/stale policy: такие tests должны задавать значимые поля явно.
|
||
- **Heavy bootstrap:** чистые money/calendar/domain tests используют imports напрямую, без AppModule. Пять persistence suites уже создают concrete services без HTTP. Stocks/bonds HTTP contracts стартуют весь AppModule; меньшая composition возможна, но сохранение настоящих guard/pipe/filter/route registration важнее немедленного сокращения setup. Это **INVESTIGATE**, не обязательный rewrite.
|
||
- **Factory-internal tests:** отдельные self-tests `resource`, `createQueryWrapper`, builders не найдены. Storybook isolation и SQLite runner regression имеют реальные границы защиты: первый запрещает network/storage, второй защищает пользовательскую БД и child-process cleanup.
|
||
|
||
## 4. Findings
|
||
|
||
### TI-001 — Банковская suite использует состояние предыдущих test cases
|
||
|
||
**Decision = FIX · Severity = P2 · Confidence = High · Effort = M**
|
||
|
||
**Evidence:** `backend/test/bank-cards.e2e-spec.ts:23–39,77–105,106–145,371–390,710–753`; consumers: все `it` в `describe('Manual bank cards configured HTTP and real SQLite')`.
|
||
|
||
**Claim.** Per-suite fresh SQLite не обеспечивает самостоятельность банковских scenarios: часть cases требует данных/изменений, выполненных предыдущими `it`.
|
||
|
||
**Current mechanism.** Один module-level owner и stranger создаются в `beforeAll`; app/DB повторно используются, `beforeEach/afterEach` cleanup отсутствует. Case `renames default category...` меняет default category на «Остальное» (`371–386`). Case `preserves history... across app restart` сам создаёт карту/операции, но после restart ожидает ту же строку «Остальное» (`728–730`), не выполняя переименование. Case rename также читает default category до собственного `createCard` (`372–373`); её появление зависит от прежней записи. Constraint-case получает существующую category (`743`).
|
||
|
||
**Impact / actual cost.** Порядок тестов становится скрытым prerequisite. Targeted run одного rename/restart/constraint case на новой БД не воспроизводит исходные условия полной suite. Добавление нового раннего case меняет shared category/card state. **Impact unmeasured:** стоимость диагностики и частота падений не измерялись; runtime isolated run в этом аудите не выполнялся.
|
||
|
||
**Why now.** Дешёвые целевые проверки нужны для финансовых изменений; текущая организационная зависимость лишает их надёжности независимо от корректности business assertions.
|
||
|
||
**Minimum change.** Каждый независимый case создаёт owner/stranger или свой явно очищенный набор с FK-aware cleanup. Rename и constraint cases сами создают prerequisite card/categories. Restart-case сам переименовывает category до restart. App можно оставить suite-scoped; не нужна отдельная БД или migration на каждый `it`.
|
||
|
||
**Guarantees at risk.** Не удалять реальную SQLite, concurrent writes, replay fingerprints/tombstones, archive/version checks и restart внутри одного case. Смена owner требует обновить auth helpers, а cleanup — соблюдать FK order.
|
||
|
||
**Counterarguments / trade-offs.** Shared DB снижает bootstrap cost, случайные card IDs изолируют многие записи, некоторые global assertions сравнивают before/after вместо абсолютного значения. Это хорошие локальные меры, но они не устраняют найденную category dependency. Намеренный sequential journey допустим внутри одного `it`; здесь scenarios оформлены как независимые cases.
|
||
|
||
**Verification or rollback.** На disposable SQLite выполнить rename и restart отдельно через Jest `--testNamePattern`, а затем полную банковскую suite. Для filtered запуска сохранить runner-owned env: обычный runner сейчас не пересылает произвольные Jest flags. Проверить отсутствие зависимости от чужого case, затем real concurrency/replay outcomes. Rollback — вернуть только изменение test setup; production/schema не затрагивать.
|
||
|
||
**Related finding IDs:** TI-002, TI-003.
|
||
|
||
### TI-002 — Настоящий scheduler запускается в suites, не управляющих его работой
|
||
|
||
**Decision = FIX · Severity = P2 · Confidence = High · Effort = S**
|
||
|
||
**Evidence:**
|
||
|
||
- Composition/lifecycle: `backend/src/app.module.ts:18–36`, `backend/src/modules/investments/tbank-sync/tbank-sync.module.ts:13–25`, `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:19–21,48–86,119–175`.
|
||
- Consumers: `backend/test/all-accounts.e2e-spec.ts:30–61`, `backend/test/tbank-connection.e2e-spec.ts:30–54`, `backend/test/tbank-local-reads.e2e-spec.ts:100–141`, `backend/test/tbank-income-local.e2e-spec.ts:58–100`, `backend/test/tbank-ledger-local.e2e-spec.ts:44–78`.
|
||
- Benchmark: `backend/test/tbank-read-baseline.e2e-spec.ts:199–247,355–357,410–485`.
|
||
- Контрпримеры: `backend/test/upcoming-events.e2e-spec.ts:140–152`, `backend/test/portfolio-structure.e2e-spec.ts:40–49`, `backend/test/tbank-value-history.e2e-spec.ts:47–56`.
|
||
|
||
**Claim.** Тестовый app lifecycle не отделён от настоящего фонового scheduler в части HTTP/read suites и benchmark.
|
||
|
||
**Current mechanism.** `AppModule` включает `TbankSyncModule`, provider создаёт `setInterval(..., 60_000)` на `app.init/listen`. `tick` читает connections, резервирует hourly work и запускает membership/portfolio/operations/events. Из 13 функциональных AppModule suites только три override scheduler; в остальных 10 таймер активен. Пять перечисленных connection-oriented functional suites создают connection fixtures. Benchmark держит app во время большой DB population, допускает длительный setup/test и собирает upstream counters.
|
||
|
||
**Impact / actual cost.** При достижении минутного срока данные/metadata и counters могут меняться вне тестового Act; возможны неожиданные upstream mock calls и новые work rows. В benchmark фоновые writes/refresh конкурируют с измеряемыми reads. **Impact unmeasured:** длительность текущих suites, факт срабатывания tick и flaky frequency не измерены. Фактический внешний сетевой запрос не установлен.
|
||
|
||
**Why now.** Isolation не должна зависеть от того, уложился ли runner в минуту. Особенно это важно для утверждений «GET не запускает upstream» и измерения read-only budget.
|
||
|
||
**Minimum change.** В не-scheduler suites применить уже имеющийся `.overrideProvider(TbankHourlyScheduler).useValue(...)` без lifecycle таймера. Benchmark также отключает scheduler и явно запускает только необходимую подготовку. Для scheduler behavior нужен отдельный управляемый time/lifecycle case; не добавлять production env flag ради tests.
|
||
|
||
**Guarantees at risk.** Сохранить настоящие controllers/guards/pipes, migrations, DB publication fences и ручной запуск source owners в relevant scenarios. Scheduler tests должны сохранять реальный `tick`/reservation behavior; override не должен стать способом скрыть их ошибки.
|
||
|
||
**Counterarguments / trade-offs.** Большинство функциональных suites, вероятно, заканчивается раньше первого tick; auth/cards/stocks/bonds/OpenAPI обычно не создают connections, поэтому там интервал может быть inert. `app.close` очищает interval. Эти гарантии уменьшают вероятность, но не делают connection suites deterministic; cleanup timer не отменяет уже запущенную работу. Три существующих overrides показывают, что меньшая boundary уже принята.
|
||
|
||
**Verification or rollback.** После override отдельно проверить один local-read HTTP case и benchmark setup с временем за минутной границей: новых hourly work/upstream calls быть не должно. Затем relevant functional suites. Проверки самого scheduler — с controlled clock. Rollback — только provider override; production scheduler не меняется.
|
||
|
||
**Related finding IDs:** TI-001, TI-003, TI-005.
|
||
|
||
### TI-003 — Readiness фоновой работы зависит от sleeps и polling вместо доступного completion handle
|
||
|
||
**Decision = FIX · Severity = P2 · Confidence = High · Effort = M**
|
||
|
||
**Evidence:**
|
||
|
||
- Consumers readiness sleeps: `backend/test/tbank-metadata-persistence.e2e-spec.ts:130–178`, `backend/test/tbank-catalog-discovery.e2e-spec.ts:179–225`, `backend/test/tbank-operations-sync.e2e-spec.ts:152–181,183–210,523–550`.
|
||
- Async launch mechanism: `backend/src/integrations/tbank/instrument-metadata.service.ts:75–108,111–129`, `backend/src/modules/investments/operations/application/brokerage-operations-sync.service.ts:157–185,233–275`.
|
||
- Consumers polling и существующий handle: `backend/src/modules/investments/portfolio/application/portfolio.service.spec.ts:100–110,150–154`, `backend/src/modules/investments/portfolio/application/portfolio.service.ts:234–306`.
|
||
- Контрпримеры: `backend/test/tbank-local-reads.e2e-spec.ts:69–77`, `backend/test/portfolio-structure.e2e-spec.ts:81–100`.
|
||
|
||
**Claim.** Некоторые Arrange/Act sequences не имеют явного сигнала «producer дошёл до управляемого RPC», а часть tests не ждёт уже существующую completion promise.
|
||
|
||
**Current mechanism.** Metadata/catalog/operations services после reservation запускают async drain через `setImmediate` и затем выполняют DB/access awaits. Tests ждут `delay(5)` или `delay(10)` и считают producer начавшимся; после этого меняют connection или присоединяют другую работу. Два portfolio unit cases опрашивают `repository.publish.mock.calls` десять event-loop turns, хотя `service.request` возвращает `{id, done}`. В metadata coalescing-case assertions стоят перед `blocked.resolve`; при failure до release нет `finally`, который освободил бы producer.
|
||
|
||
**Impact / actual cost.** Sequencing зависит от CPU/SQLite scheduling. На медленном запуске возможен другой interleaving или падение mock call assertion. При раннем failure gate может остаться открытым до teardown и затруднить диагностику. Polling publish не выражает полный lifecycle задачи. **Impact unmeasured:** нет воспроизведённого flaky-run и оценки времени; речь о доказанном способе координации.
|
||
|
||
**Why now.** Race/connection scenarios должны контролировать точки начала/завершения, сохраняя реальный repository/CAS путь. Увеличение sleep не устанавливает ordering guarantee.
|
||
|
||
**Minimum change.** Mock upstream отмечает `started` и блокируется на существующем gate; тест ждёт этот сигнал перед replace/join. Release помещается в `finally`, когда case может выйти раньше. Где есть `work.done`, await именно его. Где completion представлен только durable work row, оставить bounded state polling и meaningful error; не переводить SQLite целиком на fake timers и не создавать универсальную race DSL.
|
||
|
||
**Guarantees at risk.** Барьер должен стоять на внешнем RPC или явной publication boundary, не заменять transaction/repository fake. Не сериализовать сами конкурирующие операции, не удалять проверки stale revision/attempt и final state.
|
||
|
||
**Counterarguments / trade-offs.** Настоящая SQLite может делать короткий polling самым простым reliable completion способом. Awaited `service.read` иногда даёт producer время на запуск; на обычной машине sleep может работать стабильно. Поэтому не удаляются все `delay` и `settled`: distinction между state polling и readiness sleep принципиален. Existing `work.done` consumers подтверждают более простой путь для portfolio без нового API.
|
||
|
||
**Verification or rollback.** Запустить только затронутые cases в среде с локальными портами и disposable DB. Продемонстрировать, что replace/join выполняется после `started`, а case заканчивается после release/completion; проверить failure-path cleanup. Для portfolio unit достаточно двух existing launch cases. Повторять тесты для оценки flaky-rate только при оставшемся сомнении, не заменять correctness многократным зелёным запуском.
|
||
|
||
**Related finding IDs:** TI-001, TI-002, TI-004.
|
||
|
||
### TI-004 — Targeted runner не задаёт freshness prerequisite для crash subprocess из `dist`
|
||
|
||
**Decision = FIX · Severity = P2 · Confidence = High · Effort = S**
|
||
|
||
**Evidence:** `backend/test/sqlite.cjs:7–19,71–85`; `backend/test/jest-e2e.json:7–12`; `backend/test/tbank-operations-sync.e2e-spec.ts:424–499`; `backend/test/fixtures/tbank-sync-crash.cjs:1–17,59–82`; `backend/package.json:28–34`; `backend/README.md:250–254`.
|
||
|
||
**Claim.** Допустимый targeted launch может проверять исходники основного driver и другой build crash producer одновременно.
|
||
|
||
**Current mechanism.** Runner принимает любой existing e2e filename и запускает ts-jest. Operations-sync suite fork-ит fixture, которая требует services/repository/Prisma из `dist`. Прямой `node test/sqlite.cjs tbank-operations-sync.e2e-spec.ts` и передача такого filename в `test:sqlite` не выполняют build. `test:e2e`, performance и OpenAPI manifest commands предварительно собирают приложение.
|
||
|
||
**Impact / actual cost.** Если `dist` отсутствует, child не достигает checkpoint; если build старый, parent и producer могут иметь разную reservation/publication семантику. Возможны ложные failure или недостоверная green proof относительно текущего source. **UNVERIFIED:** текущий `dist` не проверялся на freshness; доказан разрешённый launch path без prerequisite, а не наличие устаревшего build сегодня. **Impact unmeasured.**
|
||
|
||
**Why now.** Самый дешёвый targeted SQLite path должен честно сообщать свои inputs; именно crash tests используют иной runtime artifact.
|
||
|
||
**Minimum change.** В existing README/test command guidance явно связать dist-dependent targeted suite с `npm run build && node test/sqlite.cjs tbank-operations-sync.e2e-spec.ts`. У runner сделать понятной build prerequisite для этой suite, если пользователи продолжают запускать её напрямую. Не добавлять checksum framework и не делать build обязательным для всех pure/DB-only tests; наличие файла само по себе не доказывает freshness.
|
||
|
||
**Guarantees at risk.** Сохранить реальный отдельный child process, SIGKILL, checkpoint semantics, migrated temporary DB и сравнение durable rows после recovery. Замена crash helper на mocked restart уменьшит доказательность.
|
||
|
||
**Counterarguments / trade-offs.** Штатный full e2e flow уже безопаснее: build выполняется перед всеми suites. Default `test:sqlite` выбирает только auth/cards и не использует crash fixture. Проблема локальна targeted/custom invocation. Повторный build стоит времени; достаточно explicit prerequisite, без усложнения общей схемы запусков.
|
||
|
||
**Verification or rollback.** Проверить две existing entry boundaries: targeted suite без build даёт понятное ограничение; после fresh build её три crash checkpoints работают с текущими services. Эта runtime проверка отложена, потому что требует build/migrations. Rollback — отменить только guidance/preflight change, не child protocol.
|
||
|
||
**Related finding IDs:** TI-003, TI-005.
|
||
|
||
### TI-005 — Opt-in regression runner использует слишком большой product workload
|
||
|
||
**Decision = SIMPLIFY · Severity = P3 · Confidence = High · Effort = M**
|
||
|
||
**Evidence:** `backend/test/sqlite-regression.cjs:20–76,77–91`; `backend/test/sqlite.cjs:7–17,58–89`; consumer: `backend/README.md:250–254`.
|
||
|
||
**Claim.** Regression isolation/cleanup самого runner повторяет полный functional product набор там, где для механизма достаточно меньшего существующего workload.
|
||
|
||
**Current mechanism.** Каждый `check` вызывает `test/sqlite.cjs --all`; три последовательных и два параллельных normal passes проверяют allocated directories, uniqueness, cleanup и неизменность sentinel. Затем отдельно запускаются migration failure и signals. При прохождении всех normal checks текущий workload — **5 × 19 = 95 functional suite invocations**, каждая со свежими migrations/Jest; failure/signal attempts в число 95 не включены.
|
||
|
||
**Impact / actual cost.** Maintenance/check cost связан со всеми business suites; чужая продуктовая регрессия может остановить проверку runner mechanics. Количество повторных suite invocations установлено статически. **Impact unmeasured:** wall time, CPU и CI minutes не измерялись; command opt-in, не утверждается его запуск на каждом PR.
|
||
|
||
**Why now.** Механизмы защиты DB и cleanup стоит проверять достаточно дешёво, чтобы ими действительно пользовались после изменения runner.
|
||
|
||
**Minimum change.** Использовать существующие небольшие SQLite suites как workload: один normal проход должен сохранить переход между минимум двумя suites; parallel checks должны сохранить минимум два одновременных runner process. Failure/signal/sentinel assertions оставить. Full product e2e уже имеет собственный entrypoint. Новая искусственная suite или новый test framework не требуются.
|
||
|
||
**Guarantees at risk.** Уникальность DB между suites и процессами, штатные migrations, реальная child failure, неизменность sentinel, SIGINT/SIGTERM nonzero exit и cleanup. Нельзя сократить workload до пустого mocked subprocess, который перестаёт обращаться к SQLite.
|
||
|
||
**Counterarguments / trade-offs.** Полные проходы дополнительно проверяют все suites на внешнее shared state и дают broad stress evidence. Поэтому сокращение не срочное; имеет смысл после маленького доказательства эквивалентности runner guarantees. Three identical sequential passes сами по себе не доказывают, что пять полных product проходов — минимально нужный объём.
|
||
|
||
**Verification or rollback.** Сопоставить assertions старого и малого workload: минимум две suite directories, два parallel runners, sentinel и failure/signal cleanup. Один измеренный запуск старого/нового workload даст baseline для экономии; пока не заявлять ускорение. Rollback — вернуть workload selection в opt-in runner.
|
||
|
||
**Related finding IDs:** TI-002, TI-004.
|
||
|
||
### TI-006 — `failBond` — unused flexibility в локальном frontend setup
|
||
|
||
**Decision = DELETE · Severity = P3 · Confidence = High · Effort = S**
|
||
|
||
**Evidence:** `frontend/src/pages/bond/ui/BondPage/BondPage.test.tsx:88–107,294–404`; consumers `setup` — `296,337,345,353,364,371,383`.
|
||
|
||
**Claim.** Option `failBond` и его endpoint branch существуют без выполняющего их scenario.
|
||
|
||
**Current mechanism.** Local `setup` не экспортируется. `failBond` определяется в options и читается только внутри default bond handler; ни один callsite не передаёт его. Остальные options имеют реальные consumers. В соседней stock suite `failStock` используется в recovery case (`frontend/src/pages/stock/ui/StockPage/StockPage.test.tsx:473–481`), поэтому удалять симметрично оба flags нельзя.
|
||
|
||
**Impact / actual cost.** Небольшая лишняя ветвь чтения и сопровождения helper, создающая видимость поддерживаемого scenario. **Impact unmeasured;** это необязательный cleanup, не дефект продукта и не требование добавить тест ради покрытия ветви.
|
||
|
||
**Why now.** Только при ближайшей правке этой suite; отдельная migration ради одного flag не оправданна.
|
||
|
||
**Minimum change.** Удалить option и conditional response, оставить happy-path handler. Если появляется настоящий unavailable-bond scenario, case может явно добавить соответствующий MSW handler.
|
||
|
||
**Guarantees at risk.** Не удалять ambiguity handler, failure coupons/rating и recovery UI production logic; находка относится только к невызванной test-support ветви.
|
||
|
||
**Counterarguments / trade-offs.** Flag мог готовиться под будущий сценарий; это не current consumer. Если такой scenario уже нужен, owner должен добавить его по продуктовому риску — решение о assertions относится к аудиту 08.
|
||
|
||
**Verification or rollback.** Static dependency closure уже прослежена в полном файле. При реализации достаточно existing targeted BondPage suite и typecheck; не писать test на отсутствие option. Rollback — вернуть локальный flag при появлении consumer.
|
||
|
||
**Related finding IDs:** —.
|
||
|
||
## 5. DELETE / SIMPLIFY / KEEP / INVESTIGATE
|
||
|
||
| Decision | Кандидат / механизм | Основание и граница |
|
||
| --- | --- | --- |
|
||
| FIX | Banking per-case state — TI-001 | Самостоятельные prerequisites, app может оставаться общим |
|
||
| FIX | Hourly provider в не-scheduler suites — TI-002 | Existing override; scheduler behavior сохраняется в своей boundary |
|
||
| FIX | Readiness/completion — TI-003 | Explicit RPC start/release и existing `done`; не увеличивать sleeps |
|
||
| FIX | Dist-dependent targeted command — TI-004 | Согласовать build input; не менять все runner entrypoints |
|
||
| SIMPLIFY | Workload runner regression — TI-005 | Малый real SQLite payload при сохранённых safety assertions |
|
||
| DELETE | `failBond` test option — TI-006 | Нулевой consumer подтверждён; production behavior не удаляется |
|
||
| KEEP | Real SQLite migrations + runner env gate + temp cleanup | `sqlite.cjs:60–89`, `e2e-env.cjs:1–13`; native constraint/transaction proof и защита пользовательской DB |
|
||
| KEEP | Refresh CAS, financial concurrency и replay tests | `app.e2e-spec.ts:161–213`, `bank-cards.e2e-spec.ts:118–164,391–423,462–524,622–708`; mocks не равносильны реальной записи |
|
||
| KEEP | Revision/attempt publication и crash process | `tbank-migration.e2e-spec.ts:150–168,194–231`, operations-sync crash cases; не заменять rollback/CAS обычными mocks |
|
||
| KEEP | Pure money/calendar boundary | `rub-money.spec.ts`, `bank-card.calculations.spec.ts`, `decimal.spec.ts`; никаких app/DB dependencies ради вычислений |
|
||
| KEEP | QueryClient factory + strict MSW + resource envelope | Shared technical policy уже имеет consumers; singleton client и cross-repo framework ухудшат isolation |
|
||
| KEEP | Explicit page providers и domain fixtures | Повторение короткого arrange делает local expectations видимыми |
|
||
| KEEP | Два browser viewport instances | Проверяют настоящую geometry, которой нет в JSDOM; это не suite duplication |
|
||
| KEEP | Storybook network/storage isolation | Прямое требование synthetic catalog; не «тест factory ради factory» |
|
||
| KEEP | Build/container/durability boundaries | Packaging/crash/backup risks не воспроизводятся pure assertions |
|
||
| KEEP | Локальные `deferred` сейчас | Backend `target=ES2023` (`tsconfig.json:13`) не предоставляет Promise.withResolvers в default lib. Не копировать frontend ES2024 решение, меняя TS config ради четырёх коротких helpers |
|
||
| INVESTIGATE | Узкий HTTP module для stocks/bonds | AppModule bootstrap/migrations time **not measured**; сначала сохранить реальные guards/pipes/filter/routes и проверить цену |
|
||
| INVESTIGATE | Explicit QueryClient teardown для незавершённых requests | Fresh clients и RTL cleanup уже есть; утечка/мешающий GC timer **UNVERIFIED**. Не вводить client registry или `gcTime` policy без воспроизведения |
|
||
|
||
### Ponytail complexity-pass
|
||
|
||
- `shrink:` уменьшить product workload opt-in runner regression; использовать existing small SQLite suites с теми же lifecycle/sentinel checks. [`backend/test/sqlite-regression.cjs:20–91`, TI-005]
|
||
- `shrink:` заменить два portfolio publish-poll loops на existing `work.done`. [`backend/src/modules/investments/portfolio/application/portfolio.service.spec.ts:100–110,150–154`, часть TI-003]
|
||
- `delete:` убрать unused `failBond` option и conditional handler branch; replacement — обычный success handler. [`frontend/src/pages/bond/ui/BondPage/BondPage.test.tsx:88–107`, TI-006]
|
||
|
||
**net: -3 lines, -0 deps possible.** Это консервативный подсчёт только конкретного удаления `failBond`: одна option-строка плюс замена трёх строк conditional expression одной (`1 + (3 − 1)`). Patch не применялся; сокращение polling/runner LOC не оценивалось. Кандидатов на удаление dependency не установлено.
|
||
|
||
## 6. Cost & benefit
|
||
|
||
| Действие | Ожидаемый класс пользы | Что измерено | Обязательные regressions / safety boundary |
|
||
| --- | --- | --- | --- |
|
||
| TI-001: case-owned data | Cognitive load, время targeted diagnosis, maintenance | Static dependency category rename → restart; время **not measured** | Money, FK order, concurrent refunds/refresh, replay/tombstones, restart |
|
||
| TI-002: controlled scheduler | Reliability, reproducibility; чистота benchmark measurement | 10 functional AppModule suites без override; runtime влияние **not measured** | HTTP/DB остаются настоящими; scheduler проверяется управляемо |
|
||
| TI-003: readiness / `done` | Flakiness risk, ясность Arrange/Act, меньше incidental polling | Concrete sleep/launch/completion paths; flaky frequency **not measured** | Не подменить CAS; awaited release и terminal state, failure cleanup |
|
||
| TI-004: fresh build для crash path | Достоверность proof, diagnostic clarity | Source/dist split; actual stale artifact **UNVERIFIED** | Actual separate process/SIGKILL/current services/temp DB |
|
||
| TI-005: меньший runner workload | Local verification time; CI minutes только если command используется в CI | 95 suite invocations в успешных normal checks; wall time **not measured** | Две suite boundaries, два parallel processes, sentinel, миграции, signals/failure |
|
||
| TI-006: unused flag | Очень небольшая maintenance/cognitive экономия | Dependency closure, минимальный подсчёт удаления выше | Existing BondPage behavior; новые tests ради флага не нужны |
|
||
|
||
Локальная pure unit проверка заняла сообщённые Jest **0.379 s** 2026-10-11; её нельзя экстраполировать на HTTP/SQLite или приписывать предложенному cleanup.
|
||
|
||
Исторические benchmark JSON содержат application-read metrics и различный hardware/profile (`test/fixtures/tbank-read-baseline.json:1–19`, `tbank-read-persistent.json:1–20`). Это **не CI timing**; даты измерения в просмотренных headers не указаны. Их latency не использована для расчёта экономии инфраструктуры и не представлена как актуальное состояние.
|
||
|
||
Нет доказанных процентов ускорения, объёма agent-token экономии или необходимости убрать зависимости.
|
||
|
||
## 7. Top actions
|
||
|
||
### Quick wins
|
||
|
||
1. **TI-002:** применить existing scheduler override в connection/read HTTP suites и benchmark. Большая польза isolation при маленьком diff; real time остаётся только в scheduler-specific proof.
|
||
2. **TI-001:** сделать rename/restart/constraint cases self-contained. Сначала добавить явные prerequisites, затем убрать shared owner state между независимыми cases. Полный финансовый suite остаётся необходимой regression boundary.
|
||
3. **TI-003:** в двух portfolio unit launch cases ждать `work.done`. Уже существующий handle короче и точнее нового polling helper.
|
||
4. **TI-004:** записать существующую build + targeted crash command как правильный путь. Понятное ограничение предпочтительнее проверки freshness через новый hash/manifest механизм.
|
||
5. **TI-006:** удалить unused flag при ближайшей правке BondPage suite; самостоятельная cleanup-кампания не нужна.
|
||
|
||
### Решения с тестовым подтверждением
|
||
|
||
6. **TI-003:** убрать readiness sleeps в metadata/catalog/operations producer races; поставить gate на реальную upstream boundary, гарантировать release/settlement при выходе из case. Сохранить real SQLite.
|
||
7. **TI-005:** сократить opt-in infrastructure workload после доказательства сохранности suite/process isolation, failure/signals и sentinel assertions. Измерить один before/after запуск; не объявлять неподтверждённое ускорение.
|
||
|
||
Новая ADR для этих локальных исправлений не нужна. ADR/отдельное решение потребуется только при смене базовой testing boundary, backend compiler lib ради новых APIs или принятии нового общего HTTP harness; таких изменений аудит не требует.
|
||
|
||
### Minimal Test Infrastructure Baseline
|
||
|
||
| Граница | Минимально достаточный baseline | KEEP / REMOVE / CONSOLIDATE |
|
||
| --- | --- | --- |
|
||
| Pure domain | Existing Jest/Vitest imports, explicit financial/calendar data | **KEEP**; не стартовать app/DB |
|
||
| Service orchestration | Concrete service + узкие external mocks + управляемые gates | **KEEP** real completion handles; **REMOVE** readiness sleeps там, где нужен ordering |
|
||
| Backend DB | Existing isolated SQLite runner, production migrations, case-owned state | **KEEP** per-suite DB и env gate; **CONSOLIDATE** launch guidance, не DB mocks |
|
||
| Backend HTTP | ConfigureApp + настоящие guard/pipe/filter + controlled external boundaries | **KEEP**; **REMOVE** автоматический scheduler lifecycle из не-scheduler suites |
|
||
| Crash/rollback/restore | Existing subprocess/baseline artifact/backup runners | **KEEP** независимые boundaries; **CONSOLIDATE** build prerequisite с explicit command |
|
||
| Frontend API/UI | Fresh test client + strict MSW + explicit providers + RTL cleanup | **KEEP**; page fixtures остаются локальными |
|
||
| Layout | Existing Vitest browser project на narrow/wide | **KEEP**; не имитировать dimensions в JSDOM |
|
||
| Catalog | Synthetic stories + network/storage isolation check | **KEEP**; отдельная story-fixture DSL не нужна |
|
||
| Runner regression | Small existing SQLite workload + sentinel/failure/signal assertions | **CONSOLIDATE** mechanics checks вокруг малого workload; full product e2e отдельно |
|
||
| Cross-repo reuse | Публичные contract artifacts, без runtime imports helpers | **KEEP** автономность frontend/backend/specs/docs |
|
||
|
||
## 8. Duplication vs Readability Trade-off
|
||
|
||
| Повторение | Что совпадает семантически | Решение |
|
||
| --- | --- | --- |
|
||
| Query/Mantine/Router provider trees | Framework composition; маршруты/auth requirements различаются | Factory QueryClient уже общая. Не делать omnibus `renderWithEverything` с десятком switches |
|
||
| Stock/Bond handlers | Похожая topology; financial metrics/rating/coupons, identifiers и outcomes различаются | Local ownership важнее визуального DRY. Удалить только доказанный dead option |
|
||
| Backend JWT/connection seeds | Несколько строк config; revision/account/lifecycle цели различаются | Не делить между репозиториями и не строить users/builders framework |
|
||
| `settled` loops | Metadata/catalog ждут отсутствие running work; operations ждёт конкретное state; forecast ждёт dependency group и quiet period | Не унифицировать все loops по синтаксису. При изменении owner сделать условия/диагностику явными |
|
||
| Portfolio unit publish polling | Два одинаковых обхода вместо уже предоставленного completion | Сократить через `work.done`, не извлекать `waitForPublish` |
|
||
| Banking happy-path helpers | HTTP/auth/expected success повторяются реально | Сохранить helpers; negative status assertions оставить в test body |
|
||
| Четыре `deferred<T>` | Promise control похож; часть поддерживает reject | Цена мала, lib compatibility различается с frontend. Не менять compiler contract ради DRY; отдельно устранять sequencing недостатки |
|
||
| Restart/crash/durability scenarios | Слова «restart» совпадают, failure boundaries разные | Nest provider restart, SIGKILL процесса и old/new-build rollback **не взаимозаменяемы** |
|
||
| Runner regression full passes | Повторяются все product cases, а assertions проверяют runner directories/sentinel | Малый real workload может сохранить именно infrastructure proof — TI-005 |
|
||
|
||
## 9. Cross-audit signals
|
||
|
||
- **08:** соответствие named race исходному data flow и ожидание позднего response — `backend/test/tbank-local-reads.e2e-spec.ts:489–515`, `frontend/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx:175–237`; assertions здесь не переаудировались.
|
||
- **08:** cache/error scenarios с изменением handler без следующего Act — `frontend/src/pages/bond/ui/BondPage/BondPage.test.tsx:363–369`, `src/pages/stock/ui/StockPage/StockPage.test.tsx:463–471`; вопрос содержательной regression proof.
|
||
- **08/12:** coverage include захватывает `src/pages/account/model/positions.fixtures.ts`, exclude ограничен test/stories/support globs — `frontend/vitest.config.ts:15–24`; оценить смысл coverage input, не добавлять tests на fixture.
|
||
- **06/13:** termination уже начавшейся hourly work при app shutdown — `backend/src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts:48–54,84–86,197–218`; production lifecycle не расследовался сверх необходимого для TI-002.
|
||
- **10/11:** policy-tooling tests и стоимость repeated package builds — frontend `package.json:18–26`, `scripts/test-eslint.mjs`; backend `test/eslint-config.cjs`; их обязательность/trigger budget вне этого аудита.
|
||
|
||
## 10. Open questions / verification plan
|
||
|
||
| Вопрос, реально меняющий решение | Текущее состояние | Самый дешёвый следующий шаг |
|
||
| --- | --- | --- |
|
||
| Должна banking suite быть sequential journey или набором независимых cases? | Формат `it` и отдельные titles указывают на самостоятельные cases; category dependency доказана | Запустить rename/restart отдельно на disposable DB. Если journey намеренный — держать dependent steps внутри одного scenario, а не скрывать dependency между `it` |
|
||
| Срабатывает ли hourly tick на обычном runner до конца connection suites? | **UNVERIFIED**, registration/connection path доказан | Один controlled-clock/instrumented targeted run. Отсутствие срабатывания сегодня не отменяет полезность deterministic override |
|
||
| Какие targeted SQLite команды реально используют разработчики для crash cases? | Full command собирает build; direct runner принимает dist-dependent suite | Проверить usage/guidance и выполнить один targeted launch после fresh build. Отдельная freshness registry не нужна |
|
||
| Достаточен ли маленький workload для safety regression runner? | Product повторение доказано; эквивалентность ещё не проверена | Малый prototype на existing suites; сверить directory/process/sentinel/failure/signal assertions и один before/after timing |
|
||
| Есть ли dangling frontend requests/GC timers после cleanup? | **UNVERIFIED**; fresh clients, auth generation fence и cleanup уменьшают риск | Только при воспроизведении pending-after-test симптома снять handles/requests для одной suite; затем локальный teardown. Не добавлять глобальный registry заранее |
|
||
| Стоит ли уменьшать stocks/bonds AppModule bootstrap? | Framework/DB bootstrap присутствует; цена **not measured**, pipeline coverage имеет ценность | Измерить setup одной existing HTTP suite; прототип narrow module только при значимой цене и с сохранёнными guard/pipe/filter outcomes |
|
||
|
||
### Основания для library-specific решений
|
||
|
||
- Vitest lifecycle/file isolation и default stack hooks сверены с документацией через Context7 `/vitest-dev/vitest/v5.0.3`, `docs/guide/lifecycle.md`, `docs/config/sequence.md`. В проекте manifest `5.0.0`; документация ближайшей версии использована для общих lifecycle semantics, не как proof прогона текущего runner. File isolation не сбрасывает все resources после каждого case автоматически.
|
||
- TanStack Query testing/cache guidance сверено через Context7 `/tanstack/query`, testing guide и core GC implementation. Fresh client/retry-off соответствует текущей задаче. Совет `gcTime=Infinity` относится к определённому Jest handle symptom и не является основанием без воспроизведения добавлять его в frontend Vitest.
|
||
- Decision о сохранении маленьких backend deferred helpers основан на local `tsconfig.json:13` и реальных consumers, а не на недоступном для default lib API.
|
||
|
||
### Финальная проверка отчёта
|
||
|
||
- Findings ограничены audit 09; проверены owner/consumer, контрпримеры, минимальная правка и сохраняемые guarantees.
|
||
- Числа — file/import counts, static workload selection и один explicitly logged targeted unit run; runtime экономия не приписывалась.
|
||
- Проверены source references и существование цитируемых путей; новый файл просмотрен отдельно, выполнен whitespace check.
|
||
- Финальная проверка HEAD/status четырёх репозиториев подтверждает исходные SHA и отсутствие staged/unstaged/untracked изменений. Отчёт находится вне них; root ignore/allowlist не менялись.
|
||
|
||
## Таблица для консолидации
|
||
|
||
| ID | Decision | Severity | Confidence | Repo | Files | One-line impact | Effort | Related IDs |
|
||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||
| TI-001 | FIX | P2 | High | backend | test/bank-cards.e2e-spec.ts | Межтестовая category dependency мешает самостоятельному запуску финансовых scenarios | M | TI-002, TI-003 |
|
||
| TI-002 | FIX | P2 | High | backend | src/modules/investments/tbank-sync/tbank-hourly.scheduler.ts; test/all-accounts.e2e-spec.ts; test/tbank-connection.e2e-spec.ts; test/tbank-local-reads.e2e-spec.ts; test/tbank-income-local.e2e-spec.ts; test/tbank-ledger-local.e2e-spec.ts; test/tbank-read-baseline.e2e-spec.ts | Фоновый tick способен менять fixtures/counters вне Act и загрязнять read benchmark | S | TI-001, TI-003, TI-005 |
|
||
| TI-003 | FIX | P2 | High | backend | test/tbank-metadata-persistence.e2e-spec.ts; test/tbank-catalog-discovery.e2e-spec.ts; test/tbank-operations-sync.e2e-spec.ts; src/modules/investments/portfolio/application/portfolio.service.spec.ts | Readiness sleeps и ignored done делают lifecycle зависимым от scheduling | M | TI-001, TI-002, TI-004 |
|
||
| TI-004 | FIX | P2 | High | backend | test/sqlite.cjs; test/fixtures/tbank-sync-crash.cjs; test/tbank-operations-sync.e2e-spec.ts; package.json; README.md | Targeted crash path допускает source/dist mismatch без fresh-build prerequisite | S | TI-003, TI-005 |
|
||
| TI-005 | SIMPLIFY | P3 | High | backend | test/sqlite-regression.cjs; test/sqlite.cjs; README.md | Opt-in infrastructure regression выполняет 95 functional suite invocations в normal checks | M | TI-002, TI-004 |
|
||
| TI-006 | DELETE | P3 | High | frontend | src/pages/bond/ui/BondPage/BondPage.test.tsx | Unused failBond option добавляет ветвь helper без consumer | S | — |
|