From d91d01332a253da73b119bd899e54bb8c7901dc3 Mon Sep 17 00:00:00 2001 From: Hassan Abdel-Rahman Date: Tue, 19 May 2026 11:25:37 -0400 Subject: [PATCH 1/2] test: wait for both initiation and incremental index events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `broadcasts realm events` tests race on matrix sync ordering: the initiation and completion realm events are emitted without await ordering, so the completion event can become visible before the initiation event. The previous wait helper polled only for completion, then asserted on both events — when the initiation event hadn't synced yet, the assertion threw "Incremental index initiation event not found". Wait until BOTH events are visible before returning, and dump the realm events actually seen if either is still missing, so a future regression leaves a usable breadcrumb instead of a bare error. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../realm-server/tests/helpers/indexing.ts | 67 +++++++++++++++---- .../tests/realm-endpoints-test.ts | 30 ++++++--- 2 files changed, 77 insertions(+), 20 deletions(-) diff --git a/packages/realm-server/tests/helpers/indexing.ts b/packages/realm-server/tests/helpers/indexing.ts index cf504eecf96..1564cbd2c08 100644 --- a/packages/realm-server/tests/helpers/indexing.ts +++ b/packages/realm-server/tests/helpers/indexing.ts @@ -23,18 +23,42 @@ export async function waitForIncrementalIndexEvent( since: number, timeout = 5000, ) { + // The initiation and completion events are broadcast without await + // ordering (see Realm#sendIndexInitiationEvent + broadcastIncrementalInvalidationEvent), + // so matrix sync can surface them in either order. Wait until BOTH are + // visible so callers that re-fetch and assert on each event don't race. + let lastSeenInitiation = false; + let lastSeenIncremental = false; await waitUntil( async () => { let matrixMessages = await getMessagesSince(since); - - return matrixMessages.some( - (m) => - m.type === APP_BOXEL_REALM_EVENT_TYPE && - m.content.eventName === 'index' && - m.content.indexType === 'incremental', - ); + let sawInitiation = false; + let sawIncremental = false; + for (let m of matrixMessages) { + if ( + m.type !== APP_BOXEL_REALM_EVENT_TYPE || + m.content.eventName !== 'index' + ) { + continue; + } + if (m.content.indexType === 'incremental-index-initiation') { + sawInitiation = true; + } else if (m.content.indexType === 'incremental') { + sawIncremental = true; + } + if (sawInitiation && sawIncremental) { + return true; + } + } + lastSeenInitiation = sawInitiation; + lastSeenIncremental = sawIncremental; + return false; + }, + { + timeout, + timeoutMessage: () => + `incremental index events not both received (initiation=${lastSeenInitiation}, incremental=${lastSeenIncremental}, since=${since})`, }, - { timeout }, ); } @@ -85,10 +109,29 @@ export async function expectIncrementalIndexEvent( ? trimJsonExtension(targetUrl) : targetUrl; - if (!incrementalIndexInitiationEventContent) { - throw new Error('Incremental index initiation event not found'); - } - if (!incrementalEventContent) { + if (!incrementalIndexInitiationEventContent || !incrementalEventContent) { + let realmEventSummary = messages + .filter((m) => m.type === APP_BOXEL_REALM_EVENT_TYPE) + .map((m) => ({ + eventName: (m.content as { eventName?: string })?.eventName, + indexType: (m.content as { indexType?: string })?.indexType, + updatedFile: (m.content as { updatedFile?: string })?.updatedFile, + invalidations: (m.content as { invalidations?: string[] }) + ?.invalidations, + originServerTs: m.origin_server_ts, + })); + console.error( + `[expectIncrementalIndexEvent] missing event(s) for url=${url} since=${since} realm=${realm}. ` + + `initiationPresent=${Boolean( + incrementalIndexInitiationEventContent, + )} incrementalPresent=${Boolean( + incrementalEventContent, + )}. realm events seen (${realmEventSummary.length}): ` + + JSON.stringify(realmEventSummary), + ); + if (!incrementalIndexInitiationEventContent) { + throw new Error('Incremental index initiation event not found'); + } throw new Error('Incremental event content not found'); } assert.deepEqual(incrementalIndexInitiationEventContent, { diff --git a/packages/realm-server/tests/realm-endpoints-test.ts b/packages/realm-server/tests/realm-endpoints-test.ts index 8255c2fd784..065e1dfdae3 100644 --- a/packages/realm-server/tests/realm-endpoints-test.ts +++ b/packages/realm-server/tests/realm-endpoints-test.ts @@ -2214,18 +2214,32 @@ async function waitForIncrementalIndexEvent( try { await waitUntil(async () => { let matrixMessages = await getMessagesSince(since); - - return matrixMessages.some( - (m) => - m.type === APP_BOXEL_REALM_EVENT_TYPE && - m.content.eventName === 'index' && - m.content.indexType === 'incremental', - ); + let sawInitiation = false; + let sawIncremental = false; + for (let m of matrixMessages) { + if ( + m.type !== APP_BOXEL_REALM_EVENT_TYPE || + m.content.eventName !== 'index' + ) { + continue; + } + if (m.content.indexType === 'incremental-index-initiation') { + sawInitiation = true; + } else if (m.content.indexType === 'incremental') { + sawIncremental = true; + } + if (sawInitiation && sawIncremental) { + return true; + } + } + return false; }); } catch (e) { let matrixMessages = await getMessagesSince(since); - console.log('waitForIncrementalIndexEvent failed, no event found. Events:'); + console.log( + 'waitForIncrementalIndexEvent failed: at least one of the initiation or incremental event was missing. Events:', + ); console.log(JSON.stringify(matrixMessages, null, 2)); throw e; } From 044a09372578f5ee25a649799386110102241f5f Mon Sep 17 00:00:00 2001 From: Hassan Abdel-Rahman Date: Tue, 19 May 2026 11:37:24 -0400 Subject: [PATCH 2/2] test: consolidate waitForIncrementalIndexEvent to shared helper realm-endpoints-test.ts duplicated the helper with a 1s waitUntil default, which would re-introduce flakiness now that the wait requires two events. Import the shared helper (5s timeout + timeoutMessage) instead. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../tests/realm-endpoints-test.ts | 44 ++----------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/packages/realm-server/tests/realm-endpoints-test.ts b/packages/realm-server/tests/realm-endpoints-test.ts index 065e1dfdae3..51d0d27f055 100644 --- a/packages/realm-server/tests/realm-endpoints-test.ts +++ b/packages/realm-server/tests/realm-endpoints-test.ts @@ -42,7 +42,6 @@ import { makeTestReconciler, matrixRegistrationSecret, testRealmInfo, - waitUntil, testRealmHref, createJWT, cardInfo, @@ -51,7 +50,10 @@ import { type RealmRequest, withRealmPath, } from './helpers'; -import { expectIncrementalIndexEvent } from './helpers/indexing'; +import { + expectIncrementalIndexEvent, + waitForIncrementalIndexEvent, +} from './helpers/indexing'; import '@cardstack/runtime-common/helpers/code-equality-assertion'; import { RealmServer } from '../server'; import { MatrixClient } from '@cardstack/runtime-common/matrix-client'; @@ -2207,44 +2209,6 @@ module(basename(__filename), function () { }); }); -async function waitForIncrementalIndexEvent( - getMessagesSince: (since: number) => Promise, - since: number, -) { - try { - await waitUntil(async () => { - let matrixMessages = await getMessagesSince(since); - let sawInitiation = false; - let sawIncremental = false; - for (let m of matrixMessages) { - if ( - m.type !== APP_BOXEL_REALM_EVENT_TYPE || - m.content.eventName !== 'index' - ) { - continue; - } - if (m.content.indexType === 'incremental-index-initiation') { - sawInitiation = true; - } else if (m.content.indexType === 'incremental') { - sawIncremental = true; - } - if (sawInitiation && sawIncremental) { - return true; - } - } - return false; - }); - } catch (e) { - let matrixMessages = await getMessagesSince(since); - - console.log( - 'waitForIncrementalIndexEvent failed: at least one of the initiation or incremental event was missing. Events:', - ); - console.log(JSON.stringify(matrixMessages, null, 2)); - throw e; - } -} - function findRealmEvent( events: MatrixEvent[], eventName: string,