Files

223 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});
// ─── Dateisuche: Titelstamm und Schraegstrich im Namen ───────────────────────
// Regression zu "K/DA – VILLAIN": Navidromes Tag-Pfad wurde zu "K/DA/ALL OUT/…", auf der Platte
// steht "K_DA/ALL OUT/…". Der Pfad fand nichts, und die Aehnlichkeitssuche lieferte
// "Bad Omens - Like A Villain", weil deren Stamm auf "villain" ENDET.
const { findFileByStem, albumDirFromTags } = require('../lib/metadata');
let SLASH_ROOT;
before(() => {
SLASH_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'ns-slash-'));
const put = (...segs) => {
const file = segs.pop();
const dir = path.join(SLASH_ROOT, ...segs);
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, file), '');
};
put('K_DA', 'ALL OUT', '03 - K_DA - VILLAIN.mp3');
put('K_DA', 'POP_STARS', '01 - K_DA - POP_STARS.mp3');
put('Bad Omens', 'THE DEATH OF PEACE OF MIND', '06 - Bad Omens - Like A Villain.mp3');
});
after(() => { try { fs.rmSync(SLASH_ROOT, { recursive: true, force: true }); } catch {} });
test('Titelstamm trifft nicht auf blosse Endsilbe', () => {
// "Like A Villain" darf NICHT als "VILLAIN" durchgehen
const t = findFileByStem(path.join(SLASH_ROOT, 'Bad Omens'), 'villain');
assert.equal(t, null);
});
test('Titelstamm trifft "Kuenstler - Titel" weiterhin', () => {
const t = findFileByStem(path.join(SLASH_ROOT, 'Bad Omens'), 'like a villain');
assert.ok(t && t.endsWith('06 - Bad Omens - Like A Villain.mp3'));
});
test('Ganze Bibliothek: VILLAIN findet K_DA, nicht Bad Omens', () => {
const t = findFileByStem(SLASH_ROOT, 'villain');
assert.ok(t && t.includes(path.join('K_DA', 'ALL OUT')), `unerwartet: ${t}`);
});
test('Schraegstrich im Kuenstlernamen fuehrt zum richtigen Ordner', () => {
assert.equal(albumDirFromTags(SLASH_ROOT, 'K/DA', 'ALL OUT'), path.join(SLASH_ROOT, 'K_DA', 'ALL OUT'));
assert.equal(albumDirFromTags(SLASH_ROOT, 'K/DA', 'POP/STARS'), path.join(SLASH_ROOT, 'K_DA', 'POP_STARS'));
});
test('Unbekanntes Album faellt auf den Kuenstlerordner zurueck', () => {
assert.equal(albumDirFromTags(SLASH_ROOT, 'K/DA', 'Gibt Es Nicht'), path.join(SLASH_ROOT, 'K_DA'));
});
test('Mehrere Kuenstler: Ordner haengt am ersten', () => {
assert.equal(
albumDirFromTags(SLASH_ROOT, 'K/DA, Madison Beer, Kim Petras', 'ALL OUT'),
path.join(SLASH_ROOT, 'K_DA', 'ALL OUT'),
);
});
test('Kein Treffer bleibt null', () => {
assert.equal(albumDirFromTags(SLASH_ROOT, 'Niemand', 'Nichts'), null);
});
// ─── Kuenstler im Dateinamen ────────────────────────────────────────────────
// Frueher stand die vollstaendige Gaesteliste im Namen und wurde bei 100 Zeichen mitten im
// letzten Namen abgeschnitten. Jetzt nur noch der erste Kuenstler.
const { dateinamensKuenstler } = require('../lib/metadata');
test('Nur der erste Kuenstler landet im Dateinamen', () => {
assert.equal(
dateinamensKuenstler('K/DA;Madison Beer;i-dle;Lexie Liu;Jaira Burns;Seraphine;League of Legends'),
'K/DA',
);
assert.equal(dateinamensKuenstler('Dove Cameron, Sofia Carson'), 'Dove Cameron');
assert.equal(dateinamensKuenstler('Calvin Harris & Dua Lipa'), 'Calvin Harris');
});
test('Bandnamen mit & bleiben ganz, wenn sie keine Liste sind', () => {
// "&" trennt nur MIT umgebenden Leerzeichen — "Earth, Wind & Fire" waere ohnehin durch das
// Komma getrennt, aber ein reiner Bandname ohne Liste darf nicht zerlegt werden.
assert.equal(dateinamensKuenstler('AC/DC'), 'AC/DC');
assert.equal(dateinamensKuenstler('Simon&Garfunkel'), 'Simon&Garfunkel');
});
test('Leerer Kuenstler faellt auf den Album-Kuenstler zurueck', () => {
assert.equal(dateinamensKuenstler('', 'K/DA;Madison Beer'), 'K/DA');
assert.equal(dateinamensKuenstler(undefined, undefined), '');
});
test('Der Dateiname bleibt kurz genug fuers Dateisystem', () => {
const lang = 'K/DA;Madison Beer;(G)I-DLE;Lexie Liu;Jaira Burns;Seraphine;League of Legends;Bea Miller;Wolftyla;Duckwrth';
const teil = dateinamensKuenstler(lang);
assert.equal(teil, 'K/DA');
assert.ok(teil.length < 100, 'wird nie von sanitize() abgeschnitten');
});
test('Dublettenpruefung findet auch die alt benannten Dateien', () => {
// Nach der Umstellung liegen beide Namensformen nebeneinander. Beide muessen als "schon da"
// erkannt werden, sonst laedt der Downloader die alten Dateien ein zweites Mal.
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ns-namen-'));
const put = (album, file) => {
const dir = path.join(root, 'K_DA', album);
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, file), '');
};
put('MORE', '01 - K_DA,Madison Beer,i-dle,Lexie Liu,Jaira Burns,Seraphine - MORE.mp3'); // alt
put('ALL OUT', '03 - K_DA - VILLAIN.mp3'); // neu
assert.ok(findExistingTrack(root, 'K/DA', 'MORE'), 'alte Namensform nicht erkannt');
assert.ok(findExistingTrack(root, 'K/DA', 'VILLAIN'), 'neue Namensform nicht erkannt');
assert.equal(findExistingTrack(root, 'K/DA', 'POP/STARS'), null);
fs.rmSync(root, { recursive: true, force: true });
});