Der Teilenummer-Decoder kennt bislang nur das Samsung-Schema; ein Hynix-Modul mit einwandfrei gelesenem, aber unbekanntem Barcode-Schema endete deshalb in Rot. Zum Sortieren muss keine Kapazitaet bekannt sein - eine exakt gelesene Teilenummer genuegt, um ein Modul wiederzuerkennen. recognize() greift jetzt erst dann auf Rot zurueck, wenn auch die Texterkennung keine Kapazitaet und keine eindeutige Teilenummer liefert; die bestehende Vorsicht bei widerspruechlichen unverwertbaren Barcodes bleibt dabei unangetastet. Zusaetzlich kann recognize() jetzt ueber deps.isKnownCode (optional) erfahren, dass ein gelesener Code bereits einem Stapel bekannt ist, und die bis zu 20 Sekunden dauernde Texterkennung dann ueberspringen. Ohne diese Abhaengigkeit verhaelt sich die Pipeline unveraendert. recognize() liefert zusaetzlich die rohen gelesenen Barcode-Inhalte (codes), damit session.js sie einem Stapel zuordnen kann.
284 lines
11 KiB
JavaScript
284 lines
11 KiB
JavaScript
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');
|
|
});
|