57 lines
3.9 KiB
Markdown
57 lines
3.9 KiB
Markdown
# API Envelope Runtime Contract
|
||
|
||
## Goal
|
||
|
||
Устранить расхождение между следующим API-контрактом и реальными runtime-ответами backend'а, чтобы каждый endpoint возвращал ровно один `{ data, meta }`:
|
||
|
||
```ts
|
||
// Единственный публичный контракт
|
||
ApiResponse<T> = { data: T, meta: { fromCache: boolean, cachedAt: string | null } }
|
||
```
|
||
|
||
Убрать поддержку broken double-wrapping на frontend и сделать `TransformInterceptor` единственной точкой формирования публичного envelope.
|
||
|
||
## Requirements
|
||
|
||
### Backend
|
||
|
||
1. **Единый владелец envelope** — `TransformInterceptor` является единственной точкой, формирующей `{ data, meta }` в HTTP-ответе.
|
||
2. **Internal carrier** — Services и controllers, которым нужно передать cache metadata, используют внутренний carrier-тип (`ApiEnvelopePayload<T>`), а не создают `{ data, meta }` вручную.
|
||
3. **Controllers без meta возвращают чистое DTO** — Если endpoint не использует cache, controller возвращает только domain data, interceptor оборачивает её сам.
|
||
4. **Ни один endpoint не приводит к `data.data`** — Runtime-ответ каждого endpoint проверяется интеграционным тестом на одинарный envelope.
|
||
|
||
### Frontend
|
||
|
||
1. **Единый формат envelope** — `normalizeEnvelope()` больше не поддерживает double-wrapped ответы.
|
||
2. **request()** остаётся `Promise<{ data: T; meta: ApiResponseMeta }>` — контракт не меняется, ясность не уменьшается.
|
||
|
||
### Testing
|
||
|
||
1. **HTTP contract tests** — минимальный набор проверяет, что ключевые endpoint'ы возвращают `{ data, meta }` без вложенности.
|
||
2. **Существующие тесты обновлены** — сервисные и контроллерные тесты проверяют новый carrier-механизм вместо ручного `{ data, meta }`.
|
||
3. **Regression-тесты** — endpoint'ы autentification, shares, bonds, securities, portfolios, T-Bank покрыты минимум одним contract-тестом на shape ответа.
|
||
|
||
## Acceptance Criteria
|
||
|
||
1. `GET /api/v1/health` возвращает `{ data: { ... }, meta: { fromCache, cachedAt } }` — без `data.data`.
|
||
2. `GET /api/v1/securities/shares/{secid}` — одинарный envelope.
|
||
3. `GET /api/v1/securities/shares/{secid}/marketdata` — одинарный envelope с корректным `meta` от cache.
|
||
4. `GET /api/v1/securities/bonds/{secid}` — одинарный envelope.
|
||
5. `GET /api/v1/securities/search?q=` — одинарный envelope.
|
||
6. `GET /api/v1/securities/screener?` — одинарный envelope.
|
||
7. `GET /api/v1/portfolios` … — одинарный envelope.
|
||
8. `GET /api/v1/broker/accounts` … — одинарный envelope.
|
||
9. `POST /api/v1/auth/register`, `login`, `refresh` — одинарный envelope.
|
||
10. Backend тесты проходят: `npm test -w apps/backend`.
|
||
11. Frontend тесты проходят: `npm test -w apps/frontend`.
|
||
12. `npm run build -w apps/frontend` проходит.
|
||
13. `normalizeEnvelope()` больше не проверяет `data.data`.
|
||
14. Swagger/OpenAPI артефакты не ломаются (`npm exec -w apps/backend -- vitest run openapi`).
|
||
|
||
## Non-goals
|
||
|
||
- Не менять форму DTO и Swagger-типы (они уже корректны).
|
||
- Не менять формат error-ответов.
|
||
- Не добавлять версионирование API.
|
||
- Не рефакторить бизнес-логику endpoint'ов.
|