UNPKG

@casoon/auditmysite

Version:

Professional website analysis suite with robust accessibility testing, Core Web Vitals performance monitoring, SEO analysis, and content optimization insights. Features isolated browser contexts, retry mechanisms, and comprehensive API endpoints for profe

374 lines 14.5 kB
"use strict"; /** * Report Data Validator * Ensures audit results are complete and properly aggregated */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ReportValidator = void 0; const logger_1 = require("../core/logging/logger"); class ReportValidator { constructor() { this.logger = new logger_1.Logger({ level: 'info' }); } /** * Validate complete audit results */ validateAuditResults(results) { const errors = []; const warnings = []; let validPages = 0; let pagesWithErrors = 0; let pagesWithWarnings = 0; const missingFieldsSet = new Set(); // Validate each page result results.forEach((result, index) => { const pageValidation = this.validatePageResult(result, index); if (pageValidation.errors.length > 0) { errors.push(...pageValidation.errors); pagesWithErrors++; } else { validPages++; } if (pageValidation.warnings.length > 0) { warnings.push(...pageValidation.warnings); pagesWithWarnings++; } pageValidation.missingFields.forEach(field => missingFieldsSet.add(field)); }); const completenessScore = this.calculateCompletenessScore(results); const stats = { totalPages: results.length, validPages, pagesWithErrors, pagesWithWarnings, missingFields: Array.from(missingFieldsSet), completenessScore }; return { valid: errors.length === 0, errors, warnings, stats }; } /** * Validate individual page result */ validatePageResult(result, index) { const errors = []; const warnings = []; const missingFields = []; // Required fields validation if (!result.url) { errors.push({ field: `results[${index}].url`, message: 'URL is required', severity: 'critical', context: { index } }); } if (!result.title) { warnings.push({ field: `results[${index}].title`, message: 'Page title is missing', suggestion: 'Page might not have loaded correctly' }); missingFields.push('title'); } if (result.duration === undefined || result.duration < 0) { errors.push({ field: `results[${index}].duration`, message: 'Invalid or missing duration', severity: 'error', context: { index, duration: result.duration } }); } // Accessibility data validation if (!result.errors || !Array.isArray(result.errors)) { errors.push({ field: `results[${index}].errors`, message: 'Errors array is missing or invalid', severity: 'critical' }); } if (!result.warnings || !Array.isArray(result.warnings)) { errors.push({ field: `results[${index}].warnings`, message: 'Warnings array is missing or invalid', severity: 'critical' }); } // Pa11y data validation if (result.pa11yIssues && !Array.isArray(result.pa11yIssues)) { errors.push({ field: `results[${index}].pa11yIssues`, message: 'pa11yIssues must be an array', severity: 'error' }); } if (result.pa11yScore !== undefined) { if (typeof result.pa11yScore !== 'number' || result.pa11yScore < 0 || result.pa11yScore > 100) { errors.push({ field: `results[${index}].pa11yScore`, message: 'pa11yScore must be a number between 0 and 100', severity: 'error', context: { score: result.pa11yScore } }); } } else { missingFields.push('pa11yScore'); } // Performance metrics validation if (result.performanceMetrics) { const perfErrors = this.validatePerformanceMetrics(result.performanceMetrics, index); errors.push(...perfErrors); } else { warnings.push({ field: `results[${index}].performanceMetrics`, message: 'Performance metrics are missing', suggestion: 'Enable collectPerformanceMetrics option' }); missingFields.push('performanceMetrics'); } // Status validation if (result.crashed && result.passed) { errors.push({ field: `results[${index}].status`, message: 'Page cannot be both crashed and passed', severity: 'error', context: { crashed: result.crashed, passed: result.passed } }); } return { errors, warnings, missingFields }; } /** * Validate performance metrics */ validatePerformanceMetrics(metrics, pageIndex) { const errors = []; const requiredMetrics = [ 'loadTime', 'domContentLoaded', 'firstPaint', 'renderTime', 'firstContentfulPaint', 'largestContentfulPaint' ]; requiredMetrics.forEach(metric => { if (metrics[metric] === undefined) { errors.push({ field: `results[${pageIndex}].performanceMetrics.${metric}`, message: `Performance metric '${metric}' is missing`, severity: 'error' }); } else if (typeof metrics[metric] !== 'number' || metrics[metric] < 0) { errors.push({ field: `results[${pageIndex}].performanceMetrics.${metric}`, message: `Performance metric '${metric}' must be a positive number`, severity: 'error', context: { value: metrics[metric] } }); } }); // Validate performance score if (metrics.performanceScore !== undefined) { if (typeof metrics.performanceScore !== 'number' || metrics.performanceScore < 0 || metrics.performanceScore > 100) { errors.push({ field: `results[${pageIndex}].performanceMetrics.performanceScore`, message: 'performanceScore must be between 0 and 100', severity: 'error', context: { score: metrics.performanceScore } }); } } // Validate performance grade if (metrics.performanceGrade !== undefined) { const validGrades = ['A', 'B', 'C', 'D', 'F']; if (!validGrades.includes(metrics.performanceGrade)) { errors.push({ field: `results[${pageIndex}].performanceMetrics.performanceGrade`, message: 'performanceGrade must be A, B, C, D, or F', severity: 'error', context: { grade: metrics.performanceGrade } }); } } return errors; } /** * Validate test summary */ validateTestSummary(summary) { const errors = []; const warnings = []; // Check totals consistency const expectedTotal = summary.passedPages + summary.failedPages + (summary.crashedPages || 0); if (summary.testedPages !== expectedTotal) { errors.push({ field: 'summary.testedPages', message: 'testedPages does not match sum of passed + failed + crashed', severity: 'critical', context: { testedPages: summary.testedPages, calculated: expectedTotal, passed: summary.passedPages, failed: summary.failedPages, crashed: summary.crashedPages } }); } // Validate all pages have results if (summary.results.length !== summary.testedPages) { errors.push({ field: 'summary.results', message: 'Number of results does not match testedPages count', severity: 'critical', context: { resultsCount: summary.results.length, testedPages: summary.testedPages } }); } // Validate error/warning counts const actualErrors = summary.results.reduce((sum, r) => sum + (r.errors?.length || 0), 0); const actualWarnings = summary.results.reduce((sum, r) => sum + (r.warnings?.length || 0), 0); if (summary.totalErrors !== actualErrors) { warnings.push({ field: 'summary.totalErrors', message: 'totalErrors count does not match aggregated errors from results', suggestion: `Expected ${actualErrors}, got ${summary.totalErrors}` }); } if (summary.totalWarnings !== actualWarnings) { warnings.push({ field: 'summary.totalWarnings', message: 'totalWarnings count does not match aggregated warnings from results', suggestion: `Expected ${actualWarnings}, got ${summary.totalWarnings}` }); } const stats = { totalPages: summary.testedPages, validPages: summary.passedPages, pagesWithErrors: summary.failedPages, pagesWithWarnings: 0, missingFields: [], completenessScore: this.calculateCompletenessScore(summary.results) }; return { valid: errors.length === 0, errors, warnings, stats }; } /** * Calculate completeness score (0-100) */ calculateCompletenessScore(results) { if (results.length === 0) return 0; const requiredFields = [ 'url', 'title', 'duration', 'errors', 'warnings', 'passed', 'pa11yScore', 'performanceMetrics' ]; let totalFields = 0; let presentFields = 0; results.forEach(result => { requiredFields.forEach(field => { totalFields++; if (result[field] !== undefined) { presentFields++; } }); }); return Math.round((presentFields / totalFields) * 100); } /** * Generate validation report */ generateReport(validation) { const lines = []; lines.push('═══════════════════════════════════════════════════════'); lines.push(' AUDIT DATA VALIDATION REPORT'); lines.push('═══════════════════════════════════════════════════════'); lines.push(''); // Overall status lines.push(`Status: ${validation.valid ? '✅ VALID' : '❌ INVALID'}`); lines.push(`Completeness Score: ${validation.stats.completenessScore}%`); lines.push(''); // Statistics lines.push('Statistics:'); lines.push(` Total Pages: ${validation.stats.totalPages}`); lines.push(` Valid Pages: ${validation.stats.validPages}`); lines.push(` Pages with Errors: ${validation.stats.pagesWithErrors}`); lines.push(` Pages with Warnings: ${validation.stats.pagesWithWarnings}`); lines.push(''); // Errors if (validation.errors.length > 0) { lines.push(`Errors (${validation.errors.length}):`); validation.errors.forEach((error, i) => { lines.push(` ${i + 1}. [${error.severity.toUpperCase()}] ${error.field}`); lines.push(` ${error.message}`); if (error.context) { lines.push(` Context: ${JSON.stringify(error.context)}`); } }); lines.push(''); } // Warnings if (validation.warnings.length > 0) { lines.push(`Warnings (${validation.warnings.length}):`); validation.warnings.slice(0, 10).forEach((warning, i) => { lines.push(` ${i + 1}. ${warning.field}: ${warning.message}`); if (warning.suggestion) { lines.push(` Suggestion: ${warning.suggestion}`); } }); if (validation.warnings.length > 10) { lines.push(` ... and ${validation.warnings.length - 10} more warnings`); } lines.push(''); } // Missing fields if (validation.stats.missingFields.length > 0) { lines.push('Commonly Missing Fields:'); validation.stats.missingFields.forEach(field => { lines.push(` - ${field}`); }); lines.push(''); } lines.push('═══════════════════════════════════════════════════════'); return lines.join('\n'); } /** * Log validation results */ logValidation(validation) { if (validation.valid) { this.logger.success(`Audit data validation passed (${validation.stats.completenessScore}% complete)`); } else { this.logger.error(`Audit data validation failed with ${validation.errors.length} errors`); } if (validation.warnings.length > 0) { this.logger.warn(`Found ${validation.warnings.length} warnings`); } // Log detailed report in debug mode const report = this.generateReport(validation); this.logger.debug(report); } } exports.ReportValidator = ReportValidator; //# sourceMappingURL=report-validator.js.map