140 lines
12 KiB
Markdown
140 lines
12 KiB
Markdown
# QA Analysis
|
||
|
||
Issue: WealthLedger/main#54
|
||
|
||
## Verification Scope
|
||
|
||
**In:**
|
||
- все Requirements и Scenarios `portfolio/investment-performance` и
|
||
`instruments/market-index-history`;
|
||
- контракт `investment-performance.openapi.yaml`;
|
||
- семантика ошибок System Analysis.
|
||
|
||
**Out:**
|
||
- TWR, периоды, валютная конвертация;
|
||
- корректность самой синхронизации T-Bank (покрыта её спецификациями);
|
||
- точность значений MCFTRR у источника.
|
||
|
||
## Coverage Matrix
|
||
|
||
| Requirement / Scenario | Проверка на уровне системы |
|
||
| --- | --- |
|
||
| Результат счёта: рублёвый счёт с известным началом | API: ответ 200 со всеми полями; значения совпадают с эталоном (ниже) |
|
||
| Результат счёта: счёт недоступен через подключение | API: чужой `accountId` → 404, в теле нет финансовых полей |
|
||
| Внешние потоки: пополнения, вывод, дивиденды | Эталонный счёт A: `netInvested = 130000`, `profit = 10000` |
|
||
| Внешние потоки: кандидат на перевод между счетами в `all` | Счета B и C с равными суммами в пределах 5 минут исключаются с `UNCONFIRMED_TRANSFER`, а не объявляются внутренней парой |
|
||
| Внешние потоки: перевод на счёт вне результата | Счёт B исключён (USD), его пополнение от C → вывод у C в `all` |
|
||
| Доходность: период больше года | Счёт D: 100 000 → 121 000 за 730 дн. → `return.value = 0.1`, `basis = annual` |
|
||
| Доходность: меньше года | Счёт E, 200 дн.: `basis = period`; в UI нет слова «годовых» ни в одном значении |
|
||
| Доходность: нет решения | Счёт F (текущая стоимость 0 после пополнений, без выводов): `NO_SOLUTION`, `profit` доступен, `outperformance = null` |
|
||
| Доходность: несколько решений | Набор потоков с двумя корнями XIRR: `AMBIGUOUS_RETURN`, прибыль доступна, `outperformance = null` |
|
||
| Индекс: пополнение и рост индекса | Подменённая история (1 000 → 1 100) или ручной расчёт по реальным закрытиям: `benchmark.value = 110000`, `outperformance.money = 5000` |
|
||
| Индекс: поток в выходной | Пополнение в субботу → используется закрытие пятницы (проверяется по `series` в день потока) |
|
||
| Индекс: отставание | Счёт со стоимостью ниже теневой: отрицательные `percentagePoints` и `money`, в UI «Отставание от рынка» |
|
||
| Справочный индекс недоступен | IMOEX нет, MCFTRR есть: `reference.unavailableReason` задан, `benchmark` и `outperformance` есть, в UI нет пунктирной линии |
|
||
| Недоступно: неизвестная дата открытия | Счёт с `historyBoundary = unknown`: `data: null`, `HISTORY_START_UNKNOWN`; в UI «Доходность недоступна», нет нулей |
|
||
| Недоступно: USD в `all` | 3 счёта, один с USD: `completeness = incomplete`, `excludedAccounts` содержит счёт и `NON_RUB_ACTIVITY`; в UI предупреждение |
|
||
| Недоступно: даты оценок в `all` различаются | Последние оценки A и B в разные московские дни → `data: null`, `ACCOUNT_VALUATION_DATES_DIFFER`; стоимость не складывается |
|
||
| Недоступно: индекс недоступен | MOEX заблокирован и истории нет: `profit` есть, `benchmark.unavailableReason = INDEX_HISTORY_UNAVAILABLE`, `series = []` |
|
||
| Недоступно: остальные причины | Перевод бумаг без пары → `UNVALUED_SECURITIES_TRANSFER`; позиция без цены → `PORTFOLIO_VALUATION_INCOMPLETE`; незавершённая синхронизация → `OPERATIONS_HISTORY_INCOMPLETE` |
|
||
| Вкладка: обгоняю, меньше года, неполный, недоступный, методика | UI по соответствующим данным; формулировки и тоны как в UI/UX Package; «Как считали» содержит все пункты спецификации |
|
||
| Вкладка: загрузка, ошибка не ломает другие вкладки | Задержка ответа → skeleton без чисел; 500 на `/performance` → ошибка только во вкладке, остальные вкладки работают |
|
||
| График: наведение, отсутствие прошлой стоимости | Наведение и фокус показывают дату и 3 значения; есть пояснение под графиком; `aria-label` с итогами |
|
||
| История индексов: первое получение, выходной | После первого чтения есть закрытия торговых дней без дубликатов; повторное чтение не меняет число записей; субботних дат нет |
|
||
| История индексов: источник недоступен, история есть / нет | Блокировка `iss.moex.com` после первичной загрузки → результат полный; на чистой БД → `INDEX_HISTORY_UNAVAILABLE`, остальные вкладки работают |
|
||
|
||
**Эталон.** Для счетов A–F и реальных данных пользователя XIRR
|
||
пересчитывается независимо (LibreOffice `XIRR` по тем же потокам и
|
||
`currentValue` в дату `to`). Допуск — 0,1 п.п., по Success Criteria.
|
||
|
||
## Cross-system Verification
|
||
|
||
- **Контракт.** Ответы обоих маршрутов валидируются по
|
||
`investment-performance.openapi.yaml`: required, nullable, enum, шаблон
|
||
`Decimal`. Неизвестное значение `UnavailableReason`, подставленное в ответ
|
||
через прокси или мок, UI показывает общей недоступностью без падения.
|
||
- **Ошибки доступа.** 401 без сессии; 503 `TBANK_NOT_CONNECTED` без
|
||
подключения; 404 для счёта не из подключения. `all` без подключения тоже
|
||
503.
|
||
- **Таймаут MOEX.** Ответ ISS задерживается больше 10 с: результат приходит
|
||
с сохранённой историей или с `INDEX_HISTORY_UNAVAILABLE`. Время ответа —
|
||
не больше ~10 с плюс время расчёта, без бесконечного ожидания.
|
||
- **Параллельные чтения.** Два одновременных GET на чистой БД дают одну
|
||
серию запросов к ISS и одинаковые ответы.
|
||
- **Частота догрузки.** Повторное чтение в течение часа после сбоя не
|
||
обращается к ISS (проверяется по логам).
|
||
- **Порядок данных.** Пополнение, исполненное после `fetchedAt` Portfolio, в
|
||
потоки не входит.
|
||
|
||
## Risk-based Priorities
|
||
|
||
1. **Критично — деньги.** Значения прибыли, XIRR и опережения совпадают с
|
||
эталоном. Ни один недоступный случай не показывает число.
|
||
2. **Критично — доступ.** Нет утечки чужих счетов в `all` и `meta.unavailable`.
|
||
3. **Высокий риск — переводы в `all`** (эвристика парности) и счета с
|
||
реальной историей пользователя.
|
||
4. **Высокий риск — меньше года.** Ни одного «годовых».
|
||
5. **Средний риск — устойчивость к MOEX**, график и a11y.
|
||
|
||
## Test Data and Environment Needs
|
||
|
||
- Пользователь с подключением T-Bank и наборами счетов:
|
||
- A — пополнения, вывод, дивиденды;
|
||
- B — USD;
|
||
- C — перевод с B и C;
|
||
- D — 730 дн.;
|
||
- E — 200 дн.;
|
||
- F — нет решения;
|
||
- G — `historyBoundary = unknown`;
|
||
- H — перевод бумаг без пары;
|
||
- I — позиция без цены.
|
||
|
||
Если sandbox T-Bank такие данные не даёт, их записывают напрямую в
|
||
одноразовую SQLite-копию.
|
||
- Возможность блокировать `iss.moex.com` (сеть или прокси) и задерживать
|
||
его ответы.
|
||
- Чистая БД и БД с предзагруженной историей индексов.
|
||
- Реальные данные Change Owner для финальной приёмки.
|
||
|
||
## Manual / Exploratory Scope
|
||
|
||
- Реальные счета пользователя:
|
||
- правдоподобие результата;
|
||
- парность переводов между своими счетами;
|
||
- какие счета исключаются и почему.
|
||
- Вёрстка 390px и desktop; клавиатурная навигация (вкладка, hint, «Как
|
||
считали», график); экранный диктор читает итоги графика.
|
||
- Переключение счёт ↔ `all` и обратно: нет устаревших данных другого счёта.
|
||
|
||
## Automation Candidates
|
||
|
||
- Black-box API-проверки контракта и сценариев A–I: deferred until QA repo
|
||
exists.
|
||
- E2E вкладки «Результат» (состояния ready, unavailable, incomplete,
|
||
error): deferred until QA repo exists.
|
||
|
||
## Observability Verification
|
||
|
||
- `integration.moex.index_history.succeeded` / `.failed` появляются при
|
||
догрузке и сбое; в них есть secid, диапазон, pages, durationMs и нет
|
||
данных пользователя.
|
||
- В логах расчёта нет сумм и идентификаторов операций.
|
||
|
||
## Regression Impact
|
||
|
||
- Вкладки счёта: позиции, концентрация, деньги, операции, события, доход —
|
||
переключение и загрузка не меняются.
|
||
- Запись истории оценки Portfolio после выноса признака полной оценки
|
||
(`portfolio-value-history`): полные оценки по-прежнему создают точки,
|
||
неполные — нет.
|
||
- Существующие запросы к MOEX по облигациям.
|
||
- `openapi:check` обоих репозиториев.
|
||
|
||
## Gaps / Blockers
|
||
|
||
- **Подтверждение переводов** отсутствует в текущем контракте T-Bank.
|
||
Routing: System Analysis при появлении нового признака связи; до этого
|
||
кандидаты исключаются с явной причиной, не выдавая сомнительный процент.
|
||
- **Sandbox T-Bank** может не воспроизвести все наборы. Routing: Test Data —
|
||
наполнение одноразовой SQLite-копии; не блокирует.
|