Files
bitvid/tests/nostr/adapters.test.mjs
google-labs-jules[bot]andPR0M3TH3AN 14dfedc98d Fix NIP-07 adapter race condition and CI test crashes
- Modified `js/nostr/adapters/nip07Adapter.js` to use dynamic getters for capabilities.
- Updated `js/nostr/client.js` to respect getter descriptors on signers.
- Fixed `tests/nostr-login-permissions.test.mjs` and `tests/nostr/adapters.test.mjs` to safely handle read-only global properties in CI.
- Added hardened regression test `tests/nostr/nip07Adapter-race-condition.test.mjs`.

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-01-30 19:04:55 +00:00

177 lines
5.3 KiB
JavaScript

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 {
try {
Object.defineProperty(globalThis, "__BITVID_CANONICAL_NOSTR_TOOLS__", {
value: canonicalTools,
writable: true,
configurable: true,
});
} catch (e) {
try {
globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__ = canonicalTools;
} catch (ignore) {}
}
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 {
try {
Object.defineProperty(globalThis, "__BITVID_CANONICAL_NOSTR_TOOLS__", {
value: previousCanonical,
writable: true,
configurable: true,
});
} catch (e) {
try {
globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__ = previousCanonical;
} catch (ignore) {}
}
}
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);
});