Files
support_backend/tests/load/autocannon.config.ts
T

80 lines
2.9 KiB
TypeScript
Raw Normal View History

import autocannon from 'autocannon';
import { mkdirSync, writeFileSync } from 'fs';
import path from 'path';
/**
* specs/016-load-concurrency-testing data-model.md "Load test report". Printed to the console
* and written as JSON under tests/load/reports/ (gitignored — a run artifact, not a fixture)
* for every run, so two runs of the same script can be compared.
*/
export interface LoadTestReport {
endpoint: string;
connections: number;
durationSec: number;
requestsPerSec: number;
latencyP50Ms: number;
latencyP90Ms: number;
latencyP99Ms: number;
non2xxCount: number;
rateLimitedCount: number;
}
/**
* FR-007/FR-008: a thin wrapper over autocannon's programmatic API producing this feature's own
* report shape. FR-009: deliberately has NO pass/fail assertion on the numbers — throughput and
* latency targets are an `OPEN BUSINESS DECISION` (spec.md Assumptions) this project's roadmap
* says must never be invented and shipped as if final. This tool measures and records; wiring an
* explicit CI gate is future work once the business sets a real target.
*/
export async function runLoadTest(
endpoint: string,
options: autocannon.Options,
): Promise<LoadTestReport> {
const result = await autocannon(options);
const report: LoadTestReport = {
endpoint,
connections: result.connections,
durationSec: result.duration,
requestsPerSec: Number(result.requests.average.toFixed(2)),
latencyP50Ms: result.latency.p50,
latencyP90Ms: result.latency.p90,
latencyP99Ms: result.latency.p99,
non2xxCount: result.non2xx,
rateLimitedCount: result.statusCodeStats?.['429']?.count ?? 0,
};
printReport(report);
writeReport(report);
return report;
}
function printReport(report: LoadTestReport): void {
// eslint-disable-next-line no-console
console.log(`\n=== Load test report: ${report.endpoint} ===`);
// eslint-disable-next-line no-console
console.log(`connections: ${report.connections} duration: ${report.durationSec}s`);
// eslint-disable-next-line no-console
console.log(`requests/sec (avg): ${report.requestsPerSec}`);
// eslint-disable-next-line no-console
console.log(
`latency p50/p90/p99 (ms): ${report.latencyP50Ms}/${report.latencyP90Ms}/${report.latencyP99Ms}`,
);
// eslint-disable-next-line no-console
console.log(`non-2xx: ${report.non2xxCount} rate-limited (429): ${report.rateLimitedCount}`);
// eslint-disable-next-line no-console
console.log(
'No pass/fail threshold applied — throughput/latency targets are an OPEN BUSINESS DECISION.',
);
}
function writeReport(report: LoadTestReport): void {
const dir = path.join(__dirname, 'reports');
mkdirSync(dir, { recursive: true });
const safeName = report.endpoint.replace(/[^a-z0-9-]/gi, '_');
const filePath = path.join(dir, `${safeName}-${Date.now()}.json`);
writeFileSync(filePath, JSON.stringify(report, null, 2));
// eslint-disable-next-line no-console
console.log(`Report written to ${filePath}`);
}