mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-20 06:33:38 +02:00
✅ test: implement comprehensive automated testing system with daily CI/CD
Implement rock-solid automated testing infrastructure for mana-core-auth with daily execution, notifications, and comprehensive monitoring. Test Suite Improvements: - Fix all 36 failing BetterAuthService tests (missing service mocks) - Add 21 JwtAuthGuard tests achieving 100% statement coverage - Create silentError helper to suppress intentional error logs - Fix Todo backend TaskService test structure - Add jose mock for JWT testing - Configure jest collectCoverageFrom for mana-core-auth GitHub Actions Workflow: - Daily automated test execution (2 AM UTC + manual trigger) - Matrix parallelization across 6 backend services - PostgreSQL and Redis service containers - Coverage enforcement (80% threshold) - Multi-channel notifications (Discord, Slack, GitHub Issues) - Support for success notifications (opt-in) Test Infrastructure: - Coverage aggregation across multiple services - Flaky test detection with 30-run history tracking - Performance metrics tracking with regression detection - Test data seeding and cleanup scripts - Comprehensive test reporting with formatted metrics Documentation: - TESTING_GUIDE.md (4000+ words) - Complete testing documentation - AUTOMATED_TESTING_SYSTEM.md - System architecture and workflows - DISCORD_NOTIFICATIONS_SETUP.md - Discord webhook setup guide - TESTING_DEPLOYMENT_CHECKLIST.md - Pre-deployment verification - TESTING_QUICK_REFERENCE.md - Quick command reference Final Result: - 180/180 tests passing (100% pass rate) - Zero console errors in test output - Automated daily testing with rich notifications - Production-ready test infrastructure
This commit is contained in:
parent
9dbd6e6c09
commit
304897261d
24 changed files with 5017 additions and 16 deletions
158
scripts/test-reporting/aggregate-coverage.js
Normal file
158
scripts/test-reporting/aggregate-coverage.js
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
#!/usr/bin/env node
|
||||
/* eslint-disable @typescript-eslint/no-require-imports, no-console */
|
||||
/**
|
||||
* Aggregate Coverage Reports
|
||||
*
|
||||
* Merges multiple coverage reports from different test suites into a single
|
||||
* aggregated report for overall project coverage analysis.
|
||||
*
|
||||
* Usage:
|
||||
* node aggregate-coverage.js <input-dir> <output-dir>
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function findCoverageFiles(dir) {
|
||||
const coverageFiles = [];
|
||||
|
||||
function walk(currentDir) {
|
||||
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(currentDir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
walk(fullPath);
|
||||
} else if (entry.name === 'coverage-summary.json') {
|
||||
coverageFiles.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walk(dir);
|
||||
return coverageFiles;
|
||||
}
|
||||
|
||||
function mergeCoverage(coverageFiles) {
|
||||
const merged = {
|
||||
total: {
|
||||
lines: { total: 0, covered: 0, skipped: 0, pct: 0 },
|
||||
statements: { total: 0, covered: 0, skipped: 0, pct: 0 },
|
||||
functions: { total: 0, covered: 0, skipped: 0, pct: 0 },
|
||||
branches: { total: 0, covered: 0, skipped: 0, pct: 0 },
|
||||
},
|
||||
suites: {},
|
||||
};
|
||||
|
||||
for (const file of coverageFiles) {
|
||||
const content = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
const suiteName = path.basename(path.dirname(path.dirname(file)));
|
||||
|
||||
// Store individual suite data
|
||||
merged.suites[suiteName] = content.total;
|
||||
|
||||
// Aggregate totals
|
||||
if (content.total) {
|
||||
['lines', 'statements', 'functions', 'branches'].forEach((metric) => {
|
||||
merged.total[metric].total += content.total[metric].total || 0;
|
||||
merged.total[metric].covered += content.total[metric].covered || 0;
|
||||
merged.total[metric].skipped += content.total[metric].skipped || 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate percentages
|
||||
['lines', 'statements', 'functions', 'branches'].forEach((metric) => {
|
||||
if (merged.total[metric].total > 0) {
|
||||
merged.total[metric].pct = (merged.total[metric].covered / merged.total[metric].total) * 100;
|
||||
}
|
||||
});
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
function generateMarkdownSummary(coverage) {
|
||||
let markdown = '# Test Coverage Summary\n\n';
|
||||
|
||||
// Overall coverage table
|
||||
markdown += '## Overall Coverage\n\n';
|
||||
markdown += '| Metric | Coverage | Total | Covered |\n';
|
||||
markdown += '|--------|----------|-------|--------|\n';
|
||||
|
||||
['lines', 'statements', 'functions', 'branches'].forEach((metric) => {
|
||||
const data = coverage.total[metric];
|
||||
const pct = data.pct.toFixed(2);
|
||||
const icon = data.pct >= 80 ? '✅' : data.pct >= 60 ? '⚠️' : '❌';
|
||||
markdown += `| ${metric.charAt(0).toUpperCase() + metric.slice(1)} | ${icon} ${pct}% | ${data.total} | ${data.covered} |\n`;
|
||||
});
|
||||
|
||||
// Per-suite breakdown
|
||||
markdown += '\n## Coverage by Test Suite\n\n';
|
||||
markdown += '| Suite | Lines | Statements | Functions | Branches |\n';
|
||||
markdown += '|-------|-------|------------|-----------|----------|\n';
|
||||
|
||||
Object.entries(coverage.suites).forEach(([suite, data]) => {
|
||||
const linesPct = data.lines.pct.toFixed(1);
|
||||
const stmtPct = data.statements.pct.toFixed(1);
|
||||
const funcPct = data.functions.pct.toFixed(1);
|
||||
const branchPct = data.branches.pct.toFixed(1);
|
||||
|
||||
markdown += `| ${suite} | ${linesPct}% | ${stmtPct}% | ${funcPct}% | ${branchPct}% |\n`;
|
||||
});
|
||||
|
||||
return markdown;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const inputDir = process.argv[2];
|
||||
const outputDir = process.argv[3];
|
||||
|
||||
if (!inputDir || !outputDir) {
|
||||
console.error('Usage: node aggregate-coverage.js <input-dir> <output-dir>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Ensure output directory exists
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Find all coverage files
|
||||
console.log(`Searching for coverage files in ${inputDir}...`);
|
||||
const coverageFiles = findCoverageFiles(inputDir);
|
||||
console.log(`Found ${coverageFiles.length} coverage files`);
|
||||
|
||||
if (coverageFiles.length === 0) {
|
||||
console.log('No coverage files found. Skipping aggregation.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Merge coverage data
|
||||
console.log('Merging coverage data...');
|
||||
const merged = mergeCoverage(coverageFiles);
|
||||
|
||||
// Write aggregated coverage
|
||||
const outputFile = path.join(outputDir, 'total-coverage.json');
|
||||
fs.writeFileSync(outputFile, JSON.stringify(merged, null, 2));
|
||||
console.log(`Wrote aggregated coverage to ${outputFile}`);
|
||||
|
||||
// Generate markdown summary
|
||||
const summary = generateMarkdownSummary(merged);
|
||||
const summaryFile = path.join(outputDir, 'summary.md');
|
||||
fs.writeFileSync(summaryFile, summary);
|
||||
console.log(`Wrote summary to ${summaryFile}`);
|
||||
|
||||
// Output summary to console
|
||||
console.log('\n' + summary);
|
||||
|
||||
// Exit with error if coverage is too low
|
||||
if (merged.total.lines.pct < 80) {
|
||||
console.error(`\n❌ Coverage ${merged.total.lines.pct.toFixed(2)}% is below 80% threshold`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\n✅ Coverage ${merged.total.lines.pct.toFixed(2)}% meets 80% threshold`);
|
||||
}
|
||||
|
||||
main();
|
||||
Loading…
Add table
Add a link
Reference in a new issue