Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/backend/load-and-stress-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Load & Stress Test Suite

## Overview
Baseline performance tests for critical API endpoints.

## Tools
- **autocannon** — Node.js HTTP benchmarking library

## Endpoints Covered
| Endpoint | Method | Test Type |
|---|---|---|
| /health | GET | Load + Stress |
| /api/v1/contracts | GET | Load + Stress |

## Running Tests
```bash
# Unit tests only
npm run test:unit

# Load tests
npm run test:load

# Stress tests
npm run test:stress

# All with coverage
npm run test:all
```

## Baseline Thresholds
| Endpoint | Max Avg Latency | Min RPS | Max Error Rate |
|---|---|---|---|
| /health | 100ms | 50 | 0% |
| /api/v1/contracts | 200ms | 30 | 0% |

## Security Notes
- Tests target localhost only — never run against production
- No sensitive credentials in test payloads
- Stress tests simulate up to 100 concurrent connections
34 changes: 34 additions & 0 deletions src/tests/load/contracts.load.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import autocannon from "autocannon";
import app from "../../index";
import { Server } from "http";

/**
* Load test for GET /api/v1/contracts
* Baseline: 20 concurrent connections for 15 seconds
*/
describe("Load Test - GET /api/v1/contracts", () => {
let server: Server;
beforeAll((done) => {
// Start a local server for testing
server = app.listen(3098, done);
});

afterAll((done) => {
server.close(done);
});
it("should serve contracts endpoint under load", async () => {
const result = await autocannon({
url: "http://localhost:3098/api/v1/contracts",
connections: 20,
duration: 15,
method: "GET",
});

console.log(`RPS: ${result.requests.average}`);
console.log(`Avg Latency: ${result.latency.average}ms`);
console.log(`Errors: ${result.errors}`);

expect(result.errors).toBe(0);
expect(result.latency.p99).toBeLessThan(500); // 99th percentile under 500ms
}, 40000);
});
39 changes: 39 additions & 0 deletions src/tests/load/health.load.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import autocannon from 'autocannon';
import app from '../../index';
import { Server } from 'http';

/**
* Load test for GET /health
* Baseline: 10 concurrent connections for 10 seconds
* Expects: zero errors, <100ms average latency
*/
describe('Load Test - GET /health', () => {
let server: Server;

beforeAll((done) => {
// Start a local server for testing
server = app.listen(3099, done);

});

afterAll((done) => {
server.close(done);
});

it('should handle baseline load with no errors', async () => {
const result = await autocannon({
url: 'http://localhost:3099/health',
connections: 10, // 10 concurrent users
duration: 10, // for 10 seconds
method: 'GET',
});

console.log(`RPS: ${result.requests.average}`);
console.log(`Avg Latency: ${result.latency.average}ms`);
console.log(`Errors: ${result.errors}`);

expect(result.errors).toBe(0);
expect(result.latency.average).toBeLessThan(100); // under 100ms
expect(result.requests.average).toBeGreaterThan(50); // at least 50 RPS
}, 30000); // 30s timeout for Jest
});
50 changes: 50 additions & 0 deletions src/tests/stress/stress.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import autocannon from "autocannon";
import app from "../../index";
import { Server } from "http";

/**
* Stress Test Suite
* Pushes endpoints beyond normal load to find breaking points.
* Security note: run only against local/staging — never production.
*/

let server: Server;

beforeAll((done) => {
server = app.listen(3097, done);
});

afterAll((done) => {
server.close(done);
});
describe("Stress Test - All Critical Endpoints", () => {
it("should degrade gracefully under spike load on /health", async () => {
const result = await autocannon({
url: "http://localhost:3097/health",
connections: 100, // 100 concurrent — spike!
duration: 20,
method: "GET",
});

console.log("=== STRESS RESULTS /health ===");
console.log(`RPS: ${result.requests.average}`);
console.log(`p99 Latency: ${result.latency.p99}ms`);
console.log(`Errors: ${result.errors}`);

// Under stress, we still expect no crashes — errors should be minimal
const errorRate = result.errors / result.requests.total;
expect(errorRate).toBeLessThan(0.05); // less than 5% error rate
}, 60000);

it("should degrade gracefully under spike load on /api/v1/contracts", async () => {
const result = await autocannon({
url: "http://localhost:3097/api/v1/contracts",
connections: 100,
duration: 20,
method: "GET",
});

const errorRate = result.errors / result.requests.total;
expect(errorRate).toBeLessThan(0.05);
}, 60000);
});
Loading