From ddcc7775612515780671cf8c66040bd45dde5b86 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 02:42:31 +0000 Subject: [PATCH] fix(test): resolve DM flow test timeouts and hangs Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- js/directMessagesStore.js | 66 ++++++++++++-------- tests/nostr/dm-direct-message-flow.test.mjs | 69 ++++++++++++++------- 2 files changed, 84 insertions(+), 51 deletions(-) diff --git a/js/directMessagesStore.js b/js/directMessagesStore.js index 45653a2c..c7c1d20e 100644 --- a/js/directMessagesStore.js +++ b/js/directMessagesStore.js @@ -192,16 +192,20 @@ export async function loadDirectMessageSnapshot(pubkey) { try { const db = await openSettingsDb(); if (db) { - const stored = await new Promise((resolve, reject) => { - const tx = db.transaction(STORE_NAME, "readonly"); - const store = tx.objectStore(STORE_NAME); - const request = store.get(dbKey); - request.onsuccess = () => resolve(request.result || []); - request.onerror = () => - reject(request.error || new Error("Failed to load snapshot")); - }); + try { + const stored = await new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readonly"); + const store = tx.objectStore(STORE_NAME); + const request = store.get(dbKey); + request.onsuccess = () => resolve(request.result || []); + request.onerror = () => + reject(request.error || new Error("Failed to load snapshot")); + }); - return normalizeSnapshotList(stored); + return normalizeSnapshotList(stored); + } finally { + db.close(); + } } } catch (error) { userLogger.warn( @@ -244,15 +248,19 @@ export async function saveDirectMessageSnapshot(pubkey, snapshot) { try { const db = await openSettingsDb(); if (db) { - await new Promise((resolve, reject) => { - const tx = db.transaction(STORE_NAME, "readwrite"); - const store = tx.objectStore(STORE_NAME); - const request = store.put(normalizedSnapshot, dbKey); - request.onsuccess = () => resolve(); - request.onerror = () => - reject(request.error || new Error("Failed to write snapshot")); - }); - return normalizedSnapshot; + try { + await new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readwrite"); + const store = tx.objectStore(STORE_NAME); + const request = store.put(normalizedSnapshot, dbKey); + request.onsuccess = () => resolve(); + request.onerror = () => + reject(request.error || new Error("Failed to write snapshot")); + }); + return normalizedSnapshot; + } finally { + db.close(); + } } } catch (error) { userLogger.warn( @@ -290,15 +298,19 @@ export async function clearDirectMessageSnapshot(pubkey) { try { const db = await openSettingsDb(); if (db) { - await new Promise((resolve, reject) => { - const tx = db.transaction(STORE_NAME, "readwrite"); - const store = tx.objectStore(STORE_NAME); - const request = store.delete(dbKey); - request.onsuccess = () => resolve(); - request.onerror = () => - reject(request.error || new Error("Failed to clear snapshot")); - }); - cleared = true; + try { + await new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readwrite"); + const store = tx.objectStore(STORE_NAME); + const request = store.delete(dbKey); + request.onsuccess = () => resolve(); + request.onerror = () => + reject(request.error || new Error("Failed to clear snapshot")); + }); + cleared = true; + } finally { + db.close(); + } } } catch (error) { userLogger.warn( diff --git a/tests/nostr/dm-direct-message-flow.test.mjs b/tests/nostr/dm-direct-message-flow.test.mjs index 250cbaaf..25759eeb 100644 --- a/tests/nostr/dm-direct-message-flow.test.mjs +++ b/tests/nostr/dm-direct-message-flow.test.mjs @@ -1,5 +1,12 @@ +import "../../scripts/agent/setup-test-env.js"; import assert from "node:assert/strict"; import { test, afterEach } from "node:test"; +import { indexedDB, IDBKeyRange } from "fake-indexeddb"; + +if (!globalThis.indexedDB) { + globalThis.indexedDB = indexedDB; + globalThis.IDBKeyRange = IDBKeyRange; +} import { NostrClient } from "../../js/nostr/client.js"; import { NostrService } from "../../js/services/nostrService.js"; @@ -11,13 +18,6 @@ import { listMessagesByConversation, } from "../../js/storage/dmDb.js"; -const { indexedDB, IDBKeyRange } = await import("fake-indexeddb"); - -if (!globalThis.indexedDB) { - globalThis.indexedDB = indexedDB; - globalThis.IDBKeyRange = IDBKeyRange; -} - async function deleteDatabase(name) { await new Promise((resolve) => { const request = indexedDB.deleteDatabase(name); @@ -27,6 +27,15 @@ async function deleteDatabase(name) { }); } +async function waitFor(predicate, timeoutMs = 2000, intervalMs = 50, errorMessage = "Timed out") { + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + if (predicate()) return; + await new Promise((r) => setTimeout(r, intervalMs)); + } + throw new Error(errorMessage); +} + afterEach(async () => { await deleteDatabase("bitvidDm"); await deleteDatabase("bitvidSettings"); @@ -46,7 +55,12 @@ class FakeRelayPool { return { on(eventName, handler) { if (eventName === "ok") { - setTimeout(() => handler(), 0); + // Call synchronously so tests don't depend on scheduling/timers. + try { + handler(); + } catch (err) { + // Swallow to avoid breaking tests that don't expect thrown errors here. + } } return true; }, @@ -231,10 +245,12 @@ test("DM relay duplication is deduped", async () => { relayPool.flushQueue({ duplicate: true }); // Poll for the expected state - for (let i = 0; i < 20; i++) { - if (received.length === 1 && notifications.length === 1) break; - await new Promise((resolve) => setTimeout(resolve, 50)); - } + await waitFor( + () => received.length === 1 && notifications.length === 1, + 2000, + 50, + `received=${received.length}, notifications=${notifications.length}` + ); assert.equal(received.length, 1, "duplicate relay events should be deduped"); assert.equal(notifications.length, 1, "incoming messages should notify once"); @@ -291,11 +307,15 @@ test("out-of-order DM delivery keeps newest messages first", async () => { relayPool.queue = []; let messages = []; - for (let i = 0; i < 20; i++) { + await waitFor( + () => { messages = receiverService.getDirectMessages(); - if (messages.length === 2) break; - await new Promise((resolve) => setTimeout(resolve, 50)); - } + return messages.length === 2; + }, + 2000, + 50, + "two DM messages should be present" + ); assert.equal(messages.length, 2, "two DM messages should be present"); assert.equal(messages[0].plaintext, "Second message"); @@ -354,10 +374,12 @@ test("DM reconnect replays do not duplicate messages or reset seen state", async relayPool.flushQueue(); let conversationId = `dm:${[senderPubkey, receiverPubkey].sort().join(":")}`; - for (let i = 0; i < 20; i++) { - if (receiverService.getDirectMessageUnseenCount(conversationId) === 1) break; - await new Promise((resolve) => setTimeout(resolve, 50)); - } + await waitFor( + () => receiverService.getDirectMessageUnseenCount(conversationId) === 1, + 2000, + 50, + "Waiting for unseen count 1" + ); assert.equal(receiverService.getDirectMessageUnseenCount(conversationId), 1); @@ -374,6 +396,9 @@ test("DM reconnect replays do not duplicate messages or reset seen state", async subscription.unsub(); subscribe(); + if (!relayPool.published.length) { + throw new Error("No published events to replay (relayPool.published is empty)"); + } relayPool.deliver(relayPool.published[0]); // Ensure no duplication happens @@ -394,7 +419,3 @@ test("DM reconnect replays do not duplicate messages or reset seen state", async restore(); } }); - -test.after(() => { - setTimeout(() => process.exit(0), 100); -});