# Testing gaps in `core` (unit tests only, no infra/adapters)

## Scope / rules (agreed)

* **In scope:** code under [`core/`](core:1) only.
* **Unit tests only:** tests should validate business rules and orchestration using **ports mocked in-test** (e.g., `vi.fn()`), not real persistence, HTTP, frameworks, or adapters.
* **Out of scope:** any test that relies on real IO, real repositories, or infrastructure code (including [`core/**/infrastructure/`](core/rating/infrastructure:1)).

## How gaps were identified

1. Inventory of application and domain units was built from file structure under [`core/`](core:1).
2. Existing tests were located via `describe(` occurrences in `*.test.ts` and mapped to corresponding production units.
3. Gaps were prioritized by:
   * **Business criticality:** identity/security, payments/money flows.
   * **Complex branching / invariants:** state machines, decision tables.
   * **Time-dependent logic:** `Date.now()`, `new Date()`, time windows.
   * **Error handling paths:** repository errors, partial failures.

---

## Highest-priority testing gaps (P0)

### 1) `rating` module has **no unit tests**

Why high risk: scoring/rating is a cross-cutting “truth source”, and current implementations contain test-driven hacks and inconsistent error handling.

Targets:
* [`core/rating/application/use-cases/CalculateRatingUseCase.ts`](core/rating/application/use-cases/CalculateRatingUseCase.ts:1)
* [`core/rating/application/use-cases/CalculateTeamContributionUseCase.ts`](core/rating/application/use-cases/CalculateTeamContributionUseCase.ts:1)
* [`core/rating/application/use-cases/GetRatingLeaderboardUseCase.ts`](core/rating/application/use-cases/GetRatingLeaderboardUseCase.ts:1)
* [`core/rating/application/use-cases/SaveRatingUseCase.ts`](core/rating/application/use-cases/SaveRatingUseCase.ts:1)
* [`core/rating/domain/Rating.ts`](core/rating/domain/Rating.ts:1)

Proposed unit tests (Given/When/Then):
1. **CalculateRatingUseCase: driver missing**
   * Given `driverRepository.findById` returns `null`
   * When executing with `{ driverId, raceId }`
   * Then returns `Result.err` with message `Driver not found` and does not call `ratingRepository.save`.
2. **CalculateRatingUseCase: race missing**
   * Given driver exists, `raceRepository.findById` returns `null`
   * When execute
   * Then returns `Result.err` with message `Race not found`.
3. **CalculateRatingUseCase: no results**
   * Given driver & race exist, `resultRepository.findByRaceId` returns `[]`
   * When execute
   * Then returns `Result.err` with message `No results found for race`.
4. **CalculateRatingUseCase: driver not present in results**
   * Given results array without matching `driverId`
   * When execute
   * Then returns `Result.err` with message `Driver not found in race results`.
5. **CalculateRatingUseCase: publishes event after save**
   * Given all repositories return happy-path objects
   * When execute
   * Then `ratingRepository.save` is called once before `eventPublisher.publish`.
6. **CalculateRatingUseCase: component boundaries**
   * Given a result with `incidents = 0`
   * When execute
   * Then `components.cleanDriving === 100`.
   * Given `incidents >= 5`
   * Then `components.cleanDriving === 20`.
7. **CalculateRatingUseCase: time-dependent output**
   * Given frozen time (use `vi.setSystemTime`)
   * When execute
   * Then emitted rating has deterministic `timestamp`.
8. **CalculateTeamContributionUseCase: creates rating when missing**
   * Given `ratingRepository.findByDriverAndRace` returns `null`
   * When execute
   * Then `ratingRepository.save` is called with a rating whose `components.teamContribution` matches calculation.
9. **CalculateTeamContributionUseCase: updates existing rating**
   * Given existing rating with components set
   * When execute
   * Then only `components.teamContribution` is changed and other fields preserved.
10. **GetRatingLeaderboardUseCase: pagination + sorting**
   * Given multiple drivers and multiple ratings per driver
   * When execute with `{ limit, offset }`
   * Then returns latest per driver, sorted desc, sliced by pagination.
11. **SaveRatingUseCase: repository error wraps correctly**
   * Given `ratingRepository.save` throws
   * When execute
   * Then throws `Failed to save rating:` prefixed error.

