From 84e9dcd2c14fec28da3895fb11478e14e0e043cf Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 18 Jan 2026 11:25:22 -0500 Subject: [PATCH] Add nostr adapter and registry tests --- tests/nostr/adapters.test.mjs | 156 +++++++++++++++++++++ tests/nostr/dmDecryptorPreference.test.mjs | 63 +++++++++ tests/nostr/nostrClientRegistry.test.mjs | 51 +++++++ 3 files changed, 270 insertions(+) create mode 100644 tests/nostr/adapters.test.mjs create mode 100644 tests/nostr/dmDecryptorPreference.test.mjs create mode 100644 tests/nostr/nostrClientRegistry.test.mjs diff --git a/tests/nostr/adapters.test.mjs b/tests/nostr/adapters.test.mjs new file mode 100644 index 00000000..9eebc119 --- /dev/null +++ b/tests/nostr/adapters.test.mjs @@ -0,0 +1,156 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { createNip07Adapter } from "../../js/nostr/adapters/nip07Adapter.js"; +import { createNip46Adapter } from "../../js/nostr/adapters/nip46Adapter.js"; +import { createNsecAdapter } from "../../js/nostr/adapters/nsecAdapter.js"; +import { Nip46RpcClient } from "../../js/nostr/nip46Client.js"; + +const buildHex = (value) => value.repeat(64); + +const buildEvent = (pubkey) => ({ + kind: 1, + pubkey, + created_at: 1, + tags: [], + content: "", +}); + +test("createNsecAdapter wires signing and cipher capabilities", async () => { + const previousCanonical = globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__; + const previousNostrTools = globalThis.NostrTools; + const previousReady = globalThis.nostrToolsReady; + + const nostrTools = await import("nostr-tools"); + const canonicalTools = { ...nostrTools }; + + try { + globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__ = canonicalTools; + globalThis.NostrTools = canonicalTools; + globalThis.nostrToolsReady = Promise.resolve({ + ok: true, + value: canonicalTools, + }); + + const privateKeyBytes = nostrTools.generateSecretKey(); + const privateKeyHex = nostrTools.utils.bytesToHex(privateKeyBytes); + const pubkey = nostrTools.getPublicKey(privateKeyBytes); + + const signer = await createNsecAdapter({ + privateKey: privateKeyHex, + pubkey, + }); + + assert.equal(signer.pubkey, pubkey); + assert.equal(signer.canSign(), true, "signer should allow signing"); + assert.equal(signer.capabilities.nip04, true); + assert.equal(signer.capabilities.nip44, true); + assert.equal(typeof signer.nip04Encrypt, "function"); + assert.equal(typeof signer.nip44Encrypt, "function"); + + const signed = await signer.signEvent(buildEvent(pubkey)); + assert.equal(typeof signed.id, "string"); + assert.equal(typeof signed.sig, "string"); + } finally { + if (previousCanonical === undefined) { + delete globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__; + } else { + globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__ = previousCanonical; + } + + if (previousNostrTools === undefined) { + delete globalThis.NostrTools; + } else { + globalThis.NostrTools = previousNostrTools; + } + + if (previousReady === undefined) { + delete globalThis.nostrToolsReady; + } else { + globalThis.nostrToolsReady = previousReady; + } + } +}); + +test("createNip07Adapter maps extension methods and permissions", async () => { + const originalWindow = globalThis.window; + const pubkey = buildHex("a"); + let requestedPermissions = null; + + const extension = { + getPublicKey: async () => pubkey.toUpperCase(), + signEvent: async (event) => ({ ...event, id: "signed", sig: "sig" }), + nip04: { encrypt: () => "cipher", decrypt: () => "plain" }, + nip44: { + v2: { + encrypt: () => "cipher44", + decrypt: () => "plain44", + }, + }, + requestPermissions: async ({ permissions }) => { + requestedPermissions = permissions; + return { ok: true }; + }, + }; + + globalThis.window = { nostr: extension }; + + try { + const signer = await createNip07Adapter(); + + assert.equal(signer.pubkey, pubkey); + assert.equal(signer.capabilities.sign, true); + assert.equal(signer.capabilities.nip44, true); + assert.equal(signer.capabilities.nip04, true); + assert.equal(typeof signer.nip44Encrypt, "function"); + + const permissions = await signer.requestPermissions(["sign_event"]); + assert.equal(permissions.ok, true); + assert.deepEqual(requestedPermissions, ["sign_event"]); + + const signed = await signer.signEvent(buildEvent(pubkey)); + assert.equal(signed.id, "signed"); + } finally { + if (originalWindow === undefined) { + delete globalThis.window; + } else { + globalThis.window = originalWindow; + } + } +}); + +test("createNip46Adapter wraps the remote signer client", async () => { + const pubkey = buildHex("b"); + const mockClient = Object.create(Nip46RpcClient.prototype); + let destroyed = false; + + Object.assign(mockClient, { + userPubkey: "", + getUserPubkey: async () => pubkey, + metadata: { name: "Remote signer" }, + relays: ["wss://relay.example"], + destroyed: false, + signEvent: async (event) => ({ ...event, id: "remote", sig: "sig" }), + destroy: () => { + destroyed = true; + mockClient.destroyed = true; + }, + nip04Encrypt: async () => "cipher", + nip04Decrypt: async () => "plain", + nip44Encrypt: async () => "cipher44", + nip44Decrypt: async () => "plain44", + }); + + const signer = await createNip46Adapter(mockClient); + + assert.equal(signer.pubkey, pubkey); + assert.equal(signer.capabilities.sign, true); + assert.equal(signer.capabilities.nip04, true); + assert.equal(signer.capabilities.nip44, true); + + const signed = await signer.signEvent(buildEvent(pubkey)); + assert.equal(signed.id, "remote"); + + await signer.destroy(); + assert.equal(destroyed, true); +}); diff --git a/tests/nostr/dmDecryptorPreference.test.mjs b/tests/nostr/dmDecryptorPreference.test.mjs new file mode 100644 index 00000000..168d2130 --- /dev/null +++ b/tests/nostr/dmDecryptorPreference.test.mjs @@ -0,0 +1,63 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { decryptDM } from "../../js/dmDecryptor.js"; + +const buildHex = (value) => value.repeat(64); + +const baseEvent = (pubkey) => ({ + kind: 4, + pubkey, + content: "ciphertext", + tags: [], +}); + +test("decryptDM prefers nip44 decryptors when available", async () => { + const senderPubkey = buildHex("e"); + + const result = await decryptDM(baseEvent(senderPubkey), { + actorPubkey: senderPubkey, + decryptors: [ + { + scheme: "nip04", + decrypt: async () => "nip04", + priority: 0, + }, + { + scheme: "nip44", + decrypt: async () => "nip44", + priority: 0, + }, + ], + }); + + assert.equal(result.ok, true); + assert.equal(result.scheme, "nip44"); + assert.equal(result.plaintext, "nip44"); +}); + +test("decryptDM falls back to nip04 when nip44 fails", async () => { + const senderPubkey = buildHex("f"); + + const result = await decryptDM(baseEvent(senderPubkey), { + actorPubkey: senderPubkey, + decryptors: [ + { + scheme: "nip44", + decrypt: async () => { + throw new Error("nip44 failure"); + }, + priority: 0, + }, + { + scheme: "nip04", + decrypt: async () => "fallback", + priority: 0, + }, + ], + }); + + assert.equal(result.ok, true); + assert.equal(result.scheme, "nip04"); + assert.equal(result.plaintext, "fallback"); +}); diff --git a/tests/nostr/nostrClientRegistry.test.mjs b/tests/nostr/nostrClientRegistry.test.mjs new file mode 100644 index 00000000..e42b2d5a --- /dev/null +++ b/tests/nostr/nostrClientRegistry.test.mjs @@ -0,0 +1,51 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + registerSigner, + setActiveSigner, + resolveActiveSigner, + onActiveSignerChanged, + clearActiveSigner, +} from "../../js/nostrClientRegistry.js"; + +const buildHex = (value) => value.repeat(64); + +test("registerSigner stores and resolves signer entries", () => { + const pubkey = buildHex("c"); + const signer = { pubkey }; + + registerSigner(pubkey, signer, { permissions: ["sign_event"] }); + + assert.equal(resolveActiveSigner(pubkey), signer); + + registerSigner(pubkey, null); + assert.equal(resolveActiveSigner(pubkey), null); +}); + +test("setActiveSigner notifies listeners and resolves by pubkey", () => { + const pubkey = buildHex("d"); + const signer = { pubkey }; + const events = []; + let enabled = true; + + onActiveSignerChanged((payload) => { + if (!enabled) { + return; + } + events.push(payload); + }); + + registerSigner(pubkey, signer); + const resolved = setActiveSigner(pubkey); + + assert.equal(resolved, signer); + assert.equal(resolveActiveSigner(pubkey), signer); + assert.equal(events.length, 1); + assert.equal(events[0].pubkey, pubkey); + assert.equal(events[0].previousSigner, null); + + enabled = false; + clearActiveSigner(); + registerSigner(pubkey, null); +});