Merge pull request #1320 from PR0M3TH3AN/codex/add-tests-for-nostr-adapters-and-registry

Add Nostr signer adapter, registry, and DM decryptor preference tests
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-01-18 11:25:48 -05:00
commit 3cebdad46b
3 files changed
+270

No files matched your search

+156
View File
@@ -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);
});
@@ -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");
});
+51
View File
@@ -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);
});