test: prove reads finish before source synchronization
Conventional commits / commits (pull_request) Successful in 31s
Backend security / security (pull_request) Successful in 9s
Backend verify / verify (pull_request) Successful in 10m47s
Backend security / security (push) Successful in 8s
Backend verify / verify (push) Failing after 12m14s
Conventional commits / commits (pull_request) Successful in 31s
Backend security / security (pull_request) Successful in 9s
Backend verify / verify (pull_request) Successful in 10m47s
Backend security / security (push) Successful in 8s
Backend verify / verify (push) Failing after 12m14s
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.
This commit was merged in pull request #34.
This commit is contained in:
@@ -70,10 +70,12 @@ describe('T-Bank persisted read budget', () => {
|
|||||||
}).sort((a, b) => Number(b.date.seconds) - Number(a.date.seconds));
|
}).sort((a, b) => Number(b.date.seconds) - Number(a.date.seconds));
|
||||||
const rpc: Record<string, number> = {};
|
const rpc: Record<string, number> = {};
|
||||||
let upstreamMode: 'ready' | 'delayed' | 'failed' = 'ready';
|
let upstreamMode: 'ready' | 'delayed' | 'failed' = 'ready';
|
||||||
|
let sourceGate: Promise<void> | undefined;
|
||||||
|
let releaseSource = () => {};
|
||||||
const serverTimes: number[] = [];
|
const serverTimes: number[] = [];
|
||||||
async function counted<T>(name: string, value: T): Promise<T> {
|
async function counted<T>(name: string, value: T): Promise<T> {
|
||||||
rpc[name] = (rpc[name] ?? 0) + 1;
|
rpc[name] = (rpc[name] ?? 0) + 1;
|
||||||
if (upstreamMode === 'delayed') await delay(2000);
|
if (upstreamMode === 'delayed') await sourceGate;
|
||||||
if (upstreamMode === 'failed')
|
if (upstreamMode === 'failed')
|
||||||
return Promise.reject(new Error('Synthetic outage'));
|
return Promise.reject(new Error('Synthetic outage'));
|
||||||
return Promise.resolve(value);
|
return Promise.resolve(value);
|
||||||
@@ -516,7 +518,8 @@ describe('T-Bank persisted read budget', () => {
|
|||||||
JSON.stringify(output, null, 2),
|
JSON.stringify(output, null, 2),
|
||||||
);
|
);
|
||||||
console.log(JSON.stringify(output));
|
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 fresh();
|
||||||
await app.get(PrismaService).tbankPortfolio.updateMany({
|
await app.get(PrismaService).tbankPortfolio.updateMany({
|
||||||
where: { accountId: 'baseline-0' },
|
where: { accountId: 'baseline-0' },
|
||||||
@@ -524,31 +527,45 @@ describe('T-Bank persisted read budget', () => {
|
|||||||
});
|
});
|
||||||
const beforeStale = { ...rpc };
|
const beforeStale = { ...rpc };
|
||||||
upstreamMode = 'delayed';
|
upstreamMode = 'delayed';
|
||||||
|
sourceGate = new Promise<void>((resolve) => {
|
||||||
|
releaseSource = resolve;
|
||||||
|
});
|
||||||
const startStale = performance.now();
|
const startStale = performance.now();
|
||||||
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Array.from({ length: 20 }, () =>
|
Array.from({ length: 20 }, () =>
|
||||||
request(app.getHttpServer())
|
request(app.getHttpServer())
|
||||||
.get('/api/v1/brokerage-accounts/baseline-0/portfolio')
|
.get('/api/v1/brokerage-accounts/baseline-0/portfolio')
|
||||||
.set('Authorization', `Bearer ${token}`)
|
.set('Authorization', `Bearer ${token}`)
|
||||||
|
.timeout(10_000)
|
||||||
.expect(200),
|
.expect(200),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} finally {
|
||||||
|
releaseSource();
|
||||||
|
}
|
||||||
const staleResponsesMs = performance.now() - startStale;
|
const staleResponsesMs = performance.now() - startStale;
|
||||||
expect(staleResponsesMs).toBeLessThan(2000);
|
|
||||||
await settled();
|
await settled();
|
||||||
expect(rpc.portfolio - beforeStale.portfolio).toBe(1);
|
expect(rpc.portfolio - beforeStale.portfolio).toBe(1);
|
||||||
expect(rpc.positions - beforeStale.positions).toBe(1);
|
expect(rpc.positions - beforeStale.positions).toBe(1);
|
||||||
await app
|
await app
|
||||||
.get(PrismaService)
|
.get(PrismaService)
|
||||||
.tbankPortfolio.deleteMany({ where: { accountId: 'baseline-0' } });
|
.tbankPortfolio.deleteMany({ where: { accountId: 'baseline-0' } });
|
||||||
|
sourceGate = new Promise<void>((resolve) => {
|
||||||
|
releaseSource = resolve;
|
||||||
|
});
|
||||||
const coldStart = performance.now();
|
const coldStart = performance.now();
|
||||||
|
try {
|
||||||
const missing = await request(app.getHttpServer())
|
const missing = await request(app.getHttpServer())
|
||||||
.get('/api/v1/brokerage-accounts/baseline-0/portfolio')
|
.get('/api/v1/brokerage-accounts/baseline-0/portfolio')
|
||||||
.set('Authorization', `Bearer ${token}`)
|
.set('Authorization', `Bearer ${token}`)
|
||||||
|
.timeout(10_000)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
expect((missing.body as { data: unknown }).data).toBeNull();
|
expect((missing.body as { data: unknown }).data).toBeNull();
|
||||||
|
} finally {
|
||||||
|
releaseSource();
|
||||||
|
}
|
||||||
const coldImportMs = performance.now() - coldStart;
|
const coldImportMs = performance.now() - coldStart;
|
||||||
expect(coldImportMs).toBeLessThan(2000);
|
|
||||||
await settled();
|
await settled();
|
||||||
Object.assign(output, {
|
Object.assign(output, {
|
||||||
staleResponsesMs,
|
staleResponsesMs,
|
||||||
|
|||||||
Reference in New Issue
Block a user