Files
paulandClaude Opus 4.8 e06c983b16 feat: optional direct Bandcamp/Apple/YouTube links via Odesli (flag-gated) (closes task/odesli-direct-cross-platform-links)
Source: task/odesli-direct-cross-platform-links (plan/improvement-backlog) — bandcamp was only a Google search; Odesli gives direct cross-platform links for Spotify-resolved names.

- service/odesli.js: makeOdesli({fetch,baseUrl}) -> getLinks(spotifyUrl); pure parseOdesli core; bounded AbortSignal.timeout; non-200/timeout/garbage -> {} (graceful).
- resolver.js: opt-in ODESLI / ODESLI_URL flag (default OFF). When ON, enrich each Spotify-resolved result with additive bandcamp/appleMusic/youtube fields (isHttpUrl-validated), cached per Spotify url. OFF -> no call, byte-identical behavior. /health + startup log surface the flag.
- extension: a tiny additive "bc" link to a direct Bandcamp page when result.bandcamp is present (reuses isSafeHttpUrl); primary artist/album/spotify/google rendering unchanged. styles.css: bandcamp-teal secondary link.
- odesli.test.js + odesli.fixture.json: parseOdesli unit-tested against a real captured Odesli response (Blood Incantation album -> direct bandcamp; absent apple/youtube omitted; junk -> {}); getLinks 200/non-200/throw/parse-error paths.
- docs: .env.example, README, AGENTS.md note the opt-in flag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 02:41:30 +00:00

158 lines
8.0 KiB
JavaScript

