import { test } from 'node:test'; import assert from 'node:assert/strict'; import { recognize } from '../src/pipeline.js'; import { emptySpec } from '../src/spec.js'; const OCR_TEXT = '64GB 4DRx4 PC4-2400T-LD1-11-MC0 M386A8K40BM1-CRC4Y 1908'; const deps = ({ codes = [], text = '' }) => ({ decodeBarcodes: async () => codes, runOcr: async () => text, }); test('Barcode mit bekannter Teilenummer ergibt gruen und ueberspringt OCR', async () => { let ocrCalled = false; const result = await recognize({}, { decodeBarcodes: async () => ['M386A8K40BM1-CRC4Y'], runOcr: async () => { ocrCalled = true; return ''; }, }); assert.equal(result.source, 'barcode'); assert.equal(result.confidence, 'green'); assert.equal(result.spec.capacityGb, 64); assert.equal(ocrCalled, false, 'OCR darf bei gruenem Barcode nicht laufen'); }); test('Barcode ohne bekannte Teilenummer faellt auf OCR zurueck', async () => { const result = await recognize({}, deps({ codes: ['7325773'], text: OCR_TEXT })); assert.equal(result.source, 'ocr'); assert.equal(result.confidence, 'yellow'); assert.equal(result.spec.capacityGb, 64); }); test('OCR-Ergebnis behaelt die Teilenummer aus dem Barcode bei', async () => { const result = await recognize({}, deps({ codes: ['7325773'], text: '64GB 4DRx4 PC4-2400' })); assert.equal(result.spec.partNumber, '7325773'); }); test('ohne Barcode und ohne brauchbaren Text ergibt rot', async () => { const result = await recognize({}, deps({ codes: [], text: 'Made in Philippines' })); assert.equal(result.source, 'none'); assert.equal(result.confidence, 'red'); }); test('OCR-Fehler fuehrt zu rot statt zu einem Absturz', async () => { const result = await recognize({}, { decodeBarcodes: async () => [], runOcr: async () => { throw new Error('tesseract nicht geladen'); }, }); assert.equal(result.confidence, 'red'); assert.equal(result.source, 'none'); }); test('Barcode-Fehler fuehrt nicht zum Abbruch, OCR uebernimmt', async () => { const result = await recognize({}, { decodeBarcodes: async () => { throw new Error('zxing nicht geladen'); }, runOcr: async () => OCR_TEXT, }); assert.equal(result.source, 'ocr'); assert.equal(result.spec.capacityGb, 64); }); // --- Beanstandung 1: unerwartete Rueckgabeform statt eines Fehlers --- test('decodeBarcodes liefert null statt einer Liste, kein Absturz', async () => { const result = await recognize({}, { decodeBarcodes: async () => null, runOcr: async () => OCR_TEXT, }); assert.equal(result.source, 'ocr'); assert.equal(result.confidence, 'yellow'); assert.equal(result.spec.capacityGb, 64); }); test('decodeBarcodes liefert undefined statt einer Liste, kein Absturz', async () => { const result = await recognize({}, { decodeBarcodes: async () => undefined, runOcr: async () => OCR_TEXT, }); assert.equal(result.source, 'ocr'); assert.equal(result.confidence, 'yellow'); assert.equal(result.spec.capacityGb, 64); }); test('decodeBarcodes liefert ein Objekt statt einer Liste, kein Absturz', async () => { const result = await recognize({}, { decodeBarcodes: async () => ({ notAList: true }), runOcr: async () => OCR_TEXT, }); assert.equal(result.source, 'ocr'); assert.equal(result.confidence, 'yellow'); assert.equal(result.spec.capacityGb, 64); }); test('runOcr liefert keine Zeichenkette, wird wie leerer Text behandelt', async () => { const result = await recognize({}, { decodeBarcodes: async () => [], runOcr: async () => ({ unexpected: true }), }); assert.equal(result.source, 'none'); assert.equal(result.confidence, 'red'); }); // --- Beanstandung 2: mehrere verwertbare Barcodes im selben Bild --- test('mehrere unvertraegliche Barcodes ergeben rot statt eines ungeprueften ersten Treffers', async () => { let ocrCalled = false; const result = await recognize({}, { // Gleiche Bauform und Dichte, aber widersprechende Geschwindigkeit: // zwei unterschiedliche Module im selben Bild. decodeBarcodes: async () => ['M386A8K40BM1-CRC4Y', 'M386A8K40BM1-CWE4Y'], runOcr: async () => { ocrCalled = true; return ''; }, }); assert.equal(result.source, 'none'); assert.equal(result.confidence, 'red'); assert.deepEqual(result.spec, emptySpec()); assert.equal(ocrCalled, false, 'bei mehrdeutigem Barcode-Ergebnis soll nicht geraten werden'); }); test('mehrere untereinander vertraegliche Barcodes ergeben gruen (der erste Treffer entscheidet, es gibt nichts zusammenzufuehren)', async () => { let ocrCalled = false; const result = await recognize({}, { // Zwei Lesungen desselben Etiketts (z.B. Mehrfachtreffer durch // Spiegelung) - inhaltlich identisch, nur die Schreibweise weicht // an einer verwechslungstoleranten Stelle ab (B/8). decodeBarcodes: async () => ['M386A8K40BM1-CRC4Y', 'M386A8K408M1-CRC4Y'], runOcr: async () => { ocrCalled = true; return ''; }, }); assert.equal(result.source, 'barcode'); assert.equal(result.confidence, 'green'); assert.equal(result.spec.capacityGb, 64); assert.equal(result.spec.formFactor, 'LRDIMM'); assert.equal(result.spec.rank, '4DRx4'); assert.equal(result.spec.speed, 'PC4-2400'); assert.equal(ocrCalled, false, 'OCR darf bei gruenem Mehrfachtreffer nicht laufen'); }); // --- Beanstandung 1: haengender Adapter darf die Erkennung nicht dauerhaft blockieren --- test('haengender Barcode-Adapter blockiert nicht dauerhaft, OCR uebernimmt', { timeout: 500 }, async () => { const result = await recognize({}, { decodeBarcodes: () => new Promise(() => {}), // loest nie ein und wirft nie runOcr: async () => OCR_TEXT, barcodeTimeoutMs: 20, }); assert.equal(result.source, 'ocr'); assert.equal(result.confidence, 'yellow'); assert.equal(result.spec.capacityGb, 64); }); test('haengender OCR-Adapter blockiert nicht dauerhaft, Erkennung ergibt rot', { timeout: 500 }, async () => { const result = await recognize({}, { decodeBarcodes: async () => [], runOcr: () => new Promise(() => {}), // loest nie ein und wirft nie ocrTimeoutMs: 20, }); assert.equal(result.source, 'none'); assert.equal(result.confidence, 'red'); }); // --- Beanstandung 1 (3. Runde): Mehrdeutigkeit bei unverwertbaren Barcodes --- test('zwei widersprechende unverwertbare Barcodes: keiner erhaelt Vorrang, gelesene Teilenummer bleibt', async () => { const result = await recognize({}, { // Beide ohne bekanntes Nummernschema, und sie widersprechen sich - // keiner darf die aus dem Klartext gelesene Teilenummer ueberschreiben. decodeBarcodes: async () => ['7325773', '9999999'], runOcr: async () => OCR_TEXT, }); assert.equal(result.source, 'ocr'); assert.equal(result.confidence, 'yellow'); assert.equal(result.spec.partNumber, 'M386A8K40BM1-CRC4Y'); assert.equal(result.spec.capacityGb, 64); }); test('zwei unverwertbare Barcodes mit gleicher Teilenummer: Vorrang bleibt bestehen', async () => { const result = await recognize({}, { decodeBarcodes: async () => ['7325773', '7325773'], runOcr: async () => OCR_TEXT, }); assert.equal(result.source, 'ocr'); assert.equal(result.confidence, 'yellow'); assert.equal(result.spec.partNumber, '7325773'); }); test('zwei unverwertbare Barcodes, die sich nur in einer Verwechslung unterscheiden, gelten als derselbe', async () => { const result = await recognize({}, { // 'O' und '0' sind eine typische Verwechslung (siehe CONFUSIONS in spec.js). decodeBarcodes: async () => ['732O773', '7320773'], runOcr: async () => OCR_TEXT, }); assert.equal(result.source, 'ocr'); assert.equal(result.confidence, 'yellow'); assert.equal(result.spec.partNumber, '732O773'); }); // --- Erweiterung: unbekanntes Nummernschema mit gelesener Teilenummer genuegt fuer Gruen --- test('Barcode mit unbekanntem Nummernschema und gelesener Teilenummer ergibt gruen, auch ohne Kapazitaet', async () => { const result = await recognize({}, { // Hynix-Teilenummer: Schema unbekannt (nur Samsung ist hinterlegt), aber // exakt gelesen - und OCR findet auf dem Etikett ebenfalls nichts // Verwertbares. Trotzdem genuegt die gelesene Teilenummer fuer Gruen. decodeBarcodes: async () => ['HMA84GL7AFR4N-UH'], runOcr: async () => '', }); assert.equal(result.source, 'barcode'); assert.equal(result.confidence, 'green'); assert.equal(result.spec.partNumber, 'HMA84GL7AFR4N-UH'); assert.equal(result.spec.capacityGb, null); }); test('findet OCR trotz unbekanntem Nummernschema eine Kapazitaet, wird sie uebernommen (weiterhin gelb, nicht gruen)', async () => { const result = await recognize({}, { decodeBarcodes: async () => ['HMA84GL7AFR4N-UH'], runOcr: async () => OCR_TEXT, }); assert.equal(result.source, 'ocr'); assert.equal(result.confidence, 'yellow'); assert.equal(result.spec.capacityGb, 64); // Die Teilenummer aus dem Barcode ist exakter und bleibt erhalten. assert.equal(result.spec.partNumber, 'HMA84GL7AFR4N-UH'); }); test('widersprechende unverwertbare Barcodes ohne jede OCR-Kapazitaet: die Vorsicht bleibt erhalten, kein Gruen', () => recognize({}, { decodeBarcodes: async () => ['7325773', '9999999'], runOcr: async () => '', }).then((result) => { assert.equal(result.source, 'none'); assert.equal(result.confidence, 'red'); })); test('recognize liefert die gelesenen Rohcodes zur Weitergabe an die Stapelzuordnung', async () => { const result = await recognize({}, { decodeBarcodes: async () => ['HMA84GL7AFR4N-UH', 'SN-000123'], runOcr: async () => '', }); assert.deepEqual([...result.codes].sort(), ['HMA84GL7AFR4N-UH', 'SN-000123'].sort()); }); // --- Erweiterung: ein bekannter Code ueberspringt die Texterkennung --- test('bekannter Code ueberspringt die Texterkennung vollstaendig', async () => { let ocrCalled = false; const result = await recognize({}, { decodeBarcodes: async () => ['HMA84GL7AFR4N-UH'], runOcr: async () => { ocrCalled = true; return ''; }, isKnownCode: (code) => code === 'HMA84GL7AFR4N-UH', }); assert.equal(ocrCalled, false, 'ein bekannter Code macht die Texterkennung ueberfluessig'); assert.equal(result.source, 'barcode'); assert.equal(result.confidence, 'green'); }); test('bekannter Code unter mehreren Codes ueberspringt die Texterkennung trotzdem (Seriennummer laeuft ins Leere)', async () => { let ocrCalled = false; const result = await recognize({}, { decodeBarcodes: async () => ['HMA84GL7AFR4N-UH', 'SN-000123'], runOcr: async () => { ocrCalled = true; return ''; }, isKnownCode: (code) => code === 'HMA84GL7AFR4N-UH', }); assert.equal(ocrCalled, false); assert.equal(result.confidence, 'green'); }); test('unbekannter Code (isKnownCode liefert stets false) ueberspringt die Texterkennung nicht', async () => { let ocrCalled = false; const result = await recognize({}, { decodeBarcodes: async () => ['HMA84GL7AFR4N-UH'], runOcr: async () => { ocrCalled = true; return ''; }, isKnownCode: () => false, }); assert.equal(ocrCalled, true); assert.equal(result.confidence, 'green'); }); test('ohne isKnownCode-Abhaengigkeit bleibt die Pipeline ohne sie lauffaehig (Texterkennung laeuft wie bisher)', async () => { let ocrCalled = false; const result = await recognize({}, { decodeBarcodes: async () => ['HMA84GL7AFR4N-UH'], runOcr: async () => { ocrCalled = true; return ''; }, }); assert.equal(ocrCalled, true); assert.equal(result.confidence, 'green'); });