mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
fix(server): plan 3c close-out — non-finite ids bound as NULL, trip id parsed once at every places/days/ws gate
Platform: NulSafeSqlitePlatform now renders a bound NaN as NULL and +/-Infinity as 9e999/-9e999, matching better-sqlite3's own legacy binding (rule 22 extension) — the program-wide fail-safe for any caller that still passes an unguarded Number(untrusted) into a converted statement. PlacesService.verifyTripAccess and RealtimeGateway.handleJoin stop doing that: verifyTripAccess parses once with toRowId and threads the parsed id through create/import/bulk/rating/export.gpx (deleting the seven toRowId(tripId) ?? -1 sites), and handleJoin gets a Number.isFinite guard. DaysService gets the same treatment across list/create/getDay/reorder/ insert/resyncAccommodationDays, closing the gap where a hex-spelled trip id could renumber a real trip's days while the raw-bind inverted-accommodation guard silently matched nothing. DatabaseService.getTripTitle is deleted; PlacesService injects TripsRepository directly and calls getTitle itself. Template/cheap-item cleanup: DayAssignmentsRepository.listForTimeSort uses $castTo instead of an unknown bridge; the duplicated TagRow interface in Places.repository.ts is now imported from Tags.repository.ts; AssignmentWithPlaceRow extends the guarded DayAssignmentRow instead of restating its columns; PlaceShadowPicksRepository.totals drops its unreachable ??-fallback branch; Places.repository.ts's updatePlace types the NOT NULL name column as string; trip-members.service.ts's transferOwnership threads one parsed trip id through all three writes and corrects two stale "raw-bind seam" docstrings that were actually Number() seams; a dead canAccessTrip mock is removed from reservations.e2e.test.ts. Tests: the 23 new no-explicit-any warnings in TRIP-SVC-069 are typed (lint back to 2416); HTTP and WS regression coverage for the non-finite and non-canonical id shapes across places/days routes, mutation-proved; TRIP-JOIN-005 asserts the exact concurrent-join settlement instead of >= 1; a new tests/unit/db/transaction-yield.test.ts pins rule 24's measured transaction-yield/dirty-read/nested-BEGIN behaviour.
This commit is contained in:
@@ -238,6 +238,36 @@ describe('WS rooms', () => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Plan 3c Task 9 fix wave (A-H1, live regression): before the
|
||||
* `Number.isFinite` guard on `realtime.gateway.ts::handleJoin`, a
|
||||
* non-numeric `tripId` became `NaN`, which used to reach
|
||||
* `TripsRepository.findAccessible`'s raw bind as the unquoted bareword
|
||||
* `NaN` and throw `SqliteError: no such column: NaN` — the promise this
|
||||
* handler returns never resolved, so the client got NO reply at all
|
||||
* (confirmed on the compiled dual boot: 4 `[ERROR]` lines, HEAD only).
|
||||
* The platform's own `NaN` -> `NULL` rendering (`NulSafeSqlitePlatform`)
|
||||
* fixes the crash even without this guard; this test pins the WS-level
|
||||
* contract (the SAME `Access denied` frame WS-005 gets for a real trip
|
||||
* with no access), not just "does not throw".
|
||||
*/
|
||||
it('WS-005b — join with a non-numeric tripId receives the same error frame as no access (rule 22 / A-H1)', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const token = createEphemeralToken(user.id, 'ws')!;
|
||||
|
||||
const client = await connectWs(token);
|
||||
try {
|
||||
await client.next(); // welcome
|
||||
|
||||
client.send({ type: 'join', tripId: 'abc' });
|
||||
const msg = await client.next();
|
||||
expect(msg.type).toBe('error');
|
||||
expect(msg.message).toMatch(/access denied/i);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Plan 3c Task 0b (R9): `handleJoin`'s `this.db.canAccessTrip(...)` call
|
||||
* (`realtime.gateway.ts:173`) is now `TripsRepository.findAccessible`
|
||||
|
||||
Reference in New Issue
Block a user