Files
NovaSonic/backend/test/metadata.test.js
T
marcandClaude Opus 5 3c99778567 fix(backend): Dublettenpruefung erkennt Akzente und Soundtrack-Ordner
Drei Luecken derselben Familie, die dazu fuehrten, dass dieselben Songs ein
zweites Mal geladen wurden:

- Kuenstlernamen wurden zeichengenau verglichen: "Cécile Corbel" und "Cecile
  Corbel" galten als zwei Personen. Welche Schreibweise ankommt, haengt an der
  Quelle (Deezer vs. Spotify). Der Vergleich normalisiert jetzt Akzente weg —
  nur der Vergleich, Tags und Dateinamen behalten das Original.
- Typografische Apostrophe: "Arrietty’s Song" (U+2019) und "Arrietty's Song"
  fielen auseinander. Fiel beim Testen auf, betrifft viele Soundtracks.
- findExistingTrack durchsucht nur den Ordner des uebergebenen Kuenstlers. Bei
  Soundtracks weicht der Album- vom Titel-Kuenstler ab, der Song landet je nach
  Ladeweg unter dem einen oder anderen — geprueft wurde jeweils der falsche.
  Weicht der Titel-Kuenstler ab, wird nun auch dessen Ordner geprueft (~0,6 ms
  bei 28.801 Dateien, gegenueber 3-9 s pro Download).

Sieben neue Tests zielen gezielt auf Fehlalarme: aehnliche Namen (Cecil Taylor
vs. Cécile Corbel), gleicher Titel bei verschiedenen Kuenstlern, Remix gegen
Basistitel, fremder Titel im Soundtrack-Ordner — alle bleiben getrennt.
Gesamte Suite: 42 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 08:40:02 +02:00

116 lines
6.0 KiB
JavaScript

