From e4232138d04ad6a2aa8100a7d36fdcb2d7663fa8 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 26 Sep 2026 21:45:29 +0300 Subject: [PATCH] test: prove reads finish before source synchronization Block synthetic upstream with an explicit barrier instead of comparing total HTTP batch time with a two-second timer. Keep the 500 ms server p95 gate and RPC assertions unchanged. --- test/tbank-read-baseline.e2e-spec.ts | 51 ++++++++++++++++++---------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/test/tbank-read-baseline.e2e-spec.ts b/test/tbank-read-baseline.e2e-spec.ts index 27ae001..56cf046 100644 --- a/test/tbank-read-baseline.e2e-spec.ts +++ b/test/tbank-read-baseline.e2e-spec.ts @@ -70,10 +70,12 @@ describe('T-Bank persisted read budget', () => { }).sort((a, b) => Number(b.date.seconds) - Number(a.date.seconds)); const rpc: Record = {}; let upstreamMode: 'ready' | 'delayed' | 'failed' = 'ready'; + let sourceGate: Promise | undefined; + let releaseSource = () => {}; const serverTimes: number[] = []; async function counted(name: string, value: T): Promise { rpc[name] = (rpc[name] ?? 0) + 1; - if (upstreamMode === 'delayed') await delay(2000); + if (upstreamMode === 'delayed') await sourceGate; if (upstreamMode === 'failed') return Promise.reject(new Error('Synthetic outage')); return Promise.resolve(value); @@ -516,7 +518,8 @@ describe('T-Bank persisted read budget', () => { JSON.stringify(output, null, 2), ); console.log(JSON.stringify(output)); - // Delayed dependency cannot hold a data response or multiply one stale pass. + // Hold the source until HTTP reads finish: proves independence without a + // wall-clock race against the runner's throughput. The p95 gate stays below. await fresh(); await app.get(PrismaService).tbankPortfolio.updateMany({ where: { accountId: 'baseline-0' }, @@ -524,31 +527,45 @@ describe('T-Bank persisted read budget', () => { }); const beforeStale = { ...rpc }; upstreamMode = 'delayed'; + sourceGate = new Promise((resolve) => { + releaseSource = resolve; + }); const startStale = performance.now(); - await Promise.all( - Array.from({ length: 20 }, () => - request(app.getHttpServer()) - .get('/api/v1/brokerage-accounts/baseline-0/portfolio') - .set('Authorization', `Bearer ${token}`) - .expect(200), - ), - ); + try { + await Promise.all( + Array.from({ length: 20 }, () => + request(app.getHttpServer()) + .get('/api/v1/brokerage-accounts/baseline-0/portfolio') + .set('Authorization', `Bearer ${token}`) + .timeout(10_000) + .expect(200), + ), + ); + } finally { + releaseSource(); + } const staleResponsesMs = performance.now() - startStale; - expect(staleResponsesMs).toBeLessThan(2000); await settled(); expect(rpc.portfolio - beforeStale.portfolio).toBe(1); expect(rpc.positions - beforeStale.positions).toBe(1); await app .get(PrismaService) .tbankPortfolio.deleteMany({ where: { accountId: 'baseline-0' } }); + sourceGate = new Promise((resolve) => { + releaseSource = resolve; + }); const coldStart = performance.now(); - const missing = await request(app.getHttpServer()) - .get('/api/v1/brokerage-accounts/baseline-0/portfolio') - .set('Authorization', `Bearer ${token}`) - .expect(200); - expect((missing.body as { data: unknown }).data).toBeNull(); + try { + const missing = await request(app.getHttpServer()) + .get('/api/v1/brokerage-accounts/baseline-0/portfolio') + .set('Authorization', `Bearer ${token}`) + .timeout(10_000) + .expect(200); + expect((missing.body as { data: unknown }).data).toBeNull(); + } finally { + releaseSource(); + } const coldImportMs = performance.now() - coldStart; - expect(coldImportMs).toBeLessThan(2000); await settled(); Object.assign(output, { staleResponsesMs,