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:
@@ -441,3 +441,146 @@ describe('session reads', () => {
|
||||
expect(body[0].status).toBe('active');
|
||||
});
|
||||
});
|
||||
|
||||
describe('session edge and error branches', () => {
|
||||
beforeEach(resetDb);
|
||||
|
||||
it('400s start when the body is not valid JSON', async () => {
|
||||
const app = createApp();
|
||||
const token = await authToken(app, 'sess-badjson@example.com');
|
||||
|
||||
const res = await app.request('/api/sessions/start', {
|
||||
method: 'POST',
|
||||
headers: bearer(token),
|
||||
body: '{ not json',
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('404s pause/resume/stop/discard when the id is not numeric', async () => {
|
||||
const app = createApp();
|
||||
const token = await authToken(app, 'sess-nan-id@example.com');
|
||||
|
||||
for (const action of ['pause', 'resume', 'stop', 'discard']) {
|
||||
const res = await app.request(`/api/sessions/not-a-number/${action}`, {
|
||||
method: 'POST',
|
||||
headers: bearer(token),
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
}
|
||||
});
|
||||
|
||||
it('409s pausing a session that is no longer active', async () => {
|
||||
const app = createApp();
|
||||
const token = await authToken(app, 'sess-pause-closed@example.com');
|
||||
const activityId = await seedActivity('Frezen');
|
||||
|
||||
const startRes = await app.request('/api/sessions/start', {
|
||||
method: 'POST',
|
||||
headers: bearer(token),
|
||||
body: JSON.stringify({ activity_id: activityId, insole_type: 'Kurk', pair_count: 2 }),
|
||||
});
|
||||
const started = await startRes.json();
|
||||
await app.request(`/api/sessions/${started.id}/stop`, {
|
||||
method: 'POST',
|
||||
headers: bearer(token),
|
||||
});
|
||||
|
||||
const res = await app.request(`/api/sessions/${started.id}/pause`, {
|
||||
method: 'POST',
|
||||
headers: bearer(token),
|
||||
});
|
||||
expect(res.status).toBe(409);
|
||||
});
|
||||
|
||||
it('409s resuming a session that is no longer active', async () => {
|
||||
const app = createApp();
|
||||
const token = await authToken(app, 'sess-resume-closed@example.com');
|
||||
const activityId = await seedActivity('Slijpen');
|
||||
|
||||
const startRes = await app.request('/api/sessions/start', {
|
||||
method: 'POST',
|
||||
headers: bearer(token),
|
||||
body: JSON.stringify({ activity_id: activityId, insole_type: 'Kurk', pair_count: 2 }),
|
||||
});
|
||||
const started = await startRes.json();
|
||||
await app.request(`/api/sessions/${started.id}/stop`, {
|
||||
method: 'POST',
|
||||
headers: bearer(token),
|
||||
});
|
||||
|
||||
const res = await app.request(`/api/sessions/${started.id}/resume`, {
|
||||
method: 'POST',
|
||||
headers: bearer(token),
|
||||
});
|
||||
expect(res.status).toBe(409);
|
||||
});
|
||||
|
||||
it('404s discarding a missing session', async () => {
|
||||
const app = createApp();
|
||||
const token = await authToken(app, 'sess-discard-missing@example.com');
|
||||
|
||||
const res = await app.request('/api/sessions/999999/discard', {
|
||||
method: 'POST',
|
||||
headers: bearer(token),
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
it('409s discarding an already-closed session', async () => {
|
||||
const app = createApp();
|
||||
const token = await authToken(app, 'sess-discard-closed@example.com');
|
||||
const activityId = await seedActivity('Bekleden');
|
||||
|
||||
const startRes = await app.request('/api/sessions/start', {
|
||||
method: 'POST',
|
||||
headers: bearer(token),
|
||||
body: JSON.stringify({ activity_id: activityId, insole_type: 'Kurk', pair_count: 2 }),
|
||||
});
|
||||
const started = await startRes.json();
|
||||
await app.request(`/api/sessions/${started.id}/stop`, {
|
||||
method: 'POST',
|
||||
headers: bearer(token),
|
||||
});
|
||||
|
||||
const res = await app.request(`/api/sessions/${started.id}/discard`, {
|
||||
method: 'POST',
|
||||
headers: bearer(token),
|
||||
});
|
||||
expect(res.status).toBe(409);
|
||||
});
|
||||
|
||||
it('404s stopping a missing session', async () => {
|
||||
const app = createApp();
|
||||
const token = await authToken(app, 'sess-stop-missing@example.com');
|
||||
|
||||
const res = await app.request('/api/sessions/999999/stop', {
|
||||
method: 'POST',
|
||||
headers: bearer(token),
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
it("does not let user B discard user A's session (ownership scoping → 404)", async () => {
|
||||
const app = createApp();
|
||||
const tokenA = await authToken(app, 'sess-discard-ownerA@example.com');
|
||||
const tokenB = await authToken(app, 'sess-discard-ownerB@example.com');
|
||||
const activityId = await seedActivity('Printen');
|
||||
|
||||
const startRes = await app.request('/api/sessions/start', {
|
||||
method: 'POST',
|
||||
headers: bearer(tokenA),
|
||||
body: JSON.stringify({ activity_id: activityId, insole_type: '3D', pair_count: 2 }),
|
||||
});
|
||||
const started = await startRes.json();
|
||||
|
||||
const res = await app.request(`/api/sessions/${started.id}/discard`, {
|
||||
method: 'POST',
|
||||
headers: bearer(tokenB),
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
|
||||
const [row] = await db.select().from(workSessions).where(eq(workSessions.id, started.id));
|
||||
expect(row.status).toBe('active');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user