Merge pull request #1461 from PR0M3TH3AN/ai/tests-core-coverage-20250302-14976230523453771489

Add unit tests for core modules
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-01-21 12:00:08 -05:00
commit 0873b16d06
4 files changed
+475 -234

No files matched your search

+123
View File
@@ -0,0 +1,123 @@
// Run with: node scripts/run-targeted-tests.mjs tests/nostr/sign-request-queue.test.mjs
import assert from "node:assert/strict";
import test from "node:test";
import { queueSignEvent } from "../../js/nostr/signRequestQueue.js";
// Helper to delay execution
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
test("queueSignEvent: processes requests sequentially", async () => {
const executionOrder = [];
const signer = {
signEvent: async (event) => {
executionOrder.push(`start:${event.id}`);
await delay(50);
executionOrder.push(`end:${event.id}`);
return { ...event, sig: "signature" };
},
};
const event1 = { id: "1", pubkey: "abc" };
const event2 = { id: "2", pubkey: "abc" };
const p1 = queueSignEvent(signer, event1);
const p2 = queueSignEvent(signer, event2);
await Promise.all([p1, p2]);
assert.deepEqual(executionOrder, [
"start:1",
"end:1",
"start:2",
"end:2",
]);
});
test("queueSignEvent: handles timeouts", async () => {
const signer = {
signEvent: async () => {
await delay(100);
return { sig: "slow" };
},
};
const event = { id: "timeout", pubkey: "abc" };
await assert.rejects(
() => queueSignEvent(signer, event, { timeoutMs: 20 }),
(err) => {
assert.equal(err.code, "timeout");
return true;
}
);
});
test("queueSignEvent: handles permission denied errors", async () => {
const signer = {
signEvent: async () => {
const err = new Error("User rejected");
err.message = "The user denied the permission";
throw err;
},
};
const event = { id: "denied", pubkey: "abc" };
await assert.rejects(
() => queueSignEvent(signer, event),
(err) => {
assert.equal(err.code, "permission-denied");
return true;
}
);
});
test("queueSignEvent: handles signer disconnected", async () => {
const signer = {
signEvent: async () => {
throw new Error("Extension disconnected");
},
};
const event = { id: "disconnect", pubkey: "abc" };
await assert.rejects(
() => queueSignEvent(signer, event),
(err) => {
assert.equal(err.code, "signer-disconnected");
return true;
}
);
});
test("queueSignEvent: fails if signer is missing or invalid", async () => {
await assert.rejects(
() => queueSignEvent(null, {}),
(err) => err.code === "not-capable"
);
await assert.rejects(
() => queueSignEvent({}, {}),
(err) => err.code === "not-capable"
);
});
test("queueSignEvent: continues queue processing after failure", async () => {
const signer = {
signEvent: async (event) => {
if (event.id === "fail") {
throw new Error("fail");
}
return { ...event, sig: "ok" };
},
};
const p1 = queueSignEvent(signer, { id: "fail" }).catch(() => "caught");
const p2 = queueSignEvent(signer, { id: "success" });
const [res1, res2] = await Promise.all([p1, p2]);
assert.equal(res1, "caught");
assert.equal(res2.sig, "ok");
});
+78 -234
View File
@@ -1,254 +1,98 @@
// Run with: node scripts/run-targeted-tests.mjs tests/nostr/toolkit.test.mjs
import assert from "node:assert/strict";
import test from "node:test";
const originalCanonical = globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__;
const originalNostrTools = globalThis.NostrTools;
const originalNostrToolsReady = globalThis.nostrToolsReady;
class BootstrapSimplePool {
sub() {}
close() {}
}
const bootstrapToolkit = {
ok: true,
nip19: {
decode: (value) => value,
npubEncode: (value) => value,
},
SimplePool: BootstrapSimplePool,
};
globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__ = bootstrapToolkit;
globalThis.NostrTools = bootstrapToolkit;
globalThis.nostrToolsReady = Promise.resolve(bootstrapToolkit);
const toolkitModule = await import("../../js/nostr/toolkit.js");
const {
rememberNostrTools,
getCachedNostrTools,
ensureNostrTools,
resolveSimplePoolConstructor,
import {
DEFAULT_RELAY_URLS,
shimLegacySimplePoolMethods,
} = toolkitModule;
resolveSimplePoolConstructor,
readToolkitFromScope,
normalizeToolkitCandidate,
} from "../../js/nostr/toolkit.js";
const baselineToolkit = getCachedNostrTools() || bootstrapToolkit;
test.after(() => {
if (originalCanonical === undefined) {
delete globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__;
} else {
globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__ = originalCanonical;
}
if (originalNostrTools === undefined) {
delete globalThis.NostrTools;
} else {
globalThis.NostrTools = originalNostrTools;
}
if (originalNostrToolsReady === undefined) {
delete globalThis.nostrToolsReady;
} else {
globalThis.nostrToolsReady = originalNostrToolsReady;
}
rememberNostrTools(baselineToolkit);
test("toolkit: DEFAULT_RELAY_URLS is frozen and contains valid URLs", () => {
assert.ok(Array.isArray(DEFAULT_RELAY_URLS));
assert.ok(Object.isFrozen(DEFAULT_RELAY_URLS));
assert.ok(DEFAULT_RELAY_URLS.length > 0);
assert.ok(DEFAULT_RELAY_URLS.every((url) => url.startsWith("wss://")));
});
test("ensureNostrTools resolves the cached toolkit", async () => {
class CachedSimplePool {
sub() {}
close() {}
}
const cachedToolkit = {
ok: true,
nip19: {
decode: (value) => value,
npubEncode: (value) => value,
},
SimplePool: CachedSimplePool,
};
const previousCanonicalForTest = globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__;
const previousNostrToolsForTest = globalThis.NostrTools;
globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__ = cachedToolkit;
globalThis.NostrTools = cachedToolkit;
rememberNostrTools(cachedToolkit);
try {
const ensured = await ensureNostrTools();
assert.strictEqual(ensured, cachedToolkit);
} finally {
if (previousCanonicalForTest === undefined) {
delete globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__;
} else {
globalThis.__BITVID_CANONICAL_NOSTR_TOOLS__ = previousCanonicalForTest;
}
if (previousNostrToolsForTest === undefined) {
delete globalThis.NostrTools;
} else {
globalThis.NostrTools = previousNostrToolsForTest;
}
rememberNostrTools(baselineToolkit);
}
});
test("resolveSimplePoolConstructor handles supported permutations", () => {
test("toolkit: resolveSimplePoolConstructor finds SimplePool", () => {
class MockSimplePool {
sub() {}
close() {}
}
const variations = [
{ label: "tools.SimplePool", tools: { SimplePool: MockSimplePool } },
{ label: "tools.pool.SimplePool", tools: { pool: { SimplePool: MockSimplePool } } },
{ label: "tools.pool constructor", tools: { pool: MockSimplePool } },
{
label: "tools.SimplePool.SimplePool",
tools: { SimplePool: { SimplePool: MockSimplePool } },
},
{
label: "tools.SimplePool.default",
tools: { SimplePool: { default: MockSimplePool } },
},
{ label: "tools.pool.default", tools: { pool: { default: MockSimplePool } } },
{
label: "tools.default.SimplePool",
tools: { default: { SimplePool: MockSimplePool } },
},
{
label: "tools.default.pool.SimplePool",
tools: { default: { pool: { SimplePool: MockSimplePool } } },
},
{
label: "tools.default.pool constructor",
tools: { default: { pool: MockSimplePool } },
},
];
const mockTools = {
SimplePool: MockSimplePool,
};
for (const { label, tools } of variations) {
assert.strictEqual(
resolveSimplePoolConstructor(tools),
MockSimplePool,
`expected ${label}`
);
}
const Resolved = resolveSimplePoolConstructor(mockTools);
assert.equal(Resolved, MockSimplePool);
assert.strictEqual(
resolveSimplePoolConstructor({}, { SimplePool: MockSimplePool }),
MockSimplePool,
"scope.SimplePool fallback"
);
assert.strictEqual(
resolveSimplePoolConstructor({}, { pool: { SimplePool: MockSimplePool } }),
MockSimplePool,
"scope.pool.SimplePool fallback"
);
assert.strictEqual(
resolveSimplePoolConstructor(
{},
{ NostrTools: { pool: { SimplePool: MockSimplePool } } }
),
MockSimplePool,
"scope.NostrTools.pool.SimplePool fallback"
);
assert.strictEqual(
resolveSimplePoolConstructor({}, {}),
null,
"returns null when no candidates are available"
);
});
test("shimLegacySimplePoolMethods adds legacy sub/list wrappers", async () => {
let capturedRequests = null;
let capturedParams = null;
const closeReasons = [];
const mockPool = {
subscribeMap(requests, params) {
capturedRequests = requests;
capturedParams = params;
return {
close(reason) {
closeReasons.push(reason);
},
};
const mockToolsNested = {
pool: {
SimplePool: MockSimplePool,
},
};
shimLegacySimplePoolMethods(mockPool);
assert.strictEqual(typeof mockPool.sub, "function");
assert.strictEqual(typeof mockPool.list, "function");
const relays = ["wss://relay.one", "wss://relay.two"];
const filters = [{ kinds: [1] }, { authors: ["abc"] }];
const sub = mockPool.sub(relays, filters, {
alreadyHaveEvent(id) {
return id === "skip";
const ResolvedNested = resolveSimplePoolConstructor(mockToolsNested);
assert.equal(ResolvedNested, MockSimplePool);
});
test("toolkit: shimLegacySimplePoolMethods adds sub/list/map if missing", () => {
const pool = {
subscribeMany: () => () => {},
};
shimLegacySimplePoolMethods(pool);
assert.equal(typeof pool.sub, "function");
assert.equal(typeof pool.list, "function");
// Verify shim methods return expected interface
const sub = pool.sub([], []);
assert.equal(typeof sub.on, "function");
assert.equal(typeof sub.off, "function");
assert.equal(typeof sub.unsub, "function");
});
test("toolkit: readToolkitFromScope finds NostrTools in global scope", () => {
const mockScope = {
NostrTools: {
generateSecretKey: () => {},
},
};
const tools = readToolkitFromScope(mockScope);
assert.ok(tools);
assert.equal(typeof tools.generateSecretKey, "function");
});
test("toolkit: normalizeToolkitCandidate validation", () => {
assert.equal(normalizeToolkitCandidate(null), null);
assert.equal(normalizeToolkitCandidate({ ok: false }), null);
assert.equal(normalizeToolkitCandidate({ then: () => {} }), null); // Promise-like
const valid = { generateSecretKey: () => {} };
const normalized = normalizeToolkitCandidate(valid);
assert.deepEqual(normalized, valid);
});
test("toolkit: shimLegacySimplePoolMethods handles simple list operation", async () => {
const pool = {
subscribeMany: (relays, filters, opts) => {
setTimeout(() => {
if (opts.onevent) opts.onevent({ id: "1" });
if (opts.oneose) opts.oneose();
}, 10);
return () => {};
},
});
assert.ok(Array.isArray(capturedRequests));
assert.strictEqual(capturedRequests.length, relays.length * filters.length);
assert.ok(capturedRequests.every((entry) => entry && entry.url && entry.filter));
assert.ok(capturedParams);
assert.strictEqual(typeof capturedParams.onevent, "function");
assert.strictEqual(typeof capturedParams.oneose, "function");
assert.strictEqual(typeof capturedParams.onclose, "function");
assert.strictEqual(typeof capturedParams.alreadyHaveEvent, "function");
const receivedEvents = [];
const eosePromise = new Promise((resolve) => sub.on("eose", resolve));
const closePromise = new Promise((resolve) => sub.on("close", resolve));
sub.on("event", (event) => {
receivedEvents.push(event.id);
});
assert.strictEqual(capturedParams.alreadyHaveEvent("skip"), true);
assert.strictEqual(capturedParams.alreadyHaveEvent("id-1"), false);
assert.strictEqual(capturedParams.alreadyHaveEvent("id-1"), true);
capturedParams.onevent({ id: "id-1" });
capturedParams.onevent({ id: "id-2" });
capturedParams.oneose();
capturedParams.onclose(["done"]);
await eosePromise;
const closePayload = await closePromise;
assert.deepStrictEqual(receivedEvents, ["id-1", "id-2"]);
assert.deepStrictEqual(closePayload, ["done"]);
sub.unsub();
closeReasons.length = 0;
const immediateUnsub = mockPool.sub(["wss://relay.extra"], [{ kinds: [2] }]);
immediateUnsub.unsub();
assert.ok(closeReasons.includes("closed by caller"));
capturedParams = null;
capturedRequests = null;
closeReasons.length = 0;
};
const listPromise = mockPool.list(["wss://relay.example"], [{ kinds: [1] }]);
assert.ok(capturedParams);
capturedParams.onevent({ id: "evt-1" });
capturedParams.onevent({ id: "evt-2" });
capturedParams.oneose();
shimLegacySimplePoolMethods(pool);
const listed = await listPromise;
assert.deepStrictEqual(
listed.map((event) => event.id),
["evt-1", "evt-2"]
);
const events = await pool.list(["wss://relay.com"], [{ kinds: [1] }]);
assert.equal(events.length, 1);
assert.equal(events[0].id, "1");
});
@@ -0,0 +1,166 @@
// Run with: node scripts/run-targeted-tests.mjs tests/services/relay-health-service.test.mjs
import "../test-helpers/setup-localstorage.mjs";
import assert from "node:assert/strict";
import test from "node:test";
import RelayHealthService from "../../js/services/relayHealthService.js";
// Mock SimpleEventEmitter for tests
class SimpleEventEmitter {
constructor() {
this.listeners = new Map();
}
on(event, handler) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(handler);
}
emit(event, ...args) {
if (this.listeners.has(event)) {
this.listeners.get(event).forEach((handler) => handler(...args));
}
}
}
test("RelayHealthService: initializes with default values", () => {
const service = new RelayHealthService();
assert.equal(service.getTelemetryOptIn(), false);
assert.equal(service.getRelayUrls().length, 0);
});
test("RelayHealthService: manages telemetry opt-in", () => {
const service = new RelayHealthService();
service.setTelemetryOptIn(true);
assert.equal(service.getTelemetryOptIn(), true);
assert.equal(globalThis.localStorage.getItem("bitvid:relay-health-telemetry-opt-in"), "true");
service.setTelemetryOptIn(false);
assert.equal(service.getTelemetryOptIn(), false);
assert.equal(globalThis.localStorage.getItem("bitvid:relay-health-telemetry-opt-in"), "false");
});
test("RelayHealthService: getRelayUrls fetches from relayManager", () => {
const relayManager = {
getEntries: () => [{ url: "wss://relay1.com" }, { url: "wss://relay2.com" }],
};
const service = new RelayHealthService({ relayManager });
const urls = service.getRelayUrls();
assert.deepEqual(urls, ["wss://relay1.com", "wss://relay2.com"]);
});
test("RelayHealthService: ensureRelayState creates default state", () => {
const service = new RelayHealthService();
const state = service.ensureRelayState("wss://relay.com");
assert.deepEqual(state, {
url: "wss://relay.com",
connected: false,
lastLatencyMs: null,
errorCount: 0,
lastCheckedAt: null,
lastErrorAt: null,
});
});
test("RelayHealthService: checkRelay success flow", async () => {
const mockRelay = new SimpleEventEmitter();
const nostrClient = {
ensurePool: async () => {},
pool: {
ensureRelay: async (url) => {
if (url === "wss://success.com") return mockRelay;
return null;
},
},
};
const service = new RelayHealthService({ nostrClient });
await service.checkRelay("wss://success.com");
// Simulate connection
mockRelay.emit("connect");
const state = service.ensureRelayState("wss://success.com");
assert.equal(state.connected, true);
assert.notEqual(state.lastCheckedAt, null);
assert.notEqual(state.lastLatencyMs, null);
});
test("RelayHealthService: checkRelay failure flow", async () => {
const nostrClient = {
ensurePool: async () => {},
pool: {
ensureRelay: async () => {
throw new Error("Connection failed");
},
},
};
const service = new RelayHealthService({ nostrClient });
await service.checkRelay("wss://fail.com");
const state = service.ensureRelayState("wss://fail.com");
assert.equal(state.connected, false);
assert.equal(state.errorCount, 1);
assert.notEqual(state.lastErrorAt, null);
});
test("RelayHealthService: checkRelay handles missing nostrClient", async () => {
const service = new RelayHealthService(); // No client
await service.checkRelay("wss://test.com");
const state = service.ensureRelayState("wss://test.com");
assert.equal(state.connected, false);
assert.equal(state.errorCount, 1);
});
test("RelayHealthService: refresh checks all relays", async () => {
const relayManager = {
getEntries: () => [{ url: "wss://r1.com" }, { url: "wss://r2.com" }],
};
const nostrClient = {
ensurePool: async () => {},
pool: {
ensureRelay: async (url) => {
const relay = new SimpleEventEmitter();
setTimeout(() => relay.emit("connect"), 10);
return relay;
},
},
};
const service = new RelayHealthService({ relayManager, nostrClient });
const snapshot = await service.refresh();
assert.equal(snapshot.length, 2);
assert.equal(snapshot[0].url, "wss://r1.com");
assert.equal(snapshot[1].url, "wss://r2.com");
});
test("RelayHealthService: emits telemetry if opted in", async () => {
let telemetryPayload = null;
const telemetryEmitter = (event, payload) => {
if (event === "relay_health_snapshot") {
telemetryPayload = payload;
}
};
const relayManager = {
getEntries: () => [{ url: "wss://t1.com" }],
};
const nostrClient = {
ensurePool: async () => {},
pool: {
ensureRelay: async () => new SimpleEventEmitter(),
},
};
const service = new RelayHealthService({ relayManager, nostrClient, telemetryEmitter });
service.setTelemetryOptIn(true);
await service.refresh();
assert.ok(telemetryPayload);
assert.equal(telemetryPayload.relays.length, 1);
assert.equal(telemetryPayload.relays[0].url, "wss://t1.com");
});
+108
View File
@@ -0,0 +1,108 @@
// Run with: node scripts/run-targeted-tests.mjs tests/state/profile-cache.test.mjs
import "../test-helpers/setup-localstorage.mjs";
import assert from "node:assert/strict";
import test, { beforeEach } from "node:test";
import { profileCache } from "../../js/state/profileCache.js";
beforeEach(() => {
if (globalThis.localStorage && typeof globalThis.localStorage.clear === "function") {
globalThis.localStorage.clear();
}
// Reset singleton state
if (profileCache.activePubkey) {
profileCache.clearMemoryCache(profileCache.activePubkey);
profileCache.activePubkey = null;
}
profileCache.memoryCache.clear();
});
test("ProfileCache: normalizeHexPubkey validates and cleans inputs", () => {
assert.equal(profileCache.normalizeHexPubkey(null), null);
assert.equal(profileCache.normalizeHexPubkey("invalid"), null);
const valid = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
assert.equal(profileCache.normalizeHexPubkey(valid), valid);
assert.equal(profileCache.normalizeHexPubkey(` ${valid.toUpperCase()} `), valid);
});
test("ProfileCache: resolves storage keys correctly", () => {
const pubkey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
// Test watchHistory (kind:pubkey:d)
const historyKey = profileCache.resolveAddressKey("watchHistory", { pubkey });
assert.match(historyKey, new RegExp(`bitvid:profile:${pubkey}:watchHistory:watch-history:v1`));
// Test profile (simple)
const profileKey = profileCache.resolveAddressKey("profile", { pubkey });
assert.match(profileKey, new RegExp(`bitvid:profile:${pubkey}:profileMetadata:v1`));
});
test("ProfileCache: persists data to localStorage and memory", () => {
const pubkey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const data = { foo: "bar" };
profileCache.setProfileData(pubkey, "test-section", data);
// Check memory
const memData = profileCache.getProfileData(pubkey, "test-section");
assert.deepEqual(memData, data);
// Check storage
const key = profileCache.getStorageKey(pubkey, "test-section");
const stored = globalThis.localStorage.getItem(key);
assert.ok(stored);
assert.deepEqual(JSON.parse(stored), data);
});
test("ProfileCache: loads from localStorage if memory miss", () => {
const pubkey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const data = { loaded: true };
const key = profileCache.getStorageKey(pubkey, "test-load");
// Pre-seed storage
globalThis.localStorage.setItem(key, JSON.stringify(data));
// Clear memory to force load
profileCache.clearMemoryCache(pubkey);
const result = profileCache.getProfileData(pubkey, "test-load");
assert.deepEqual(result, data);
});
test("ProfileCache: handles active profile switching", () => {
const pubkey1 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const pubkey2 = "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210";
profileCache.setActiveProfile(pubkey1);
profileCache.set("section", { p: 1 });
assert.deepEqual(profileCache.get("section"), { p: 1 });
profileCache.setActiveProfile(pubkey2);
// Should not access pubkey1's data via active convenience methods
assert.equal(profileCache.get("section"), null);
// But pubkey1 data should still exist in persistence if saved
const p1Data = profileCache.getProfileData(pubkey1, "section");
assert.deepEqual(p1Data, { p: 1 });
});
test("ProfileCache: setProfile normalizes and saves", () => {
const pubkey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const rawProfile = {
name: " Tester ",
picture: "https://example.com/pic.jpg",
about: "I am a tester",
};
const entry = profileCache.setProfile(pubkey, rawProfile);
assert.equal(entry.profile.name, "Tester");
assert.equal(entry.profile.picture, "https://example.com/pic.jpg");
assert.ok(entry.timestamp);
const stored = profileCache.getProfile(pubkey);
assert.deepEqual(stored, entry.profile);
});