import "./test-helpers/setup-localstorage.mjs"; import assert from "node:assert/strict"; const { buildRepostEvent } = await import("../js/nostrEventSchemas.js"); const { repostEvent, mirrorVideoEvent, } = await import("../js/nostr/publishHelpers.js"); const resolveActiveSignerStub = () => ({ signEvent: async (event) => ({ ...event, id: `${event.kind}-${event.created_at}`, sig: "active-signature", }), }); const shouldRequestExtensionPermissionsStub = () => false; const signEventWithPrivateKeyStub = (event) => ({ ...event, id: `${event.kind}-${event.created_at}-session`, sig: "session-signature", }); const inferMimeTypeStub = (url = "") => { if (typeof url !== "string") { return ""; } if (url.toLowerCase().endsWith(".mp4")) { return "video/mp4"; } if (url.toLowerCase().endsWith(".webm")) { return "video/webm"; } return ""; }; const eventToAddressPointerStub = () => ""; function createPublishClient({ actorPubkey = "", sessionPubkey = "", failPublish = false } = {}) { const publishCalls = []; const pool = { publish(urls, event) { publishCalls.push({ urls, event }); return { on(eventName, handler) { if (!failPublish && eventName === "ok") { handler(); } if (failPublish && eventName === "failed") { handler(new Error("publish failed")); } return true; }, }; }, }; const client = { pubkey: actorPubkey, relays: ["wss://relay.example"], writeRelays: ["wss://relay.example"], allEvents: new Map(), rawEvents: new Map(), pool, ensurePool: async () => pool, ensureSessionActor: async () => sessionPubkey, ensureExtensionPermissions: async () => ({ ok: true }), fetchRawEventById: async (id, { relays: requestedRelays } = {}) => { void requestedRelays; return client.rawEvents.get(id) || null; }, countEventsAcrossRelays: async () => ({ total: 0, perRelay: [] }), }; if (sessionPubkey) { client.sessionActor = { pubkey: sessionPubkey, privateKey: "session-private-key" }; } return { client, publishCalls }; } (function testBuildRepostEventIncludesPointerTags() { const createdAt = 1_700_000_000; const event = buildRepostEvent({ pubkey: "actorpubkey", created_at: createdAt, eventId: "event123", eventRelay: "wss://origin", publishRelay: "wss://relay.example", address: "30078:deadbeef:identifier", addressRelay: "wss://address", authorPubkey: "deadbeef", targetKind: 1, }); assert.equal(event.kind, 6); assert.equal(event.pubkey, "actorpubkey"); assert.equal(event.created_at, createdAt); assert.deepEqual(event.tags[0], ["e", "event123", "wss://origin"]); assert.deepEqual(event.tags[1], ["a", "30078:deadbeef:identifier", "wss://address"]); assert.deepEqual(event.tags[2], ["p", "deadbeef"]); })(); (function testBuildRepostEventRequiresRelay() { const createdAt = 1_700_000_001; assert.throws( () => buildRepostEvent({ pubkey: "actorpubkey", created_at: createdAt, eventId: "event456", authorPubkey: "cafebabe", targetKind: 30078, }), /missing-event-relay/, ); })(); await (async function testRepostEventUsesSessionActorAndDerivesAddress() { const eventId = "test-event"; const authorPubkey = "f".repeat(64); const sessionPubkey = "a".repeat(64); const { client } = createPublishClient({ sessionPubkey }); client.rawEvents.set(eventId, { id: eventId, pubkey: authorPubkey, relays: ["wss://relay.example"], }); client.allEvents.set(eventId, { id: eventId, pubkey: authorPubkey, videoRootId: "root-id", title: "Video", url: "https://cdn.example/video.mp4", }); const result = await repostEvent({ client, eventId, options: { pointer: ["e", eventId], authorPubkey, }, resolveActiveSigner: () => null, shouldRequestExtensionPermissions: shouldRequestExtensionPermissionsStub, signEventWithPrivateKey: signEventWithPrivateKeyStub, eventToAddressPointer: eventToAddressPointerStub, }); assert.equal(result.ok, true); assert.equal(result.sessionActor, true); assert.equal(result.signerPubkey, sessionPubkey); assert.equal(result.event?.kind, 16); const tags = result.event?.tags || []; assert.ok(Array.isArray(tags), "Repost event should include tags"); assert.deepEqual(tags[0], ["e", eventId, "wss://relay.example"]); assert.equal( tags.some((tag) => Array.isArray(tag) && tag[0] === "a" && tag[1].startsWith("30078:")), true, "Repost event should include an address pointer", ); assert.equal( tags.some((tag) => Array.isArray(tag) && tag[0] === "p" && tag[1] === authorPubkey), true, "Repost event should include a p tag", ); })(); await (async function testRepostEventHandlesPublishFailure() { const eventId = "repost-fail"; const authorPubkey = "d".repeat(64); const sessionPubkey = "b".repeat(64); const { client } = createPublishClient({ sessionPubkey, failPublish: true }); client.allEvents.set(eventId, { id: eventId, pubkey: authorPubkey, videoRootId: "root-id", title: "Video", }); const result = await repostEvent({ client, eventId, options: { pointer: ["e", eventId, "wss://origin"], authorPubkey, }, resolveActiveSigner: resolveActiveSignerStub, shouldRequestExtensionPermissions: shouldRequestExtensionPermissionsStub, signEventWithPrivateKey: signEventWithPrivateKeyStub, eventToAddressPointer: eventToAddressPointerStub, }); assert.equal(result.ok, false); assert.equal(result.error, "publish-rejected"); assert.ok(result.details instanceof Error); })(); await (async function testRepostEventFailsWhenRelayMissing() { const eventId = "repost-no-relay"; const authorPubkey = "c".repeat(64); const sessionPubkey = "e".repeat(64); const { client } = createPublishClient({ sessionPubkey }); client.fetchRawEventById = async () => null; const result = await repostEvent({ client, eventId, options: { pointer: ["e", eventId], authorPubkey, }, resolveActiveSigner: resolveActiveSignerStub, shouldRequestExtensionPermissions: shouldRequestExtensionPermissionsStub, signEventWithPrivateKey: signEventWithPrivateKeyStub, eventToAddressPointer: eventToAddressPointerStub, }); assert.equal(result.ok, false); assert.equal(result.error, "missing-event-relay"); assert.ok(Array.isArray(result.details?.relays)); })(); await (async function testRepostEventSerializesKindOneTargetsWhenSourceAvailable() { const eventId = "repost-kind1-content"; const authorPubkey = "d".repeat(64); const sessionPubkey = "f".repeat(64); const { client } = createPublishClient({ sessionPubkey }); const rawEvent = { id: eventId, pubkey: authorPubkey, created_at: 1_700_000_100, kind: 1, tags: [], content: "hello from kind1", }; client.rawEvents.set(eventId, rawEvent); const result = await repostEvent({ client, eventId, options: { pointer: ["e", eventId, "wss://origin"], authorPubkey, }, resolveActiveSigner: resolveActiveSignerStub, shouldRequestExtensionPermissions: shouldRequestExtensionPermissionsStub, signEventWithPrivateKey: signEventWithPrivateKeyStub, eventToAddressPointer: eventToAddressPointerStub, }); assert.equal(result.ok, true); const expectedContent = JSON.stringify(rawEvent); assert.equal(result.event?.kind, 6); assert.equal(result.event?.content, expectedContent); assert.equal(result.source?.serialized, expectedContent); assert.equal(result.source?.raw, rawEvent); })(); await (async function testRepostEventSerializesGenericTargetsWhenSourceAvailable() { const eventId = "repost-kind30078-content"; const authorPubkey = "e".repeat(64); const sessionPubkey = "g".repeat(64); const { client } = createPublishClient({ sessionPubkey }); const identifier = "video-identifier"; const rawEvent = { id: eventId, pubkey: authorPubkey, created_at: 1_700_000_200, kind: 30078, tags: [["d", identifier]], content: JSON.stringify({ version: 3, title: "Demo" }), }; client.rawEvents.set(eventId, rawEvent); const address = `30078:${authorPubkey}:${identifier}`; const result = await repostEvent({ client, eventId, options: { pointer: ["e", eventId, "wss://origin"], authorPubkey, address, addressRelay: "wss://origin", }, resolveActiveSigner: resolveActiveSignerStub, shouldRequestExtensionPermissions: shouldRequestExtensionPermissionsStub, signEventWithPrivateKey: signEventWithPrivateKeyStub, eventToAddressPointer: eventToAddressPointerStub, }); assert.equal(result.ok, true); const expectedContent = JSON.stringify(rawEvent); assert.equal(result.event?.kind, 16); assert.equal(result.event?.content, expectedContent); assert.equal(result.source?.serialized, expectedContent); assert.equal(result.source?.raw, rawEvent); const kTag = result.event?.tags?.find( (tag) => Array.isArray(tag) && tag[0] === "k", ); assert.deepEqual(kTag, ["k", String(rawEvent.kind)]); })(); await (async function testMirrorEventIncludesHostedMetadata() { const eventId = "mirror-event"; const actorPubkey = "1".repeat(64); const { client } = createPublishClient({ actorPubkey }); const result = await mirrorVideoEvent({ client, eventId, options: { url: "https://videos.example/video.mp4", magnet: "magnet:?xt=urn:btih:abc", thumbnail: "https://videos.example/thumb.jpg", description: "Sample clip", title: "Sample", }, resolveActiveSigner: resolveActiveSignerStub, shouldRequestExtensionPermissions: shouldRequestExtensionPermissionsStub, signEventWithPrivateKey: signEventWithPrivateKeyStub, inferMimeTypeFromUrl: inferMimeTypeStub, }); assert.equal(result.ok, true); const tags = result.event?.tags; assert.ok(Array.isArray(tags)); const tagValues = Object.fromEntries(tags.map((tag) => [tag[0], tag.slice(1)])); assert.equal(tagValues.url[0], "https://videos.example/video.mp4"); assert.equal(tagValues.m[0], "video/mp4"); assert.equal(tagValues.thumb[0], "https://videos.example/thumb.jpg"); assert.equal(tagValues.alt[0], "Sample clip"); assert.equal(tagValues.magnet[0], "magnet:?xt=urn:btih:abc"); })(); await (async function testMirrorSkipsMagnetWhenPrivate() { const eventId = "private-mirror"; const actorPubkey = "2".repeat(64); const { client } = createPublishClient({ actorPubkey }); const result = await mirrorVideoEvent({ client, eventId, options: { url: "https://videos.example/private.mp4", magnet: "magnet:?xt=urn:btih:hidden", isPrivate: true, }, resolveActiveSigner: resolveActiveSignerStub, shouldRequestExtensionPermissions: shouldRequestExtensionPermissionsStub, signEventWithPrivateKey: signEventWithPrivateKeyStub, inferMimeTypeFromUrl: inferMimeTypeStub, }); assert.equal(result.ok, true); const tags = result.event?.tags || []; assert.equal( tags.some((tag) => Array.isArray(tag) && tag[0] === "magnet"), false, "Private mirrors must omit the magnet tag", ); })(); await (async function testMirrorEventPublishFailureIsSurfaced() { const eventId = "mirror-fail"; const actorPubkey = "3".repeat(64); const { client } = createPublishClient({ actorPubkey, failPublish: true }); const result = await mirrorVideoEvent({ client, eventId, options: { url: "https://videos.example/video.mp4", title: "Failure", }, resolveActiveSigner: resolveActiveSignerStub, shouldRequestExtensionPermissions: shouldRequestExtensionPermissionsStub, signEventWithPrivateKey: signEventWithPrivateKeyStub, inferMimeTypeFromUrl: inferMimeTypeStub, }); assert.equal(result.ok, false); assert.equal(result.error, "publish-rejected"); assert.ok(result.details instanceof Error); })(); console.log("nostr boost action tests passed");