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>
158 lines
8.0 KiB
JavaScript
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");
|
|
});
|