import { test } from 'node:test'; import assert from 'node:assert/strict'; import { buildResultFilename, timeStamp, shortTag, slugify, KLARBILD_NAME, splitOldName, newNameFor } from '../src/lib/naming.ts'; const AT = new Date('2026-08-20T12:32:07Z'); // = 14:32:07 in Berlin (Sommerzeit) const ID1 = 'a3f9c1de-1111-2222-3333-444455556666'; const ID2 = '77b21e00-1111-2222-3333-444455556666'; test('Der gemeldete Fehler: zwei Badges am selben Tag überschreiben sich nicht mehr', () => { // Gleiches Motiv, gleiches Format, gleiche Sekunde — früher zweimal derselbe Name. const a = buildResultFilename('Lea Badge.png', 'the-frame', 'jpg', { at: AT, id: ID1 }); const b = buildResultFilename('Lea Badge.png', 'the-frame', 'jpg', { at: AT, id: ID2 }); assert.notEqual(a, b, 'zwei Bilder dürfen nie denselben Namen tragen'); assert.equal(a, '2026-08-20_143207_lea-badge_the-frame_a3f9.jpg'); assert.equal(b, '2026-08-20_143207_lea-badge_the-frame_77b2.jpg'); }); test('Namen sortieren lexikografisch = chronologisch', () => { const zeiten = [ new Date('2026-08-19T23:59:59Z'), new Date('2026-08-20T00:00:01Z'), new Date('2026-08-20T12:32:07Z'), new Date('2026-12-24T08:00:00Z'), // Winterzeit — anderer UTC-Versatz new Date('2027-01-01T00:00:00Z'), ]; const namen = zeiten.map((at, i) => buildResultFilename('foto', '30x40', 'jpg', { at, id: `${i}${i}${i}${i}0000-0000-0000-0000-000000000000` })); const sortiert = [...namen].sort(); assert.deepEqual(sortiert, namen, `Sortierung weicht ab:\n${sortiert.join('\n')}`); }); test('Zeitstempel steht in Berliner Zeit, nicht in UTC', () => { // 00:30 Uhr Berliner Sommerzeit ist 22:30 UTC am Vortag — der Name muss den // Tag zeigen, an dem Till das Bild gemacht hat. assert.equal(timeStamp(new Date('2026-08-19T22:30:00Z')), '2026-08-20_003000'); assert.equal(timeStamp(new Date('2026-12-24T07:05:09Z')), '2026-12-24_080509'); // Winterzeit: +1 }); test('Mitternacht wird als 00 geschrieben, nicht als 24', () => { assert.match(timeStamp(new Date('2026-08-19T22:00:00Z')), /^2026-08-20_00000/); }); test('Kürzel ist stabil — ein zweiter Lauf ergibt denselben Namen', () => { assert.equal(shortTag(ID1), shortTag(ID1)); assert.equal(shortTag(ID1), 'a3f9'); const it = { id: ID1, filename: '2026-07-23_lea_the-frame.png', created_at: AT }; assert.equal(newNameFor(it), newNameFor(it)); }); test('Erneutes Verarbeiten stapelt keine Stempel', () => { const einmal = buildResultFilename('Lea Badge.png', 'the-frame', 'jpg', { at: AT, id: ID1 }); const zweimal = buildResultFilename(einmal, '30x40', 'jpg', { at: AT, id: ID2 }); assert.equal(zweimal, '2026-08-20_143207_lea-badge_30x40_77b2.jpg'); const dreimal = buildResultFilename(zweimal, 'a4', 'jpg', { at: AT, id: ID1 }); assert.equal(dreimal, '2026-08-20_143207_lea-badge_a4_a3f9.jpg'); }); test('Das eigene Muster erkennt eigene Namen und nur die', () => { assert.ok(KLARBILD_NAME.test('2026-08-20_143207_lea-badge_the-frame_a3f9')); assert.ok(KLARBILD_NAME.test('2026-08-20_143207_lea_30x40_a3f9-2')); assert.ok(!KLARBILD_NAME.test('2026-07-23_lea_the-frame')); // altes Schema assert.ok(!KLARBILD_NAME.test('urlaub-2026_strand')); // fremder Name }); test('Alte Namen werden korrekt zerlegt', () => { assert.deepEqual(splitOldName('2026-07-23_lea_the-frame.png'), { motif: 'lea', format: 'the-frame', ext: 'png' }); assert.deepEqual(splitOldName('2026-07-23_klarbild_30x40.jpg'), { motif: 'klarbild', format: '30x40', ext: 'jpg' }); // Fremde Datei ohne unser Schema: alles wird zum Motiv, Format „original". assert.deepEqual(splitOldName('Urlaub Kreta.JPEG'), { motif: 'urlaub-kreta', format: 'original', ext: 'jpeg' }); // Schon neues Schema: Motiv und Format bleiben, damit die Nachbenennung idempotent ist. assert.deepEqual(splitOldName('2026-08-20_143207_lea-badge_the-frame_a3f9.jpg'), { motif: 'lea-badge', format: 'the-frame', ext: 'jpg' }); }); test('Nachbenennung ist idempotent — zweiter Lauf ändert nichts', () => { const alt = { id: ID1, filename: '2026-07-23_lea_the-frame.png', created_at: new Date('2026-07-23T09:15:42Z') }; const neu = newNameFor(alt); assert.equal(neu, '2026-07-23_111542_lea_the-frame_a3f9.png'); assert.equal(newNameFor({ ...alt, filename: neu }), neu, 'zweiter Lauf muss denselben Namen liefern'); }); test('Nachbenennung nimmt das Erstelldatum, nicht das Datum aus dem alten Namen', () => { // Falls ein Name mal ein falsches Datum trug, gewinnt created_at aus der Datenbank. const it = { id: ID2, filename: '2020-01-01_lea_a4.png', created_at: new Date('2026-03-05T06:00:00Z') }; assert.match(newNameFor(it), /^2026-03-05_070000_lea_a4_77b2\.png$/); }); test('Zähler hängt sich an, wenn ein Name wirklich vergeben ist', () => { const a = buildResultFilename('lea', 'a4', 'png', { at: AT, id: ID1 }); const b = buildResultFilename('lea', 'a4', 'png', { at: AT, id: ID1, seq: 2 }); assert.equal(b, a.replace('.png', '-2.png')); assert.ok(KLARBILD_NAME.test(b.replace('.png', ''))); }); test('Motive bleiben lesbar und dateisystemtauglich', () => { const n = buildResultFilename('Größe & Maß – Übersicht!.png', '13x18', 'jpg', { at: AT, id: ID1 }); assert.equal(n, '2026-08-20_143207_groesse-mass-uebersicht_13x18_a3f9.jpg'); assert.ok(!/[^a-z0-9._-]/.test(n), `unerwartete Zeichen in ${n}`); }); test('Generische Namen werden zu „klarbild", statt Unsinn zu erben', () => { for (const n of ['IMG.png', 'foto.jpg', 'telegram.jpg', null]) { const out = buildResultFilename(n as any, 'a4', 'png', { at: AT, id: ID1 }); assert.match(out, /_klarbild_a4_a3f9\.png$/, `${n} → ${out}`); } }); test('slugify bleibt unverändert nutzbar', () => { assert.equal(slugify('Grüße aus Köln'), 'gruesse-aus-koeln'); }); // --- Kollisionsschleife (der Gürtel zum Hosenträger) -------------------------- // Die Schleife wird mit einer eingesetzten „ist vergeben"-Prüfung getestet, damit // dafür keine Datenbank laufen muss. import { uniqueFilename } from '../src/lib/renames.ts'; test('Ist der Wunschname wider Erwarten vergeben, hängt sich ein Zähler an', async () => { const belegt = new Set(['2026-08-20_143207_lea_a4_a3f9.png']); const n = await uniqueFilename(ID1, 'lea', 'a4', 'png', AT, async (x) => belegt.has(x)); assert.equal(n, '2026-08-20_143207_lea_a4_a3f9-2.png'); }); test('Freier Name wird unverändert vergeben', async () => { const n = await uniqueFilename(ID1, 'lea', 'a4', 'png', AT, async () => false); assert.equal(n, '2026-08-20_143207_lea_a4_a3f9.png'); }); test('Die Schleife gibt auch bei dauerhaft belegten Namen etwas zurück', async () => { const n = await uniqueFilename(ID1, 'lea', 'a4', 'png', AT, async () => true); assert.ok(n.endsWith('.png') && n.length > 10, `unbrauchbarer Rückfall: ${n}`); });