Some checks failed
CI / lint-typecheck (pull_request) Failing after 4m51s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
249 lines
15 KiB
Markdown
249 lines
15 KiB
Markdown
# Testing concept: fully testing [`adapters/`](adapters/:1)
|
||
|
||
This is a Clean Architecture-aligned testing concept for completely testing the code under [`adapters/`](adapters/:1), using:
|
||
|
||
- [`docs/TESTING_LAYERS.md`](docs/TESTING_LAYERS.md:1) (where test types belong)
|
||
- [`docs/architecture/shared/ADAPTERS.md`](docs/architecture/shared/ADAPTERS.md:1) (what adapters are)
|
||
- [`docs/architecture/shared/REPOSITORY_STRUCTURE.md`](docs/architecture/shared/REPOSITORY_STRUCTURE.md:1) (where things live)
|
||
- [`docs/architecture/shared/DATA_FLOW.md`](docs/architecture/shared/DATA_FLOW.md:1) (dependency rule)
|
||
- [`docs/TESTS.md`](docs/TESTS.md:1) (current repo testing practices)
|
||
|
||
---
|
||
|
||
## 1) Goal + constraints
|
||
|
||
### 1.1 Goal
|
||
Make [`adapters/`](adapters/:1) **safe to change** by covering:
|
||
|
||
1. Correct port behavior (adapters implement Core ports correctly)
|
||
2. Correct mapping across boundaries (domain ⇄ persistence, domain ⇄ external system)
|
||
3. Correct error shaping at boundaries (adapter-scoped schema errors)
|
||
4. Correct composition (small clusters like composite resolvers)
|
||
5. Correct wiring assumptions (DI boundaries: repositories don’t construct their own mappers)
|
||
|
||
### 1.2 Constraints / non-negotiables
|
||
|
||
- Dependencies point inward: delivery apps → adapters → core per [`docs/architecture/shared/DATA_FLOW.md`](docs/architecture/shared/DATA_FLOW.md:13)
|
||
- Adapters are reusable infrastructure implementations (no delivery concerns) per [`docs/architecture/shared/REPOSITORY_STRUCTURE.md`](docs/architecture/shared/REPOSITORY_STRUCTURE.md:25)
|
||
- Tests live as close as possible to the code they verify per [`docs/TESTING_LAYERS.md`](docs/TESTING_LAYERS.md:6)
|
||
|
||
---
|
||
|
||
## 2) Test taxonomy for adapters (mapped to repo locations)
|
||
|
||
This section translates [`docs/TESTING_LAYERS.md`](docs/TESTING_LAYERS.md:1) into concrete rules for adapter code.
|
||
|
||
### 2.1 Local tests (live inside [`adapters/`](adapters/:1))
|
||
|
||
These are the default for adapter correctness.
|
||
|
||
#### A) Unit tests (file-adjacent)
|
||
**Use for:**
|
||
|
||
- schema guards (validate persisted/remote shapes)
|
||
- error types (message formatting, details)
|
||
- pure mappers (domain ⇄ orm/DTO)
|
||
- in-memory repositories and deterministic services
|
||
|
||
**Location:** next to implementation, e.g. [`adapters/logging/ConsoleLogger.test.ts`](adapters/logging/ConsoleLogger.test.ts:1)
|
||
|
||
**Style:** behavior-focused with BDD structure from [`docs/TESTS.md`](docs/TESTS.md:23). Use simple `Given/When/Then` comments; do not assert internal calls unless that’s the observable contract.
|
||
|
||
Reference anchor: [`typescript.describe()`](adapters/logging/ConsoleLogger.test.ts:4)
|
||
|
||
#### B) Sociable unit tests (small collaborating cluster)
|
||
**Use for:**
|
||
|
||
- a repository using an injected mapper (repository + mapper + schema guard)
|
||
- composite adapters (delegation and resolution order)
|
||
|
||
**Location:** still adjacent to the “root” of the cluster, not necessarily to each file.
|
||
|
||
Reference anchor: [`adapters/media/MediaResolverAdapter.test.ts`](adapters/media/MediaResolverAdapter.test.ts:1)
|
||
|
||
#### C) Component / module tests (module invariants without infrastructure)
|
||
**Use for:**
|
||
|
||
- “module-level” adapter compositions that should behave consistently as a unit (e.g. a group of in-memory repos that are expected to work together)
|
||
|
||
**Location:** adjacent to the module root.
|
||
|
||
Reference anchor: [`adapters/racing/persistence/inmemory/InMemoryScoringRepositories.test.ts`](adapters/racing/persistence/inmemory/InMemoryScoringRepositories.test.ts:1)
|
||
|
||
### 2.2 Global tests (live outside adapters)
|
||
|
||
#### D) Contract tests (boundary tests)
|
||
Contract tests belong at system boundaries per [`docs/TESTING_LAYERS.md`](docs/TESTING_LAYERS.md:88).
|
||
|
||
For this repo there are two contract categories:
|
||
|
||
1. **External system contracts** (API ↔ website) already documented in [`docs/CONTRACT_TESTING.md`](docs/CONTRACT_TESTING.md:1)
|
||
2. **Internal port contracts** (core port interface ↔ adapter implementation)
|
||
|
||
Internal port contracts are still valuable, but they are not “between systems”. Treat them as **shared executable specifications** for a port.
|
||
|
||
**Proposed location:** [`tests/contracts/`](tests/:1)
|
||
|
||
Principle: the contract suite imports the port interface from core and runs the same assertions against multiple adapter implementations (in-memory and TypeORM-DB-free where possible).
|
||
|
||
#### E) Integration / E2E (system-level)
|
||
Per [`docs/TESTS.md`](docs/TESTS.md:106):
|
||
|
||
- Integration tests live in [`tests/integration/`](tests/:1) and use in-memory adapters.
|
||
- E2E tests live in [`tests/e2e/`](tests/:1) and can use TypeORM/Postgres.
|
||
|
||
Adapter code should *enable* these tests, but adapter *unit correctness* should not depend on these tests.
|
||
|
||
---
|
||
|
||
## 3) Canonical adapter test recipes (what to test, not how)
|
||
|
||
These are reusable patterns to standardize how we test adapters.
|
||
|
||
### 3.1 In-memory repositories (pure adapter behavior)
|
||
|
||
**Minimum spec for an in-memory repository implementation:**
|
||
|
||
- persists and retrieves the aggregate/value (happy path)
|
||
- supports negative paths (not found returns null / empty)
|
||
- enforces invariants that the real implementation must also enforce (uniqueness, idempotency)
|
||
- does not leak references if immutability is expected (optional; depends on domain semantics)
|
||
|
||
Examples:
|
||
|
||
- [`adapters/identity/persistence/inmemory/InMemoryUserRepository.test.ts`](adapters/identity/persistence/inmemory/InMemoryUserRepository.test.ts:1)
|
||
- [`adapters/racing/persistence/inmemory/InMemorySessionRepository.test.ts`](adapters/racing/persistence/inmemory/InMemorySessionRepository.test.ts:1)
|
||
|
||
### 3.2 TypeORM mappers (mapping + validation)
|
||
|
||
**Minimum spec for a mapper:**
|
||
|
||
- domain → orm mapping produces a persistable shape
|
||
- orm → domain mapping reconstitutes without calling “create” semantics (i.e., preserves persisted identity)
|
||
- invalid persisted shape throws adapter-scoped schema error type
|
||
|
||
Examples:
|
||
|
||
- [`adapters/media/persistence/typeorm/mappers/MediaOrmMapper.test.ts`](adapters/media/persistence/typeorm/mappers/MediaOrmMapper.test.ts:1)
|
||
- [`adapters/racing/persistence/typeorm/mappers/DriverOrmMapper.test.ts`](adapters/racing/persistence/typeorm/mappers/DriverOrmMapper.test.ts:1)
|
||
|
||
### 3.3 TypeORM repositories (DB-free correctness + DI boundaries)
|
||
|
||
**We split repository tests into 2 categories:**
|
||
|
||
1. **DB-free repository behavior tests**: verify mapping is applied and correct ORM repository methods are called with expected shapes (using a stubbed TypeORM repository).
|
||
2. **DI boundary tests**: verify no internal instantiation of mappers and that constructor requires injected dependencies.
|
||
|
||
Examples:
|
||
|
||
- [`adapters/media/persistence/typeorm/repositories/TypeOrmMediaRepository.test.ts`](adapters/media/persistence/typeorm/repositories/TypeOrmMediaRepository.test.ts:1)
|
||
- [`adapters/payments/persistence/typeorm/repositories/TypeOrmPaymentRepository.test.ts`](adapters/payments/persistence/typeorm/repositories/TypeOrmPaymentRepository.test.ts:1)
|
||
|
||
### 3.4 Schema guards + schema errors (adapter boundary hardening)
|
||
|
||
**Minimum spec:**
|
||
|
||
- guard accepts valid shapes
|
||
- guard rejects invalid shapes with deterministic error messages
|
||
- schema error contains enough details to debug (entity, field, reason)
|
||
|
||
Examples:
|
||
|
||
- [`adapters/admin/persistence/typeorm/schema/TypeOrmAdminSchemaGuards.test.ts`](adapters/admin/persistence/typeorm/schema/TypeOrmAdminSchemaGuards.test.ts:1)
|
||
- [`adapters/admin/persistence/typeorm/errors/TypeOrmAdminSchemaError.test.ts`](adapters/admin/persistence/typeorm/errors/TypeOrmAdminSchemaError.test.ts:1)
|
||
|
||
### 3.5 Gateways (external side effects)
|
||
|
||
**Minimum spec:**
|
||
|
||
- correct request construction (mapping domain intent → external API payload)
|
||
- error handling and retries (if present)
|
||
- logging behavior (only observable outputs)
|
||
|
||
These tests should stub the external client; no real network.
|
||
|
||
---
|
||
|
||
## 4) Gap matrix (folder-level)
|
||
|
||
Legend:
|
||
|
||
- ✅ = present (at least one meaningful test exists)
|
||
- ⚠️ = partially covered
|
||
- ❌ = missing
|
||
|
||
> Important: this matrix is based on the current directory contents under [`adapters/`](adapters/:1). It’s folder-level, not per-class.
|
||
|
||
| Adapter folder | What exists | Local tests status | Missing tests (minimum) |
|
||
|---|---|---:|---|
|
||
| [`adapters/achievement/`](adapters/achievement/:1) | TypeORM entities/mappers/repository/schema guard | ❌ | Mapper tests, schema guard tests, repo DI boundary tests, schema error tests |
|
||
| [`adapters/activity/`](adapters/activity/:1) | In-memory repository | ❌ | In-memory repo behavior test suite |
|
||
| [`adapters/admin/`](adapters/admin/:1) | In-memory repo + TypeORM layer | ✅ | Consider adding DB-free repo tests consistency patterns for TypeORM (if not already), ensure schema guard coverage is complete |
|
||
| [`adapters/analytics/`](adapters/analytics/:1) | In-memory repos + TypeORM layer | ⚠️ | Tests for TypeORM repos without tests, tests for non-tested mappers (`AnalyticsSnapshotOrmMapper`, `EngagementEventOrmMapper`), schema guard tests, schema error tests |
|
||
| [`adapters/automation/`](adapters/automation/:1) | Config objects | ❌ | Unit tests for config parsing/merging defaults (if behavior exists); otherwise explicitly accept no tests |
|
||
| [`adapters/bootstrap/`](adapters/bootstrap/:1) | Seeders + many config modules + factories | ⚠️ | Add unit tests for critical deterministic configs/factories not yet covered; establish module tests for seeding workflows (DB-free) |
|
||
| [`adapters/drivers/`](adapters/drivers/:1) | In-memory repository | ❌ | In-memory repo behavior tests |
|
||
| [`adapters/events/`](adapters/events/:1) | In-memory event publishers | ❌ | Behavior tests: publishes expected events to subscribers/collectors; ensure “no-op” safety |
|
||
| [`adapters/health/`](adapters/health/:1) | In-memory health check adapter | ❌ | Behavior tests: healthy/unhealthy reporting, edge cases |
|
||
| [`adapters/http/`](adapters/http/:1) | Request context module | ❌ | Unit tests for any parsing/propagation logic; otherwise explicitly accept no tests |
|
||
| [`adapters/identity/`](adapters/identity/:1) | In-memory repos + TypeORM repos/mappers + services + session adapter | ⚠️ | Add tests for in-memory files without tests (company/external game rating), tests for TypeORM repos without tests, schema guards tests, cookie session adapter tests |
|
||
| [`adapters/leaderboards/`](adapters/leaderboards/:1) | In-memory repo + event publisher | ❌ | Repo tests + publisher tests |
|
||
| [`adapters/leagues/`](adapters/leagues/:1) | In-memory repo + event publisher | ❌ | Repo tests + publisher tests |
|
||
| [`adapters/logging/`](adapters/logging/:1) | Console logger + error reporter | ⚠️ | Add tests for error reporter behavior; keep logger tests |
|
||
| [`adapters/media/`](adapters/media/:1) | Resolvers + in-memory repos + TypeORM layer + ports | ⚠️ | Add tests for in-memory repos without tests, file-system storage adapter tests, gateway/event publisher tests if behavior exists |
|
||
| [`adapters/notifications/`](adapters/notifications/:1) | Gateways + persistence + ports | ⚠️ | Add gateway tests, registry tests, port adapter tests; schema guard tests for TypeORM |
|
||
| [`adapters/payments/`](adapters/payments/:1) | In-memory repos + TypeORM layer | ⚠️ | Add tests for non-tested mappers, non-tested repos, schema guard tests |
|
||
| [`adapters/persistence/`](adapters/persistence/:1) | In-memory achievement repo + migration script | ⚠️ | Decide whether migrations are tested (usually via E2E/integration). If treated as code, add smoke test for migration shape |
|
||
| [`adapters/races/`](adapters/races/:1) | In-memory repository | ❌ | In-memory repo behavior tests |
|
||
| [`adapters/racing/`](adapters/racing/:1) | Large in-memory + TypeORM layer; many tests | ✅ | Add tests for remaining untested files (notably some in-memory repos and TypeORM repos/mappers without tests) |
|
||
| [`adapters/rating/`](adapters/rating/:1) | In-memory repository | ❌ | In-memory repo behavior tests |
|
||
| [`adapters/social/`](adapters/social/:1) | In-memory + TypeORM; some tests | ⚠️ | Add tests for TypeORM social graph repository, schema guards, and any missing in-memory invariants |
|
||
| [`adapters/eslint-rules/`](adapters/eslint-rules/:1) | ESLint rules | ⚠️ | Optional: rule tests (if the project values rule stability); otherwise accept manual verification |
|
||
|
||
---
|
||
|
||
## 5) Priority order (risk-first)
|
||
|
||
If “completely tested” is the goal, this is the order I’d implement missing tests.
|
||
|
||
1. Persistence adapters that can corrupt or misread data (TypeORM mappers + schema guards) under [`adapters/racing/persistence/typeorm/`](adapters/racing/persistence/typeorm/:1), [`adapters/identity/persistence/typeorm/`](adapters/identity/persistence/typeorm/:1), [`adapters/payments/persistence/typeorm/`](adapters/payments/persistence/typeorm/:1)
|
||
2. Un-tested persistence folders with real production impact: [`adapters/achievement/`](adapters/achievement/:1), [`adapters/analytics/`](adapters/analytics/:1)
|
||
3. External side-effect gateways: [`adapters/notifications/gateways/`](adapters/notifications/gateways/:1)
|
||
4. Small but foundational shared utilities (request context, health, event publishers): [`adapters/http/`](adapters/http/:1), [`adapters/health/`](adapters/health/:1), [`adapters/events/`](adapters/events/:1)
|
||
5. Remaining in-memory repos to keep integration tests trustworthy: [`adapters/activity/`](adapters/activity/:1), [`adapters/drivers/`](adapters/drivers/:1), [`adapters/races/`](adapters/races/:1), [`adapters/rating/`](adapters/rating/:1), [`adapters/leaderboards/`](adapters/leaderboards/:1), [`adapters/leagues/`](adapters/leagues/:1)
|
||
|
||
---
|
||
|
||
## 6) Definition of done (what “completely tested adapters” means)
|
||
|
||
For each adapter module under [`adapters/`](adapters/:1):
|
||
|
||
1. Every in-memory repository has a behavior test (happy path + at least one negative path).
|
||
2. Every TypeORM mapper has a mapping test and an invalid-shape test.
|
||
3. Every TypeORM repository has at least a DB-free test proving:
|
||
- dependencies are injected (no internal `new Mapper()` patterns)
|
||
- mapping is applied on save/load
|
||
4. Every schema guard and schema error class is tested.
|
||
5. Every external gateway has a stubbed-client unit test verifying payload mapping and error shaping.
|
||
6. At least one module-level test exists for any composite adapter (delegation order + null-handling).
|
||
7. Anything that is intentionally “not worth unit-testing” is explicitly declared and justified in the gap matrix (to avoid silent omissions).
|
||
|
||
---
|
||
|
||
## 7) Optional: internal port-contract test harness (shared executable specs)
|
||
|
||
If we want the same behavioral contract applied across multiple adapter implementations, add a tiny harness under [`tests/contracts/`](tests/:1):
|
||
|
||
- `tests/contracts/<feature>/<PortName>.contract.ts`
|
||
- exports a function that takes a factory creating an implementation
|
||
- Each adapter test imports that contract and runs it
|
||
|
||
This keeps contracts central **without** moving tests away from the code (the adapter still owns the “run this contract for my implementation” test file).
|
||
|
||
---
|
||
|
||
## 8) Mode switch intent
|
||
|
||
After you approve this concept, the implementation phase is to add the missing tests adjacent to the adapter files and (optionally) introduce `tests/contracts/` without breaking dependency rules.
|
||
|