From c3c22fca31258e6a408ac334ea72f7668f49895d Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:57:21 -0400 Subject: [PATCH] Add admin list publish regression test --- tests/admin-list-store.test.mjs | 172 +++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/tests/admin-list-store.test.mjs b/tests/admin-list-store.test.mjs index 33ebf951..c723786f 100644 --- a/tests/admin-list-store.test.mjs +++ b/tests/admin-list-store.test.mjs @@ -35,7 +35,20 @@ const mockNip19 = { globalThis.window.NostrTools.nip19 = mockNip19; -const { __adminListStoreTestHooks } = await import("../js/adminListStore.js"); +const capturedWarnings = []; +const originalConsoleWarn = console.warn; +console.warn = (...args) => { + capturedWarnings.push(args); +}; +process.on("exit", () => { + console.warn = originalConsoleWarn; +}); + +const { persistAdminState, __adminListStoreTestHooks } = await import( + "../js/adminListStore.js" +); +const { nostrClient } = await import("../js/nostr.js"); +const { ADMIN_SUPER_NPUB } = await import("../js/config.js"); const { extractNpubsFromEvent, normalizeParticipantTagValue } = __adminListStoreTestHooks; @@ -86,4 +99,161 @@ assert.equal( "should preserve hex when nip19 helpers are unavailable" ); +mockNip19.decode = (value) => { + if (typeof value !== "string") { + throw new Error("invalid input"); + } + const trimmed = value.trim(); + if (trimmed.toLowerCase().startsWith("npub")) { + return { type: "npub", data: `${trimmed}-hex` }; + } + throw new Error("unsupported value"); +}; +mockNip19.npubEncode = (hex) => { + if (typeof hex !== "string") { + throw new Error("invalid hex"); + } + const trimmed = hex.trim(); + if (!trimmed) { + throw new Error("empty hex"); + } + return `npub1${trimmed}`; +}; + +globalThis.window.NostrTools.nip19 = mockNip19; + +nostrClient.ensureExtensionPermissions = async () => ({ ok: true }); + +const relays = [ + "wss://relay1.example.com", + "wss://relay2.example.com", +]; +nostrClient.relays = [...relays]; + +let publishBehaviors = []; +let publishCallIndex = 0; + +function setPublishBehaviors(behaviors) { + publishBehaviors = Array.isArray(behaviors) ? behaviors : []; + publishCallIndex = 0; +} + +nostrClient.pool = { + publish(urls) { + const url = Array.isArray(urls) && urls.length > 0 ? urls[0] : ""; + const behavior = publishBehaviors[publishCallIndex++]; + if (!behavior) { + throw new Error(`Missing publish behavior for ${url}`); + } + if (behavior.url && behavior.url !== url) { + throw new Error( + `Unexpected publish url ${url}; expected ${behavior.url}`, + ); + } + return { + on(eventName, handler) { + if (eventName === "ok" && behavior.success) { + handler(); + return true; + } + if (eventName === "failed" && !behavior.success) { + handler( + behavior.error || new Error(`relay failure for ${url}`), + ); + return true; + } + return false; + }, + }; + }, +}; + +if (!globalThis.window.nostr) { + globalThis.window.nostr = {}; +} + +globalThis.window.nostr.signEvent = async (event) => ({ + ...event, + id: "signed-event", +}); + +const additionBehaviors = [ + { url: relays[0], success: false, error: new Error("relay1 add failure") }, + { url: relays[1], success: true }, + { url: relays[0], success: false, error: new Error("relay1 blacklist add failure") }, + { url: relays[1], success: true }, +]; +setPublishBehaviors(additionBehaviors); + +await assert.doesNotReject( + () => + persistAdminState(ADMIN_SUPER_NPUB, { + whitelist: ["npub1whitelistmember"], + blacklist: ["npub1blacklistmember"], + }), + "should allow adding whitelist/blacklist entries without rejection", +); + +assert.equal( + publishCallIndex, + additionBehaviors.length, + "should attempt to publish additions to every relay", +); + +const removalBehaviors = [ + { url: relays[0], success: false, error: new Error("relay1 remove failure") }, + { url: relays[1], success: true }, + { url: relays[0], success: false, error: new Error("relay1 blacklist remove failure") }, + { url: relays[1], success: true }, +]; +setPublishBehaviors(removalBehaviors); + +await assert.doesNotReject( + () => + persistAdminState(ADMIN_SUPER_NPUB, { + whitelist: [], + blacklist: [], + }), + "should allow removing whitelist/blacklist entries without rejection", +); + +assert.equal( + publishCallIndex, + removalBehaviors.length, + "should attempt to publish removals to every relay", +); + +const relayFailureWarnings = capturedWarnings.filter((entry) => { + const [, message] = entry; + return ( + typeof message === "string" && + message.startsWith("[adminListStore] Publish failed") + ); +}); +const failureMessages = relayFailureWarnings.map(([, message]) => message); +const whitelistFailureCount = failureMessages.filter((message) => + typeof message === "string" && + message.includes("whitelist") && + message.includes(relays[0]) +).length; +const blacklistFailureCount = failureMessages.filter((message) => + typeof message === "string" && + message.includes("blacklist") && + message.includes(relays[0]) +).length; + +assert.equal( + relayFailureWarnings.length, + 4, + "should warn once per relay failure across additions and removals", +); +assert.ok( + whitelistFailureCount >= 2, + "should warn when whitelist updates fail on a relay", +); +assert.ok( + blacklistFailureCount >= 2, + "should warn when blacklist updates fail on a relay", +); + console.log("admin-list-store tests passed");