Files
gridpilot.gg/plans/testing-concept-adapters.md
Marc Mintel 838f1602de
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
adapter tests
2026-01-24 21:39:59 +01:00

249 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 dont 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 thats 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). Its 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 Id 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.