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