mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Network wiring on top of the pure playlistService core: - fetchCreatorPlaylists(pubkey) — lists kind-30082 by author via the L1 SubscriptionManager, collapses to newest-per-`d` (NIP-33 replaceable), drops empty playlists from the listing (a cleared playlist republishes empty), sorts newest-first. - fetchPlaylist(pubkey, id) — newest matching event, parsed. - publishPlaylist(playlist) — build + sign + publish via nostrClient.signAndPublishEvent (active signer), replacing the same `d`. All entry points take an injectable client for hermetic testing. tests/playlist-facade.test.mjs (5): newest-per-id + empty/other-author drop + sort, includeEmpty, blank/no-relay guards, fetch-one newest, publish emits a kind-30082 event with the right d/title/ordered a-refs under the "playlist" context. Mock client; no relays/signer. Lint clean; 5/5. Co-Authored-By: Claude Opus 4.8 <[email protected]>
139 lines
5.0 KiB
JavaScript
139 lines
5.0 KiB
JavaScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import {
|
|
fetchCreatorPlaylists,
|
|
fetchPlaylist,
|
|
publishPlaylist,
|
|
} from "../js/playlists/playlistFacade.js";
|
|
import {
|
|
buildPlaylistEvent,
|
|
PLAYLIST_KIND,
|
|
} from "../js/nostrEventSchemas.js";
|
|
|
|
const AUTHOR = "a".repeat(64);
|
|
const OTHER = "c".repeat(64);
|
|
const coord = (d) => `30078:${"b".repeat(64)}:${d}`;
|
|
|
|
function playlistEvent({ pubkey = AUTHOR, id, title, coords = [], created_at }) {
|
|
const event = buildPlaylistEvent({
|
|
pubkey,
|
|
created_at,
|
|
dTagValue: id,
|
|
title,
|
|
videoCoordinates: coords,
|
|
});
|
|
event.id = `id-${id}-${created_at}`;
|
|
return event;
|
|
}
|
|
|
|
// Mock client: a subscription manager whose list() filters an in-memory event
|
|
// set by kinds/authors/#d, plus a signAndPublishEvent that captures the event.
|
|
function mockClient({ events = [], onPublish } = {}) {
|
|
return {
|
|
relays: ["wss://mock.relay"],
|
|
getSubscriptionManager: () => ({
|
|
list: async ({ filters }) => {
|
|
const f = filters[0] || {};
|
|
return events.filter((ev) => {
|
|
if (f.kinds && !f.kinds.includes(ev.kind)) return false;
|
|
if (f.authors && !f.authors.includes(ev.pubkey)) return false;
|
|
if (f["#d"]) {
|
|
const d = ev.tags.find((t) => t[0] === "d")?.[1];
|
|
if (!f["#d"].includes(d)) return false;
|
|
}
|
|
return true;
|
|
});
|
|
},
|
|
}),
|
|
signAndPublishEvent: async (event, options) => {
|
|
if (onPublish) onPublish(event, options);
|
|
return { signedEvent: { ...event, id: "signed-id", sig: "sig" } };
|
|
},
|
|
};
|
|
}
|
|
|
|
test("fetchCreatorPlaylists returns newest-per-id, drops empties, sorts newest-first", async () => {
|
|
const events = [
|
|
// Same id "a" published twice — the newer (created_at 200) must win.
|
|
playlistEvent({ id: "a", title: "A old", coords: [coord("v1")], created_at: 100 }),
|
|
playlistEvent({ id: "a", title: "A new", coords: [coord("v1"), coord("v2")], created_at: 200 }),
|
|
playlistEvent({ id: "b", title: "B", coords: [coord("v9")], created_at: 300 }),
|
|
// Empty playlist — dropped from the listing by default.
|
|
playlistEvent({ id: "empty", title: "Empty", coords: [], created_at: 400 }),
|
|
// Another author's playlist — must not appear.
|
|
playlistEvent({ pubkey: OTHER, id: "x", title: "Other", coords: [coord("v0")], created_at: 500 }),
|
|
];
|
|
|
|
const playlists = await fetchCreatorPlaylists(AUTHOR, { client: mockClient({ events }) });
|
|
|
|
assert.deepEqual(playlists.map((p) => p.id), ["b", "a"], "sorted newest-first, empties + other author dropped");
|
|
const a = playlists.find((p) => p.id === "a");
|
|
assert.equal(a.title, "A new", "newest copy wins");
|
|
assert.equal(a.items.length, 2);
|
|
});
|
|
|
|
test("fetchCreatorPlaylists can include empty playlists when asked", async () => {
|
|
const events = [playlistEvent({ id: "empty", title: "Empty", coords: [], created_at: 1 })];
|
|
const playlists = await fetchCreatorPlaylists(AUTHOR, {
|
|
client: mockClient({ events }),
|
|
includeEmpty: true,
|
|
});
|
|
assert.deepEqual(playlists.map((p) => p.id), ["empty"]);
|
|
});
|
|
|
|
test("fetchCreatorPlaylists returns [] for blank pubkey or no relays", async () => {
|
|
assert.deepEqual(await fetchCreatorPlaylists("", { client: mockClient({}) }), []);
|
|
const noRelays = { ...mockClient({}), relays: [] };
|
|
assert.deepEqual(await fetchCreatorPlaylists(AUTHOR, { client: noRelays }), []);
|
|
});
|
|
|
|
test("fetchPlaylist returns the newest matching playlist parsed", async () => {
|
|
const events = [
|
|
playlistEvent({ id: "list", title: "old", coords: [coord("v1")], created_at: 10 }),
|
|
playlistEvent({ id: "list", title: "new", coords: [coord("v1"), coord("v2")], created_at: 20 }),
|
|
playlistEvent({ id: "other", title: "nope", coords: [coord("v3")], created_at: 99 }),
|
|
];
|
|
const parsed = await fetchPlaylist(AUTHOR, "list", { client: mockClient({ events }) });
|
|
assert.equal(parsed.title, "new");
|
|
assert.equal(parsed.id, "list");
|
|
assert.equal(parsed.items.length, 2);
|
|
|
|
assert.equal(await fetchPlaylist(AUTHOR, "missing", { client: mockClient({ events }) }), null);
|
|
});
|
|
|
|
test("publishPlaylist builds a kind-30082 event and publishes with the playlist context", async () => {
|
|
let published = null;
|
|
let ctx = null;
|
|
const client = mockClient({
|
|
onPublish: (event, options) => {
|
|
published = event;
|
|
ctx = options?.context;
|
|
},
|
|
});
|
|
|
|
const signed = await publishPlaylist(
|
|
{
|
|
pubkey: AUTHOR,
|
|
id: "new-list",
|
|
title: "Fresh",
|
|
items: [
|
|
{ type: "a", value: coord("v1") },
|
|
{ type: "a", value: coord("v2") },
|
|
],
|
|
created_at: 777,
|
|
},
|
|
{ client },
|
|
);
|
|
|
|
assert.equal(published.kind, PLAYLIST_KIND);
|
|
assert.equal(ctx, "playlist");
|
|
assert.equal(published.tags.find((t) => t[0] === "d")?.[1], "new-list");
|
|
assert.equal(published.tags.find((t) => t[0] === "title")?.[1], "Fresh");
|
|
assert.deepEqual(
|
|
published.tags.filter((t) => t[0] === "a").map((t) => t[1]),
|
|
[coord("v1"), coord("v2")],
|
|
);
|
|
assert.equal(signed.id, "signed-id", "returns the signed event");
|
|
});
|