Files
sentryagent-idp/tests/load/token-issuance.js
SentryAgent.ai Developer 1b682c22b2 feat(phase-4): WS1 — Production Hardening (Redis rate limiting, DB pool, health endpoint, k6)
Rate limiting:
- Replace in-memory express-rate-limit with ioredis + rate-limiter-flexible (sliding window)
- Graceful fallback to RateLimiterMemory when Redis unreachable
- RATE_LIMIT_WINDOW_MS / RATE_LIMIT_MAX_REQUESTS env var config
- Retry-After header on 429 responses
- agentidp_rate_limit_hits_total Prometheus counter

Database pool:
- Explicit pg.Pool config via DB_POOL_MAX/MIN/IDLE_TIMEOUT_MS/CONNECTION_TIMEOUT_MS
- Defaults: max=20, min=2, idle=30s, conn timeout=5s
- agentidp_db_pool_active_connections + agentidp_db_pool_waiting_requests gauges

Health endpoint:
- GET /health/detailed — per-service status (database, Redis, Vault, OPA)
- healthy / degraded (>1000ms) / unreachable classification
- HTTP 200 (all healthy) / 207 (any degraded) / 503 (any unreachable)

Load tests:
- tests/load/ with k6 scenarios for agent registration (100 VUs), token issuance (1000 VUs), credential rotation (50 VUs)
- npm run load-test script

Tests: 586 passing, zero TypeScript errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 04:20:37 +00:00

90 lines
2.8 KiB
JavaScript

/**
* k6 load test — Token Issuance
*
* Scenario : POST /api/v1/token (OAuth2 client_credentials grant)
* VUs : 1000
* Duration : 60 seconds
* Thresholds:
* p95 response time < 500 ms
* HTTP error rate < 1 %
*
* Usage:
* BASE_URL=http://localhost:3000 \
* CLIENT_ID=your-client-id \
* CLIENT_SECRET=your-secret \
* k6 run tests/load/token-issuance.js
*/
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';
// ── Custom metrics ─────────────────────────────────────────────────────────────
const errorRate = new Rate('error_rate');
const tokenIssuanceDuration = new Trend('token_issuance_duration_ms', true);
// ── Configuration ──────────────────────────────────────────────────────────────
export const options = {
vus: 1000,
duration: '60s',
thresholds: {
http_req_duration: ['p(95)<500'],
error_rate: ['rate<0.01'],
},
};
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';
const CLIENT_ID = __ENV.CLIENT_ID || 'load-test-client-id';
const CLIENT_SECRET = __ENV.CLIENT_SECRET || 'load-test-client-secret';
// ── Default function (executed per VU iteration) ───────────────────────────────
export default function tokenIssuance() {
const url = `${BASE_URL}/api/v1/token`;
// OAuth2 client_credentials grant — application/x-www-form-urlencoded body
const payload = {
grant_type: 'client_credentials',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
scope: 'agents:read agents:write',
};
const params = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json',
},
timeout: '10s',
};
const response = http.post(url, payload, params);
tokenIssuanceDuration.add(response.timings.duration);
const success = check(response, {
'status is 200': (r) => r.status === 200,
'response has access_token': (r) => {
try {
const body = JSON.parse(r.body);
return typeof body.access_token === 'string' && body.access_token.length > 0;
} catch {
return false;
}
},
'token_type is Bearer': (r) => {
try {
const body = JSON.parse(r.body);
return body.token_type === 'Bearer';
} catch {
return false;
}
},
'response time < 500ms': (r) => r.timings.duration < 500,
});
errorRate.add(!success);
// Minimal think-time — token issuance is typically called without delays
sleep(0.05);
}