12 KiB
Testing gaps in core (unit tests only, no infra/adapters)
Scope / rules (agreed)
- In scope: code under
core/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/).
How gaps were identified
- Inventory of application and domain units was built from file structure under
core/. - Existing tests were located via
describe(occurrences in*.test.tsand mapped to corresponding production units. - 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.tscore/rating/application/use-cases/CalculateTeamContributionUseCase.tscore/rating/application/use-cases/GetRatingLeaderboardUseCase.tscore/rating/application/use-cases/SaveRatingUseCase.tscore/rating/domain/Rating.ts
Proposed unit tests (Given/When/Then):
- CalculateRatingUseCase: driver missing
- Given
driverRepository.findByIdreturnsnull - When executing with
{ driverId, raceId } - Then returns
Result.errwith messageDriver not foundand does not callratingRepository.save.
- Given
- CalculateRatingUseCase: race missing
- Given driver exists,
raceRepository.findByIdreturnsnull - When execute
- Then returns
Result.errwith messageRace not found.
- Given driver exists,
- CalculateRatingUseCase: no results
- Given driver & race exist,
resultRepository.findByRaceIdreturns[] - When execute
- Then returns
Result.errwith messageNo results found for race.
- Given driver & race exist,
- CalculateRatingUseCase: driver not present in results
- Given results array without matching
driverId - When execute
- Then returns
Result.errwith messageDriver not found in race results.
- Given results array without matching
- CalculateRatingUseCase: publishes event after save
- Given all repositories return happy-path objects
- When execute
- Then
ratingRepository.saveis called once beforeeventPublisher.publish.
- CalculateRatingUseCase: component boundaries
- Given a result with
incidents = 0 - When execute
- Then
components.cleanDriving === 100. - Given
incidents >= 5 - Then
components.cleanDriving === 20.
- Given a result with
- CalculateRatingUseCase: time-dependent output
- Given frozen time (use
vi.setSystemTime) - When execute
- Then emitted rating has deterministic
timestamp.
- Given frozen time (use
- CalculateTeamContributionUseCase: creates rating when missing
- Given
ratingRepository.findByDriverAndRacereturnsnull - When execute
- Then
ratingRepository.saveis called with a rating whosecomponents.teamContributionmatches calculation.
- Given
- CalculateTeamContributionUseCase: updates existing rating
- Given existing rating with components set
- When execute
- Then only
components.teamContributionis changed and other fields preserved.
- 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.
- SaveRatingUseCase: repository error wraps correctly
- Given
ratingRepository.savethrows - 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:
Why high risk: timeouts, parallelization, filtering/sorting, and “log but don’t fail” event publishing.
Proposed unit tests (Given/When/Then):
- Validation of driverId
- Given
driverIdis''or whitespace - When execute
- Then throws
ValidationError(or the module’s equivalent) and does not hit repositories.
- Given
- Driver not found
- Given
driverRepository.findDriverByIdreturnsnull - When execute
- Then throws
DriverNotFoundError.
- Given
- Filters invalid races
- Given
getUpcomingRacesreturns races missingtrackNameor with pastscheduledDate - When execute
- Then
upcomingRacesin DTO excludes them.
- Given
- 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.
- Activity is sorted newest-first
- Given activities with different timestamps
- When execute
- Then DTO is sorted desc by timestamp.
- Repository failures are logged and rethrown
- Given one of the repositories rejects
- When execute
- Then logger.error called and error is rethrown.
- Event publishing failure is swallowed
- Given
eventPublisher.publishDashboardAccessedthrows - When execute
- Then use case still returns DTO and logger.error was called.
- Given
- Timeout behavior (if retained)
- Given
raceRepository.getUpcomingRacesnever resolves - When using fake timers and advancing by TIMEOUT
- Then
upcomingRacesbecomes[]and use case completes.
- Given
Ports to mock: all repositories, publisher, and Logger.
3) leagues module has multiple untested use-cases (time-dependent logic)
Targets likely missing tests:
core/leagues/application/use-cases/JoinLeagueUseCase.tscore/leagues/application/use-cases/LeaveLeagueUseCase.tscore/leagues/application/use-cases/ApproveMembershipRequestUseCase.ts- plus others without
*.test.tssiblings incore/leagues/application/use-cases/
Proposed unit tests (Given/When/Then):
- JoinLeagueUseCase: league missing
- Given
leagueRepository.findByIdreturnsnull - When execute
- Then throws
League not found.
- Given
- JoinLeagueUseCase: driver missing
- Given league exists,
driverRepository.findDriverByIdreturnsnull - Then throws
Driver not found.
- Given league exists,
- JoinLeagueUseCase: approvalRequired path uses pending requests
- Given
league.approvalRequired === true - When execute
- Then
leagueRepository.addPendingRequestscalled with a request containing frozenDate.now()andnew Date().
- Given
- JoinLeagueUseCase: no-approval path adds member
- Given
approvalRequired === false - Then
leagueRepository.addLeagueMemberscalled with rolemember.
- Given
- ApproveMembershipRequestUseCase: request not found
- Given pending requests list without
requestId - Then throws
Request not found.
- Given pending requests list without
- ApproveMembershipRequestUseCase: happy path adds member then removes request
- Given request exists
- Then
addLeagueMemberscalled beforeremovePendingRequest.
- LeaveLeagueUseCase: delegates to repository
- Given repository mock
- Then
removeLeagueMemberis 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.tscore/ports/media/MediaResolverPort.comprehensive.test.tscore/notifications/domain/repositories/NotificationRepository.test.tscore/notifications/application/ports/NotificationService.test.ts
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.tscore/notifications/domain/entities/NotificationPreference.test.ts- many files under
core/racing/domain/entities/
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/, some .ts appear without matching .test.ts siblings:
core/racing/application/use-cases/GetAllLeaguesWithCapacityUseCase.tscore/racing/application/use-cases/GetRaceProtestsUseCase.tscore/racing/application/use-cases/GetRaceRegistrationsUseCase.ts(appears tested, confirm)core/racing/application/use-cases/GetSponsorsUseCase.ts(no test file listed)core/racing/application/use-cases/GetLeagueAdminUseCase.tscore/racing/application/use-cases/UnpublishLeagueSeasonScheduleUseCase.tscore/racing/application/use-cases/SubmitProtestDefenseUseCase.test.tsexists, confirm content quality
Suggested scenarios depend on each use case’s branching, but the common minimum is:
- repository error →
Result.errwith 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 onDate.now(). - Use cases should return
Resultconsistently (some throw, some returnResult). Testing should expose this inconsistency and drive convergence.
Proposed execution plan (next step: implement tests)
- Add missing unit tests for
ratinguse-cases andrating/domain/Rating. - Add unit tests for
GetDashboardUseCasefocusing on filtering/sorting, timeout, and publish failure behavior. - Add unit tests for
leaguesmembership flow (JoinLeagueUseCase,ApproveMembershipRequestUseCase,LeaveLeagueUseCase). - Replace “imports-only” tests with invariant tests in
notificationsentities, starting with the most used aggregates. - Audit remaining racing use-cases without tests and add the top 5 based on branching and business impact.