All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 30m15s
223 lines
8.3 KiB
JavaScript
223 lines
8.3 KiB
JavaScript
/**
|
|
* PM2 Safeguard Logic Validation Tests
|
|
*
|
|
* This script tests the safeguard logic implemented in deployment workflows
|
|
* to prevent accidental deletion of all PM2 processes.
|
|
*
|
|
* Run with: node tests/qa/test-pm2-safeguard-logic.js
|
|
*/
|
|
|
|
// Simulate the safeguard logic from workflows
|
|
function evaluateSafeguard(totalProcesses, targetProcesses, threshold = 3) {
|
|
// SAFEGUARD 1: Process count validation
|
|
// If we're about to delete ALL processes AND there are more than threshold processes,
|
|
// this indicates a potential filter bug
|
|
const shouldAbort = targetProcesses === totalProcesses && totalProcesses > threshold;
|
|
return { shouldAbort, totalProcesses, targetProcesses };
|
|
}
|
|
|
|
// Test scenarios
|
|
const scenarios = [
|
|
// Normal operations - should NOT abort
|
|
{
|
|
name: 'Normal production cleanup - 3 errored out of 15',
|
|
totalProcs: 15,
|
|
targetProcs: 3,
|
|
expectedAbort: false,
|
|
description: 'Production deployment cleans up only the 3 errored production processes',
|
|
},
|
|
{
|
|
name: 'Normal test cleanup - 3 test processes out of 15',
|
|
totalProcs: 15,
|
|
targetProcs: 3,
|
|
expectedAbort: false,
|
|
description: 'Test deployment cleans up only the 3 test processes',
|
|
},
|
|
{
|
|
name: 'Single process cleanup - 1 errored out of 10',
|
|
totalProcs: 10,
|
|
targetProcs: 1,
|
|
expectedAbort: false,
|
|
description: 'Only one process is errored and targeted for cleanup',
|
|
},
|
|
{
|
|
name: 'No processes to clean - 0 out of 10',
|
|
totalProcs: 10,
|
|
targetProcs: 0,
|
|
expectedAbort: false,
|
|
description: 'No processes match the cleanup criteria',
|
|
},
|
|
{
|
|
name: 'Fresh server - 3 out of 3 (at threshold)',
|
|
totalProcs: 3,
|
|
targetProcs: 3,
|
|
expectedAbort: false,
|
|
description: 'Server with only 3 processes (threshold boundary - should proceed)',
|
|
},
|
|
{
|
|
name: 'Minimal server - 2 out of 2',
|
|
totalProcs: 2,
|
|
targetProcs: 2,
|
|
expectedAbort: false,
|
|
description: 'Server with only 2 processes (below threshold)',
|
|
},
|
|
{
|
|
name: 'Empty PM2 state - 0 out of 0',
|
|
totalProcs: 0,
|
|
targetProcs: 0,
|
|
expectedAbort: false,
|
|
description: 'No PM2 processes at all (fresh install)',
|
|
},
|
|
|
|
// Dangerous operations - SHOULD abort
|
|
{
|
|
name: 'Filter bug - all 10 processes targeted',
|
|
totalProcs: 10,
|
|
targetProcs: 10,
|
|
expectedAbort: true,
|
|
description: 'DANGEROUS: Filter would delete ALL 10 processes - indicates bug',
|
|
},
|
|
{
|
|
name: 'Filter bug - all 15 processes targeted',
|
|
totalProcs: 15,
|
|
targetProcs: 15,
|
|
expectedAbort: true,
|
|
description: 'DANGEROUS: Filter would delete ALL 15 processes - indicates bug',
|
|
},
|
|
{
|
|
name: 'Filter bug - all 4 processes targeted',
|
|
totalProcs: 4,
|
|
targetProcs: 4,
|
|
expectedAbort: true,
|
|
description: 'DANGEROUS: Filter would delete ALL 4 processes (just above threshold)',
|
|
},
|
|
{
|
|
name: 'Filter bug - all 100 processes targeted',
|
|
totalProcs: 100,
|
|
targetProcs: 100,
|
|
expectedAbort: true,
|
|
description: 'DANGEROUS: Filter would delete ALL 100 processes - extreme case',
|
|
},
|
|
];
|
|
|
|
// Run tests
|
|
console.log('========================================');
|
|
console.log('PM2 SAFEGUARD LOGIC VALIDATION');
|
|
console.log('========================================\n');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
scenarios.forEach((scenario, index) => {
|
|
const result = evaluateSafeguard(scenario.totalProcs, scenario.targetProcs);
|
|
const testPassed = result.shouldAbort === scenario.expectedAbort;
|
|
|
|
if (testPassed) {
|
|
passed++;
|
|
console.log(`[PASS] Test ${index + 1}: ${scenario.name}`);
|
|
console.log(` Total: ${scenario.totalProcs}, Target: ${scenario.targetProcs}`);
|
|
console.log(` Expected abort: ${scenario.expectedAbort}, Got: ${result.shouldAbort}`);
|
|
} else {
|
|
failed++;
|
|
console.log(`[FAIL] Test ${index + 1}: ${scenario.name}`);
|
|
console.log(` Total: ${scenario.totalProcs}, Target: ${scenario.targetProcs}`);
|
|
console.log(` Expected abort: ${scenario.expectedAbort}, Got: ${result.shouldAbort}`);
|
|
console.log(` Description: ${scenario.description}`);
|
|
}
|
|
console.log('');
|
|
});
|
|
|
|
console.log('========================================');
|
|
console.log(`RESULTS: ${passed} passed, ${failed} failed`);
|
|
console.log('========================================');
|
|
|
|
// Edge case tests for specific workflow patterns
|
|
console.log('\n========================================');
|
|
console.log('WORKFLOW-SPECIFIC FILTER TESTS');
|
|
console.log('========================================\n');
|
|
|
|
// Simulate production workflow filter
|
|
function simulateProdFilter(processList) {
|
|
const prodProcesses = [
|
|
'flyer-crawler-api',
|
|
'flyer-crawler-worker',
|
|
'flyer-crawler-analytics-worker',
|
|
];
|
|
return processList.filter(
|
|
(p) => (p.status === 'errored' || p.status === 'stopped') && prodProcesses.includes(p.name),
|
|
);
|
|
}
|
|
|
|
// Simulate test workflow filter
|
|
function simulateTestFilter(processList) {
|
|
return processList.filter((p) => p.name && p.name.endsWith('-test'));
|
|
}
|
|
|
|
// Test case: Normal mixed environment
|
|
const mixedEnvProcesses = [
|
|
{ name: 'flyer-crawler-api', status: 'online' },
|
|
{ name: 'flyer-crawler-worker', status: 'errored' },
|
|
{ name: 'flyer-crawler-analytics-worker', status: 'online' },
|
|
{ name: 'flyer-crawler-api-test', status: 'online' },
|
|
{ name: 'flyer-crawler-worker-test', status: 'online' },
|
|
{ name: 'flyer-crawler-analytics-worker-test', status: 'online' },
|
|
{ name: 'stock-alert-api', status: 'online' },
|
|
{ name: 'stock-alert-worker', status: 'online' },
|
|
];
|
|
|
|
const prodFiltered = simulateProdFilter(mixedEnvProcesses);
|
|
const testFiltered = simulateTestFilter(mixedEnvProcesses);
|
|
|
|
console.log('Test: Mixed environment with production processes');
|
|
console.log(`Total processes: ${mixedEnvProcesses.length}`);
|
|
console.log(`Production filter matches: ${prodFiltered.length}`);
|
|
console.log(` Names: ${prodFiltered.map((p) => p.name).join(', ') || '(none)'}`);
|
|
console.log(`Test filter matches: ${testFiltered.length}`);
|
|
console.log(` Names: ${testFiltered.map((p) => p.name).join(', ')}`);
|
|
|
|
// Verify production filter does NOT match test or other apps
|
|
const prodFilterSafe = prodFiltered.every(
|
|
(p) => !p.name.endsWith('-test') && p.name.startsWith('flyer-crawler-'),
|
|
);
|
|
console.log(`Production filter safe (no test/other apps): ${prodFilterSafe ? 'PASS' : 'FAIL'}`);
|
|
|
|
// Verify test filter does NOT match production or other apps
|
|
const testFilterSafe = testFiltered.every((p) => p.name.endsWith('-test'));
|
|
console.log(`Test filter safe (only -test suffix): ${testFilterSafe ? 'PASS' : 'FAIL'}`);
|
|
|
|
// Test case: All processes errored (dangerous scenario)
|
|
console.log('\nTest: All production processes errored (edge case)');
|
|
const allErroredProd = [
|
|
{ name: 'flyer-crawler-api', status: 'errored' },
|
|
{ name: 'flyer-crawler-worker', status: 'errored' },
|
|
{ name: 'flyer-crawler-analytics-worker', status: 'errored' },
|
|
{ name: 'flyer-crawler-api-test', status: 'online' },
|
|
{ name: 'flyer-crawler-worker-test', status: 'online' },
|
|
{ name: 'stock-alert-api', status: 'online' },
|
|
];
|
|
|
|
const allErroredFiltered = simulateProdFilter(allErroredProd);
|
|
const safeguardCheck = evaluateSafeguard(allErroredProd.length, allErroredFiltered.length);
|
|
console.log(`Total processes: ${allErroredProd.length}`);
|
|
console.log(`Production errored processes: ${allErroredFiltered.length}`);
|
|
console.log(`Safeguard would abort: ${safeguardCheck.shouldAbort}`);
|
|
console.log(`Expected: false (3 out of 6 is not ALL processes)`);
|
|
console.log(`Result: ${safeguardCheck.shouldAbort === false ? 'PASS' : 'FAIL'}`);
|
|
|
|
// Test case: Bug simulation - filter returns everything
|
|
console.log('\nTest: Bug simulation - filter returns all processes');
|
|
const buggyFilterResult = mixedEnvProcesses; // Simulating a bug where filter returns everything
|
|
const buggySafeguardCheck = evaluateSafeguard(mixedEnvProcesses.length, buggyFilterResult.length);
|
|
console.log(`Total processes: ${mixedEnvProcesses.length}`);
|
|
console.log(`Buggy filter matches: ${buggyFilterResult.length}`);
|
|
console.log(`Safeguard would abort: ${buggySafeguardCheck.shouldAbort}`);
|
|
console.log(`Expected: true (prevents all-process deletion)`);
|
|
console.log(`Result: ${buggySafeguardCheck.shouldAbort === true ? 'PASS' : 'FAIL'}`);
|
|
|
|
console.log('\n========================================');
|
|
console.log('ALL TESTS COMPLETE');
|
|
console.log('========================================');
|
|
|
|
// Exit with appropriate code
|
|
process.exit(failed > 0 ? 1 : 0);
|