import { test } from 'node:test'; import assert from 'node:assert/strict'; import type { ClockEvent } from './clock'; import { attendanceSummary, employeeIdFromSlug, employeeSlug, periodDays, sessionsOf, shiftPeriod, weekStart } from './workforce'; let n=0; const ev=(direction:'in'|'out',scanned_at:string):ClockEvent=>({id:`event${String(++n).padStart(10,'0')}`,employee:'mechanics:abcdefghijklmno', employee_no:'MCW001',employee_name:'Worker',site:'',direction,scanned_at,actor:'',previous_event:'',session_minutes:0,has_location:false}); test('slug round-trips and rejects anything else',()=>{ assert.equal(employeeIdFromSlug(employeeSlug('operators:abcdefghijklmno')),'operators:abcdefghijklmno'); assert.equal(employeeIdFromSlug('assets-abcdefghijklmno'),null); assert.equal(employeeIdFromSlug('operators-short'),null); }); test('weeks start on Monday',()=>{ assert.equal(weekStart('2026-10-22'),'2026-10-19'); // Thursday assert.equal(weekStart('2026-10-19'),'2026-10-19'); assert.equal(weekStart('2026-10-25'),'2026-10-19'); // Sunday }); test('an open IN today counts to now; an open IN on an earlier day counts nothing',()=>{ const now='2026-10-22T10:00:00Z'; const s=sessionsOf([ev('in','2026-10-21T05:00:00Z'),ev('in','2026-10-22T06:00:00Z')],now); assert.deepEqual(s.map(x=>x.minutes),[0,240]); }); test('summary adds lunch-split days and reports a missed clock-out',()=>{ const now='2026-10-22T14:00:00Z'; const events=[ ev('in','2026-10-20T05:00:00Z'), // Tue, never clocked out ev('in','2026-10-21T05:00:00Z'),ev('out','2026-10-21T10:00:00Z'),ev('in','2026-10-21T10:30:00Z'),ev('out','2026-10-21T14:30:00Z'), ev('in','2026-10-22T05:00:00Z'),ev('out','2026-10-22T13:00:00Z'), ev('in','2026-09-30T05:00:00Z'),ev('out','2026-09-30T06:00:00Z'), // last month ]; const s=attendanceSummary(events,now); assert.equal(s.status,'out'); assert.equal(s.todayMinutes,480); assert.equal(s.weekMinutes,480+540); assert.equal(s.monthMinutes,480+540); assert.equal(s.daysThisMonth,3); assert.deepEqual(s.missed.map(e=>e.scanned_at),['2026-10-20T05:00:00Z']); assert.equal(s.days[0].day,'2026-10-22'); }); test('nobody scanned today is away',()=>{ assert.equal(attendanceSummary([],'2026-10-22T10:00:00Z').status,'away'); assert.equal(attendanceSummary([ev('in','2026-10-21T05:00:00Z')],'2026-10-22T10:00:00Z').status,'away'); }); test('period days: a day, a Monday-first week, a whole month',()=>{ assert.deepEqual(periodDays('day','2026-10-22'),['2026-10-22']); const w=periodDays('week','2026-10-22');assert.equal(w[0],'2026-10-19');assert.equal(w[6],'2026-10-25'); assert.equal(periodDays('month','2026-02-10').length,28);assert.equal(periodDays('month','2026-10-22').at(-1),'2026-10-31'); }); test('shifting a period crosses month and year boundaries',()=>{ assert.equal(shiftPeriod('day','2026-10-31',1),'2026-11-01'); assert.equal(shiftPeriod('week','2026-10-22',-1),'2026-10-12'); assert.equal(shiftPeriod('month','2026-12-15',1),'2027-01-01'); assert.equal(shiftPeriod('month','2026-01-31',-1),'2025-12-01'); });