src/lib/preview/ — framework-free ESM modules, per design §12–14: - contracts.js: versioned manifest/item/source-ref factories; signed URLs structurally excluded from manifests; Apple identity first-class. - lease.js: discriminated MediaLease union; computeSafeForMs derives lifetimes provider-clock-first with fallback/contradiction handling (margins are ADRs backed by Phase 0 soak evidence). - redact.js: URL redaction (host+hash+exp only) and the HTTPS + exact host allowlist gate applied to initial URLs and every redirect. - manifest.js: artist (rank order, exact-id dedupe), album (disc/track sort, context inheritance) and fixture builders — pure, no fetching. - session.js: reducer with session/generation guards on every async action; keyed active item with derived playable indices/cursor; auto-advance under fresh generations; close is terminal. - resolver.js: PreviewResolver contract + scripted fixture resolver with classified failures (stale_lease/not_found/network/…). - transport.js: transport seam + deterministic fake (manual ticks). - focus.js: revision gateway (all main-player entry points; focus- internal tag) and the coordinator — acquisition only via a generation-bound silence ack, restore only with revisions unchanged. tests/preview/*.test.mjs run via `npm test` (node --test): 61 passing, covering the full Phase 1 list in IMPLEMENTATION-PLAN §6 including the exit gate: stale lease/play events are inert, no restoration after any simulated main command, and no component renders anywhere.
123 lines
4.7 KiB
JavaScript
123 lines
4.7 KiB
JavaScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { AVAILABILITY, isValidManifest } from "../../src/lib/preview/contracts.js";
|
|
import { buildArtistManifest, buildAlbumManifest, buildFixtureManifest } from "../../src/lib/preview/manifest.js";
|
|
|
|
const T0 = 1_700_000_000_000;
|
|
|
|
const catalogTrack = (id, over = {}) => ({
|
|
id,
|
|
title: `Track ${id}`,
|
|
artist: "Röyksopp",
|
|
album: "Melody A.M.",
|
|
album_id: 9,
|
|
cover: `cover-${id}`,
|
|
duration_secs: 219,
|
|
preview: `https://cdnt-preview.dzcdn.net/x-${id}.mp3?hdnea=exp=1784761537~hmac=aa`,
|
|
track_no: 1,
|
|
disk_number: 1,
|
|
unreleased: false,
|
|
...over,
|
|
});
|
|
|
|
test("artist manifest: rank order kept, exact provider-id dupes collapse", () => {
|
|
const m = buildArtistManifest({
|
|
artist: { id: 480, name: "Röyksopp", picture: "pic" },
|
|
popularTracks: [catalogTrack(1), catalogTrack(2), catalogTrack(1), catalogTrack(3)],
|
|
createdAtMs: T0,
|
|
});
|
|
assert.equal(m.items.length, 3); // dupe of id 1 collapsed
|
|
assert.deepEqual(m.items.map((i) => i.sources[0].providerTrackId), ["1", "2", "3"]);
|
|
assert.deepEqual(m.items.map((i) => i.sourceOrdinal), [0, 1, 2]);
|
|
assert.equal(m.context.kind, "artist");
|
|
assert.equal(m.context.sourceTrackCount, 4); // source count is truthful
|
|
assert.ok(isValidManifest(m));
|
|
});
|
|
|
|
test("artist manifest: same title, different provider ids do NOT collapse", () => {
|
|
const m = buildArtistManifest({
|
|
artist: { id: 480, name: "Röyksopp" },
|
|
popularTracks: [catalogTrack(1, { title: "Same" }), catalogTrack(2, { title: "Same" })],
|
|
createdAtMs: T0,
|
|
});
|
|
assert.equal(m.items.length, 2);
|
|
});
|
|
|
|
test("availability from catalog rows: preview/empty/unreleased", () => {
|
|
const m = buildArtistManifest({
|
|
artist: { id: 1, name: "A" },
|
|
popularTracks: [
|
|
catalogTrack(1),
|
|
catalogTrack(2, { preview: "" }),
|
|
catalogTrack(3, { preview: undefined }),
|
|
catalogTrack(4, { unreleased: true }),
|
|
],
|
|
createdAtMs: T0,
|
|
});
|
|
assert.deepEqual(
|
|
m.items.map((i) => i.initialAvailability),
|
|
[AVAILABILITY.AVAILABLE, AVAILABILITY.NOT_PROVIDED, AVAILABILITY.NOT_PROVIDED, AVAILABILITY.UNRELEASED]
|
|
);
|
|
// unreleased rows lose their mutating actions
|
|
assert.equal(m.items[3].actions.playFull, false);
|
|
assert.equal(m.items[3].actions.save, false);
|
|
});
|
|
|
|
test("no signed URL survives into the manifest", () => {
|
|
const m = buildArtistManifest({
|
|
artist: { id: 1, name: "A" },
|
|
popularTracks: [catalogTrack(1)],
|
|
createdAtMs: T0,
|
|
});
|
|
const json = JSON.stringify(m);
|
|
assert.ok(!json.includes("hdnea"), "signed URL leaked into manifest");
|
|
assert.ok(!json.includes("cdnt-preview"), "media host leaked into manifest");
|
|
});
|
|
|
|
test("album manifest: disc/track sort with stable fallback + context inheritance", () => {
|
|
const m = buildAlbumManifest({
|
|
album: { id: 77, title: "The Wall", artist: "Pink Floyd", cover: "wall.jpg" },
|
|
tracks: [
|
|
catalogTrack(21, { disk_number: 2, track_no: 1, album: "", album_id: null, cover: null }),
|
|
catalogTrack(11, { disk_number: 1, track_no: 1 }),
|
|
catalogTrack(12, { disk_number: 1, track_no: 2 }),
|
|
catalogTrack(13, { disk_number: null, track_no: null }), // sorts by response order within disc 1
|
|
],
|
|
createdAtMs: T0,
|
|
});
|
|
assert.deepEqual(m.items.map((i) => i.sources[0].providerTrackId), ["13", "11", "12", "21"]);
|
|
const disc2 = m.items[3];
|
|
assert.equal(disc2.album, "The Wall"); // inherited
|
|
assert.equal(disc2.albumId, "77");
|
|
assert.equal(disc2.artwork, "wall.jpg");
|
|
assert.equal(m.context.sourceTrackCount, 4);
|
|
});
|
|
|
|
test("fixture manifest: playlist duplicates keep distinct sessionKeys, shared recordingKey", () => {
|
|
const m = buildFixtureManifest({
|
|
kind: "playlist",
|
|
rows: [
|
|
{ providerTrackId: "9", title: "Song" },
|
|
{ providerTrackId: "8", title: "Other" },
|
|
{ providerTrackId: "9", title: "Song" }, // duplicate occurrence
|
|
],
|
|
createdAtMs: T0,
|
|
});
|
|
assert.equal(m.items.length, 3);
|
|
assert.notEqual(m.items[0].sessionKey, m.items[2].sessionKey);
|
|
assert.equal(m.items[0].recordingKey, m.items[2].recordingKey);
|
|
assert.ok(isValidManifest(m));
|
|
});
|
|
|
|
test("fixture manifest: apple identity is a first-class source ref", () => {
|
|
const m = buildFixtureManifest({
|
|
rows: [{ provider: "apple", providerTrackId: "ap-123", isrc: "USX9P1234567", matchConfidence: "isrc", title: "X" }],
|
|
createdAtMs: T0,
|
|
});
|
|
const src = m.items[0].sources[0];
|
|
assert.equal(src.provider, "apple");
|
|
assert.equal(src.providerTrackId, "ap-123");
|
|
assert.equal(src.matchConfidence, "isrc");
|
|
});
|