feat(load-tests): add K6 load testing suite for critical API paths
K6 scripts for 4 critical paths: - Auth (100 VU): login, register, profile - Listings (500 VU): search with filters, detail view - Search (200 VU): full-text + geo search - Payments (50 VU): create payment, list transactions SLA thresholds: p50<200ms, p95<500ms, p99<1s, error<1%. CI: manual workflow_dispatch with suite selector. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
95
load-tests/scripts/payments.js
Normal file
95
load-tests/scripts/payments.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import http from 'k6/http';
|
||||
import { check, sleep } from 'k6';
|
||||
import { Rate, Trend } from 'k6/metrics';
|
||||
import { BASE_URL, SLA_THRESHOLDS, registerTestUser, authHeaders } from '../lib/config.js';
|
||||
|
||||
const paymentCreateDuration = new Trend('payment_create_duration', true);
|
||||
const paymentListDuration = new Trend('payment_list_duration', true);
|
||||
const paymentFailRate = new Rate('payment_failures');
|
||||
|
||||
export const options = {
|
||||
scenarios: {
|
||||
payments_load: {
|
||||
executor: 'ramping-vus',
|
||||
startVUs: 0,
|
||||
stages: [
|
||||
{ duration: '30s', target: 20 },
|
||||
{ duration: '1m', target: 50 }, // peak: 50 concurrent
|
||||
{ duration: '30s', target: 0 },
|
||||
],
|
||||
gracefulRampDown: '10s',
|
||||
},
|
||||
},
|
||||
thresholds: {
|
||||
...SLA_THRESHOLDS,
|
||||
payment_create_duration: ['p(95)<500'],
|
||||
payment_list_duration: ['p(95)<300'],
|
||||
payment_failures: ['rate<0.05'],
|
||||
},
|
||||
};
|
||||
|
||||
export function setup() {
|
||||
// Register multiple users for payment tests
|
||||
const tokens = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const phone = `0920${String(i).padStart(6, '0')}`;
|
||||
const t = registerTestUser(http, phone);
|
||||
if (t) tokens.push(t.accessToken);
|
||||
}
|
||||
return { tokens };
|
||||
}
|
||||
|
||||
export default function (data) {
|
||||
if (!data.tokens.length) return;
|
||||
|
||||
const token = data.tokens[__VU % data.tokens.length];
|
||||
const headers = authHeaders(token);
|
||||
const iter = __ITER;
|
||||
|
||||
if (iter % 3 === 0) {
|
||||
// List transactions
|
||||
const res = http.get(`${BASE_URL}/payments?limit=10&offset=0`, {
|
||||
headers,
|
||||
tags: { name: 'GET /payments (list)' },
|
||||
});
|
||||
|
||||
paymentListDuration.add(res.timings.duration);
|
||||
check(res, {
|
||||
'list payments: status 200': (r) => r.status === 200,
|
||||
'list payments: has data': (r) => {
|
||||
try { return JSON.parse(r.body).data !== undefined; } catch { return false; }
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Create payment
|
||||
const amounts = [100000, 500000, 1000000, 2500000, 5000000];
|
||||
const payload = JSON.stringify({
|
||||
provider: 'VNPAY',
|
||||
type: 'LISTING_FEE',
|
||||
amountVND: amounts[iter % amounts.length],
|
||||
description: `K6 load test payment ${__VU}-${iter}`,
|
||||
returnUrl: 'http://localhost:3000/payments/callback',
|
||||
});
|
||||
|
||||
const res = http.post(`${BASE_URL}/payments`, payload, {
|
||||
headers,
|
||||
tags: { name: 'POST /payments (create)' },
|
||||
});
|
||||
|
||||
paymentCreateDuration.add(res.timings.duration);
|
||||
|
||||
// Payment gateway may not be available — 502/503 is acceptable in test
|
||||
const ok = check(res, {
|
||||
'create payment: status 201|502|503': (r) =>
|
||||
r.status === 201 || r.status === 502 || r.status === 503,
|
||||
'create payment: has id or gateway error': (r) => {
|
||||
if (r.status >= 500) return true; // gateway unavailable
|
||||
try { return JSON.parse(r.body).id !== undefined; } catch { return false; }
|
||||
},
|
||||
});
|
||||
|
||||
if (!ok) paymentFailRate.add(1);
|
||||
}
|
||||
|
||||
sleep(Math.random() * 2 + 1);
|
||||
}
|
||||
Reference in New Issue
Block a user