Files
pos-system/microservices/tests/load/k6/catalog-listing.js
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

129 lines
4.5 KiB
JavaScript

/**
* EN: k6 load test — Catalog listing endpoint (GET /api/v1/products).
* VI: k6 load test — endpoint danh sách catalog (GET /api/v1/products).
*
* Usage:
* k6 run tests/load/k6/catalog-listing.js
* k6 run --env BASE_URL=http://api.staging.goodgo.vn tests/load/k6/catalog-listing.js
*
* Thresholds:
* - 95th-percentile response time < 200ms (read-heavy endpoint must be fast)
* - Error rate < 0.5%
*/
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';
// ---------------------------------------------------------------------------
// Custom metrics
// ---------------------------------------------------------------------------
const catalogErrors = new Rate('catalog_errors');
const catalogListDuration = new Trend('catalog_list_duration', true);
const catalogDetailDuration = new Trend('catalog_detail_duration', true);
const catalogRequests = new Counter('catalog_total_requests');
// ---------------------------------------------------------------------------
// Test options — higher concurrency for read endpoint
// ---------------------------------------------------------------------------
export const options = {
stages: [
{ duration: '30s', target: 50 },
{ duration: '2m', target: 100 },
{ duration: '30s', target: 200 }, // EN: Stress test / VI: Stress test
{ duration: '30s', target: 0 },
],
thresholds: {
// EN: Listing must be fast — p95 < 200ms / VI: Listing phải nhanh — p95 < 200ms
http_req_duration: ['p(95)<200', 'p(99)<500'],
catalog_errors: ['rate<0.005'],
catalog_list_duration: ['p(95)<200'],
catalog_detail_duration: ['p(95)<150'],
},
};
// ---------------------------------------------------------------------------
// Config
// ---------------------------------------------------------------------------
const BASE_URL = __ENV.BASE_URL || 'http://localhost:5020';
const JWT_TOKEN = __ENV.JWT_TOKEN || 'test-bearer-token';
const SHOP_ID = __ENV.SHOP_ID || '00000000-0000-0000-0000-000000000001';
const SAMPLE_PRODUCT_ID = __ENV.PRODUCT_ID || '00000000-0000-0000-0000-000000000002';
const headers = {
Authorization: `Bearer ${JWT_TOKEN}`,
'X-Shop-Id': SHOP_ID,
};
// ---------------------------------------------------------------------------
// Scenarios
// ---------------------------------------------------------------------------
export default function () {
// EN: Scenario 1 — List all products with pagination / VI: Tình huống 1 — danh sách sản phẩm có phân trang
group('list products', () => {
const page = Math.floor(Math.random() * 5) + 1;
const limit = [10, 20, 50][Math.floor(Math.random() * 3)];
const res = http.get(
`${BASE_URL}/api/v1/products?page=${page}&limit=${limit}&shopId=${SHOP_ID}`,
{ headers }
);
catalogListDuration.add(res.timings.duration);
catalogRequests.add(1);
const ok = check(res, {
'list status 200': (r) => r.status === 200,
'list body has items': (r) => {
try {
const body = JSON.parse(r.body);
return body.success === true && Array.isArray(body.data?.items ?? body.data);
} catch {
return false;
}
},
'list response < 200ms': (r) => r.timings.duration < 200,
});
if (!ok) catalogErrors.add(1);
else catalogErrors.add(0);
});
// EN: Scenario 2 — Get product by ID / VI: Tình huống 2 — lấy sản phẩm theo ID
group('get product detail', () => {
const res = http.get(
`${BASE_URL}/api/v1/products/${SAMPLE_PRODUCT_ID}`,
{ headers }
);
catalogDetailDuration.add(res.timings.duration);
catalogRequests.add(1);
const ok = check(res, {
'detail status 200 or 404': (r) => r.status === 200 || r.status === 404,
'detail response < 150ms': (r) => r.timings.duration < 150,
});
if (!ok) catalogErrors.add(1);
else catalogErrors.add(0);
});
// EN: Scenario 3 — Search products / VI: Tình huống 3 — tìm kiếm sản phẩm
group('search products', () => {
const terms = ['cafe', 'tra sua', 'banh', 'pho', 'com'];
const keyword = terms[Math.floor(Math.random() * terms.length)];
const res = http.get(
`${BASE_URL}/api/v1/products?q=${encodeURIComponent(keyword)}&shopId=${SHOP_ID}`,
{ headers }
);
catalogRequests.add(1);
const ok = check(res, {
'search status 200': (r) => r.status === 200,
});
if (!ok) catalogErrors.add(1);
else catalogErrors.add(0);
});
sleep(0.2 + Math.random() * 0.3);
}