diff --git a/tests/nostr/decryptionSchemeCache.test.mjs b/tests/nostr/decryptionSchemeCache.test.mjs new file mode 100644 index 00000000..e056f49c --- /dev/null +++ b/tests/nostr/decryptionSchemeCache.test.mjs @@ -0,0 +1,91 @@ +import { test, describe, beforeEach, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import { + getLastSuccessfulScheme, + setLastSuccessfulScheme, + clearDecryptionSchemeCache, +} from "../../js/nostr/decryptionSchemeCache.js"; + +describe("decryptionSchemeCache", () => { + let originalDateNow; + let mockTime = 1000000; + + beforeEach(() => { + originalDateNow = Date.now; + // We need to override the global Date.now + Object.defineProperty(Date, "now", { + value: () => mockTime, + writable: true, + configurable: true // Important to allow restoring + }); + clearDecryptionSchemeCache(); + }); + + afterEach(() => { + if (originalDateNow) { + Object.defineProperty(Date, "now", { + value: originalDateNow, + writable: true, + configurable: true + }); + } + clearDecryptionSchemeCache(); + mockTime = 1000000; // Reset mock time + }); + + test("stores and retrieves a scheme", () => { + const pubkey = "pubkey1"; + const scheme = "nip44_v2"; + setLastSuccessfulScheme(pubkey, scheme); + assert.equal(getLastSuccessfulScheme(pubkey), scheme); + }); + + test("returns null for unknown pubkey", () => { + assert.equal(getLastSuccessfulScheme("unknown"), null); + }); + + test("handles invalid inputs gracefully", () => { + setLastSuccessfulScheme(null, "scheme"); + assert.equal(getLastSuccessfulScheme(null), null); + + setLastSuccessfulScheme("pubkey", null); + assert.equal(getLastSuccessfulScheme("pubkey"), null); + + // @ts-ignore + setLastSuccessfulScheme(123, "scheme"); + // @ts-ignore + assert.equal(getLastSuccessfulScheme(123), null); + }); + + test("expires entries after TTL (2 hours)", () => { + const pubkey = "pubkey_ttl"; + const scheme = "nip04"; + setLastSuccessfulScheme(pubkey, scheme); + + // Advance time by 2 hours + 1ms + mockTime += 2 * 60 * 60 * 1000 + 1; + + assert.equal(getLastSuccessfulScheme(pubkey), null); + }); + + test("does not expire entries before TTL", () => { + const pubkey = "pubkey_ttl_ok"; + const scheme = "nip04"; + setLastSuccessfulScheme(pubkey, scheme); + + // Advance time by 2 hours - 1ms + mockTime += 2 * 60 * 60 * 1000 - 1; + + assert.equal(getLastSuccessfulScheme(pubkey), scheme); + }); + + test("clears cache", () => { + const pubkey = "pubkey_clear"; + const scheme = "nip04"; + setLastSuccessfulScheme(pubkey, scheme); + + clearDecryptionSchemeCache(); + + assert.equal(getLastSuccessfulScheme(pubkey), null); + }); +}); diff --git a/tests/nostr/videoPayloadBuilder.test.mjs b/tests/nostr/videoPayloadBuilder.test.mjs new file mode 100644 index 00000000..b2c911de --- /dev/null +++ b/tests/nostr/videoPayloadBuilder.test.mjs @@ -0,0 +1,119 @@ +import { test, describe } from "node:test"; +import assert from "node:assert/strict"; +import { + extractVideoPublishPayload, + prepareVideoPublishPayload, +} from "../../js/nostr/videoPayloadBuilder.js"; + +describe("videoPayloadBuilder", () => { + describe("extractVideoPublishPayload", () => { + test("extracts videoData and nip71Metadata", () => { + const rawPayload = { + title: "My Video", + nip71: { genre: "Action" }, + }; + const { videoData, nip71Metadata } = extractVideoPublishPayload(rawPayload); + assert.equal(videoData.title, "My Video"); + assert.deepEqual(nip71Metadata, { genre: "Action" }); + }); + + test("handles legacyFormData structure", () => { + const rawPayload = { + legacyFormData: { title: "Legacy Video" }, + }; + const { videoData } = extractVideoPublishPayload(rawPayload); + assert.equal(videoData.title, "Legacy Video"); + }); + + test("normalizes boolean flags", () => { + const rawPayload = { + isNsfw: true, + isForKids: true, // Should be false if nsfw is true + }; + const { videoData } = extractVideoPublishPayload(rawPayload); + assert.equal(videoData.isNsfw, true); + assert.equal(videoData.isForKids, false); + }); + + test("normalizes boolean flags (kids only)", () => { + const rawPayload = { + isNsfw: false, + isForKids: true, + }; + const { videoData } = extractVideoPublishPayload(rawPayload); + assert.equal(videoData.isNsfw, false); + assert.equal(videoData.isForKids, true); + }); + }); + + describe("prepareVideoPublishPayload", () => { + const pubkey = "0000000000000000000000000000000000000000000000000000000000000001"; + + test("throws if pubkey is missing", async () => { + await assert.rejects( + async () => { + await prepareVideoPublishPayload({}, ""); + }, + { message: "Not logged in to publish video." } + ); + }); + + test("generates a valid event structure", async () => { + const videoPayload = { + title: "Test Video", + magnet: "magnet:?xt=urn:btih:1234567890abcdef1234567890abcdef12345678", + url: "https://example.com/video.mp4", + thumbnail: "https://example.com/thumb.jpg", + description: "Test description", + }; + + const result = await prepareVideoPublishPayload(videoPayload, pubkey); + + assert.ok(result.event); + assert.equal(result.event.pubkey, pubkey); + assert.equal(result.event.kind, 30078); // Assuming KIND_VIDEO_POST is 30078, check schema if fails + + const content = JSON.parse(result.event.content); + assert.equal(content.title, "Test Video"); + assert.equal(content.url, "https://example.com/video.mp4"); + assert.equal(content.magnet, "magnet:?xt=urn:btih:1234567890abcdef1234567890abcdef12345678"); + assert.equal(content.thumbnail, "https://example.com/thumb.jpg"); + assert.equal(content.description, "Test description"); + assert.equal(content.videoRootId, result.videoRootId); + }); + + test("generates unique d tag and videoRootId if not provided", async () => { + const result = await prepareVideoPublishPayload({}, pubkey); + assert.ok(result.videoRootId); + assert.ok(result.dTagValue); + assert.equal(result.videoRootId, result.dTagValue); + }); + + test("uses provided seriesIdentifier as d tag", async () => { + const result = await prepareVideoPublishPayload({ seriesIdentifier: "my-series" }, pubkey); + assert.equal(result.videoRootId, "my-series"); + assert.equal(result.dTagValue, "my-series"); + }); + + test("resolves infoHash from magnet", async () => { + const magnet = "magnet:?xt=urn:btih:1234567890abcdef1234567890abcdef12345678"; + const result = await prepareVideoPublishPayload({ magnet }, pubkey); + assert.equal(result.contentObject.infoHash, "1234567890abcdef1234567890abcdef12345678"); + }); + + test("includes NIP-71 tags", async () => { + const payload = { + title: "Metadata Test", + nip71: { + hashtags: ["technology"] + } + }; + const result = await prepareVideoPublishPayload(payload, pubkey); + // nip71 tags should be in additionalTags or event tags + // buildVideoPostEvent merges additionalTags into event tags + const tags = result.event.tags; + const hashtagTag = tags.find(t => t[0] === "t" && t[1] === "technology"); + assert.ok(hashtagTag, "Should have hashtag tag"); + }); + }); +}); diff --git a/tests/services/authUtils.test.mjs b/tests/services/authUtils.test.mjs new file mode 100644 index 00000000..8fd30c31 --- /dev/null +++ b/tests/services/authUtils.test.mjs @@ -0,0 +1,58 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { normalizeProviderId, normalizeAuthType } from "../../js/services/authUtils.js"; + +test("authUtils", async (t) => { + await t.test("normalizeProviderId", async (t) => { + await t.test("returns trimmed string if valid", () => { + assert.equal(normalizeProviderId(" provider123 "), "provider123"); + }); + + await t.test("returns 'nip07' fallback if empty", () => { + assert.equal(normalizeProviderId(""), "nip07"); + }); + + await t.test("returns 'nip07' fallback if null", () => { + assert.equal(normalizeProviderId(null), "nip07"); + }); + + await t.test("returns 'nip07' fallback if undefined", () => { + assert.equal(normalizeProviderId(undefined), "nip07"); + }); + + await t.test("returns 'nip07' fallback if not a string", () => { + assert.equal(normalizeProviderId(123), "nip07"); + }); + }); + + await t.test("normalizeAuthType", async (t) => { + await t.test("prioritizes authTypeCandidate", () => { + assert.equal(normalizeAuthType("candidateType", "fallbackProvider", {}), "candidateType"); + }); + + await t.test("falls back to providerResult.authType", () => { + assert.equal(normalizeAuthType(null, "fallbackProvider", { authType: "resultAuthType" }), "resultAuthType"); + }); + + await t.test("falls back to providerResult.providerId", () => { + assert.equal(normalizeAuthType(null, "fallbackProvider", { providerId: "resultProviderId" }), "resultProviderId"); + }); + + await t.test("falls back to providerId", () => { + assert.equal(normalizeAuthType(null, "fallbackProvider", {}), "fallbackProvider"); + }); + + await t.test("returns 'nip07' if all else fails", () => { + assert.equal(normalizeAuthType(null, null, {}), "nip07"); + }); + + await t.test("trims whitespace from results", () => { + assert.equal(normalizeAuthType(" candidateType ", "fallbackProvider", {}), "candidateType"); + }); + + await t.test("ignores empty strings in candidates", () => { + // Should skip empty candidate and use providerResult + assert.equal(normalizeAuthType("", "fallbackProvider", { authType: "resultAuthType" }), "resultAuthType"); + }); + }); +});