Skip to main content
Glama

In Memoria

test-reporter.ts9.12 kB
/** * Test Report Generator * * Generates a comprehensive test report from Vitest results */ import { readFileSync, existsSync, writeFileSync } from 'fs'; import { join } from 'path'; interface TestResult { name: string; status: 'passed' | 'failed' | 'skipped'; duration: number; error?: string; } interface TestSuite { name: string; tests: TestResult[]; totalTests: number; passed: number; failed: number; skipped: number; duration: number; } interface TestReport { timestamp: string; totalSuites: number; totalTests: number; passed: number; failed: number; skipped: number; duration: number; coverage?: { lines: number; functions: number; branches: number; statements: number; }; suites: TestSuite[]; } function generateMarkdownReport(report: TestReport): string { const passRate = report.totalTests > 0 ? ((report.passed / report.totalTests) * 100).toFixed(2) : '0'; let markdown = `# Test Report **Generated**: ${report.timestamp} ## Summary | Metric | Value | |--------|-------| | Total Test Suites | ${report.totalSuites} | | Total Tests | ${report.totalTests} | | ✅ Passed | ${report.passed} | | ❌ Failed | ${report.failed} | | ⏭️ Skipped | ${report.skipped} | | Pass Rate | ${passRate}% | | Duration | ${(report.duration / 1000).toFixed(2)}s | `; if (report.coverage) { markdown += `## Coverage | Type | Percentage | |------|------------| | Lines | ${report.coverage.lines}% | | Functions | ${report.coverage.functions}% | | Branches | ${report.coverage.branches}% | | Statements | ${report.coverage.statements}% | `; } if (report.failed > 0) { markdown += `## ❌ Failed Tests `; for (const suite of report.suites) { const failedTests = suite.tests.filter(t => t.status === 'failed'); if (failedTests.length > 0) { markdown += `### ${suite.name}\n\n`; for (const test of failedTests) { markdown += `- **${test.name}** (${test.duration}ms)\n`; if (test.error) { markdown += ` \`\`\`\n ${test.error}\n \`\`\`\n`; } } markdown += '\n'; } } } markdown += `## Test Suites `; for (const suite of report.suites) { const icon = suite.failed > 0 ? '❌' : '✅'; markdown += `### ${icon} ${suite.name} - **Tests**: ${suite.totalTests} - **Passed**: ${suite.passed} - **Failed**: ${suite.failed} - **Skipped**: ${suite.skipped} - **Duration**: ${(suite.duration / 1000).toFixed(2)}s `; } markdown += `--- *Generated by In-Memoria Test Reporter* `; return markdown; } function generateHtmlReport(report: TestReport): string { const passRate = report.totalTests > 0 ? ((report.passed / report.totalTests) * 100).toFixed(2) : '0'; const statusColor = report.failed === 0 ? '#28a745' : '#dc3545'; return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Test Report - In-Memoria</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; background: #f5f5f5; } .header { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; } .header h1 { margin: 0 0 10px 0; color: #333; } .timestamp { color: #666; font-size: 14px; } .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 20px; } .metric { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .metric-label { font-size: 14px; color: #666; margin-bottom: 5px; } .metric-value { font-size: 32px; font-weight: bold; color: ${statusColor}; } .coverage { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; } .coverage h2 { margin-top: 0; } .coverage-bar { height: 20px; background: #e0e0e0; border-radius: 10px; overflow: hidden; margin: 10px 0; } .coverage-fill { height: 100%; background: linear-gradient(to right, #28a745, #20c997); transition: width 0.3s; } .suites { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .suite { border-left: 4px solid #28a745; padding-left: 15px; margin-bottom: 20px; } .suite.failed { border-left-color: #dc3545; } .suite-name { font-size: 18px; font-weight: bold; margin-bottom: 10px; } .test { padding: 8px 0; border-bottom: 1px solid #eee; } .test:last-child { border-bottom: none; } .test.passed { color: #28a745; } .test.failed { color: #dc3545; } .test.skipped { color: #ffc107; } .error { background: #f8f9fa; padding: 10px; border-left: 3px solid #dc3545; margin-top: 5px; font-family: monospace; font-size: 12px; white-space: pre-wrap; } </style> </head> <body> <div class="header"> <h1>Test Report</h1> <div class="timestamp">${report.timestamp}</div> </div> <div class="summary"> <div class="metric"> <div class="metric-label">Pass Rate</div> <div class="metric-value">${passRate}%</div> </div> <div class="metric"> <div class="metric-label">Total Tests</div> <div class="metric-value">${report.totalTests}</div> </div> <div class="metric"> <div class="metric-label">Passed</div> <div class="metric-value" style="color: #28a745;">${report.passed}</div> </div> <div class="metric"> <div class="metric-label">Failed</div> <div class="metric-value" style="color: #dc3545;">${report.failed}</div> </div> </div> ${report.coverage ? ` <div class="coverage"> <h2>Coverage</h2> <div> <div class="metric-label">Lines</div> <div class="coverage-bar"> <div class="coverage-fill" style="width: ${report.coverage.lines}%"></div> </div> <div>${report.coverage.lines}%</div> </div> <div> <div class="metric-label">Functions</div> <div class="coverage-bar"> <div class="coverage-fill" style="width: ${report.coverage.functions}%"></div> </div> <div>${report.coverage.functions}%</div> </div> <div> <div class="metric-label">Branches</div> <div class="coverage-bar"> <div class="coverage-fill" style="width: ${report.coverage.branches}%"></div> </div> <div>${report.coverage.branches}%</div> </div> </div> ` : ''} <div class="suites"> <h2>Test Suites</h2> ${report.suites.map(suite => ` <div class="suite ${suite.failed > 0 ? 'failed' : ''}"> <div class="suite-name">${suite.failed > 0 ? '❌' : '✅'} ${suite.name}</div> <div>Tests: ${suite.totalTests} | Passed: ${suite.passed} | Failed: ${suite.failed} | Duration: ${(suite.duration / 1000).toFixed(2)}s</div> ${suite.tests.filter(t => t.status === 'failed').map(test => ` <div class="test failed"> ❌ ${test.name} (${test.duration}ms) ${test.error ? `<div class="error">${test.error}</div>` : ''} </div> `).join('')} </div> `).join('')} </div> </body> </html>`; } // Export for use as a module export { TestReport, generateMarkdownReport, generateHtmlReport }; // CLI usage if (import.meta.url === `file://${process.argv[1]}`) { const reportPath = process.argv[2] || './test-results.json'; const outputFormat = process.argv[3] || 'markdown'; if (!existsSync(reportPath)) { console.error(`Error: Report file not found: ${reportPath}`); process.exit(1); } const reportData: TestReport = JSON.parse(readFileSync(reportPath, 'utf-8')); if (outputFormat === 'markdown') { const markdown = generateMarkdownReport(reportData); const outputPath = reportPath.replace('.json', '.md'); writeFileSync(outputPath, markdown); console.log(`✅ Markdown report generated: ${outputPath}`); } else if (outputFormat === 'html') { const html = generateHtmlReport(reportData); const outputPath = reportPath.replace('.json', '.html'); writeFileSync(outputPath, html); console.log(`✅ HTML report generated: ${outputPath}`); } else { console.error(`Unknown format: ${outputFormat}. Use 'markdown' or 'html'.`); process.exit(1); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pi22by7/In-Memoria'

If you have feedback or need assistance with the MCP directory API, please join our Discord server