- Barcode- und OCR-Adapter erhalten je eine eigene, ueberschreibbare Zeitobergrenze (deps.barcodeTimeoutMs / deps.ocrTimeoutMs). Ein Adapter, der nie einloest, blockiert recognize() damit nicht mehr dauerhaft und faellt stattdessen auf den Ersatzwert zurueck, genau wie bei einem geworfenen Fehler. Kein offener Timer bleibt zurueck. - mergeCompatibleSpecs entfernt: vertraegliche verwertbare Barcode-Treffer sind wegen der Kopplung von partNumber und den uebrigen Feldern ohnehin identisch, der erste Treffer genuegt. Test entsprechend umbenannt, um das tatsaechlich gepruefte Verhalten zu benennen statt eine nicht mehr vorhandene Auffuell-Semantik zu behaupten. - Kommentar in pn-tables.js von einer inhaltlichen Wiederholung befreit.
158 lines
6.1 KiB
JavaScript
158 lines
6.1 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 ocrAufgerufen = false;
|
|
const result = await recognize({}, {
|
|
decodeBarcodes: async () => ['M386A8K40BM1-CRC4Y'],
|
|
runOcr: async () => { ocrAufgerufen = true; return ''; },
|
|
});
|
|
assert.equal(result.source, 'barcode');
|
|
assert.equal(result.confidence, 'green');
|
|
assert.equal(result.spec.capacityGb, 64);
|
|
assert.equal(ocrAufgerufen, 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 () => ({ unerwartet: 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 ocrAufgerufen = 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 () => { ocrAufgerufen = true; return ''; },
|
|
});
|
|
assert.equal(result.source, 'none');
|
|
assert.equal(result.confidence, 'red');
|
|
assert.deepEqual(result.spec, emptySpec());
|
|
assert.equal(ocrAufgerufen, 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 ocrAufgerufen = 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 () => { ocrAufgerufen = 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(ocrAufgerufen, 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');
|
|
});
|