155 lines
4.7 KiB
JavaScript
155 lines
4.7 KiB
JavaScript
|
|
/**
|
||
|
|
* Scénario combiné: health, auth, tracks, playlists, marketplace
|
||
|
|
* Usage: k6 run loadtests/backend/full.js
|
||
|
|
*/
|
||
|
|
import http from 'k6/http';
|
||
|
|
import { check, sleep } from 'k6';
|
||
|
|
import { randomString } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
|
||
|
|
|
||
|
|
const BASE_URL = __ENV.BASE_URL || __ENV.API_ORIGIN || 'http://localhost:8080';
|
||
|
|
const TEST_EMAIL_PREFIX = __ENV.TEST_EMAIL_PREFIX || 'user+full';
|
||
|
|
const TEST_EMAIL_DOMAIN = __ENV.TEST_EMAIL_DOMAIN || 'example.com';
|
||
|
|
const TEST_PASSWORD_PREFIX = __ENV.TEST_PASSWORD_PREFIX || 'V3za!full-';
|
||
|
|
|
||
|
|
function generateTestUser() {
|
||
|
|
const rand = randomString(8);
|
||
|
|
const pwd = `${TEST_PASSWORD_PREFIX}${rand}`;
|
||
|
|
return {
|
||
|
|
email: `${TEST_EMAIL_PREFIX}${rand}@${TEST_EMAIL_DOMAIN}`,
|
||
|
|
password: pwd,
|
||
|
|
password_confirmation: pwd,
|
||
|
|
username: `fu${rand}`,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
export const options = {
|
||
|
|
scenarios: {
|
||
|
|
health: {
|
||
|
|
executor: 'constant-vus',
|
||
|
|
vus: 5,
|
||
|
|
duration: '1m',
|
||
|
|
startTime: '0s',
|
||
|
|
exec: 'healthScenario',
|
||
|
|
},
|
||
|
|
auth: {
|
||
|
|
executor: 'ramping-vus',
|
||
|
|
stages: [
|
||
|
|
{ duration: '30s', target: 10 },
|
||
|
|
{ duration: '1m', target: 10 },
|
||
|
|
{ duration: '30s', target: 0 },
|
||
|
|
],
|
||
|
|
startTime: '10s',
|
||
|
|
exec: 'authScenario',
|
||
|
|
},
|
||
|
|
tracks: {
|
||
|
|
executor: 'ramping-vus',
|
||
|
|
stages: [
|
||
|
|
{ duration: '30s', target: 15 },
|
||
|
|
{ duration: '2m', target: 15 },
|
||
|
|
{ duration: '30s', target: 0 },
|
||
|
|
],
|
||
|
|
startTime: '20s',
|
||
|
|
exec: 'tracksScenario',
|
||
|
|
},
|
||
|
|
playlists: {
|
||
|
|
executor: 'ramping-vus',
|
||
|
|
stages: [
|
||
|
|
{ duration: '30s', target: 5 },
|
||
|
|
{ duration: '1m', target: 5 },
|
||
|
|
{ duration: '30s', target: 0 },
|
||
|
|
],
|
||
|
|
startTime: '30s',
|
||
|
|
exec: 'playlistsScenario',
|
||
|
|
},
|
||
|
|
marketplace: {
|
||
|
|
executor: 'ramping-vus',
|
||
|
|
stages: [
|
||
|
|
{ duration: '30s', target: 5 },
|
||
|
|
{ duration: '1m', target: 5 },
|
||
|
|
{ duration: '30s', target: 0 },
|
||
|
|
],
|
||
|
|
startTime: '40s',
|
||
|
|
exec: 'marketplaceScenario',
|
||
|
|
},
|
||
|
|
},
|
||
|
|
thresholds: {
|
||
|
|
http_req_duration: ['p(95)<1000', 'p(99)<2000'],
|
||
|
|
http_req_failed: ['rate<0.15'],
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
export function setup() {
|
||
|
|
const users = [];
|
||
|
|
const baseURL = `${BASE_URL}/api/v1/auth`;
|
||
|
|
for (let i = 0; i < 25; i++) {
|
||
|
|
const user = generateTestUser();
|
||
|
|
const registerRes = http.post(`${baseURL}/register`, JSON.stringify(user), {
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
});
|
||
|
|
if (registerRes.status === 201) {
|
||
|
|
const loginRes = http.post(`${baseURL}/login`, JSON.stringify({ email: user.email, password: user.password }), {
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
});
|
||
|
|
if (loginRes.status === 200) {
|
||
|
|
try {
|
||
|
|
const body = JSON.parse(loginRes.body);
|
||
|
|
const token = body.data?.token?.access_token;
|
||
|
|
if (token) users.push({ ...user, token });
|
||
|
|
} catch (e) {}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return { users };
|
||
|
|
}
|
||
|
|
|
||
|
|
export function healthScenario() {
|
||
|
|
const healthRes = http.get(`${BASE_URL}/health`);
|
||
|
|
check(healthRes, { 'health 200': (r) => r.status === 200 });
|
||
|
|
sleep(0.5);
|
||
|
|
const readyzRes = http.get(`${BASE_URL}/readyz`);
|
||
|
|
check(readyzRes, { 'readyz 200': (r) => r.status === 200 });
|
||
|
|
sleep(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function authScenario(data) {
|
||
|
|
const { users } = data;
|
||
|
|
if (users.length === 0) return;
|
||
|
|
const user = users[Math.floor(Math.random() * users.length)];
|
||
|
|
const loginRes = http.post(
|
||
|
|
`${BASE_URL}/api/v1/auth/login`,
|
||
|
|
JSON.stringify({ email: user.email, password: user.password }),
|
||
|
|
{ headers: { 'Content-Type': 'application/json' } }
|
||
|
|
);
|
||
|
|
check(loginRes, { 'login 200': (r) => r.status === 200 });
|
||
|
|
sleep(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function tracksScenario(data) {
|
||
|
|
const { users } = data;
|
||
|
|
const headers = users.length > 0
|
||
|
|
? { Authorization: `Bearer ${users[Math.floor(Math.random() * users.length)].token}` }
|
||
|
|
: {};
|
||
|
|
const tracksRes = http.get(`${BASE_URL}/api/v1/tracks`, { headers });
|
||
|
|
check(tracksRes, { 'tracks 200 or 401': (r) => r.status === 200 || r.status === 401 });
|
||
|
|
sleep(0.5);
|
||
|
|
const searchRes = http.get(`${BASE_URL}/api/v1/tracks/search?q=test`, { headers });
|
||
|
|
check(searchRes, { 'search 200 or 401': (r) => r.status === 200 || r.status === 401 });
|
||
|
|
sleep(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function playlistsScenario(data) {
|
||
|
|
const { users } = data;
|
||
|
|
if (users.length === 0) return;
|
||
|
|
const user = users[Math.floor(Math.random() * users.length)];
|
||
|
|
const headers = { Authorization: `Bearer ${user.token}` };
|
||
|
|
const listRes = http.get(`${BASE_URL}/api/v1/playlists`, { headers });
|
||
|
|
check(listRes, { 'playlists 200': (r) => r.status === 200 });
|
||
|
|
sleep(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function marketplaceScenario() {
|
||
|
|
const productsRes = http.get(`${BASE_URL}/api/v1/marketplace/products`);
|
||
|
|
check(productsRes, { 'products 200': (r) => r.status === 200 });
|
||
|
|
sleep(1);
|
||
|
|
}
|