ctrlshiftleft
Version:
AI-powered toolkit for embedding QA and security testing into development workflows
268 lines (231 loc) • 8.1 kB
JavaScript
/**
* Comprehensive integration test for ctrl.shift.left functionality
* Tests all major components and their interactions
*/
const fs = require('fs').promises;
const path = require('path');
const chalk = require('chalk');
const { execSync } = require('child_process');
// Import core components
const { TestGenerator } = require('../../dist/core/testGenerator');
const { TestRunner } = require('../../dist/core/testRunner');
const { ChecklistGenerator } = require('../../dist/core/checklistGenerator');
const { diff, highlightSecurityChanges } = require('../../dist/utils/diffUtils');
const { generateSecurityRiskReport, formatSeverityBadge } = require('../../dist/utils/securityRiskUtils');
// Test configuration
const TEST_FILE_PATH = path.join(__dirname, '../demo-security/LoginComponent.tsx');
const TEST_OUTPUT_DIR = path.join(__dirname, '../output');
// Test execution state
const results = {
pass: 0,
fail: 0,
tests: []
};
// Utility functions
async function ensureDirectoryExists(dir) {
try {
await fs.mkdir(dir, { recursive: true });
} catch (err) {
console.error(`Error creating directory ${dir}:`, err);
}
}
function testCase(name, fn) {
return { name, fn };
}
async function runTest(test) {
console.log(`\n${chalk.blue('Running test:')} ${test.name}`);
try {
await test.fn();
console.log(`${chalk.green('✓')} ${test.name}`);
results.pass++;
results.tests.push({ name: test.name, passed: true });
return true;
} catch (err) {
console.error(`${chalk.red('✗')} ${test.name}`);
console.error(` ${chalk.red('Error:')} ${err.message}`);
results.fail++;
results.tests.push({ name: test.name, passed: false, error: err.message });
return false;
}
}
// Define test suite
const tests = [
testCase('Build checks - TypeScript compilation', async () => {
try {
execSync('npx tsc --noEmit', { cwd: path.join(__dirname, '../../') });
} catch (err) {
throw new Error(`TypeScript compilation failed: ${err.message}`);
}
}),
testCase('TestGenerator - Can instantiate', async () => {
// Mock the OpenAI dependency for testing
const originalEnv = process.env.OPENAI_API_KEY;
process.env.OPENAI_API_KEY = 'mock-api-key-for-testing';
try {
const generator = new TestGenerator({
format: 'playwright',
timeout: 60
});
if (!generator) {
throw new Error('Failed to create TestGenerator instance');
}
} finally {
// Restore original environment
process.env.OPENAI_API_KEY = originalEnv;
}
}),
testCase('TestRunner - Can instantiate', async () => {
const runner = new TestRunner({
browser: 'chromium',
headless: true,
timeout: 30,
reporter: 'list',
workers: 1
});
if (!runner) {
throw new Error('Failed to create TestRunner instance');
}
}),
testCase('ChecklistGenerator - Can instantiate', async () => {
// Mock the OpenAI dependency for testing
const originalEnv = process.env.OPENAI_API_KEY;
process.env.OPENAI_API_KEY = 'mock-api-key-for-testing';
try {
const generator = new ChecklistGenerator();
if (!generator) {
throw new Error('Failed to create ChecklistGenerator instance');
}
} finally {
// Restore original environment
process.env.OPENAI_API_KEY = originalEnv;
}
}),
testCase('diffUtils - Generates correct diff', async () => {
const oldCode = "function test() {\n return 123;\n}";
const newCode = "function test() {\n return 456;\n}";
const changes = diff(oldCode, newCode);
if (!Array.isArray(changes)) {
throw new Error('diff should return an array');
}
if (changes.length < 1) {
throw new Error('diff should detect changes between different strings');
}
const hasAddedChange = changes.some(c => c.added);
const hasRemovedChange = changes.some(c => c.removed);
if (!hasAddedChange || !hasRemovedChange) {
throw new Error('diff should identify added and removed content');
}
}),
testCase('securityRiskUtils - Formats severity correctly', async () => {
const critical = formatSeverityBadge('critical');
const high = formatSeverityBadge('high');
const medium = formatSeverityBadge('medium');
const low = formatSeverityBadge('low');
if (!critical.includes('CRITICAL') || !high.includes('HIGH') ||
!medium.includes('MEDIUM') || !low.includes('LOW')) {
throw new Error('formatSeverityBadge should format severity levels correctly');
}
}),
testCase('securityRiskUtils - Generates risk report', async () => {
const mockItems = [
{
id: 'SEC-001',
title: 'Test Issue',
description: 'Test description',
category: 'Security',
severity: 'high',
riskScore: {
score: 7.5,
impact: 'high',
likelihood: 'medium'
}
}
];
const report = generateSecurityRiskReport(mockItems);
if (!report || typeof report !== 'string' || !report.includes('SECURITY RISK REPORT')) {
throw new Error('generateSecurityRiskReport should return a formatted report string');
}
}),
testCase('End-to-end test of security checklist generation - mock mode', async () => {
await ensureDirectoryExists(TEST_OUTPUT_DIR);
// Instead of using real OpenAI, create a mock result for testing
const mockResult = {
items: [
{
id: 'SEC-001',
title: 'Test Security Issue',
description: 'This is a mock security issue for testing',
category: 'Security',
severity: 'high',
status: 'failed',
file: TEST_FILE_PATH,
riskScore: {
score: 7.5,
impact: 'high',
likelihood: 'medium'
}
}
],
itemCount: 1,
categories: ['Security'],
file: path.join(TEST_OUTPUT_DIR, 'mock-checklist.json')
};
// Write the mock result to a file to simulate the full process
await fs.writeFile(
mockResult.file,
JSON.stringify(mockResult, null, 2)
);
console.log(` Generated mock checklist with ${mockResult.items.length} items`);
console.log(` Output file: ${mockResult.file}`);
})
];
// Integration test for CLI commands
const cliTests = [
testCase('CLI help command works', async () => {
try {
const output = execSync('node ../../dist/cli.js --help', { cwd: __dirname }).toString();
if (!output.includes('Usage:') || !output.includes('Commands:')) {
throw new Error('CLI help output does not contain expected content');
}
} catch (err) {
throw new Error(`CLI help command failed: ${err.message}`);
}
}),
testCase('CLI version command works', async () => {
try {
execSync('node ../../dist/cli.js --version', { cwd: __dirname });
} catch (err) {
throw new Error(`CLI version command failed: ${err.message}`);
}
})
];
// Run all tests
async function runTests() {
console.log(chalk.blue.bold('\n===== CTRL.SHIFT.LEFT INTEGRATION TESTS =====\n'));
console.log(chalk.blue('Core Component Tests:'));
for (const test of tests) {
await runTest(test);
}
console.log(chalk.blue('\nCLI Command Tests:'));
for (const test of cliTests) {
await runTest(test);
}
// Print summary
console.log('\n===== TEST RESULTS =====');
console.log(`${chalk.green('Passed:')} ${results.pass}/${results.pass + results.fail}`);
console.log(`${chalk.red('Failed:')} ${results.fail}/${results.pass + results.fail}`);
if (results.fail > 0) {
console.log('\n===== FAILED TESTS =====');
results.tests
.filter(t => !t.passed)
.forEach(t => {
console.log(`${chalk.red('✗')} ${t.name}`);
console.log(` ${chalk.red('Error:')} ${t.error}`);
});
}
console.log('\n===== END OF TEST REPORT =====');
}
runTests().catch(err => {
console.error('Test runner error:', err);
process.exit(1);
});