test(api): Reports + admin export — fill gaps + lifecycle test

Drives the report/export parts of src/routes/admin.ts to 100%
stmts/branch/funcs/lines. Adds one lifecycle test plus gap tests for the
previously-uncovered branches; no production behavior changed.

Tests added (apps/api/test):
- report.test.ts: invalid insole_type / non-numeric activity_id → 400;
  activity filter narrows; null insole_type bucketed "Onbekend"; null
  duration counts as 0 worked; export 400 on missing/inverted range.
  Migrated the report + export describes to resetDb(beforeEach) so exact
  totals are order-independent (removed the activity_id isolation
  workaround in the all-users export test).
- integration/report-aggregation.test.ts: lifecycle — 2 workers × 2
  activities × 2 types → exact totals + every by_worker/by_activity/
  by_type row (values and worked-desc order) → user_id filter narrows.

Defensive annotations in admin.ts report loop (behavior unchanged):
- pausedSeconds/pairCount ?? 0 — NOT NULL columns, DB-impossible nulls.
- user_name/activity_name ?? 'Onbekend' — unreachable; userId FK cascades
  on delete and activityId FK is enforced (libsql enforces FKs), so every
  session always joins to a real user and activity.

No bugs found; the code was already correct.
This commit is contained in:
Bas van Rossem
2026-07-22 13:03:04 +02:00
parent bdf65ae685
commit 31d13a8381
4 changed files with 325 additions and 6 deletions

View File

@@ -152,6 +152,7 @@ adminRoutes.get('/api/admin/report', async (c) => {
for (const r of rows) {
const worked = r.session.durationSeconds ?? 0;
/* c8 ignore next 2 -- pausedSeconds & pairCount are NOT NULL (schema defaults); the ?? guards DB-impossible nulls */
const paused = r.session.pausedSeconds ?? 0;
const pairs = r.session.pairCount ?? 0;
@@ -163,6 +164,7 @@ adminRoutes.get('/api/admin/report', async (c) => {
const uid = r.session.userId;
const w = workers.get(uid) ?? {
user_id: uid,
/* c8 ignore next -- userId FK cascades on user delete, so a session's user always joins */
user_name: r.userName ?? 'Onbekend',
worked_seconds: 0,
paused_seconds: 0,
@@ -178,6 +180,7 @@ adminRoutes.get('/api/admin/report', async (c) => {
const aid = r.session.activityId;
const a = acts.get(aid) ?? {
activity_id: aid,
/* c8 ignore next -- activityId FK is enforced, so a session's activity always joins */
activity_name: r.activityName ?? 'Onbekend',
worked_seconds: 0,
paused_seconds: 0,