UNPKG

@nostr-dev-kit/ndk

Version:

NDK - Nostr Development Kit. Includes AI Guardrails to catch common mistakes during development.

208 lines (175 loc) 9.85 kB
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import NDK, { NDKNip07Signer, NDKNip46Signer, NDKPrivateKeySigner, NDKUser, ndkSignerFromPayload } from "../index.js"; // Mock window.nostr for NIP-07 tests const mockNostr = { getPublicKey: vi.fn().mockResolvedValue("nip07pubkey"), signEvent: vi.fn().mockResolvedValue({ sig: "nip07sig" }), nip04: { encrypt: vi.fn().mockResolvedValue("encrypted"), decrypt: vi.fn().mockResolvedValue("decrypted"), }, nip44: { encrypt: vi.fn().mockResolvedValue("encrypted44"), decrypt: vi.fn().mockResolvedValue("decrypted44"), }, getRelays: vi.fn().mockResolvedValue({ "wss://relay.example.com": { read: true, write: true } }), }; describe("Signer Serialization/Deserialization", () => { let ndk: NDK; beforeEach(() => { ndk = new NDK(); // Assign the mock to the global window object before each test vi.stubGlobal("window", { nostr: mockNostr }); }); afterEach(() => { // Restore the original window object after each test vi.unstubAllGlobals(); vi.clearAllMocks(); }); it("serializes and deserializes NDKPrivateKeySigner", async () => { const originalSigner = NDKPrivateKeySigner.generate(); const payloadString = originalSigner.toPayload(); expect(payloadString).toBeTypeOf("string"); const parsedPayload = JSON.parse(payloadString); // Check the exact structure from toPayload expect(parsedPayload).toEqual({ type: "private-key", payload: originalSigner.privateKey, }); const restoredSigner = await ndkSignerFromPayload(payloadString, ndk); expect(restoredSigner).toBeInstanceOf(NDKPrivateKeySigner); const restoredPrivateKeySigner = restoredSigner as NDKPrivateKeySigner; expect(restoredPrivateKeySigner.privateKey).toBe(originalSigner.privateKey); const user = await restoredPrivateKeySigner.user(); const originalUser = await originalSigner.user(); expect(user.pubkey).toBe(originalUser.pubkey); }); it("serializes and deserializes NDKNip07Signer", async () => { // Need to initialize the signer so it attempts to get the pubkey const originalSigner = new NDKNip07Signer(100, ndk); // Short timeout for test await originalSigner.blockUntilReady(); // Ensure pubkey is fetched const payloadString = originalSigner.toPayload(); expect(payloadString).toBeTypeOf("string"); const parsedPayload = JSON.parse(payloadString); // Check the exact structure from toPayload expect(parsedPayload).toEqual({ type: "nip07", payload: "", // NIP-07 has no payload data }); const restoredSigner = await ndkSignerFromPayload(payloadString, ndk); expect(restoredSigner).toBeInstanceOf(NDKNip07Signer); // We can't easily check the pubkey without calling blockUntilReady again // which involves async operations and relies on the mock. // The main check is that it correctly identifies and creates the NIP-07 signer. expect(mockNostr.getPublicKey).toHaveBeenCalledTimes(1); // From originalSigner.blockUntilReady() }); it("serializes and deserializes NDKNip46Signer (with private key local signer)", async () => { const localSigner = NDKPrivateKeySigner.generate(); const localUser = await localSigner.user(); const bunkerPubkey = NDKPrivateKeySigner.generate().pubkey; // Mock bunker pubkey const userPubkey = NDKPrivateKeySigner.generate().pubkey; // Mock user pubkey const relayUrls = ["wss://nip46.example.com"]; const secret = "testsecret"; // Manually construct a NIP-46 signer instance for testing serialization // We bypass the complex connection logic for this test const originalSigner = new NDKNip46Signer(ndk, userPubkey, localSigner); originalSigner.bunkerPubkey = bunkerPubkey; originalSigner.userPubkey = userPubkey; // Set explicitly after construction originalSigner.relayUrls = relayUrls; originalSigner.secret = secret; // We don't need to mock the rpc object itself for serialization testing // as toPayload doesn't rely on it. const payloadString = originalSigner.toPayload(); expect(payloadString).toBeTypeOf("string"); const parsedPayload = JSON.parse(payloadString); // Check the exact structure from toPayload expect(parsedPayload.type).toBe("nip46"); expect(parsedPayload.payload).toBeDefined(); expect(parsedPayload.payload.bunkerPubkey).toBe(bunkerPubkey); expect(parsedPayload.payload.userPubkey).toBe(userPubkey); expect(parsedPayload.payload.relayUrls).toEqual(relayUrls); expect(parsedPayload.payload.secret).toBe(secret); expect(parsedPayload.payload.localSignerPayload).toBeTypeOf("string"); // Check the nested local signer payload string const localSignerPayloadParsed = JSON.parse(parsedPayload.payload.localSignerPayload); expect(localSignerPayloadParsed).toEqual({ type: "private-key", payload: localSigner.privateKey, }); // Mock blockUntilReady for the deserialized instance to avoid network calls // and simulate successful connection/user retrieval. const mockBlockUntilReady = vi .spyOn(NDKNip46Signer.prototype, "blockUntilReady") .mockImplementation(async function (this: NDKNip46Signer): Promise<NDKUser> { // Simulate the outcome of blockUntilReady: setting the user object. // The actual userPubkey should have been set by fromPayload. if (!this.userPubkey) { throw new Error("Mock Error: userPubkey not set before blockUntilReady mock"); } // Access private 'ndk' for the mock setup // @ts-expect-error - Accessing private member for test mock const instanceNdk = this.ndk as NDK; if (!instanceNdk) { throw new Error("Mock Error: NDK instance not available in blockUntilReady mock"); } const user = instanceNdk.getUser({ pubkey: this.userPubkey }); // Set the private _user via assignment for the mock // @ts-expect-error - Accessing private member for test mock this._user = user; return user; }); const restoredSigner = await ndkSignerFromPayload(payloadString, ndk); expect(restoredSigner).toBeInstanceOf(NDKNip46Signer); const restoredNip46Signer = restoredSigner as NDKNip46Signer; // Check properties set during deserialization before blockUntilReady expect(restoredNip46Signer.bunkerPubkey).toBe(bunkerPubkey); // userPubkey is set *during* blockUntilReady in the real flow, // but fromPayload sets it based on the payload for reconstruction. expect(restoredNip46Signer.userPubkey).toBe(userPubkey); expect(restoredNip46Signer.relayUrls).toEqual(relayUrls); expect(restoredNip46Signer.secret).toBe(secret); expect(restoredNip46Signer.localSigner).toBeInstanceOf(NDKPrivateKeySigner); expect((restoredNip46Signer.localSigner as NDKPrivateKeySigner).privateKey).toBe(localSigner.privateKey); // Call user() - since fromPayload now sets _user directly, // blockUntilReady won't be called (it only gets called when _user is not set) const restoredUser = await restoredNip46Signer.user(); expect(restoredUser).toBeInstanceOf(NDKUser); expect(restoredUser.pubkey).toBe(userPubkey); // Restore the original implementation vi.restoreAllMocks(); }); it("returns undefined for invalid JSON payload", async () => { const invalidPayload = "{invalid json"; const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); // Suppress console error const restoredSigner = await ndkSignerFromPayload(invalidPayload, ndk); expect(restoredSigner).toBeUndefined(); expect(consoleErrorSpy).toHaveBeenCalledWith( "Failed to parse signer payload string", invalidPayload, expect.any(Error), ); consoleErrorSpy.mockRestore(); }); it("returns undefined for payload without type", async () => { const invalidPayload = JSON.stringify({ payload: "data" }); const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); // Suppress console error const restoredSigner = await ndkSignerFromPayload(invalidPayload, ndk); }); it("throws error for unknown signer type", async () => { const unknownPayload = JSON.stringify({ type: "unknown-signer", payload: {} }); await expect(ndkSignerFromPayload(unknownPayload, ndk)).rejects.toThrow("Unknown signer type: unknown-signer"); }); it("throws error if signer's fromPayload throws", async () => { // Mock NDKPrivateKeySigner.fromPayload to throw an error const errorMessage = "Deserialization failed"; vi.spyOn(NDKPrivateKeySigner, "fromPayload").mockRejectedValue(new Error(errorMessage)); const signer = NDKPrivateKeySigner.generate(); const payload = signer.toPayload(); await expect(ndkSignerFromPayload(payload, ndk)).rejects.toThrow( `Failed to deserialize signer type private-key: ${errorMessage}`, ); expect(NDKPrivateKeySigner.fromPayload).toHaveBeenCalledWith(payload, ndk); // Restore mocks vi.restoreAllMocks(); }); });