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>
117 lines
3.4 KiB
JavaScript
117 lines
3.4 KiB
JavaScript
import http from 'k6/http';
|
|
import { check, sleep } from 'k6';
|
|
import { Trend } from 'k6/metrics';
|
|
import { BASE_URL, SLA_THRESHOLDS, registerTestUser, authHeaders } from '../lib/config.js';
|
|
|
|
const searchDuration = new Trend('listings_search_duration', true);
|
|
const detailDuration = new Trend('listings_detail_duration', true);
|
|
|
|
export const options = {
|
|
scenarios: {
|
|
listings_load: {
|
|
executor: 'ramping-vus',
|
|
startVUs: 0,
|
|
stages: [
|
|
{ duration: '30s', target: 100 },
|
|
{ duration: '2m', target: 500 }, // peak: 500 concurrent
|
|
{ duration: '30s', target: 0 },
|
|
],
|
|
gracefulRampDown: '10s',
|
|
},
|
|
},
|
|
thresholds: {
|
|
...SLA_THRESHOLDS,
|
|
listings_search_duration: ['p(95)<500'],
|
|
listings_detail_duration: ['p(95)<300'],
|
|
},
|
|
};
|
|
|
|
export function setup() {
|
|
// Register a user and create some listings for search/detail tests
|
|
const tokens = registerTestUser(http);
|
|
if (!tokens) return { accessToken: null, listingIds: [] };
|
|
|
|
const listingIds = [];
|
|
const cities = ['Hà Nội', 'TP. Hồ Chí Minh', 'Đà Nẵng'];
|
|
const types = ['APARTMENT', 'HOUSE', 'LAND'];
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
const listing = {
|
|
title: `K6 Load Test Listing ${i}`,
|
|
description: `Performance test listing #${i} for load testing`,
|
|
transactionType: i % 2 === 0 ? 'SALE' : 'RENT',
|
|
propertyType: types[i % types.length],
|
|
address: `${100 + i} Đường Tải Thử`,
|
|
ward: 'Phường 1',
|
|
district: 'Quận 1',
|
|
city: cities[i % cities.length],
|
|
latitude: 10.7769 + (i * 0.001),
|
|
longitude: 106.7009 + (i * 0.001),
|
|
area: 50 + (i * 10),
|
|
bedrooms: 1 + (i % 4),
|
|
bathrooms: 1 + (i % 3),
|
|
floors: 1 + (i % 3),
|
|
priceVND: 1000000000 + (i * 500000000),
|
|
direction: 'EAST',
|
|
};
|
|
|
|
const res = http.post(
|
|
`${BASE_URL}/listings`,
|
|
JSON.stringify(listing),
|
|
{ headers: authHeaders(tokens.accessToken), tags: { name: 'setup_create_listing' } },
|
|
);
|
|
|
|
if (res.status === 201 || res.status === 200) {
|
|
try {
|
|
listingIds.push(JSON.parse(res.body).id);
|
|
} catch (_) { /* skip */ }
|
|
}
|
|
}
|
|
|
|
return { accessToken: tokens.accessToken, listingIds };
|
|
}
|
|
|
|
export default function (data) {
|
|
const iter = __ITER;
|
|
|
|
if (iter % 3 === 0 && data.listingIds.length > 0) {
|
|
// Detail view — pick a random listing
|
|
const id = data.listingIds[iter % data.listingIds.length];
|
|
const res = http.get(`${BASE_URL}/listings/${id}`, {
|
|
tags: { name: 'GET /listings/:id' },
|
|
});
|
|
|
|
detailDuration.add(res.timings.duration);
|
|
check(res, {
|
|
'detail: status 200': (r) => r.status === 200,
|
|
'detail: has id': (r) => {
|
|
try { return JSON.parse(r.body).id !== undefined; } catch { return false; }
|
|
},
|
|
});
|
|
} else {
|
|
// Search listings with various filters
|
|
const queries = [
|
|
'?limit=20&offset=0',
|
|
'?propertyType=APARTMENT&limit=10',
|
|
'?transactionType=SALE&limit=10',
|
|
'?city=Hà Nội&limit=10',
|
|
'?limit=20&offset=20',
|
|
];
|
|
const query = queries[iter % queries.length];
|
|
|
|
const res = http.get(`${BASE_URL}/listings${query}`, {
|
|
tags: { name: 'GET /listings (search)' },
|
|
});
|
|
|
|
searchDuration.add(res.timings.duration);
|
|
check(res, {
|
|
'search: status 200': (r) => r.status === 200,
|
|
'search: has data array': (r) => {
|
|
try { return Array.isArray(JSON.parse(r.body).data); } catch { return false; }
|
|
},
|
|
});
|
|
}
|
|
|
|
sleep(Math.random() * 1 + 0.3);
|
|
}
|