From 15e480ee9aeb922a1c8f0e3d01e119a8f9b1cbe0 Mon Sep 17 00:00:00 2001 From: flfeders Date: Wed, 7 Oct 2026 19:02:23 +0200 Subject: [PATCH] fix(build): isolate time tests by Docker build context --- backend/tests/timeV2.test.ts | 31 ++++++++++------------ frontend/Dockerfile | 2 +- frontend/tests/time-v2.test.cjs | 46 +++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 18 deletions(-) create mode 100644 frontend/tests/time-v2.test.cjs diff --git a/backend/tests/timeV2.test.ts b/backend/tests/timeV2.test.ts index 45f15051..2a835821 100644 --- a/backend/tests/timeV2.test.ts +++ b/backend/tests/timeV2.test.ts @@ -10,8 +10,6 @@ import { TimeService, authorize } from '../src/modules/time-v2/service'; import { TimeMigration, previewLegacy } from '../src/modules/time-v2/migration'; import { legacySpans, installTimeCompatibility, legacyEvent } from '../src/modules/time-v2/compatibility'; import timeV2Routes from '../src/routes/staff/time-v2'; -import { timeSpanInput } from '../../frontend/utils/timeSpanInput'; -import { staffTimeRows, workingMinutes } from '../../frontend/utils/staffTimeSpans'; const at = (day: string, time: string) => `${day}T${time}:00Z`; const work = (start = '2026-10-01T06:00:00Z', end: string | null = '2026-10-01T14:30:00Z') => normalizeSpan({ type: 'work', started_at: start, ended_at: end, breaks: end ? [{ started_at: '2026-10-01T10:00:00Z', ended_at: '2026-10-01T10:30:00Z' }] : [] }); @@ -189,19 +187,18 @@ test('HTTP routes: validation, permissions, compatibility and migration conflict await f.close(); } }); -test('editing repeated local times preserves the original instant and rejects ambiguous new inputs', () => { - assert.equal(timeSpanInput('2026-10-25T02:15', 'Europe/Berlin', '2026-10-25T01:15:00.000Z'), '2026-10-25T01:15:00.000Z'); - assert.equal(timeSpanInput('2026-10-25T02:15', 'Europe/Berlin', '2026-10-25T00:15:00.000Z'), '2026-10-25T00:15:00.000Z'); - assert.throws(() => timeSpanInput('2026-10-25T02:15', 'Europe/Berlin'), /zweimal/); - assert.throws(() => timeSpanInput('2026-03-29T02:15', 'Europe/Berlin'), /existiert nicht/); - assert.equal(timeSpanInput('2026-10-25T01:15', 'UTC'), '2026-10-25T01:15:00.000Z'); -}); - -test('dashboard keeps a paused attendance active and freezes its net working duration', () => { - const s = work(undefined, null); s.id = randomUUID(); s.user_id = randomUUID(); - s.breaks = [{started_at:'2026-10-01T10:00:00Z',ended_at:null}]; - const entries = staffTimeRows(legacySpans([s]),s.user_id,Date.parse('2026-10-01T11:00:00Z')); - assert.equal(entries.length,1); assert.equal(entries[0].type,'work'); - assert.equal((entries[0] as any).paused,true);assert.equal(entries[0].stopped_at,null); - assert.equal(workingMinutes(entries[0],Date.parse('2026-10-01T12:00:00Z')),240); +test('legacy compatibility keeps paused attendance on both work and pause segments', () => { + const s = work(undefined, null); s.id = randomUUID(); s.user_id = randomUUID(); + s.breaks = [{ started_at: '2026-10-01T10:00:00Z', ended_at: null }]; + const segments = legacySpans([s]); + assert.equal(segments.length, 2); + assert.equal(segments[0].type, 'work'); + assert.equal(segments[0].endedAt, s.breaks[0].started_at); + assert.equal(segments[1].type, 'pause'); + assert.equal(segments[1].endedAt, null); + for (const segment of segments) { + assert.equal(segment.model, 'spans-v2'); + assert.equal(segment.id, s.id); + assert.deepEqual(segment.attendance, s); + } }); diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 4165720e..08e56c3c 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -13,7 +13,7 @@ ARG FEDEO_VERSION=0.0.0-dev ARG FEDEO_REVISION=unknown ENV FEDEO_VERSION=$FEDEO_VERSION ENV FEDEO_REVISION=$FEDEO_REVISION -RUN npm run build +RUN node --test tests/time-v2.test.cjs && npm run build # --- Stage 2: Runtime --- FROM node:22-alpine AS runner diff --git a/frontend/tests/time-v2.test.cjs b/frontend/tests/time-v2.test.cjs new file mode 100644 index 00000000..290567ad --- /dev/null +++ b/frontend/tests/time-v2.test.cjs @@ -0,0 +1,46 @@ +const assert = require('node:assert/strict') +const test = require('node:test') +const fs = require('node:fs') +const vm = require('node:vm') +const ts = require('typescript') + +function loadUtility(name) { + const source = fs.readFileSync(`${__dirname}/../utils/${name}.ts`, 'utf8') + const { outputText } = ts.transpileModule(source, { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2021 }, + }) + const exports = {} + vm.runInNewContext(outputText, { exports }) + return exports +} + +const { timeSpanInput } = loadUtility('timeSpanInput') +const { staffTimeRows, workingMinutes } = loadUtility('staffTimeSpans') + +test('editing repeated local times preserves the original instant and rejects ambiguous new inputs', () => { + assert.equal(timeSpanInput('2026-10-25T02:15', 'Europe/Berlin', '2026-10-25T01:15:00.000Z'), '2026-10-25T01:15:00.000Z'); + assert.equal(timeSpanInput('2026-10-25T02:15', 'Europe/Berlin', '2026-10-25T00:15:00.000Z'), '2026-10-25T00:15:00.000Z'); + assert.throws(() => timeSpanInput('2026-10-25T02:15', 'Europe/Berlin'), /zweimal/); + assert.throws(() => timeSpanInput('2026-03-29T02:15', 'Europe/Berlin'), /existiert nicht/); + assert.equal(timeSpanInput('2026-10-25T01:15', 'UTC'), '2026-10-25T01:15:00.000Z'); +}); + +test('dashboard keeps a paused attendance active and freezes its net working duration', () => { + const attendance = { + id: 'attendance-1', user_id: 'user-1', type: 'work', status: 'factual', + started_at: '2026-10-01T06:00:00Z', ended_at: null, + breaks: [{ started_at: '2026-10-01T10:00:00Z', ended_at: null }], + } + const base = { id: attendance.id, model: 'spans-v2', attendance } + const segments = [ + { ...base, type: 'work', startedAt: attendance.started_at, endedAt: attendance.breaks[0].started_at }, + { ...base, type: 'pause', startedAt: attendance.breaks[0].started_at, endedAt: null }, + ] + const entries = staffTimeRows(segments, attendance.user_id, Date.parse('2026-10-01T11:00:00Z')) + assert.equal(entries.length, 1) + assert.equal(entries[0].type, 'work') + assert.equal(entries[0].paused, true) + assert.equal(entries[0].stopped_at, null) + assert.equal(entries[0].duration_minutes, 240) + assert.equal(workingMinutes(entries[0], Date.parse('2026-10-01T12:00:00Z')), 240) +})