agmission/Development/server/tests/integration/check_controller_contract.js
2026-04-29 09:40:51 -04:00

274 lines
11 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 = {
dlq: 'No Jest integration suite yet.',
export: 'Route-wiring controller module; not covered by the current Jest integration pattern.',
geoutil: 'No Jest integration suite yet.',
health: 'No Jest integration suite yet.',
location: 'No Jest integration suite yet.',
subscription: 'No Jest integration suite yet.',
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.',
},
api_pub: {
getSessionRecords: 'No Jest integration case yet.',
getAreas: 'No Jest integration case yet.',
},
billing: {
exportUsageDetail_post: 'No Jest integration case yet.',
getCustBillingStatus_get: 'No Jest integration case yet.',
uploadLogo_post: 'No Jest integration case yet.',
deleteLogo_delete: 'No Jest integration case yet.',
},
client: {
updateClient_put: 'No Jest integration case yet.',
deleteClient: 'Skipped in Jest because the controller requires MongoDB transactions.',
searchWithSetting_post: 'No Jest integration case yet.',
},
customer: {
createCustomer_post: 'No Jest integration case yet.',
getCustomer_get: 'No Jest integration case yet.',
updateCustomer_put: 'No Jest integration case yet.',
deleteCustomer: 'No Jest integration case yet.',
updateUser_put: 'No Jest integration case yet.',
deleteUser: 'No Jest integration case yet.',
getHierarchyById_get: 'No Jest integration case yet.',
},
dealer: {
updateUser_put: 'No Jest integration case yet.',
deleteUser: 'No Jest integration case yet.',
},
geoitem: {
search_post: 'No Jest integration case yet.',
getSharedItems_get: 'No Jest integration case yet.',
},
invoice: {
createInvoice_post: 'No Jest integration case yet.',
getInvoiceById_get: 'No Jest integration case yet.',
updateInvoice_put: 'No Jest integration case yet.',
updateInvoiceById_put: 'No Jest integration case yet.',
deleteInvoice_delete: 'No Jest integration case yet.',
deleteInvoiceById: 'No Jest integration case yet.',
deleteInvoices: 'No Jest integration case yet.',
getInvoiceJobs_post: 'No Jest integration case yet.',
sendInvoiceEmail_post: 'No Jest integration case yet.',
getPreviewUrl_post: 'No Jest integration case yet.',
getPrintInformation_get: 'No Jest integration case yet.',
exportInvoices_get: 'No Jest integration case yet.',
payment_get: 'No Jest integration case yet.',
},
invoice_settings: {
getInvoiceSettingDefault_get: 'No Jest integration case yet.',
copyInvoiceSetting_post: 'No Jest integration case yet.',
uploadLogo_post: 'No Jest integration case yet.',
deleteLogo_delete: 'No Jest integration case yet.',
},
job: {
getData_post: 'No Jest integration case yet.',
getReportOps_get: 'No Jest integration case yet.',
preAppReport_post: 'No Jest integration case yet.',
getRptVars_post: 'No Jest integration case yet.',
setRptVars_post: 'No Jest integration case yet.',
saveReport_post: 'No Jest integration case yet.',
preLoadReport_post: 'No Jest integration case yet.',
getUploadedFiles_post: 'No Jest integration case yet.',
importStatus_post: 'No Jest integration case yet.',
importingStatus_post: 'No Jest integration case yet.',
deleteAppFile_post: 'No Jest integration case yet.',
getJobLogs_post: 'No Jest integration case yet.',
assign_post: 'No Jest integration case yet.',
assignments_post: 'No Jest integration case yet.',
countByClient_post: 'No Jest integration case yet.',
saveMapOps_post: 'No Jest integration case yet.',
appFiles_post: 'No Jest integration case yet.',
filesdata_post: 'No Jest integration case yet.',
getAppDataByJobId: 'No Jest integration case yet.',
fetchInvReadyJobs_post: 'No Jest integration case yet.',
searchJobs_post: 'No Jest integration case yet.',
},
log_payment: {
createLogPayment_post: 'Skipped in Jest because the controller requires MongoDB transactions.',
createLogPayments_post: 'Skipped in Jest because the controller requires MongoDB transactions.',
},
main: {
getSiteVer_post: 'No Jest integration case yet.',
doLongOp_post: 'No Jest integration case yet.',
getActivePromos_get: 'No Jest integration case yet.',
getSubscriptionPromos_get: 'No Jest integration case yet.',
setSubscriptionPromos_post: 'No Jest integration case yet.',
addSubscriptionPromo_post: 'No Jest integration case yet.',
deleteSubscriptionPromo_delete: 'No Jest integration case yet.',
updateSubscriptionPromo_put: 'No Jest integration case yet.',
getForeverCoupons_get: 'No Jest integration case yet.',
},
obstacle: {
updateObstacle_put: 'No Jest integration case yet.',
deleteObstacle: 'No Jest integration case yet.',
},
partner: {
getPartnerById_post: 'No Jest integration case yet.',
updatePartner_put: 'No Jest integration case yet.',
syncData_post: 'No Jest integration case yet.',
uploadJob_post: 'No Jest integration case yet.',
getSystemUsers_get: 'No Jest integration case yet.',
getCurrentSystemUser_get: 'No Jest integration case yet.',
getSystemUser_get: 'No Jest integration case yet.',
createSystemUser_post: 'No Jest integration case yet.',
updateSystemUser_put: 'No Jest integration case yet.',
updateSystemUser_post: 'No Jest integration case yet.',
deleteSystemUser: 'No Jest integration case yet.',
testPartnerAuth_post: 'No Jest integration case yet.',
getPartnerCustomers_get: 'No Jest integration case yet.',
getPartnerAircraft_get: 'No Jest integration case yet.',
getPartnerCustomerStats_get: 'No Jest integration case yet.',
getPartnerJobStats_get: 'No Jest integration case yet.',
syncPartnerCustomer_post: 'No Jest integration case yet.',
getPartnerServices_get: 'No Jest integration case yet.',
},
pilot: {
updatePilot_put: 'No Jest integration case yet.',
deletePilot: 'Skipped in Jest because the controller requires MongoDB transactions.',
},
product: {
search_post: 'No Jest integration assertion tied to a distinct behavior yet.',
},
user: {
createUser_post: 'No Jest integration case yet.',
deleteUser: 'No Jest integration case yet.',
login_post: 'No Jest integration case yet.',
clearTempData_post: 'No Jest integration case yet.',
setUserLanguage_post: 'No Jest integration case yet.',
getUserDetail_post: 'No Jest integration case yet.',
mailPwdReset_post: 'No Jest integration case yet.',
validateResetPwdToken_post: 'No Jest integration case yet.',
resetPassword_post: 'No Jest integration case yet.',
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.',
requestEmailVerification_post: 'No Jest integration case yet.',
verifyEmailCode_post: 'No Jest integration case yet.',
signup_post: 'No Jest integration case yet.',
},
vehicle: {
updateVehicle_put: 'No Jest integration case yet.',
deleteVehicle: 'Skipped in Jest because the controller requires MongoDB transactions.',
unitIdExists_post: 'No Jest integration case yet.',
},
};
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]);
}
}
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();