fix(web): resolve edition-qualified album titles against MusicBrainz

searchReleaseGroup used a strict exact-phrase releasegroup:"<album>", so
Last.fm album names with per-release edition/version qualifiers ("The Stranger
(Legacy Edition)", "Nevermind - Remastered") returned 0 hits → 404 → "No
MusicBrainz match", even though MB knows the artist and the base title.

Add stripEditionQualifiers (removes a trailing edition parenthetical/bracket or
" - " suffix only when it names an edition — deluxe/remaster/legacy/edition/…,
never a plain title parenthetical) and search in decreasing precision: exact
phrase on the given title → exact phrase on the stripped title → a loose
unquoted fallback. Clean titles still resolve in one request; albumPreferenceRank
ranking unchanged. Fixes both callers (/api/mb/release-group + /api/wanted).

Verified live against real MB: "The Stranger (Legacy Edition)"→The Stranger,
"Nevermind - Remastered"→Nevermind, Continuum unchanged. web 141 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan
2026-07-14 10:54:31 +02:00
parent 5e10dc24a5
commit d275a0ddd6
2 changed files with 72 additions and 4 deletions
+36 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect, vi, afterEach } from "vitest";
import { searchArtists, browseReleaseGroups, searchReleaseGroup } from "./musicbrainz";
import { searchArtists, browseReleaseGroups, searchReleaseGroup, stripEditionQualifiers } from "./musicbrainz";
function jsonResponse(data: unknown) {
return Promise.resolve({ ok: true, status: 200, json: () => Promise.resolve(data) } as Response);
@@ -76,4 +76,39 @@ describe("musicbrainz module", () => {
// the decoded query must contain the escaped quote, not a bare one that closes the phrase
expect(decodeURIComponent(url)).toContain('Back in \\"Black\\"');
});
it("retries with an edition-stripped title when the exact phrase finds nothing", async () => {
// Last.fm hands us "The Stranger (Legacy Edition)"; the strict phrase for that returns 0,
// then the stripped "The Stranger" resolves to the release group.
const fetchMock = vi
.fn()
.mockReturnValueOnce(jsonResponse({ "release-groups": [] }))
.mockReturnValueOnce(jsonResponse({ "release-groups": [
{ id: "rg-str", title: "The Stranger", "primary-type": "Album", "secondary-types": [], "first-release-date": "1977", "artist-credit": [{ artist: { id: "bj", name: "Billy Joel" } }] },
] }));
vi.stubGlobal("fetch", fetchMock);
const m = await searchReleaseGroup("Billy Joel", "The Stranger (Legacy Edition)");
expect(m?.rgMbid).toBe("rg-str");
// first attempt used the full title, second used the stripped title
expect(decodeURIComponent(fetchMock.mock.calls[0][0])).toContain("The Stranger (Legacy Edition)");
expect(decodeURIComponent(fetchMock.mock.calls[1][0])).toContain('releasegroup:"The Stranger"');
});
});
describe("stripEditionQualifiers", () => {
it("removes trailing edition parentheticals and suffixes", () => {
expect(stripEditionQualifiers("The Stranger (Legacy Edition)")).toBe("The Stranger");
expect(stripEditionQualifiers("21 (Deluxe)")).toBe("21");
expect(stripEditionQualifiers("Nevermind - Remastered")).toBe("Nevermind");
expect(stripEditionQualifiers("Rumours - Remastered 2011")).toBe("Rumours");
expect(stripEditionQualifiers("Blue [Deluxe Edition]")).toBe("Blue");
expect(stripEditionQualifiers("Vitalogy (2016 Remaster)")).toBe("Vitalogy");
});
it("leaves non-edition titles untouched", () => {
expect(stripEditionQualifiers("(What's the Story) Morning Glory?")).toBe("(What's the Story) Morning Glory?");
expect(stripEditionQualifiers("Continuum")).toBe("Continuum");
expect(stripEditionQualifiers("Songs in the Key of Life")).toBe("Songs in the Key of Life");
expect(stripEditionQualifiers("Give Up")).toBe("Give Up"); // "Up" is not a stray edition word
});
});