test(api): Work-sessions — fill gaps + lifecycle test

Drives src/routes/sessions.ts and src/lib/work-session.ts to 100%
stmts/branch/funcs/lines (branch was 84.12% / 83.33%).

- sessions.test.ts: added error/edge branch tests — malformed-JSON start
  (400), non-numeric id on pause/resume/stop/discard (404), pause/resume of a
  no-longer-active session (409), discard missing (404) + already-closed (409),
  stop missing (404), and discard ownership scoping (user B → 404).
- work-session.test.ts: mapper unit tests — null insoleType, nullish
  pausedSeconds → 0, null durationSeconds/notes, full-field shape with
  activity/user opts, and opts defaulting to undefined.
- test/integration/worker-session-lifecycle.test.ts: the anchor lifecycle test
  (start→pause→resume→stop→history→export).

No bugs found — the code was correct. The two remaining uncovered branches were
the defensive `row.pausedSeconds ?? 0` guards in resume/stop; pausedSeconds is
NOT NULL DEFAULT 0 in the schema, so the null branch is unreachable via any
route. Annotated both with `/* c8 ignore next -- reason */` (comment only, no
behavior change).
This commit is contained in:
Bas van Rossem
2026-07-22 12:14:32 +02:00
parent af7251398c
commit 9ecbf952d3
5 changed files with 319 additions and 0 deletions

View File

@@ -37,3 +37,71 @@ describe('toWorkSession paused fields', () => {
expect(result.paused_at).toBe(pausedAt.toISOString());
});
});
describe('toWorkSession field mapping', () => {
it('maps a null insoleType to null', () => {
const result = toWorkSession(baseRow({ insoleType: null }));
expect(result.insole_type).toBeNull();
});
it('coalesces a nullish pausedSeconds to 0 (defensive against a null column value)', () => {
const result = toWorkSession(baseRow({ pausedSeconds: null as unknown as number }));
expect(result.paused_seconds).toBe(0);
});
it('coalesces a nullish durationSeconds to null and a nullish notes to null', () => {
const result = toWorkSession(baseRow({ durationSeconds: null, notes: null }));
expect(result.duration_seconds).toBeNull();
expect(result.notes).toBeNull();
});
it('shapes every field, threading activity/user opts through', () => {
const start = new Date('2026-06-17T08:00:00.000Z');
const end = new Date('2026-06-17T09:30:00.000Z');
const created = new Date('2026-06-17T07:59:00.000Z');
const result = toWorkSession(
baseRow({
id: 42,
userId: 'user-42',
activityId: 7,
insoleType: 'Berk',
pairCount: 4,
startTime: start,
endTime: end,
durationSeconds: 5000,
pausedSeconds: 400,
status: 'completed',
source: 'manual',
notes: 'handmatig',
createdAt: created,
}),
{ activityName: 'Frezen', userName: 'Jan', userEmail: 'jan@example.com' },
);
expect(result).toEqual({
id: 42,
user_id: 'user-42',
activity_id: 7,
activity_name: 'Frezen',
user_name: 'Jan',
user_email: 'jan@example.com',
insole_type: 'Berk',
pair_count: 4,
start_time: start.toISOString(),
end_time: end.toISOString(),
duration_seconds: 5000,
paused_seconds: 400,
paused_at: null,
status: 'completed',
source: 'manual',
notes: 'handmatig',
created_at: created.toISOString(),
});
});
it('defaults activity/user opts to undefined when omitted', () => {
const result = toWorkSession(baseRow());
expect(result.activity_name).toBeUndefined();
expect(result.user_name).toBeUndefined();
expect(result.user_email).toBeUndefined();
});
});