import { test } from 'node:test'; import assert from 'node:assert/strict'; import { saveSession, loadSession, clearSession } from '../src/storage.js'; import { createSession, commitAssignment, proposeAssignment, moveEntry, removeEntry, } from '../src/session.js'; import { emptySpec } from '../src/spec.js'; function fakeStore() { const data = new Map(); return { data, getItem: (key) => (data.has(key) ? data.get(key) : null), setItem: (key, value) => data.set(key, value), removeItem: (key) => data.delete(key), }; } test('leerer Speicher liefert null', () => { assert.equal(loadSession(fakeStore()), null); }); test('Sitzung ueberlebt Sichern und Laden', () => { const store = fakeStore(); const session = createSession(); commitAssignment(session, { ...emptySpec(), capacityGb: 64 }, 'barcode', 'A'); saveSession(session, store); const reloaded = loadSession(store); assert.equal(reloaded.stacks.length, 1); assert.equal(reloaded.stacks[0].count, 1); assert.equal(reloaded.entries[0].spec.capacityGb, 64); assert.equal(reloaded.nextEntryId, 2); }); test('clearSession raeumt auf', () => { const store = fakeStore(); saveSession(createSession(), store); clearSession(store); assert.equal(loadSession(store), null); }); test('kaputter Inhalt liefert null statt einer Ausnahme', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', '{kein json'); assert.equal(loadSession(store), null); }); test('Sichern ohne funktionierenden Speicher wirft nicht', () => { const broken = { getItem: () => null, setItem: () => { throw new Error('quota exceeded'); }, removeItem: () => {}, }; assert.doesNotThrow(() => saveSession(createSession(), broken)); }); test('stacks mit Nicht-Objekten liefert null', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: ['x', 'y'], entries: [], nextEntryId: 1, })); assert.equal(loadSession(store), null); }); test('fehlender nextEntryId liefert null', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [], entries: [], })); assert.equal(loadSession(store), null); }); test('nextEntryId als Zeichenkette liefert null', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [], entries: [], nextEntryId: '2', })); assert.equal(loadSession(store), null); }); test('Stapel mit Zaehler als Zeichenkette liefert null', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [{ id: 'A', count: '3', spec: emptySpec() }], entries: [], nextEntryId: 1, })); assert.equal(loadSession(store), null); }); test('Eintrag ohne Spec liefert null', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [], entries: [{ entryId: 1, stackId: 'A' }], nextEntryId: 2, })); assert.equal(loadSession(store), null); }); test('Stapelzaehler wird beim Laden aus den Eintraegen neu berechnet', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [{ id: 'A', count: 5, spec: emptySpec() }], entries: [{ entryId: 1, stackId: 'A', spec: emptySpec(), source: 'barcode' }], nextEntryId: 2, })); const reloaded = loadSession(store); assert.notEqual(reloaded, null); assert.equal(reloaded.stacks[0].count, 1); }); test('Stapelzaehler werden beim Laden fuer mehrere Stapel unabhaengig neu berechnet, auch fuer einen ohne Eintraege', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [ { id: 'A', count: 99, spec: emptySpec() }, { id: 'B', count: 99, spec: emptySpec() }, { id: 'C', count: 99, spec: emptySpec() }, ], entries: [ { entryId: 1, stackId: 'A', spec: emptySpec(), source: 'barcode' }, { entryId: 2, stackId: 'A', spec: emptySpec(), source: 'barcode' }, { entryId: 3, stackId: 'B', spec: emptySpec(), source: 'ocr' }, ], nextEntryId: 4, })); const reloaded = loadSession(store); assert.notEqual(reloaded, null); assert.equal(reloaded.stacks.find((s) => s.id === 'A').count, 2, 'Stapel A hat zwei zugeordnete Eintraege'); assert.equal(reloaded.stacks.find((s) => s.id === 'B').count, 1, 'Stapel B hat einen zugeordneten Eintrag'); assert.equal(reloaded.stacks.find((s) => s.id === 'C').count, 0, 'Stapel C ohne jeden Eintrag wird auf 0 neu berechnet, nicht auf den gespeicherten Wert'); }); test('Eintrag mit unbekannter Stapelkennung liefert null', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [], entries: [{ entryId: 1, stackId: 'Z', spec: emptySpec(), source: 'barcode' }], nextEntryId: 2, })); assert.equal(loadSession(store), null); }); test('doppelte Eintragsnummern liefert null', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [{ id: 'A', count: 2, spec: emptySpec() }], entries: [ { entryId: 1, stackId: 'A', spec: emptySpec(), source: 'barcode' }, { entryId: 1, stackId: 'A', spec: emptySpec(), source: 'barcode' }, ], nextEntryId: 2, })); assert.equal(loadSession(store), null); }); test('doppelte Stapelkennungen liefert null', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [ { id: 'A', count: 0, spec: emptySpec() }, { id: 'A', count: 0, spec: emptySpec() }, ], entries: [], nextEntryId: 1, })); assert.equal(loadSession(store), null); }); test('nextEntryId nicht groesser als vorhandene Eintragsnummer liefert null', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [{ id: 'A', count: 1, spec: emptySpec() }], entries: [{ entryId: 5, stackId: 'A', spec: emptySpec(), source: 'barcode' }], nextEntryId: 5, })); assert.equal(loadSession(store), null); }); test('nach Sichern und Laden funktioniert Weiterarbeiten weiterhin', () => { const store = fakeStore(); const session = createSession(); const spec = { ...emptySpec(), capacityGb: 64 }; commitAssignment(session, spec, 'barcode', 'A'); const secondEntry = commitAssignment(session, spec, 'barcode', 'A'); saveSession(session, store); const reloaded = loadSession(store); const proposal = proposeAssignment(reloaded, spec); assert.equal(proposal.kind, 'match'); assert.equal(proposal.stackId, 'A'); commitAssignment(reloaded, spec, 'barcode', proposal.stackId); assert.equal(reloaded.stacks[0].count, 3); assert.equal(reloaded.nextEntryId, 4); moveEntry(reloaded, secondEntry.entryId, 'B'); assert.equal(reloaded.stacks.find((s) => s.id === 'A').count, 2); assert.equal(reloaded.stacks.find((s) => s.id === 'B').count, 1); removeEntry(reloaded, secondEntry.entryId); assert.equal(reloaded.entries.find((e) => e.entryId === secondEntry.entryId), undefined); assert.equal(reloaded.stacks.find((s) => s.id === 'B'), undefined); }); test('nextEntryId als negative Zahl liefert null', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [], entries: [], nextEntryId: -5, })); assert.equal(loadSession(store), null); }); test('nextEntryId als gebrochene Zahl liefert null', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [], entries: [], nextEntryId: 1.5, })); assert.equal(loadSession(store), null); }); test('nextEntryId als Null liefert null', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [], entries: [], nextEntryId: 0, })); assert.equal(loadSession(store), null); }); test('Eintrag mit gebrochener entryId liefert null', () => { const store = fakeStore(); store.setItem('ram-sortierhilfe:session', JSON.stringify({ stacks: [{ id: 'A', count: 1, spec: emptySpec() }], entries: [{ entryId: 1.5, stackId: 'A', spec: emptySpec(), source: 'barcode' }], nextEntryId: 2, })); assert.equal(loadSession(store), null); });