Files
bitvid/tests/nostr/dmSignalEvents.test.mjs
2026-02-09 02:24:53 +00:00

182 lines
5.7 KiB
JavaScript

import { test, describe, before, after, beforeEach, afterEach, mock } from "node:test";
import assert from "node:assert";
import { ensureNostrTools } from "../../js/nostr/toolkit.js";
import "../test-helpers/setup-localstorage.mjs";
// Mock globals needed for nostr-tools or other libs
if (!globalThis.crypto) {
globalThis.crypto = {
getRandomValues: (arr) => {
for (let i = 0; i < arr.length; i++) {
arr[i] = Math.floor(Math.random() * 256);
}
return arr;
}
};
}
function bytesToHex(bytes) {
return Buffer.from(bytes).toString('hex');
}
// Helper to generate 32-byte hex string
// Note: relies on global/closure tools if available, or just random bytes
function generateHexId(tools) {
if (tools && typeof tools.generateSecretKey === 'function') {
return bytesToHex(tools.generateSecretKey());
}
const bytes = new Uint8Array(32);
if (globalThis.crypto && globalThis.crypto.getRandomValues) {
globalThis.crypto.getRandomValues(bytes);
} else {
// Simple fallback for mock
for(let i=0; i<32; i++) bytes[i] = Math.floor(Math.random() * 256);
}
return bytesToHex(bytes);
}
describe("dmSignalEvents", () => {
let nostrClient;
let dmSignalEvents;
let originalPool;
let originalRelays;
let originalPubkey;
let originalSessionActor;
let signer;
let getActiveSigner;
let tools;
before(async () => {
// Import modules
tools = await ensureNostrTools();
const facadeModule = await import("../../js/nostrClientFacade.js");
const registryModule = await import("../../js/nostrClientRegistry.js");
dmSignalEvents = await import("../../js/nostr/dmSignalEvents.js");
nostrClient = facadeModule.nostrClient;
getActiveSigner = registryModule.getActiveSigner;
// Generate keys
const secret = tools.generateSecretKey();
const privateKey = bytesToHex(secret);
const pubkey = tools.getPublicKey(secret);
// Register signer to ensure getActiveSigner returns something
await nostrClient.registerPrivateKeySigner({ privateKey, pubkey });
signer = getActiveSigner();
});
beforeEach(() => {
originalPool = nostrClient.pool;
originalRelays = nostrClient.relays;
originalPubkey = nostrClient.pubkey;
originalSessionActor = nostrClient.sessionActor;
// Mock pool
nostrClient.pool = {
publish: mock.fn((urls, event) => {
return {
on: (event, cb) => {
if (event === 'ok') setTimeout(cb, 0);
}
};
})
};
nostrClient.relays = ["wss://relay.mock"];
nostrClient.writeRelays = ["wss://relay.mock"];
// Ensure we are logged in
nostrClient.pubkey = signer.pubkey;
// Ensure we are NOT a session actor (which blocks signals)
nostrClient.sessionActor = null;
// Mock signEvent on the signer
// We keep the original signer object but replace the method
signer.signEvent = mock.fn(async (evt) => ({ ...evt, sig: "mock-sig", id: "mock-id" }));
});
afterEach(() => {
nostrClient.pool = originalPool;
nostrClient.relays = originalRelays;
nostrClient.pubkey = originalPubkey;
nostrClient.sessionActor = originalSessionActor;
mock.reset();
});
test("publishDmReadReceipt should succeed", async () => {
const eventId = generateHexId(tools);
const recipientPubkey = generateHexId(tools);
const payload = {
eventId,
recipientPubkey,
messageKind: 4
};
const result = await dmSignalEvents.publishDmReadReceipt(nostrClient, payload);
assert.strictEqual(result.ok, true);
assert.strictEqual(signer.signEvent.mock.callCount(), 1);
assert.strictEqual(nostrClient.pool.publish.mock.callCount(), 1);
const signedEvent = result.event;
// Check tags
const tags = signedEvent.tags;
assert.ok(tags.find(t => t[0] === "e" && t[1] === eventId));
assert.ok(tags.find(t => t[0] === "p" && t[1] === recipientPubkey));
// Check 'k' tag for messageKind
assert.ok(tags.find(t => t[0] === "k" && t[1] === "4"));
assert.strictEqual(signedEvent.content, "");
});
test("publishDmReadReceipt should fail if session actor", async () => {
// Set sessionActor to look like the current user
nostrClient.sessionActor = { pubkey: nostrClient.pubkey };
const payload = {
eventId: generateHexId(tools),
recipientPubkey: generateHexId(tools),
messageKind: 4
};
const result = await dmSignalEvents.publishDmReadReceipt(nostrClient, payload);
assert.strictEqual(result.ok, false);
assert.strictEqual(result.error.code, "session-actor-publish-blocked");
});
test("publishDmTypingIndicator should succeed", async () => {
const recipientPubkey = generateHexId(tools);
const conversationEventId = generateHexId(tools);
const payload = {
recipientPubkey,
conversationEventId
};
const result = await dmSignalEvents.publishDmTypingIndicator(nostrClient, payload);
assert.strictEqual(result.ok, true);
assert.strictEqual(signer.signEvent.mock.callCount(), 1);
const signedEvent = result.event;
// Check tags
assert.ok(signedEvent.tags.find(t => t[0] === "p" && t[1] === recipientPubkey));
assert.ok(signedEvent.tags.find(t => t[0] === "e" && t[1] === conversationEventId));
});
test("should fail if no signer", async () => {
// Temporarily break signer
const originalSignEvent = signer.signEvent;
signer.signEvent = undefined;
const payload = { eventId: generateHexId(tools), recipientPubkey: generateHexId(tools) };
const result = await dmSignalEvents.publishDmReadReceipt(nostrClient, payload);
assert.strictEqual(result.ok, false);
assert.strictEqual(result.error, "auth-required");
signer.signEvent = originalSignEvent;
});
});