Files
SoulStream/tests/preview/catalog-match.test.mjs
T

319 lines
12 KiB
JavaScript

// The catalog matcher, against the real import that exposed it: a 258-row
// playlist where 41 rows read "not in catalog" while plainly being in it.
// Every case below is a pairing taken from that run.
import { test } from "node:test";
import assert from "node:assert/strict";
import {
splitVersion,
qualifierClass,
primaryArtist,
bandCore,
pickCatalogMatch,
catalogQueries,
findCatalogTrack,
pickCrossScript,
} from "../../src/lib/catalogMatch.js";
const track = (artist, title, duration_secs, rank = 500_000) => ({
id: 1,
artist,
title,
duration_secs,
rank,
});
test("version wording is split off, not thrown away", () => {
assert.deepEqual(splitVersion("Bohemian Rhapsody - Remastered 2011"), {
base: "Bohemian Rhapsody",
qualifier: "Remastered 2011",
});
assert.deepEqual(splitVersion("Lola (2020 Stereo Remaster)"), {
base: "Lola",
qualifier: "2020 Stereo Remaster",
});
assert.deepEqual(splitVersion("グッドバイ -album version-"), {
base: "グッドバイ",
qualifier: "album version",
});
assert.deepEqual(splitVersion("Here's to you (feat. Joan Baez)"), {
base: "Here's to you",
qualifier: "feat. Joan Baez",
});
// A hyphen inside a word is part of the name, never a qualifier.
assert.deepEqual(splitVersion("A-Punk"), { base: "A-Punk", qualifier: "" });
// A dash-separated title that names no version keeps its shape.
assert.deepEqual(splitVersion("Sunday Bloody Sunday - Live"), {
base: "Sunday Bloody Sunday",
qualifier: "Live",
});
assert.deepEqual(splitVersion("Detroit Rock City"), {
base: "Detroit Rock City",
qualifier: "",
});
});
test("qualifiers are classed by whether they change the recording", () => {
assert.equal(qualifierClass(""), "none");
assert.equal(qualifierClass("2011 Remaster"), "edition");
assert.equal(qualifierClass("Remastered 2011"), "edition");
assert.equal(qualifierClass("2020 Stereo Remaster"), "edition");
assert.equal(qualifierClass("feat. Joan Baez"), "edition");
assert.equal(qualifierClass("Samurai Breaks Remix"), "identity");
assert.equal(qualifierClass("Acoustic"), "identity");
assert.equal(qualifierClass("Live"), "identity");
assert.equal(qualifierClass("Karaoke Version"), "identity");
});
test("the same master worded differently now matches", () => {
// The headline failure: Spotify's wording vs Deezer's.
const hit = pickCatalogMatch(
[track("Queen", "Bohemian Rhapsody (2011 Remaster)", 358)],
{ artist: "Queen", title: "Bohemian Rhapsody - Remastered 2011", duration_secs: 354 },
);
assert.equal(hit?.title, "Bohemian Rhapsody (2011 Remaster)");
assert.ok(
pickCatalogMatch([track("The Kinks", "Lola (Stereo Remastered 2020)", 241)], {
artist: "The Kinks",
title: "Lola (2020 Stereo Remaster)",
duration_secs: 241,
}),
);
// A plain catalog title answering a remastered request, and vice versa.
assert.ok(
pickCatalogMatch([track("David Bowie", "Suffragette City", 206)], {
artist: "David Bowie",
title: "Suffragette City - 2012 Remastered Version",
duration_secs: 206,
}),
);
});
test("artist wording: ampersands and comma-credited guests", () => {
assert.ok(
pickCatalogMatch([track("Joan Jett and the Blackhearts", "Bad Reputation", 170)], {
artist: "Joan Jett & the Blackhearts",
title: "Bad Reputation",
duration_secs: 170,
}),
);
assert.ok(
pickCatalogMatch([track("Ennio Morricone", "Here's to You", 190)], {
artist: "Ennio Morricone, Joan Baez",
title: "Here's to you (feat. Joan Baez)",
duration_secs: 188,
}),
);
assert.ok(
pickCatalogMatch([track("King Krule", "Seagirl", 202)], {
artist: "King Krule, Raveena",
title: "Seagirl",
duration_secs: 202,
}),
);
assert.equal(primaryArtist("Ennio Morricone, Joan Baez"), "Ennio Morricone");
// "and" is part of the act's name, never a credit separator.
assert.equal(primaryArtist("Joan Jett and the Blackhearts"), "Joan Jett and the Blackhearts");
// Deezer files the STUDIO cut under the shorter credit and gives the
// live takes the full band name; both must reach the same record.
assert.ok(
pickCatalogMatch([track("Joan Jett", "Bad Reputation", 169)], {
artist: "Joan Jett & the Blackhearts",
title: "Bad Reputation",
duration_secs: 170,
}),
);
assert.equal(bandCore("Joan Jett and the Blackhearts"), "joan jett");
// A bare word is never a backing band.
assert.equal(bandCore("Queen Latifah"), "queen latifah");
assert.equal(
pickCatalogMatch([track("Queen Latifah", "U.N.I.T.Y.", 200)], {
artist: "Queen",
title: "U.N.I.T.Y.",
duration_secs: 200,
}),
null,
);
});
test("a different PERFORMANCE still never answers for the original", () => {
// The DJ-set lesson: a remix must not match the track it samples.
assert.equal(
pickCatalogMatch([track("Rebound X", "Rhythm 'N' Gash", 240)], {
artist: "Rebound X",
title: "Rhythm 'N' Gash (Samurai Breaks Remix)",
duration_secs: 240,
}),
null,
);
// Nor the reverse: asking for the original must not get the remix.
assert.equal(
pickCatalogMatch([track("Rebound X", "Rhythm 'N' Gash (Samurai Breaks Remix)", 240)], {
artist: "Rebound X",
title: "Rhythm 'N' Gash",
duration_secs: 240,
}),
null,
);
// The acoustic/live re-recordings Deezer offered for Bad Reputation.
assert.equal(
pickCatalogMatch(
[
track("Joan Jett and the Blackhearts", "Bad Reputation (Acoustic)", 160),
track("Joan Jett and the Blackhearts", "Bad Reputation (Live)", 131),
],
{ artist: "Joan Jett & the Blackhearts", title: "Bad Reputation", duration_secs: 170 },
),
null,
);
});
test("covers, karaoke and namesakes are still refused", () => {
// What Deezer's plain search actually returned for A-Punk.
assert.equal(
pickCatalogMatch(
[
track("Midium", "A-Punk (Vampire Weekend cover)", 139),
track("Zoom Karaoke", "A-Punk (Originally By Vampire Weekend) [Karaoke Version]", 133),
],
{ artist: "Vampire Weekend", title: "A-Punk", duration_secs: 137 },
),
null,
);
// A short name inside a longer one is a coincidence, not a match.
assert.equal(
pickCatalogMatch([track("Fleetwood Mac", "Go Your Own Way", 223)], {
artist: "Fleetwood Mac",
title: "Go",
duration_secs: 223,
}),
null,
);
});
test("a badly disagreeing duration disqualifies a different cut", () => {
// The single edit Deezer offered against the album track.
assert.equal(
pickCatalogMatch([track("Eagles", "Lyin' Eyes (Single Version; 2018 Remaster)", 255)], {
artist: "Eagles",
title: "Lyin' Eyes - 2013 Remaster",
duration_secs: 381,
}),
null,
);
// With no duration known, the same pairing is allowed — nothing
// contradicts it, and a name match is all there is.
assert.ok(
pickCatalogMatch([track("Eagles", "Lyin' Eyes (Single Version; 2018 Remaster)", 255)], {
artist: "Eagles",
title: "Lyin' Eyes - 2013 Remaster",
duration_secs: null,
}),
);
});
test("the closest cut wins when several versions qualify", () => {
const hit = pickCatalogMatch(
[
track("Queen", "Bohemian Rhapsody (2011 Remaster)", 358, 900_000),
track("Queen", "Bohemian Rhapsody", 367, 950_000),
],
{ artist: "Queen", title: "Bohemian Rhapsody - Remastered 2011", duration_secs: 354 },
);
assert.equal(hit?.title, "Bohemian Rhapsody (2011 Remaster)");
});
test("the query ladder asks precisely before asking bluntly", () => {
assert.deepEqual(
catalogQueries({ artist: "Eagles", title: "Take It Easy - 2013 Remaster" }),
[
'artist:"Eagles" track:"Take It Easy"',
'artist:"Eagles" track:"Take It Easy - 2013 Remaster"',
"Eagles Take It Easy",
],
);
// Comma-credited guests are dropped from the query's artist.
assert.equal(
catalogQueries({ artist: "King Krule, Raveena", title: "Seagirl" })[0],
'artist:"King Krule" track:"Seagirl"',
);
});
test("the ladder stops at the first rung that answers", async () => {
const asked = [];
const hit = await findCatalogTrack(
async (query) => {
asked.push(query);
// Mirrors the measured reality: the precise query finds it, the
// plain one would have returned nothing.
return query.startsWith("artist:") ? [track("Vashti Bunyan", "Diamond Day", 107)] : [];
},
{ artist: "Vashti Bunyan", title: "Diamond Day", duration_secs: 107 },
);
assert.equal(hit?.title, "Diamond Day");
assert.equal(asked.length, 1);
});
test("a romanized catalog entry matches by duration, strictly", () => {
const want = { artist: "toe", title: "孤独の発明", duration_secs: 203 };
// The real pairing: Deezer files the track romanized.
assert.ok(pickCrossScript([track("toe", "Kodokunohatsumei", 203)], want));
// Two near-length candidates → ambiguous → null.
assert.equal(
pickCrossScript(
[track("toe", "Kodokunohatsumei", 203), track("toe", "Two Moons", 204)],
want,
),
null,
);
// No duration on the row → no anchor → null.
assert.equal(
pickCrossScript([track("toe", "Kodokunohatsumei", 203)], { ...want, duration_secs: null }),
null,
);
// A Latin-titled row never takes this path.
assert.equal(
pickCrossScript([track("toe", "Loneliness Will Shine", 356)], {
artist: "toe",
title: "Loneliness Will Shine",
duration_secs: 356,
}),
null,
);
});
test("the artist shelf is the last rung and answers what search cannot", async () => {
const asked = { search: 0, shelf: 0 };
const hit = await findCatalogTrack(
async () => {
asked.search += 1;
return []; // the search index has never heard of it
},
{ artist: "Mock Orange", title: "End of the World", duration_secs: null },
async () => {
asked.shelf += 1;
// What the supplemented artist page carries.
return [track("Mock Orange", "End of the World", 262)];
},
);
assert.equal(hit?.title, "End of the World");
assert.ok(asked.search >= 2);
assert.equal(asked.shelf, 1);
});
test("nothing matching still returns null after every rung", async () => {
const asked = [];
const hit = await findCatalogTrack(
async (query) => {
asked.push(query);
return [track("Someone Else", "A Different Song", 200)];
},
{ artist: "Mock Orange", title: "End of the World", duration_secs: 210 },
);
assert.equal(hit, null);
// Two rungs, not three: this title carries no version wording, so the
// base-name query and the full-title query are the same string.
assert.equal(asked.length, 2);
});