Ports to mock: `driverRepository`, `raceRepository`, `resultRepository`, `ratingRepository`, `eventPublisher`.

---

### 2) `dashboard` orchestration has no unit tests

Target:
* [`core/dashboard/application/use-cases/GetDashboardUseCase.ts`](core/dashboard/application/use-cases/GetDashboardUseCase.ts:1)

Why high risk: timeouts, parallelization, filtering/sorting, and “log but don’t fail” event publishing.

Proposed unit tests (Given/When/Then):
1. **Validation of driverId**
   * Given `driverId` is `''` or whitespace
   * When execute
   * Then throws [`ValidationError`](core/shared/errors/ValidationError.ts:1) (or the module’s equivalent) and does not hit repositories.
2. **Driver not found**
   * Given `driverRepository.findDriverById` returns `null`
   * When execute
   * Then throws [`DriverNotFoundError`](core/dashboard/domain/errors/DriverNotFoundError.ts:1).
3. **Filters invalid races**
   * Given `getUpcomingRaces` returns races missing `trackName` or with past `scheduledDate`
   * When execute
   * Then `upcomingRaces` in DTO excludes them.
4. **Limits upcoming races to 3 and sorts by date ascending**
   * Given 5 valid upcoming races out of order
   * When execute
   * Then DTO contains only 3 earliest.
5. **Activity is sorted newest-first**
   * Given activities with different timestamps
   * When execute
   * Then DTO is sorted desc by timestamp.
6. **Repository failures are logged and rethrown**
   * Given one of the repositories rejects
   * When execute
   * Then logger.error called and error is rethrown.
7. **Event publishing failure is swallowed**
   * Given `eventPublisher.publishDashboardAccessed` throws
   * When execute
   * Then use case still returns DTO and logger.error was called.
8. **Timeout behavior** (if retained)
   * Given `raceRepository.getUpcomingRaces` never resolves
   * When using fake timers and advancing by TIMEOUT
   * Then `upcomingRaces` becomes `[]` and use case completes.

Ports to mock: all repositories, publisher, and [`Logger`](core/shared/domain/Logger.ts:1).

---

### 3) `leagues` module has multiple untested use-cases (time-dependent logic)

Targets likely missing tests:
* [`core/leagues/application/use-cases/JoinLeagueUseCase.ts`](core/leagues/application/use-cases/JoinLeagueUseCase.ts:1)
* [`core/leagues/application/use-cases/LeaveLeagueUseCase.ts`](core/leagues/application/use-cases/LeaveLeagueUseCase.ts:1)
* [`core/leagues/application/use-cases/ApproveMembershipRequestUseCase.ts`](core/leagues/application/use-cases/ApproveMembershipRequestUseCase.ts:1)
* plus others without `*.test.ts` siblings in [`core/leagues/application/use-cases/`](core/leagues/application/use-cases:1)

Proposed unit tests (Given/When/Then):
1. **JoinLeagueUseCase: league missing**
   * Given `leagueRepository.findById` returns `null`
   * When execute
   * Then throws `League not found`.
2. **JoinLeagueUseCase: driver missing**
   * Given league exists, `driverRepository.findDriverById` returns `null`
   * Then throws `Driver not found`.
3. **JoinLeagueUseCase: approvalRequired path uses pending requests**
   * Given `league.approvalRequired === true`
   * When execute
   * Then `leagueRepository.addPendingRequests` called with a request containing frozen `Date.now()` and `new Date()`.
4. **JoinLeagueUseCase: no-approval path adds member**
   * Given `approvalRequired === false`
   * Then `leagueRepository.addLeagueMembers` called with role `member`.
5. **ApproveMembershipRequestUseCase: request not found**
   * Given pending requests list without `requestId`
   * Then throws `Request not found`.
6. **ApproveMembershipRequestUseCase: happy path adds member then removes request**
   * Given request exists
   * Then `addLeagueMembers` called before `removePendingRequest`.
7. **LeaveLeagueUseCase: delegates to repository**
   * Given repository mock
   * Then `removeLeagueMember` is called once with inputs.

Note: these use cases currently ignore injected `eventPublisher` in several places; tests should either (a) enforce event publication (drive implementation), or (b) remove the unused port.

