feat: add server authoritative travel flow

This commit is contained in:
Bastian Wagner
2026-08-18 20:30:09 +02:00
parent e9b111daf8
commit d06f05047b
12 changed files with 904 additions and 3 deletions

View File

@@ -0,0 +1,105 @@
# Task 5 Report: Server-authoritative travel
## Outcome
Implemented the NestJS travel domain and public endpoints:
- `POST /api/travel`
- `GET /api/travel/current`
- injected deterministic/system clock abstraction
- exact `StartTravelDto` input validation
- stable idle, travelling, completed, and domain-error response shapes
- `TravelModule` registration 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:
```powershell
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:
```text
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:
```powershell
npm test --workspace=@ashen-realms/api -- travel.service.spec.ts travel.controller.spec.ts --runInBand
```
```text
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:
```powershell
npm test --workspace=@ashen-realms/api -- --runInBand
```
```text
Test Suites: 7 passed, 7 total
Tests: 18 passed, 18 total
Time: 15.87 s
```
API E2E:
```powershell
npm run test:e2e --workspace=@ashen-realms/api -- --runInBand
```
```text
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Time: 9.406 s
```
API build:
```powershell
npm run build:api
```
Result: exit code 0 (`nest build`).
Formatting and lint:
```powershell
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 `TS1272` for a decorated `Clock` parameter under `isolatedModules`; importing `Clock` as a type fixed the root cause, after which tests and build were rerun.
- Registering the database-backed `TravelModule` exposed that the existing health E2E test replaced `DatabaseModule` and `CharactersModule` but not travel. The harness now replaces `TravelModule` as 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 === now` as 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.