const { test, before, after } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { findExistingTrack } = require('../lib/metadata');
let ROOT;
const touch = (artist, album, file) => {
const dir = path.join(ROOT, artist, album);
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, file), '');
};
before(() => {
ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'ns-dedup-'));
// KH single: normal "01 - " prefix, title contains multiple " - "
touch('Geek Music', 'Kingdom Hearts 2 - Dearly Beloved - Main Theme - Single',
'01 - Kingdom Hearts 2 - Dearly Beloved - Main Theme.mp3');
// KH album variant: garbage "01-01" artist prefix, no track number
touch('Geek Music', 'Kingdom Hearts 2 - Dearly Beloved - Main Theme',
'01-01 - Kingdom Hearts 2 - Dearly Beloved - Main Theme.mp3');
// Normal "Artist - Title" file
touch('Some Artist', 'Therapy - Single', '01 - Some Artist - Therapy.mp3');
// Akzent-Schreibweise: liegt MIT Akzent auf der Platte
touch('Cécile Corbel', 'SongBook Vol. 2', '03 - Cécile Corbel - Arrietty\u2019s Song.mp3');
// Soundtrack: liegt unter dem ALBUM-Kuenstler, nicht unter dem Titel-Kuenstler
touch('Arcane', 'Arcane League of Legends (Soundtrack)', '01 - Bea Miller - Playground.mp3');
// Zwei verschiedene Kuenstler mit demselben Songtitel — duerfen sich NIE gegenseitig treffen
touch('Cro', 'Raop', '03 - Cro - Easy.mp3');
touch('Kraftklub', 'Mit K', '05 - Kraftklub - Easy.mp3');
// Aehnlicher, aber anderer Kuenstlername
touch('Cecil Taylor', 'Unit Structures', '01 - Cecil Taylor - Steps.mp3');
});
after(() => { try { fs.rmSync(ROOT, { recursive: true, force: true }); } catch {} });
test('detects a re-download when the title itself contains " - " (KH bug)', () => {
assert.ok(findExistingTrack(ROOT, 'Geek Music', 'Kingdom Hearts 2 - Dearly Beloved - Main Theme'));
});
test('detects it even with a garbage "01-01 - " artist prefix', () => {
// Both KH files should resolve; at least one must be found → dedup skips the re-download.
const hit = findExistingTrack(ROOT, 'Geek Music', 'Kingdom Hearts 2 - Dearly Beloved - Main Theme');
assert.ok(hit && hit.includes('Kingdom Hearts 2 - Dearly Beloved - Main Theme'));
});
test('normal "Artist - Title" filename still matches on title alone', () => {
assert.ok(findExistingTrack(ROOT, 'Some Artist', 'Therapy'));
});
test('no false positive: a different title is not matched', () => {
assert.equal(findExistingTrack(ROOT, 'Geek Music', 'Some Unrelated Song'), null);
assert.equal(findExistingTrack(ROOT, 'Some Artist', 'Kingdom Hearts 2'), null);
});
test('remix stays distinct from the base track (no regression from the version-marker fix)', () => {
assert.equal(findExistingTrack(ROOT, 'Some Artist', 'Therapy (Remix)'), null);
assert.ok(findExistingTrack(ROOT, 'Some Artist', 'Therapy')); // base still found
});
// ── Akzente (Cécile Corbel) ────────────────────────────────────────────────────
test('findet den Song auch ohne Akzent in der Anfrage', () => {
assert.ok(findExistingTrack(ROOT, 'Cecile Corbel', "Arrietty's Song"),
'ASCII-Schreibweise muss die Datei mit Akzent finden');
});
test('findet ihn ebenso mit Akzent', () => {
assert.ok(findExistingTrack(ROOT, 'Cécile Corbel', "Arrietty's Song"));
});
test('kein Fehlalarm: aehnlicher Name ist nicht dieselbe Person', () => {
// "Cecil Taylor" darf nicht auf "Cécile Corbel" passen — und umgekehrt.
assert.equal(findExistingTrack(ROOT, 'Cécile Corbel', 'Steps'), null);
assert.equal(findExistingTrack(ROOT, 'Cecil Taylor', "Arrietty's Song"), null);
});
test('kein Fehlalarm: Akzent-Normalisierung kollabiert keine echten Titel', () => {
assert.equal(findExistingTrack(ROOT, 'Cecile Corbel', 'Ein anderer Song'), null);
});
// ── Gleicher Titel, verschiedene Kuenstler ─────────────────────────────────────
test('kein Fehlalarm: gleicher Titel bei zwei Kuenstlern bleibt getrennt', () => {
const cro = findExistingTrack(ROOT, 'Cro', 'Easy');
const kk = findExistingTrack(ROOT, 'Kraftklub', 'Easy');
assert.ok(cro && cro.includes('Cro'), 'Cro-Version muss im Cro-Ordner gefunden werden');
assert.ok(kk && kk.includes('Kraftklub'), 'Kraftklub-Version im Kraftklub-Ordner');
assert.notEqual(cro, kk, 'die beiden duerfen nicht dieselbe Datei sein');
// Ein dritter Kuenstler mit demselben Titel findet nichts.
assert.equal(findExistingTrack(ROOT, 'Irgendwer', 'Easy'), null);
});
// ── Soundtrack: Album-Kuenstler vs. Titel-Kuenstler ────────────────────────────
test('Soundtrack unter dem Album-Kuenstler wird ueber diesen gefunden', () => {
assert.ok(findExistingTrack(ROOT, 'Arcane', 'Playground'));
});
test('unter dem Titel-Kuenstler allein wird er NICHT gefunden (Grund fuer die Zweitpruefung)', () => {
// Genau diese Luecke erzeugte die Dublette: geprueft wurde nur "Bea Miller".
assert.equal(findExistingTrack(ROOT, 'Bea Miller', 'Playground'), null);
});
test('die Zweitpruefung schliesst die Luecke, ohne Fremdes zu treffen', () => {
const pruefe = (albumArtist, trackArtist, titel) =>
findExistingTrack(ROOT, albumArtist, titel)
|| (trackArtist !== albumArtist ? findExistingTrack(ROOT, trackArtist, titel) : null);
// Soundtrack-Fall: wird jetzt erkannt.
assert.ok(pruefe('Bea Miller', 'Bea Miller', 'Playground') === null,
'ohne Album-Kuenstler weiterhin nichts — richtig, die Datei liegt unter Arcane');
assert.ok(pruefe('Arcane', 'Bea Miller', 'Playground'), 'mit Album-Kuenstler gefunden');
// Fremder Titel im selben Ordner darf trotz zweier Durchlaeufe nicht anschlagen.
assert.equal(pruefe('Arcane', 'Bea Miller', 'Irgendein anderer Titel'), null);
// Remix bleibt auch ueber zwei Durchlaeufe hinweg eigenstaendig.
assert.equal(pruefe('Arcane', 'Bea Miller', 'Playground (Remix)'), null);
});