apiveritas
Version:
Lightweight CLI tool for consumer-driven API contract testing via JSON schema and payload comparisons.
138 lines (126 loc) • 4.45 kB
text/typescript
/**
* @file HtmlReporter.ts
* @author Mario Galea
* @description
* Generates a styled HTML report comparing old and new API responses,
* including textual differences and JSON payload previews.
*/
import fs from 'fs';
import path from 'path';
import chalk from 'chalk';
import { Logger } from '../utils/Logger';
interface ComparisonResult {
fileName: string;
matched: boolean;
differences?: string[];
oldContent?: any;
newContent?: any;
}
/**
* Responsible for generating an HTML report summarizing API response comparisons.
* Each report includes match status, textual differences, and side-by-side JSON views.
*/
export class HtmlReporter {
private reportsDir: string;
private logger: Logger;
/**
* Constructs the HtmlReporter.
* Creates the reports directory if it does not exist, and initializes the logger.
*/
constructor() {
this.reportsDir = path.join(process.cwd(), 'apiveritas', 'reports');
this.logger = new Logger({ level: 'info' });
if (!fs.existsSync(this.reportsDir)) {
fs.mkdirSync(this.reportsDir, { recursive: true });
this.logger.info(`Reports directory created at: ${this.reportsDir}`);
}
}
/**
* Generates a timestamped HTML report file showing comparison results between two payload folders.
*
* @param oldFolder - Path to the older version of payloads.
* @param newFolder - Path to the newer version of payloads.
* @param comparisonResults - An array of comparison result objects with diff and content data.
* @returns The full path to the generated HTML report.
*/
generateReport(oldFolder: string, newFolder: string, comparisonResults: ComparisonResult[]): string {
const now = new Date();
const timestamp = `${now.getFullYear()}.${(now.getMonth() + 1)
.toString()
.padStart(2, '0')}.${now.getDate().toString().padStart(2, '0')}.${now
.getHours()
.toString()
.padStart(2, '0')}${now.getMinutes().toString().padStart(2, '0')}${now
.getSeconds()
.toString()
.padStart(2, '0')}`;
const filename = `comparison-report-${timestamp}.html`;
const filepath = path.join(this.reportsDir, filename);
const html = `
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>API Payload Comparison Report</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; background: #fdfdfd; }
h1 { color: #2c3e50; }
.match { color: green; }
.diff { color: red; }
.container { max-width: 1000px; margin: auto; }
pre { background: #f4f4f4; padding: 10px; border-radius: 4px; white-space: pre-wrap; }
.section { margin-bottom: 40px; border-bottom: 1px dashed #ddd; padding-bottom: 20px; }
.file-name { font-weight: bold; }
details { margin-top: 10px; }
summary { cursor: pointer; color: #3498db; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<h1>API Payload Comparison Report</h1>
<p><strong>Comparing folders:</strong></p>
<ul>
<li>Previous: <code>${oldFolder}</code></li>
<li>Latest: <code>${newFolder}</code></li>
</ul>
<h2>Results</h2>
${comparisonResults
.map(
(result) => `
<div class="section">
<div class="file-name">${result.fileName}</div>
<div>Status: <span class="${result.matched ? 'match' : 'diff'}">
${result.matched ? '✅ MATCH' : '❌ DIFFERENCES FOUND'}
</span></div>
${
!result.matched && result.differences
? `
<details open>
<summary>View Differences (Text)</summary>
<pre>${result.differences.join('\n')}</pre>
</details>
<details>
<summary>View Old Response</summary>
<pre>${JSON.stringify(result.oldContent, null, 2)}</pre>
</details>
<details>
<summary>View New Response</summary>
<pre>${JSON.stringify(result.newContent, null, 2)}</pre>
</details>
`
: ''
}
</div>
`
)
.join('')}
</div>
</body>
</html>
`;
fs.writeFileSync(filepath, html, 'utf-8');
this.logger.info(chalk.magenta.bold('HTML Report Generated Successfully in:'));
this.logger.info(chalk.white(filepath + ':\n'));
return filepath;
}
}