// Unit tests for the optional Odesli (song.link) enrichment client (run with `node --test`).
// Zero-dep, hermetic: parseOdesli runs against a REAL captured response (odesli.fixture.json —
// Blood Incantation "Hidden History of the Human Race", a Spotify ALBUM url that Odesli resolves
// to a direct Bandcamp page), and getLinks runs against an injected fake fetch (no network).
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { parseOdesli, makeOdesli, shouldEnrich } from "./odesli.js";
// The captured live response. Its linksByPlatform has bandcamp (+ many other stores) but no
// appleMusic / youtube — exactly the real-world underground-album case the linker targets.
const fixture = JSON.parse(readFileSync(new URL("./odesli.fixture.json", import.meta.url), "utf8"));
test("parseOdesli: extracts the direct Bandcamp url from the real captured response", () => {
const out = parseOdesli(fixture);
assert.equal(
out.bandcamp,
"https://darkdescentrecords.bandcamp.com/album/hidden-history-of-the-human-race",
"the real bandcamp url is pulled from linksByPlatform.bandcamp.url",
);
});
test("parseOdesli: a platform absent from the response is OMITTED (not null/undefined keys)", () => {
const out = parseOdesli(fixture);
// This album has no appleMusic / youtube on Odesli — those fields must be absent, not present.
assert.ok(!("appleMusic" in out), "absent appleMusic -> field omitted");
assert.ok(!("youtube" in out), "absent youtube -> field omitted");
assert.deepEqual(Object.keys(out).sort(), ["bandcamp"], "only the present platform appears");
});
test("parseOdesli: extracts bandcamp + appleMusic + youtube when all three are present", () => {
// Odesli's live Apple/YouTube enrichment can be empty for a given album (as in the fixture), so a
// shape-complete case proves all three extract correctly when the platform IS present.
const all = {
linksByPlatform: {
bandcamp: { url: "https://artist.bandcamp.com/album/x" },
appleMusic: { url: "https://music.apple.com/us/album/x/123" },
youtube: { url: "https://www.youtube.com/watch?v=abc" },
tidal: { url: "https://tidal.com/album/999" }, // a platform we don't surface -> ignored
},
};
assert.deepEqual(parseOdesli(all), {
bandcamp: "https://artist.bandcamp.com/album/x",
appleMusic: "https://music.apple.com/us/album/x/123",
youtube: "https://www.youtube.com/watch?v=abc",
});
});
test("parseOdesli: junk / empty / missing linksByPlatform -> {}", () => {
assert.deepEqual(parseOdesli(null), {}, "null body");
assert.deepEqual(parseOdesli(undefined), {}, "undefined body");
assert.deepEqual(parseOdesli({}), {}, "no linksByPlatform key");
assert.deepEqual(parseOdesli({ linksByPlatform: null }), {}, "null linksByPlatform");
assert.deepEqual(parseOdesli({ linksByPlatform: "nope" }), {}, "non-object linksByPlatform");
assert.deepEqual(parseOdesli({ linksByPlatform: {} }), {}, "empty linksByPlatform");
assert.deepEqual(parseOdesli("garbage"), {}, "string body");
assert.deepEqual(
parseOdesli({ linksByPlatform: { bandcamp: {} } }),
{},
"platform entry with no url -> omitted",
);
assert.deepEqual(
parseOdesli({ linksByPlatform: { bandcamp: { url: 42 } } }),
{},
"non-string url -> omitted",
);
});
// --- shouldEnrich gate (album-only) --------------------------------------------
test("shouldEnrich: TRUE only for an album result with a direct Spotify url", () => {
const sp = "https://open.spotify.com/album/0SdKqbCAKdkEFPW5NyzK7R";
assert.equal(shouldEnrich({ kind: "album", spotify: sp }), true, "album + spotify url -> enrich");
// Artist results are skipped — Odesli 405s on artist urls, so calling it would be a wasted trip.
assert.equal(shouldEnrich({ kind: "artist", spotify: sp }), false, "artist -> skip");
// A Google-fallback result (no spotify url) is never enriched, regardless of kind.
assert.equal(shouldEnrich({ kind: "album", google: "https://www.google.com/search?q=x" }), false, "no spotify url -> skip");
assert.equal(shouldEnrich({ kind: "album", spotify: null }), false, "null spotify -> skip");
assert.equal(shouldEnrich(null), false, "null result -> skip");
assert.equal(shouldEnrich({ spotify: sp }), false, "missing kind (defaults artist-ish) -> skip");
});
test("enrichment gate: an artist result makes ZERO Odesli calls; an album result makes one", async () => {
// A getLinks counter standing in for the resolver's enrichWithOdesli, which guards on shouldEnrich.
const state = { calls: 0 };
const getLinks = async () => { state.calls += 1; return { bandcamp: "https://x.bandcamp.com/album/y" }; };
const enrich = async (result) => {
if (!shouldEnrich(result)) return result; // the real guard
const links = await getLinks(result.spotify); // only album+spotify reaches here
if (typeof links.bandcamp === "string") result.bandcamp = links.bandcamp;
return result;
};
const sp = "https://open.spotify.com/album/0SdKqbCAKdkEFPW5NyzK7R";
const artist = await enrich({ name: "Carcass", kind: "artist", spotify: sp });
assert.equal(state.calls, 0, "artist result -> Odesli NOT called (no wasted 405)");
assert.ok(!("bandcamp" in artist), "artist result is left unenriched");
const album = await enrich({ name: "Heartwork", kind: "album", spotify: sp });
assert.equal(state.calls, 1, "album result -> exactly one Odesli call");
assert.equal(album.bandcamp, "https://x.bandcamp.com/album/y", "album result is enriched");
});
// --- getLinks (injected fetch) -------------------------------------------------
// A fetch fake that records the requested url and returns a scripted response (or throws).
function fakeFetch({ ok = true, status = 200, body = {}, throws = null } = {}) {
const state = { calls: 0, url: null, hadSignal: false };
const fetch = async (url, opts) => {
state.calls += 1;
state.url = url;
state.hadSignal = Boolean(opts && opts.signal);
if (throws) throw throws;
return { ok, status, json: async () => body };
};
return { fetch, state };
}
test("getLinks: a 200 is parsed; the request hits the right endpoint with a timeout signal", async () => {
const { fetch, state } = fakeFetch({ body: fixture });
const od = makeOdesli({ fetch });
const spUrl = "https://open.spotify.com/album/0SdKqbCAKdkEFPW5NyzK7R";
const out = await od.getLinks(spUrl);
assert.equal(out.bandcamp, "https://darkdescentrecords.bandcamp.com/album/hidden-history-of-the-human-race");
assert.equal(state.calls, 1);
assert.ok(
state.url === `https://api.song.link/v1-alpha.1/links?url=${encodeURIComponent(spUrl)}`,
"GET /links?url=<encoded spotify url> against the default base",
);
assert.ok(state.hadSignal, "an AbortSignal.timeout is passed (bounded like the other calls)");
});
test("getLinks: a non-200 returns {} (graceful — e.g. 405 on a non-entity url)", async () => {
const { fetch } = fakeFetch({ ok: false, status: 405, body: { code: "UNSUPPORTED_URL" } });
const od = makeOdesli({ fetch });
assert.deepEqual(await od.getLinks("https://open.spotify.com/artist/xyz"), {});
});
test("getLinks: a network/timeout throw returns {} (no crash)", async () => {
const { fetch } = fakeFetch({ throws: new Error("AbortError: timed out") });
const od = makeOdesli({ fetch });
assert.deepEqual(await od.getLinks("https://open.spotify.com/album/abc"), {});
});
test("getLinks: a JSON parse error returns {} (graceful)", async () => {
const fetch = async () => ({ ok: true, status: 200, json: async () => { throw new SyntaxError("bad json"); } });
const od = makeOdesli({ fetch });
assert.deepEqual(await od.getLinks("https://open.spotify.com/album/abc"), {});
});
test("getLinks: baseUrl override is honored", async () => {
const { fetch, state } = fakeFetch({ body: { linksByPlatform: {} } });
const od = makeOdesli({ fetch, baseUrl: "http://localhost:9999/v1" });
await od.getLinks("https://open.spotify.com/album/abc");
assert.ok(state.url.startsWith("http://localhost:9999/v1/links?url="), "uses the injected baseUrl");
});