mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 07:30:16 +03:00
feat: доменный API
This commit is contained in:
@@ -1,91 +1,189 @@
|
||||
# Тестирование доменного пакета
|
||||
|
||||
> Проверка владельцев и публичных границ Level 2.
|
||||
> Проверка Domain API, port contracts, production wiring и framework projections Level 2.
|
||||
|
||||
## Связанные правила
|
||||
|
||||
- [`SLM-L2-TEST-R016`](../../rules/level-2.md#slm-l2-test-r016)
|
||||
- [`SLM-L2-BUSINESS-A019`](../../rules/level-2.md#slm-l2-business-a019)
|
||||
- [`SLM-L2-API-A019`](../../rules/level-2.md#slm-l2-api-a019)
|
||||
- [`SLM-L2-ASSEMBLY-A020`](../../rules/level-2.md#slm-l2-assembly-a020)
|
||||
- [`SLM-L2-ADAPTER-R021`](../../rules/level-2.md#slm-l2-adapter-r021)
|
||||
- [`SLM-L2-BUSINESS-A022`](../../rules/level-2.md#slm-l2-business-a022)
|
||||
- [`SLM-L2-API-A022`](../../rules/level-2.md#slm-l2-api-a022)
|
||||
- [`SLM-L2-ASSEMBLY-R023`](../../rules/level-2.md#slm-l2-assembly-r023)
|
||||
- [`SLM-L2-BUSINESS-R024`](../../rules/level-2.md#slm-l2-business-r024)
|
||||
- [`SLM-L2-API-R024`](../../rules/level-2.md#slm-l2-api-r024)
|
||||
- [`SLM-L2-PORT-R027`](../../rules/level-2.md#slm-l2-port-r027)
|
||||
- [`SLM-L2-REALTIME-R029`](../../rules/level-2.md#slm-l2-realtime-r029)
|
||||
- [`SLM-L2-ASSEMBLY-R030`](../../rules/level-2.md#slm-l2-assembly-r030)
|
||||
|
||||
## Размещение
|
||||
|
||||
Тест находится рядом с модулем-владельцем проверяемой ответственности. У доменного пакета нет общей корневой папки `tests`.
|
||||
Тест находится рядом с module-owner проверяемой ответственности. У доменного пакета нет общей корневой папки `tests`.
|
||||
|
||||
| Проверяемая граница | Владелец теста |
|
||||
|---|---|
|
||||
| Сценарии, Domain API, данные и ошибки | `business` |
|
||||
| Публичная pure-функция или guard | `business/runtime` внутри тестов модуля business |
|
||||
| Техническое преобразование | Adapter |
|
||||
| Выбор API, dependencies и environment boundary | Assembly |
|
||||
| Provider, hook, query integration, form или guard | Соответствующий framework binding module |
|
||||
| Граф нескольких доменов | Модуль `composition`, точка входа `app` или другое место сборки |
|
||||
| Domain API operations, models, outcomes и errors | `api` через factory |
|
||||
| Deterministic runtime и guards | `api` |
|
||||
| Реализация dependency port | Adapter |
|
||||
| Default и специальный production graph | Assembly |
|
||||
| Provider, hook, query/store integration и hydration | Framework binding |
|
||||
| Cross-domain graph | Graph owner |
|
||||
|
||||
## Business через фабрику
|
||||
## Domain API через фабрику
|
||||
|
||||
Каждый публичный сценарий проверяется через фабрику владеющего им API с управляемыми dependencies:
|
||||
Каждый публичный сценарий проверяется через фабрику владеющего им Domain API с управляемыми fake ports:
|
||||
|
||||
```ts
|
||||
import type { AuthSessionApi } from '@/domains/auth/business'
|
||||
import { authSessionFactory } from '@/domains/auth/business/factory'
|
||||
import type {
|
||||
AuthSessionApi,
|
||||
} from '@/domains/auth/api'
|
||||
|
||||
import type {
|
||||
AuthIdentityPort,
|
||||
} from '@/domains/auth/api/ports'
|
||||
|
||||
import {
|
||||
AUTH_ERROR_CODES,
|
||||
isAuthError,
|
||||
} from '@/domains/auth/business/runtime'
|
||||
createAuthSessionApi,
|
||||
} from '@/domains/auth/api/factory'
|
||||
|
||||
const api: AuthSessionApi = authSessionFactory(createAuthSessionTestDeps({
|
||||
clock: { now: () => 1_700_000_000_000 },
|
||||
phone: { requestCode: async () => ({ ok: true }) },
|
||||
}))
|
||||
const identity: AuthIdentityPort = createIdentityPortFake({
|
||||
signIn: {
|
||||
ok: true,
|
||||
value: {
|
||||
expiresAt: 1_700_000_000_000,
|
||||
subject: 'user-1',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await api.requestPhoneOtp('+79991112233')
|
||||
const api: AuthSessionApi = createAuthSessionApi({
|
||||
identity,
|
||||
runtime: {
|
||||
createId: () => 'id-1',
|
||||
now: () => 1_700_000_000_000,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Набор проверяет успешные и ожидаемые ошибочные результаты, validation, преобразование внешних данных и публичные изменения состояния. Способ assertion для ошибки зависит от `throw` или `Result`, но наружу проверяется только собственный AuthErrorCode.
|
||||
API suite проверяет:
|
||||
|
||||
Business-тест не использует React, реальный SDK, database, production assembly или системные clock/random. Локальная fake implementation допустима в тесте и не становится adapter-модулем, потому что не входит в production graph.
|
||||
- public models и outcomes;
|
||||
- validation commands и port records;
|
||||
- mapping каждого expected port failure;
|
||||
- отсутствие raw provider details в domain errors;
|
||||
- cancellation и outcome uncertainty при наличии;
|
||||
- pure transitions и reconciliation;
|
||||
- каждый API отдельно при нескольких factories.
|
||||
|
||||
Если `business` объявляет несколько API, каждый тестируется через свою фабрику. Общая внутренняя pure-логика не требует повторять одинаковые cases на уровне всех API.
|
||||
API-тест не использует React, production assembly, реальный SDK, backend, system clock или module singleton.
|
||||
|
||||
## Остальные модули
|
||||
## Adapter contract test
|
||||
|
||||
Тест adapter-модуля проверяет технический вызов, аргументы, преобразование результата и передачу исходного сбоя business-слою. Он не повторяет mapping в доменные ошибки.
|
||||
Adapter test доказывает, что concrete provider реализует port:
|
||||
|
||||
Тест обязательной assembly проверяет:
|
||||
- правильно преобразует arguments;
|
||||
- валидно читает provider record;
|
||||
- возвращает port record, а не raw DTO;
|
||||
- различает закрытые port failures;
|
||||
- не создаёт public domain error;
|
||||
- соблюдает cancellation, timeout и lifecycle contract;
|
||||
- использует заявленный environment capability set.
|
||||
|
||||
- вызов только нужных business-фабрик;
|
||||
- точный именованный состав возвращённого графа;
|
||||
- выбор публичных adapter-модулей;
|
||||
- отсутствие несовместимого environment-кода;
|
||||
- передачу cross-domain API аргументом, а не импортом;
|
||||
- cleanup handle, если assembly создаёт ресурс жизненного цикла.
|
||||
Fake port в API-тесте не заменяет adapter contract test. Идеальный fake может соответствовать типу, пока реальный endpoint или SDK уже изменился.
|
||||
|
||||
Assembly без собственного lifecycle-ресурса не тестирует пустой `dispose`, потому что не обязана его предоставлять.
|
||||
## Default assembly
|
||||
|
||||
Framework-тест импортирует только конкретный модуль, например `auth/react/session`, передаёт тестовый `AuthSessionApi` и проверяет Provider, hook или component. Query binding дополнительно проверяет keys, invalidation и отсутствие raw DTO в публичном результате, но не повторяет полный набор business-сценариев.
|
||||
Тест `assemblies/default` проверяет:
|
||||
|
||||
- вызов только нужных API factories;
|
||||
- выбор штатных adapter modules;
|
||||
- точный именованный состав graph;
|
||||
- объявленный baseline capability set;
|
||||
- отсутствие module-import side effects;
|
||||
- передачу cross-domain API аргументом;
|
||||
- отсутствие factory/adapter leakage наружу;
|
||||
- aggregate cleanup, если assembly создаёт owned resource или получает adapter lifecycle handle.
|
||||
|
||||
Каждая дополнительная assembly тестирует отличие своего production context, а не повторяет полный API suite.
|
||||
|
||||
## Partial construction и cleanup
|
||||
|
||||
Assembly test моделирует ошибку после регистрации каждого cleanup obligation, включая adapter-owned handle:
|
||||
|
||||
```text
|
||||
resource A created
|
||||
resource B creation failed
|
||||
→ cleanup A awaited
|
||||
→ original failure propagated
|
||||
```
|
||||
|
||||
Проверяются reverse dependency order, idempotent repeated disposal, попытка очистить все resources и отсутствие callbacks после завершившегося cleanup.
|
||||
|
||||
Assembly без cleanup obligations не тестирует пустой `dispose`, потому что не обязана его предоставлять. Если adapter передал lifecycle handle, obligation существует независимо от resource ownership.
|
||||
|
||||
## Framework binding
|
||||
|
||||
Framework test получает fake готового Domain API и проверяет собственную responsibility:
|
||||
|
||||
- Provider и hook;
|
||||
- query keys, stale policy и invalidation;
|
||||
- store projection;
|
||||
- optimistic update через API-owned function;
|
||||
- hydration payload;
|
||||
- public domain errors;
|
||||
- subscription cleanup;
|
||||
- отсутствие direct SDK/external source access.
|
||||
|
||||
Framework test не повторяет validation и failure mapping всех API operations.
|
||||
|
||||
## Realtime
|
||||
|
||||
API realtime test с fake port проверяет public events, stable errors, acknowledgement semantics и `OUTCOME_UNKNOWN` mapping.
|
||||
|
||||
Adapter realtime contract test проверяет:
|
||||
|
||||
- command correlation;
|
||||
- duplicate и late acknowledgement;
|
||||
- disconnect до ACK;
|
||||
- ordering и sequence gaps;
|
||||
- reconnect и resync;
|
||||
- malformed frames;
|
||||
- cancellation и unsubscribe;
|
||||
- отсутствие callbacks после cleanup.
|
||||
|
||||
Assembly test отдельно проверяет shared connection, multiplexing, rollback и graph-level disposal. Framework test проверяет только materialization events и invalidation.
|
||||
|
||||
## SSR, RSC и Server Actions
|
||||
|
||||
Environment tests подтверждают:
|
||||
|
||||
- request-scoped API и cache не разделяются между users;
|
||||
- API instance не входит в hydration payload;
|
||||
- Client Component создаёт отдельный client graph;
|
||||
- SSR-enabled Client Component проверяется в server prerender и browser hydration graphs;
|
||||
- browser-only effect не выполняется во время server render;
|
||||
- Server Action создаёт и очищает graph на каждый вызов;
|
||||
- framework reference edge не превращается в executable client/server leak;
|
||||
- `default` проверяется под всеми объявленными resolver conditions.
|
||||
|
||||
## Cross-domain graph
|
||||
|
||||
Graph owner test создаёт assemblies и construction points модулей Level 1 в dependency order и проверяет runtime inputs и callbacks. Отдельно проверяется невозможность mixed L1/L2 циклической сборки и reverse cleanup order.
|
||||
|
||||
Не достаточно проверить только статический import DAG: runtime dependencies, передаваемые arguments, должны быть представлены architecture mapping или review evidence.
|
||||
|
||||
## Автоматические структурные проверки
|
||||
|
||||
Проверка файлов, exports и import-графа подтверждает:
|
||||
Import и export checks подтверждают:
|
||||
|
||||
- отсутствие root API доменного пакета и Framework Groups;
|
||||
- обязательные `business` и `business/factory`, type-only exports в корневом barrel и допустимый опциональный `business/runtime`;
|
||||
- соблюдение матрицы потребителей фасетов business;
|
||||
- наличие непосредственно в корне пакета непустой Group `assemblies` с объявленными модульными границами;
|
||||
- отсутствие запрещённых runtime cross-domain imports;
|
||||
- отсутствие type-only импортов из чужих assemblies, adapters и framework-модулей;
|
||||
- отсутствие cross-domain framework hooks, contexts и components;
|
||||
- отсутствие server-only достижимости из client modules;
|
||||
- отсутствие runtime- и type-only циклов.
|
||||
- отсутствие root API пакета и Groups;
|
||||
- обязательные `api`, `api/factory` и `assemblies/default`;
|
||||
- `api/ports` только при наличии declared ports;
|
||||
- допустимые exports каждого фасета;
|
||||
- importer matrix factories, ports и concrete adapters;
|
||||
- отсутствие deep imports;
|
||||
- отсутствие SDK, framework и state/query runtime в graph `api`;
|
||||
- отсутствие запрещённых cross-domain imports;
|
||||
- environment compatibility под configured conditions;
|
||||
- отсутствие статических cycles.
|
||||
|
||||
## Архитектурное ревью
|
||||
|
||||
На ревью проверяется, что `business/factory` экспортирует только объявленные фабрики, а `business/runtime` при наличии содержит только публичный deterministic runtime. Для каждой технической зависимости рассматриваются все production implementations: каждая связная implementation должна принадлежать одному модулю Group `adapters`, но один такой модуль может реализовать несколько тесно связанных capabilities одного provider.
|
||||
|
||||
Отдельно проверяются прямые обращения business к `Date.now`, `Math.random`, `crypto.randomUUID`, timers, env и другим скрытым runtime-capabilities.
|
||||
|
||||
Runtime-тест не заменяет автоматическую проверку или архитектурное ревью.
|
||||
Runtime tests не заменяют import-graph checks и architecture review.
|
||||
|
||||
Reference in New Issue
Block a user