diff --git a/src/session.js b/src/session.js index da36cd6..59cb81e 100644 --- a/src/session.js +++ b/src/session.js @@ -18,15 +18,31 @@ function nextStackId(session) { return `A${session.stacks.length}`; } +const FINGERPRINT_FIELDS = ['capacityGb', 'formFactor', 'rank', 'speed', 'partNumber']; + +/** + * Ein Stapel ist nur dann Kandidat, wenn er mit dem Modul mindestens ein + * Merkmal gemeinsam belegt hat. Ein Stapel ohne jede erkannte Angabe (z.B. + * aus einem Rot-Fall mit leerem Spec entstanden) hat mit jedem Modul null + * gemeinsame Felder und darf deshalb nie als "passt" gelten - sonst wuerde + * er lautlos alles an sich ziehen. + */ +function sharesSetField(a, b) { + return FINGERPRINT_FIELDS.some((field) => ( + a[field] !== null && a[field] !== undefined + && b[field] !== null && b[field] !== undefined + )); +} + /** * Schlaegt vor, wohin ein Modul gehoert. - * Genau ein vertraeglicher Stapel -> Zuweisung. + * Genau ein vertraeglicher Stapel mit gemeinsamer Grundlage -> Zuweisung. * Keiner -> neuer Stapel. * Mehrere -> mehrdeutig, der Nutzer entscheidet. */ export function proposeAssignment(session, spec) { const candidates = session.stacks - .filter((stack) => specsCompatible(stack.spec, spec)) + .filter((stack) => specsCompatible(stack.spec, spec) && sharesSetField(stack.spec, spec)) .map((stack) => stack.id); if (candidates.length === 1) { diff --git a/test/session.test.js b/test/session.test.js index b7e27ed..dd9b82f 100644 --- a/test/session.test.js +++ b/test/session.test.js @@ -131,6 +131,33 @@ test('moveEntry legt fuer einen neuen Stapel eine eigene Kopie des Specs an', () assert.equal(a.spec.partNumber, null, 'eine Ergaenzung des neu angelegten Stapel-Specs darf das Eintrags-Spec nicht veraendern'); }); +test('ein Stapel ohne jede erkannte Angabe ist fuer kein Modul Kandidat', () => { + const session = createSession(); + scan(session, emptySpec()); // Stapel A, vollstaendig leeres Spec (Rot-Fall) + const plan = proposeAssignment(session, s64()); + assert.equal(plan.kind, 'new', 'ein leerer Stapel darf keine Kandidatur begruenden'); + assert.notEqual(plan.stackId, 'A'); +}); + +test('ein Stapel mit nur der Kapazitaet ist Kandidat fuer ein Modul gleicher Kapazitaet', () => { + const session = createSession(); + scan(session, { ...emptySpec(), capacityGb: 64 }); + const plan = proposeAssignment(session, { ...emptySpec(), capacityGb: 64 }); + assert.equal(plan.kind, 'match'); + assert.equal(plan.stackId, 'A'); +}); + +test('ein voellig unerkanntes Modul erzeugt einen eigenen Stapel und zieht keine weiteren an sich', () => { + const session = createSession(); + const erster = scan(session, emptySpec()); + const zweiter = scan(session, emptySpec()); + assert.notEqual(erster.stackId, zweiter.stackId, 'zwei unerkannte Module ohne gemeinsame Grundlage teilen sich keinen Stapel'); + assert.equal(session.stacks.length, 2); + + const plan = proposeAssignment(session, s64()); + assert.equal(plan.kind, 'new', 'der leere Stapel darf ein erkanntes Modul nicht an sich ziehen'); +}); + test('undoLast-Vertrag: nach einem Umsortieren wird weiterhin der zuletzt erfasste Eintrag zurueckgenommen', () => { const session = createSession(); const a = scan(session, s64());