4.4 KiB
Task 5 Report: Server-authoritative travel
Outcome
Implemented the NestJS travel domain and public endpoints:
POST /api/travelGET /api/travel/current- injected deterministic/system clock abstraction
- exact
StartTravelDtoinput validation - stable idle, travelling, completed, and domain-error response shapes
TravelModuleregistration in the application
Travel start and completion use DataSource.transaction. Both operations acquire a pessimistic write lock on the character row followed by the active travel row. Relations are deliberately not joined into either locking query, which keeps the queries compatible with TypeORM/PostgreSQL FOR UPDATE behavior. Due completion changes the travel status and character location through repositories owned by the same transaction; a failed second save rolls back both mutations.
No ambush evaluation or other later-slice logic was added. Existing untracked frontend images and the visual asset guide were preserved and excluded from the commit.
RED evidence
Command:
npm test --workspace=@ashen-realms/api -- travel.service.spec.ts travel.controller.spec.ts --runInBand
Initial result: exit code 1. Both suites failed to resolve the missing production modules:
Cannot find module './travel.service' from 'travel/travel.service.spec.ts'
Cannot find module './travel.controller' from 'travel/travel.controller.spec.ts'
Test Suites: 2 failed, 2 total
Tests: 0 total
The active-travel regression test was also mutation-checked. Temporarily removing the TRAVEL_ALREADY_ACTIVE guard produced the expected focused failure (Expected constructor: TravelDomainError; Received constructor: Object); restoring the guard returned the test to green.
GREEN evidence
Focused travel tests:
npm test --workspace=@ashen-realms/api -- travel.service.spec.ts travel.controller.spec.ts --runInBand
Test Suites: 2 passed, 2 total
Tests: 9 passed, 9 total
Time: 10.973 s
The service suite covers all five required behaviors plus active-travel rejection, public current-travel mapping, idempotent repeat completion, pessimistic-lock observation, and rollback when the second completion save fails. The controller suite drives a real Nest HTTP pipeline and verifies that server-owned timestamp/duration fields receive HTTP 400.
Full API suite:
npm test --workspace=@ashen-realms/api -- --runInBand
Test Suites: 7 passed, 7 total
Tests: 18 passed, 18 total
Time: 15.87 s
API E2E:
npm run test:e2e --workspace=@ashen-realms/api -- --runInBand
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Time: 9.406 s
API build:
npm run build:api
Result: exit code 0 (nest build).
Formatting and lint:
apps/api/node_modules/.bin/prettier.cmd --check <Task 5 files>
node_modules/.bin/eslint.cmd <Task 5 files>
Both commands exited 0. Prettier reported All matched files use Prettier code style!; ESLint reported no findings.
Integration notes and self-review
- The first build exposed TypeScript
TS1272for a decoratedClockparameter underisolatedModules; importingClockas a type fixed the root cause, after which tests and build were rerun. - Registering the database-backed
TravelModuleexposed that the existing health E2E test replacedDatabaseModuleandCharactersModulebut not travel. The harness now replacesTravelModuleas well, preserving the database-free health test. - Lock order is identical in start and completion, reducing deadlock risk.
- The character row serializes concurrent starts even when no active travel row exists yet; the partial unique database index remains the final invariant.
- The due comparison treats
arrivesAt === nowas due and never moves the character earlier. - Public responses contain only location summaries and travel timestamps/status, never entities, duration input, ambush probability, or persistence metadata.
Remaining concern
No live-PostgreSQL travel integration test was added because the repository's current E2E harness intentionally runs without a database; clean migration/seed/API database smoke coverage belongs to Task 10. The transaction code uses supported real EntityManager.getRepository, Repository.findOne lock options, findOneBy, create, and save behavior, and the stateful fake verifies transaction commit/rollback semantics rather than mock call counts.