agmission/Development/server/tests/integration/check_controller_contract.js
Devin Major 6ff07c9d74
All checks were successful
Server Tests / Mocha – Unit & Utility Tests (push) Successful in 1m13s
Server Tests / Jest – Integration Tests (push) Successful in 1m43s
additional integration tests
2026-05-08 14:51:04 -04:00

163 lines
6.0 KiB
JavaScript

'use strict';
const fs = require('fs');
const path = require('path');
const ROOT_DIR = path.resolve(__dirname, '..', '..');
const CONTROLLERS_DIR = path.join(ROOT_DIR, 'controllers');
const TESTS_DIR = __dirname;
const EXCLUDED_CONTROLLERS = {
export: 'Route-wiring controller module; not covered by the current Jest integration pattern.',
subscription: 'Stripe-dependent controller; virtually all methods require live Stripe API and cannot run against mongodb-memory-server alone.',
upload_job: 'Route-wiring controller module; not covered by the current Jest integration pattern.',
};
const EXCLUDED_METHODS = {
api_export: {
downloadExport: 'File download path is not covered by the current integration suite.',
},
client: {
deleteClient: 'Skipped in Jest because the controller requires MongoDB transactions.',
},
customer: {
createCustomer_post: 'Skipped in Jest because the controller requires MongoDB transactions.',
deleteCustomer: 'Skipped in Jest because the controller requires MongoDB transactions.',
},
invoice: {
createInvoice_post: 'Skipped in Jest because the controller requires MongoDB transactions.',
updateInvoiceById_put: 'Skipped in Jest because the controller requires MongoDB transactions.',
deleteInvoiceById: 'Skipped in Jest because the controller requires MongoDB transactions.',
deleteInvoices: 'Skipped in Jest because the controller requires MongoDB transactions.',
},
log_payment: {
createLogPayment_post: 'Skipped in Jest because the controller requires MongoDB transactions.',
createLogPayments_post: 'Skipped in Jest because the controller requires MongoDB transactions.',
},
pilot: {
deletePilot: 'Skipped in Jest because the controller requires MongoDB transactions.',
},
user: {
resetPassword_post: 'Skipped in Jest because the controller requires MongoDB transactions.',
signup_post: 'Skipped in Jest because the controller requires MongoDB transactions.',
ensureParentExists: 'Internal helper exported for reuse; not a request handler test target.',
clearTempData: 'Internal helper exported for reuse; not a request handler test target.',
getHostUrlFromReq: 'Internal helper exported for reuse; not a request handler test target.',
},
vehicle: {
deleteVehicle: 'Skipped in Jest because the controller requires MongoDB transactions.',
},
};
function stripComments(input) {
return input
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/(^|\s)\/\/.*$/gm, '$1');
}
function parseObjectMembers(block) {
return stripComments(block)
.split(',')
.map(part => part.trim())
.filter(Boolean)
.map(part => part.replace(/[}\s]+$/g, ''))
.map(part => part.split(':')[0].trim())
.filter(Boolean)
.filter(part => /^[A-Za-z_$][\w$]*$/.test(part));
}
function parseExportedMethods(controllerSource) {
const directMatch = controllerSource.match(/module\.exports\s*=\s*\{([\s\S]*?)\}\s*;?\s*$/m);
if (directMatch) return parseObjectMembers(directMatch[1]);
if (controllerSource.includes('module.exports = function')) {
const returnMatches = [...controllerSource.matchAll(/return\s*\{([\s\S]*?)\}\s*;?/g)];
if (returnMatches.length > 0) {
return parseObjectMembers(returnMatches[returnMatches.length - 1][1]);
}
}
// Handle controllers that export via individual `exports.name = ...` assignments.
const namedExportsRegex = /^exports\.([A-Za-z_$][\w$]*)\s*=/gm;
const namedMatches = [...controllerSource.matchAll(namedExportsRegex)];
if (namedMatches.length > 0) {
return namedMatches.map(m => m[1]);
}
return [];
}
function getCoveredMethods(testSource, methods) {
const covered = new Set();
for (const method of methods) {
const invocationRegex = new RegExp(`\\.${method}\\s*\\(`);
if (invocationRegex.test(testSource)) covered.add(method);
}
return covered;
}
function getControllerFiles() {
return fs.readdirSync(CONTROLLERS_DIR)
.filter(fileName => fileName.endsWith('.js'))
.sort();
}
function main() {
const failures = [];
const warnings = [];
for (const fileName of getControllerFiles()) {
const controllerName = path.basename(fileName, '.js');
if (EXCLUDED_CONTROLLERS[controllerName]) {
warnings.push(`SKIP ${controllerName}: ${EXCLUDED_CONTROLLERS[controllerName]}`);
continue;
}
const controllerPath = path.join(CONTROLLERS_DIR, fileName);
const testPath = path.join(TESTS_DIR, `${controllerName}.integration.test.js`);
if (!fs.existsSync(testPath)) {
failures.push(`Missing integration suite for controller '${controllerName}': expected tests/integration/${controllerName}.integration.test.js`);
continue;
}
const controllerSource = fs.readFileSync(controllerPath, 'utf8');
const exportedMethods = parseExportedMethods(controllerSource);
if (exportedMethods.length === 0) {
failures.push(`Could not determine exported methods for controller '${controllerName}'.`);
continue;
}
const testSource = fs.readFileSync(testPath, 'utf8');
const coveredMethods = getCoveredMethods(testSource, exportedMethods);
const excludedMethods = EXCLUDED_METHODS[controllerName] || {};
for (const method of exportedMethods) {
if (coveredMethods.has(method)) continue;
if (excludedMethods[method]) {
warnings.push(`SKIP ${controllerName}.${method}: ${excludedMethods[method]}`);
continue;
}
failures.push(
`Missing integration test coverage for ${controllerName}.${method}: add at least one test that invokes this exported method in tests/integration/${controllerName}.integration.test.js`
);
}
}
if (warnings.length > 0) {
console.log('Integration contract exclusions:');
for (const warning of warnings) console.log(`- ${warning}`);
console.log('');
}
if (failures.length > 0) {
console.error('Integration controller contract check failed:');
for (const failure of failures) console.error(`- ${failure}`);
process.exit(1);
}
console.log('Integration controller contract check passed.');
}
main();