Skip to content

Commit f02247b

Browse files
d-csclaude
andcommitted
test(run-ops): stub org-data-stores registry singleton + deterministic empty CH window
Two webapp unit-test shards were red on this branch: - Shard 6 (apiRetrieveRunPresenter.readroute.test.ts): the organizationDataStoresRegistry singleton is constructed at import (pulled in transitively via the ClickHouse factory instance) and immediately fires a `forever` pRetry(loadFromDatabase) plus a setInterval reload against db.server's $replica. In CI (no Postgres on localhost) those retry forever and saturate the worker event loop, timing out an awaiting test's hook. Mock the instance module to a no-op in test/setup.ts, mirroring the other eager-singleton stubs. No unit test uses this singleton — the registry-behavior tests construct the class directly — so this is safe and should also help other shards/branches that import the presenter graph. - Shard 2 (nextRunListPresenter.readthrough.test.ts): the two empty-state tests seeded a run then assumed it had NOT yet replicated to ClickHouse within the page window, so result.runs would be empty. That held on a slow local stack but raced on CI, where replication had completed and the run surfaced (length 1, not 0). Scope those two calls to a `to` one hour in the past; the CH page filters created_at <= to, so a just-created run is deterministically excluded regardless of replication timing. The PG existence probe has no time filter, so it still finds the row and hasAnyRuns stays true — the exact behavior each test verifies. Reproduced both failures and verified the fix under a CI-faithful env (DATABASE_URL + REDIS at dead ports, GITHUB_ACTIONS=true, --no-file-parallelism): shard 2 now 18 files / 118 tests green, shard 6 now 16 files / 212 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c1b31f0 commit f02247b

2 files changed

Lines changed: 33 additions & 3 deletions

File tree

apps/webapp/test/nextRunListPresenter.readthrough.test.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,17 @@ function throwingFindFirst(prisma: PrismaClient, label: string): PrismaClient {
169169
}) as unknown as PrismaClient;
170170
}
171171

172-
const callOptions = (ctx: SeedContext) => ({ projectId: ctx.projectId, pageSize: 10 });
172+
const callOptions = (ctx: SeedContext, overrides?: { to?: number }) => ({
173+
projectId: ctx.projectId,
174+
pageSize: 10,
175+
...overrides,
176+
});
177+
178+
// `to` one hour in the past. The CH page filters `created_at <= to`, so a just-created run is
179+
// deterministically excluded regardless of replication timing — the empty-state tests otherwise
180+
// raced on the run not having replicated yet (held locally, failed on CI). The PG existence probe
181+
// has no time filter, so it still finds the row and `hasAnyRuns` stays true.
182+
const emptyPageWindow = (): { to: number } => ({ to: Date.now() - 60 * 60 * 1000 });
173183

174184
describe("NextRunListPresenter dual-DB empty-state probe + routed hydrate (legacy + new Postgres)", () => {
175185
// no-false-empty. Runs ONLY on legacy, none on new. Empty CH page -> listRuns returns [].
@@ -209,7 +219,7 @@ describe("NextRunListPresenter dual-DB empty-state probe + routed hydrate (legac
209219
const result = await presenter.call(
210220
ctx.organizationId,
211221
ctx.environmentId,
212-
callOptions(ctx)
222+
callOptions(ctx, emptyPageWindow())
213223
);
214224

215225
// CH id-set is empty within the page window, but the legacy probe finds the row.
@@ -337,7 +347,11 @@ describe("NextRunListPresenter dual-DB empty-state probe + routed hydrate (legac
337347
// (passthrough) and the probe is one plain `this.replica.taskRun.findFirst`.
338348
const presenter = new NextRunListPresenter(prisma, clickhouse);
339349

340-
const result = await presenter.call(ctx.organizationId, ctx.environmentId, callOptions(ctx));
350+
const result = await presenter.call(
351+
ctx.organizationId,
352+
ctx.environmentId,
353+
callOptions(ctx, emptyPageWindow())
354+
);
341355

342356
expect(result.runs).toHaveLength(0);
343357
expect(result.hasAnyRuns).toBe(true);

apps/webapp/test/setup.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,22 @@ vi.mock("~/services/taskMetadataCacheInstance.server", async () => {
140140
return { taskMetadataCacheInstance: new NoopTaskMetadataCache() };
141141
});
142142

143+
// The org-data-stores registry singleton is constructed at import (transitively via
144+
// the ClickHouse factory instance, which many presenters pull in). Its ctor fires a
145+
// `forever` pRetry(loadFromDatabase) plus a setInterval reload against db.server's
146+
// $replica; in CI (no Postgres) those retry forever, blocking the worker until any
147+
// awaiting test's hook times out. Stub the instance to a no-op — no unit test uses
148+
// this singleton (the registry-behavior tests construct the class directly).
149+
vi.mock("~/services/dataStores/organizationDataStoresRegistryInstance.server", () => ({
150+
organizationDataStoresRegistry: {
151+
isReady: Promise.resolve(),
152+
isLoaded: true,
153+
get: vi.fn().mockReturnValue(null),
154+
reload: vi.fn().mockResolvedValue(undefined),
155+
loadFromDatabase: vi.fn().mockResolvedValue(undefined),
156+
},
157+
}));
158+
143159
vi.mock("~/v3/runEngine.server", () => ({ engine: noopProxy() }));
144160
vi.mock("~/v3/marqs/index.server", () => ({ marqs: noopProxy(), MarQS: class {} }));
145161
vi.mock("~/v3/marqs/devPubSub.server", () => ({ devPubSub: noopProxy() }));

0 commit comments

Comments
 (0)