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

15 KiB
Raw Permalink Blame History

Testing concept: fully testing adapters/

This is a Clean Architecture-aligned testing concept for completely testing the code under adapters/, using:


1) Goal + constraints

1.1 Goal

Make adapters/ 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


2) Test taxonomy for adapters (mapped to repo locations)

This section translates docs/TESTING_LAYERS.md into concrete rules for adapter code.

2.1 Local tests (live inside adapters/)

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

Style: behavior-focused with BDD structure from docs/TESTS.md. Use simple Given/When/Then comments; do not assert internal calls unless thats the observable contract.

Reference anchor: typescript.describe()

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

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

2.2 Global tests (live outside adapters)

D) Contract tests (boundary tests)

Contract tests belong at system boundaries per docs/TESTING_LAYERS.md.

For this repo there are two contract categories:

  1. External system contracts (API ↔ website) already documented in docs/CONTRACT_TESTING.md
  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/

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:

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:

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:

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:

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:

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/. Its folder-level, not per-class.

Adapter folder What exists Local tests status Missing tests (minimum)
adapters/achievement/ TypeORM entities/mappers/repository/schema guard Mapper tests, schema guard tests, repo DI boundary tests, schema error tests
adapters/activity/ In-memory repository In-memory repo behavior test suite
adapters/admin/ 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/ 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/ Config objects Unit tests for config parsing/merging defaults (if behavior exists); otherwise explicitly accept no tests
adapters/bootstrap/ 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/ In-memory repository In-memory repo behavior tests
adapters/events/ In-memory event publishers Behavior tests: publishes expected events to subscribers/collectors; ensure “no-op” safety
adapters/health/ In-memory health check adapter Behavior tests: healthy/unhealthy reporting, edge cases
adapters/http/ Request context module Unit tests for any parsing/propagation logic; otherwise explicitly accept no tests
adapters/identity/ 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/ In-memory repo + event publisher Repo tests + publisher tests
adapters/leagues/ In-memory repo + event publisher Repo tests + publisher tests
adapters/logging/ Console logger + error reporter ⚠️ Add tests for error reporter behavior; keep logger tests
adapters/media/ 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/ Gateways + persistence + ports ⚠️ Add gateway tests, registry tests, port adapter tests; schema guard tests for TypeORM
adapters/payments/ In-memory repos + TypeORM layer ⚠️ Add tests for non-tested mappers, non-tested repos, schema guard tests
adapters/persistence/ 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/ In-memory repository In-memory repo behavior tests
adapters/racing/ 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/ In-memory repository In-memory repo behavior tests
adapters/social/ In-memory + TypeORM; some tests ⚠️ Add tests for TypeORM social graph repository, schema guards, and any missing in-memory invariants
adapters/eslint-rules/ 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/identity/persistence/typeorm/, adapters/payments/persistence/typeorm/
  2. Un-tested persistence folders with real production impact: adapters/achievement/, adapters/analytics/
  3. External side-effect gateways: adapters/notifications/gateways/
  4. Small but foundational shared utilities (request context, health, event publishers): adapters/http/, adapters/health/, adapters/events/
  5. Remaining in-memory repos to keep integration tests trustworthy: adapters/activity/, adapters/drivers/, adapters/races/, adapters/rating/, adapters/leaderboards/, adapters/leagues/

6) Definition of done (what “completely tested adapters” means)

For each adapter module under adapters/:

  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/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.