#!/usr/bin/env node
/**
* Test Organization Script
* Helps organize, tag, and manage test files for the NIST CSF 2.0 MCP Server
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const TEST_DIR = path.join(__dirname, '..', 'tests');
const TEST_CATEGORIES = {
unit: {
pattern: /\.unit\.test\.ts$/,
directory: 'tests/**',
description: 'Unit tests for individual components'
},
integration: {
pattern: /\.integration\.test\.ts$/,
directory: 'tests/integration',
description: 'Integration tests for component interactions'
},
e2e: {
pattern: /\.e2e\.test\.ts$/,
directory: 'tests/e2e',
description: 'End-to-end workflow tests'
},
security: {
pattern: /\.security\.test\.ts$/,
directory: 'tests/security',
description: 'Security and vulnerability tests'
},
performance: {
pattern: /\.performance\.test\.ts$/,
directory: 'tests/performance',
description: 'Performance and benchmark tests'
}
};
const TEST_TOOLS = [
'create_profile',
'csf_lookup',
'search_framework',
'generate_gap_analysis',
'generate_report',
'calculate_risk_score',
'quick_assessment',
'assess_maturity',
'upload_evidence',
'track_audit_trail',
'get_implementation_guidance'
];
/**
* Get all test files recursively
*/
function getTestFiles(dir) {
let files = [];
try {
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
files = files.concat(getTestFiles(fullPath));
} else if (item.endsWith('.test.ts')) {
files.push(fullPath);
}
}
} catch (error) {
console.warn(`Warning: Could not read directory ${dir}`);
}
return files;
}
/**
* Analyze test files and categorize them
*/
function analyzeTestFiles() {
console.log('🔍 Analyzing test files...\n');
const allTestFiles = getTestFiles(TEST_DIR);
const analysis = {
total: allTestFiles.length,
categorized: {},
uncategorized: [],
byTool: {},
coverage: {}
};
// Categorize files
Object.keys(TEST_CATEGORIES).forEach(category => {
analysis.categorized[category] = [];
});
allTestFiles.forEach(filePath => {
const fileName = path.basename(filePath);
const relativePath = path.relative(TEST_DIR, filePath);
let categorized = false;
// Check each category
Object.entries(TEST_CATEGORIES).forEach(([category, config]) => {
if (config.pattern.test(fileName)) {
analysis.categorized[category].push(relativePath);
categorized = true;
}
});
if (!categorized) {
analysis.uncategorized.push(relativePath);
}
// Check tool coverage
TEST_TOOLS.forEach(tool => {
if (fileName.includes(tool.replace('_', '-'))) {
if (!analysis.byTool[tool]) {
analysis.byTool[tool] = [];
}
analysis.byTool[tool].push(relativePath);
}
});
});
return analysis;
}
/**
* Display test analysis results
*/
function displayAnalysis(analysis) {
console.log(`📊 Test File Analysis Results\n`);
console.log(`Total test files: ${analysis.total}\n`);
console.log('📁 By Category:');
Object.entries(analysis.categorized).forEach(([category, files]) => {
const count = files.length;
const icon = count > 0 ? '✅' : '❌';
console.log(` ${icon} ${category}: ${count} files`);
if (files.length > 0) {
files.forEach(file => {
console.log(` - ${file}`);
});
}
});
if (analysis.uncategorized.length > 0) {
console.log('\n⚠️ Uncategorized files:');
analysis.uncategorized.forEach(file => {
console.log(` - ${file}`);
});
}
console.log('\n🛠️ Tool Coverage:');
TEST_TOOLS.forEach(tool => {
const files = analysis.byTool[tool] || [];
const icon = files.length > 0 ? '✅' : '❌';
console.log(` ${icon} ${tool}: ${files.length} test(s)`);
if (files.length > 0) {
files.forEach(file => {
console.log(` - ${file}`);
});
}
});
}
/**
* Generate test execution scripts
*/
function generateTestScripts() {
console.log('\n📝 Generating test execution scripts...\n');
const scriptDir = path.join(__dirname, '..', 'scripts', 'test-runners');
// Create script directory if it doesn't exist
if (!fs.existsSync(scriptDir)) {
fs.mkdirSync(scriptDir, { recursive: true });
}
// Generate category-specific test scripts
Object.entries(TEST_CATEGORIES).forEach(([category, config]) => {
const scriptContent = `#!/usr/bin/env node
/**
* ${config.description}
* Generated by test-organization.js
*/
const { execSync } = require('child_process');
const path = require('path');
console.log('🧪 Running ${category} tests...');
try {
const command = 'npx jest --testPathPattern="${config.directory}" --coverage --verbose';
console.log('Executing:', command);
const result = execSync(command, {
stdio: 'inherit',
cwd: path.join(__dirname, '..', '..'),
encoding: 'utf8'
});
console.log('✅ ${category} tests completed successfully');
} catch (error) {
console.error('❌ ${category} tests failed');
console.error(error.message);
process.exit(1);
}
`;
const scriptPath = path.join(scriptDir, `run-${category}-tests.js`);
fs.writeFileSync(scriptPath, scriptContent);
// Make script executable
try {
execSync(`chmod +x "${scriptPath}"`);
console.log(`✅ Generated: scripts/test-runners/run-${category}-tests.js`);
} catch (error) {
console.log(`✅ Generated: scripts/test-runners/run-${category}-tests.js (chmod not available)`);
}
});
}
/**
* Generate test summary report
*/
function generateTestReport(analysis) {
console.log('\n📄 Generating test summary report...\n');
const report = `# Test Organization Report
Generated: ${new Date().toISOString()}
## Summary
- **Total test files**: ${analysis.total}
- **Categorized files**: ${Object.values(analysis.categorized).flat().length}
- **Uncategorized files**: ${analysis.uncategorized.length}
## Test Categories
${Object.entries(analysis.categorized).map(([category, files]) => `
### ${category.charAt(0).toUpperCase() + category.slice(1)} Tests (${files.length})
${TEST_CATEGORIES[category].description}
${files.length > 0 ? files.map(file => `- \`${file}\``).join('\n') : '_No files found_'}
`).join('\n')}
${analysis.uncategorized.length > 0 ? `
## Uncategorized Files
${analysis.uncategorized.map(file => `- \`${file}\``).join('\n')}
` : ''}
## Tool Coverage
${TEST_TOOLS.map(tool => {
const files = analysis.byTool[tool] || [];
const status = files.length > 0 ? '✅' : '❌';
return `- ${status} **${tool}**: ${files.length} test file(s)`;
}).join('\n')}
## Recommendations
${analysis.uncategorized.length > 0 ? '- Categorize uncategorized test files with proper naming conventions' : ''}
${TEST_TOOLS.filter(tool => !analysis.byTool[tool] || analysis.byTool[tool].length === 0).length > 0 ?
'- Add unit tests for tools without coverage: ' +
TEST_TOOLS.filter(tool => !analysis.byTool[tool] || analysis.byTool[tool].length === 0).join(', ') : ''}
## Test Execution Commands
\`\`\`bash
# Run all tests
npm test
# Run by category
npm run test:unit
npm run test:integration
npm run test:e2e
npm run test:security
npm run test:performance
# Run specific tool tests
npm run test:tools
npm run test:services
\`\`\`
`;
const reportPath = path.join(__dirname, '..', 'TEST-ANALYSIS-REPORT.md');
fs.writeFileSync(reportPath, report);
console.log(`✅ Generated: TEST-ANALYSIS-REPORT.md`);
}
/**
* Check for missing test patterns
*/
function checkMissingPatterns() {
console.log('\n🔍 Checking for missing test patterns...\n');
const srcDir = path.join(__dirname, '..', 'src');
const toolsDir = path.join(srcDir, 'tools');
const servicesDir = path.join(srcDir, 'services');
const warnings = [];
// Check tools directory
if (fs.existsSync(toolsDir)) {
const toolFiles = fs.readdirSync(toolsDir).filter(f => f.endsWith('.ts'));
toolFiles.forEach(toolFile => {
const toolName = path.basename(toolFile, '.ts');
const expectedUnitTest = `${toolName.replace('_', '-')}.unit.test.ts`;
const testExists = getTestFiles(TEST_DIR).some(testPath =>
path.basename(testPath) === expectedUnitTest
);
if (!testExists) {
warnings.push(`Missing unit test for tool: ${toolName}`);
}
});
}
// Check services directory
if (fs.existsSync(servicesDir)) {
const serviceFiles = fs.readdirSync(servicesDir).filter(f => f.endsWith('.ts'));
serviceFiles.forEach(serviceFile => {
const serviceName = path.basename(serviceFile, '.ts');
const expectedUnitTest = `${serviceName}.unit.test.ts`;
const testExists = getTestFiles(TEST_DIR).some(testPath =>
path.basename(testPath) === expectedUnitTest
);
if (!testExists) {
warnings.push(`Missing unit test for service: ${serviceName}`);
}
});
}
if (warnings.length > 0) {
console.log('⚠️ Missing test patterns:');
warnings.forEach(warning => {
console.log(` - ${warning}`);
});
} else {
console.log('✅ No missing test patterns detected');
}
return warnings;
}
/**
* Main execution function
*/
function main() {
console.log('🧪 NIST CSF 2.0 MCP Server - Test Organization Tool\n');
const analysis = analyzeTestFiles();
displayAnalysis(analysis);
const warnings = checkMissingPatterns();
generateTestScripts();
generateTestReport(analysis);
console.log('\n✅ Test organization completed!');
console.log('\n📋 Next steps:');
console.log(' 1. Review the generated TEST-ANALYSIS-REPORT.md');
console.log(' 2. Use npm run test:<category> to run specific test types');
console.log(' 3. Address any missing test patterns identified');
if (warnings.length > 0) {
console.log(` 4. Create ${warnings.length} missing test file(s)`);
}
}
// Run if called directly
if (require.main === module) {
main();
}
module.exports = {
analyzeTestFiles,
generateTestScripts,
generateTestReport,
checkMissingPatterns,
TEST_CATEGORIES,
TEST_TOOLS
};