Add load testing job to GitHub Actions CI pipeline [FRE-4931]

This commit is contained in:
2026-05-09 07:56:52 -04:00
parent 92476653b4
commit 6f90db8503
6 changed files with 301 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
const fs = require('fs');
const path = require('path');
async function compareBaseline() {
const reportsDir = path.join(__dirname, 'reports');
const baselinePath = path.join(reportsDir, 'baseline.json');
const currentPath = path.join(reportsDir, 'current.json');
const baselineThreshold = parseFloat(process.env.BASELINE_THRESHOLD) || 0.1;
if (!fs.existsSync(baselinePath)) {
console.log('No baseline found, creating initial baseline');
createBaseline();
return;
}
const baseline = JSON.parse(fs.readFileSync(baselinePath, 'utf8'));
const current = JSON.parse(fs.readFileSync(currentPath, 'utf8'));
const avgTimeChange = (current.avgResponseTime - baseline.avgResponseTime) / baseline.avgResponseTime;
const successRateChange = current.successRate - baseline.successRate;
console.log('\n=== Baseline Comparison ===');
console.log(`Baseline Avg Response Time: ${baseline.avgResponseTime.toFixed(2)}ms`);
console.log(`Current Avg Response Time: ${current.avgResponseTime.toFixed(2)}ms`);
console.log(`Change: ${(avgTimeChange * 100).toFixed(2)}%`);
console.log(`Baseline Success Rate: ${baseline.successRate.toFixed(2)}%`);
console.log(`Current Success Rate: ${current.successRate.toFixed(2)}%`);
console.log(`Change: ${successRateChange.toFixed(2)}%`);
const passed = Math.abs(avgTimeChange) <= baselineThreshold && successRateChange >= -1;
if (passed) {
console.log('\n✓ Performance baseline check PASSED');
process.exit(0);
} else {
console.log('\n✗ Performance baseline check FAILED');
if (Math.abs(avgTimeChange) > baselineThreshold) {
console.log(` - Response time changed by ${(avgTimeChange * 100).toFixed(2)}% (threshold: ${baselineThreshold * 100}%)`);
}
if (successRateChange < -1) {
console.log(` - Success rate dropped by ${successRateChange.toFixed(2)}%`);
}
process.exit(1);
}
}
function createBaseline() {
const reportsDir = path.join(__dirname, 'reports');
const baseline = {
avgResponseTime: 100,
successRate: 99.0,
createdAt: new Date().toISOString()
};
if (!fs.existsSync(reportsDir)) {
fs.mkdirSync(reportsDir, { recursive: true });
}
fs.writeFileSync(baselinePath, JSON.stringify(baseline, null, 2));
console.log('Initial baseline created');
}
const baselinePath = path.join(__dirname, 'reports', 'baseline.json');
if (!fs.existsSync(baselinePath)) {
createBaseline();
} else {
compareBaseline();
}

View File

@@ -0,0 +1,18 @@
{
"name": "frenocorp-load-tests",
"version": "1.0.0",
"description": "Load testing suite for FrenoCorp API",
"scripts": {
"load-test": "node run-load-test.js",
"baseline": "node run-baseline-test.js",
"compare-baseline": "node compare-baseline.js",
"create-baseline": "node create-baseline.js"
},
"dependencies": {
"k6": "^0.1.0",
"axios": "^1.6.0"
},
"devDependencies": {
"vitest": "^1.0.0"
}
}

View File

@@ -0,0 +1,6 @@
{
"avgResponseTime": 100,
"successRate": 99.0,
"createdAt": "2026-05-09T00:00:00.000Z",
"description": "Initial baseline for FRE-4931 load testing implementation"
}

View File

@@ -0,0 +1,68 @@
const axios = require('axios');
const API_BASE_URL = process.env.API_BASE_URL || 'https://api.frenocorp.com';
const CONCURRENCY = parseInt(process.env.LOAD_TEST_CONCURRENCY) || 10;
const DURATION = parseInt(process.env.LOAD_TEST_DURATION) || 60;
const endpoints = [
'/api/v1/auth/status',
'/api/v1/users/profile',
'/api/v1/activities/recent',
'/api/v1/plans/current'
];
async function runLoadTest() {
console.log(`Starting load test with ${CONCURRENCY} concurrent users for ${DURATION}s`);
console.log(`Target: ${API_BASE_URL}`);
const results = {
totalRequests: 0,
successful: 0,
failed: 0,
avgResponseTime: 0,
responseTimes: []
};
const startTime = Date.now();
const endTime = startTime + (DURATION * 1000);
while (Date.now() < endTime) {
const promises = endpoints.map(async (endpoint) => {
const requestStart = Date.now();
try {
const response = await axios.get(`${API_BASE_URL}${endpoint}`);
results.successful++;
results.responseTimes.push(Date.now() - requestStart);
} catch (error) {
results.failed++;
console.error(`Failed request to ${endpoint}:`, error.message);
}
results.totalRequests++;
});
await Promise.all(promises);
}
if (results.responseTimes.length > 0) {
results.avgResponseTime = results.responseTimes.reduce((a, b) => a + b, 0) / results.responseTimes.length;
}
console.log('\n=== Load Test Results ===');
console.log(`Total Requests: ${results.totalRequests}`);
console.log(`Successful: ${results.successful}`);
console.log(`Failed: ${results.failed}`);
console.log(`Success Rate: ${(results.successful / results.totalRequests * 100).toFixed(2)}%`);
console.log(`Average Response Time: ${results.avgResponseTime.toFixed(2)}ms`);
return results;
}
runLoadTest()
.then(() => {
console.log('\nLoad test completed successfully');
process.exit(0);
})
.catch((error) => {
console.error('Load test failed:', error);
process.exit(1);
});