---

## Medium-priority gaps (P1)

### 4) “Contract tests” that don’t test behavior (replace or move)

These tests validate TypeScript shapes and mocked method existence, but do not protect business behavior:
* [`core/ports/media/MediaResolverPort.test.ts`](core/ports/media/MediaResolverPort.test.ts:1)
* [`core/ports/media/MediaResolverPort.comprehensive.test.ts`](core/ports/media/MediaResolverPort.comprehensive.test.ts:1)
* [`core/notifications/domain/repositories/NotificationRepository.test.ts`](core/notifications/domain/repositories/NotificationRepository.test.ts:1)
* [`core/notifications/application/ports/NotificationService.test.ts`](core/notifications/application/ports/NotificationService.test.ts:1)

Recommended action:
* Either delete these (if they add noise), or replace with **behavior tests of the code that consumes the port**.
* If you want explicit “contract tests”, keep them in a dedicated layer and ensure they test the *adapter implementation* (but that would violate the current constraint, so keep them out of this scope).

### 5) Racing and Notifications include “imports-only” tests

Several tests are effectively “module loads” checks (no business assertions). Example patterns show up in:
* [`core/notifications/domain/entities/Notification.test.ts`](core/notifications/domain/entities/Notification.test.ts:1)
* [`core/notifications/domain/entities/NotificationPreference.test.ts`](core/notifications/domain/entities/NotificationPreference.test.ts:1)
* many files under [`core/racing/domain/entities/`](core/racing/domain/entities:1)

Replace with invariant-focused tests:
* Given invalid props (empty IDs, invalid status transitions)
* When creating or transitioning state
* Then throws domain error (or returns `Result.err`) with specific code/kind.

### 6) Racing use-cases with no tests (spot list)

From a quick scan of [`core/racing/application/use-cases/`](core/racing/application/use-cases:1), some `.ts` appear without matching `.test.ts` siblings:
* [`core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase.ts`](core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase.ts:1)
* [`core/racing/application/use-cases/GetRaceProtestsUseCase.ts`](core/racing/application/use-cases/GetRaceProtestsUseCase.ts:1)
* [`core/racing/application/use-cases/GetRaceRegistrationsUseCase.ts`](core/racing/application/use-cases/GetRaceRegistrationsUseCase.ts:1) (appears tested, confirm)
* [`core/racing/application/use-cases/GetSponsorsUseCase.ts`](core/racing/application/use-cases/GetSponsorsUseCase.ts:1) (no test file listed)
* [`core/racing/application/use-cases/GetLeagueAdminUseCase.ts`](core/racing/application/use-cases/GetLeagueAdminUseCase.ts:1)
* [`core/racing/application/use-cases/UnpublishLeagueSeasonScheduleUseCase.ts`](core/racing/application/use-cases/UnpublishLeagueSeasonScheduleUseCase.ts:1)
* [`core/racing/application/use-cases/SubmitProtestDefenseUseCase.test.ts`](core/racing/application/use-cases/SubmitProtestDefenseUseCase.test.ts:1) exists, confirm content quality

Suggested scenarios depend on each use case’s branching, but the common minimum is:
* repository error → `Result.err` with code
* happy path → updates correct aggregates + publishes domain event if applicable
* permission/invariant violations → domain error codes

---

## Lower-priority gaps (P2)

### 7) Coverage consistency and determinism

Patterns to standardize across modules:
* Tests that touch time should freeze time (`vi.setSystemTime`) rather than relying on `Date.now()`.
* Use cases should return `Result` consistently (some throw, some return `Result`). Testing should expose this inconsistency and drive convergence.

---

## Proposed execution plan (next step: implement tests)

1. Add missing unit tests for `rating` use-cases and `rating/domain/Rating`.
2. Add unit tests for `GetDashboardUseCase` focusing on filtering/sorting, timeout, and publish failure behavior.
3. Add unit tests for `leagues` membership flow (`JoinLeagueUseCase`, `ApproveMembershipRequestUseCase`, `LeaveLeagueUseCase`).
4. Replace “imports-only” tests with invariant tests in `notifications` entities, starting with the most used aggregates.
5. Audit remaining racing use-cases without tests and add the top 5 based on branching and business impact.

