From 703c92c776b903bd1ed62a3dd3f307bb22ab0b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 10:14:03 +0200 Subject: [PATCH] test(integration): take the clock and the probe budget out of two verdict tests The grant expiry case gave the grant 400 ms and slept 500: on a loaded runner accepting, signing in and switching took longer than the grant lived, and the grant expired before it was accepted. The grant now runs 30 seconds and the case moves the clock the resolver reads to the instant the grant names, so the row still does not change and nothing sleeps. The key check's start-up probe is bounded at ten seconds and two per query. Under load a probe query ran past two seconds and the check answered inconclusive where the data says mismatch. The verdict tests now run the check with time to finish; the cases that test the bounds pass their own budget or sampler and keep them. --- .../acting-account-resolver.test.ts | 17 ++++++--- .../integration/encryption-key-check.test.ts | 35 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/tests/integration/acting-account-resolver.test.ts b/tests/integration/acting-account-resolver.test.ts index 0c61815a4..923088189 100644 --- a/tests/integration/acting-account-resolver.test.ts +++ b/tests/integration/acting-account-resolver.test.ts @@ -282,21 +282,30 @@ describe("acting-account resolver — the decision is never cached", () => { const owner = await makeUser("owner"); const delegate = await makeUser("delegate"); await seedWeight(owner.id, 81); + // Far enough ahead that accepting, signing in and switching cannot + // outrun it on a loaded runner, and well inside the session's minute. + const expiresAt = new Date(Date.now() + 30_000); const invited = await inviteGrant({ grantorId: owner.id, granteeId: delegate.id, access: "READ", scope: null, - expiresAt: new Date(Date.now() + 400), + expiresAt, }); await acceptGrant({ grantId: invited.id, granteeId: delegate.id }); const session = await signIn(delegate.id); await switchTo(session.id, owner.id); expect((await call()).status).toBe(200); - await new Promise((resolve) => setTimeout(resolve, 500)); - // The row did not change. Only the clock the resolver reads moved. - expect((await call()).status).toBe(403); + // The row does not change. Only the clock the resolver reads moves, to + // the instant the grant names: the first one it no longer holds. + vi.useFakeTimers({ toFake: ["Date"] }); + try { + vi.setSystemTime(expiresAt); + expect((await call()).status).toBe(403); + } finally { + vi.useRealTimers(); + } }); it("refuses the moment the grant is only pending", async () => { diff --git a/tests/integration/encryption-key-check.test.ts b/tests/integration/encryption-key-check.test.ts index eae7d1f87..f7d1a8ea5 100644 --- a/tests/integration/encryption-key-check.test.ts +++ b/tests/integration/encryption-key-check.test.ts @@ -44,6 +44,41 @@ vi.mock("@/lib/db-compat", () => ({ ensureDbCompatibility: vi.fn().mockResolvedValue(undefined), })); +/** + * The start-up probe is bounded in time on purpose: ten seconds in all and + * two per query, so a host with a huge table still starts. Those bounds are + * not what this file asks about. On a loaded runner a probe query can take + * longer than two seconds, and the check then answers "inconclusive" + * (`incomplete`) where the data says "mismatch", which is the right answer + * for a slow host and the wrong one for a test of the verdicts. So the check + * runs here with time to finish, unless a case passes its own budget or + * sampler: the cases that test the bounds themselves do. + */ +vi.mock("@/lib/crypto/canary", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + checkEncryptionKeyCanaries: ( + client: Parameters[0], + options: Parameters[1] = {}, + ) => { + let sampler; + try { + sampler = actual.sqlColumnSampler(client, { + statementTimeoutMs: 60_000, + }); + } catch { + // A client the sampler cannot use: the check reports that itself. + } + return actual.checkEncryptionKeyCanaries(client, { + probeBudgetMs: 120_000, + ...(sampler ? { sampler } : {}), + ...options, + }); + }, + }; +}); + import { _resetCryptoCacheForTests, encrypt,