daily-progress-report-generator.test.js
1 /** 2 * Tests for src/reports/daily-progress-report-generator.js 3 * 4 * Covers generateDailyReport() with various data shapes (PDF generation). 5 */ 6 7 import { test, describe, after } from 'node:test'; 8 import assert from 'node:assert/strict'; 9 import { existsSync, unlinkSync } from 'fs'; 10 import { join } from 'path'; 11 import { tmpdir } from 'os'; 12 13 import { generateDailyReport } from '../../src/reports/daily-progress-report-generator.js'; 14 15 const OUTPUT_DIR = tmpdir(); 16 const generatedFiles = []; 17 18 after(() => { 19 for (const f of generatedFiles) { 20 if (existsSync(f)) { 21 try { 22 unlinkSync(f); 23 } catch { 24 /* ignore */ 25 } 26 } 27 } 28 }); 29 30 function makeReportData(overrides = {}) { 31 return { 32 gitActivity: { 33 commits: [ 34 { hash: 'abc123', message: 'feat: add feature', date: '2026-03-08' }, 35 { hash: 'def456', message: 'fix: fix bug', date: '2026-03-08' }, 36 ], 37 filesChanged: 5, 38 insertions: 100, 39 deletions: 20, 40 branches: ['main', 'feature/test'], 41 }, 42 dbChanges: { 43 newSites: 10, 44 newOutreaches: 5, 45 newConversations: 2, 46 statusBreakdown: { enriched: 100, outreach_sent: 50 }, 47 }, 48 codeQuality: { 49 coverage: 85.5, 50 todosAdded: 2, 51 todosResolved: 1, 52 newDependencies: ['pdfkit@3.0.0'], 53 }, 54 systemHealth: { 55 errors: ['Error A occurred', 'Error B occurred'], 56 warnings: ['Warning X', 'Warning Y'], 57 uptime: '24h', 58 }, 59 ...overrides, 60 }; 61 } 62 63 describe('generateDailyReport', () => { 64 test('generates a PDF file at specified output path', async () => { 65 const outputPath = join(OUTPUT_DIR, `test-daily-pdf-${Date.now()}.pdf`); 66 generatedFiles.push(outputPath); 67 68 const result = await generateDailyReport(makeReportData(), outputPath); 69 70 assert.equal(result, outputPath, 'should return the output path'); 71 assert.ok(existsSync(outputPath), 'PDF file should exist'); 72 }); 73 74 test('generated file has non-zero size', async () => { 75 const outputPath = join(OUTPUT_DIR, `test-daily-size-${Date.now()}.pdf`); 76 generatedFiles.push(outputPath); 77 78 await generateDailyReport(makeReportData(), outputPath); 79 80 const { statSync } = await import('fs'); 81 const stat = statSync(outputPath); 82 assert.ok(stat.size > 0, 'PDF file should not be empty'); 83 }); 84 85 test('handles empty gitActivity commits array', async () => { 86 const outputPath = join(OUTPUT_DIR, `test-daily-nocommits-${Date.now()}.pdf`); 87 generatedFiles.push(outputPath); 88 89 const data = makeReportData({ 90 gitActivity: { 91 commits: [], 92 filesChanged: 0, 93 insertions: 0, 94 deletions: 0, 95 branches: [], 96 }, 97 }); 98 99 await assert.doesNotReject(generateDailyReport(data, outputPath)); 100 assert.ok(existsSync(outputPath), 'should create PDF even with no commits'); 101 }); 102 103 test('handles null optional fields gracefully', async () => { 104 const outputPath = join(OUTPUT_DIR, `test-daily-null-${Date.now()}.pdf`); 105 generatedFiles.push(outputPath); 106 107 const data = makeReportData({ 108 systemHealth: null, 109 codeQuality: null, 110 }); 111 112 await assert.doesNotReject(generateDailyReport(data, outputPath)); 113 }); 114 115 test('handles missing dbChanges', async () => { 116 const outputPath = join(OUTPUT_DIR, `test-daily-nodb-${Date.now()}.pdf`); 117 generatedFiles.push(outputPath); 118 119 const data = makeReportData({ dbChanges: null }); 120 121 await assert.doesNotReject(generateDailyReport(data, outputPath)); 122 }); 123 124 test('handles empty arrays in systemHealth', async () => { 125 const outputPath = join(OUTPUT_DIR, `test-daily-noerrors-${Date.now()}.pdf`); 126 generatedFiles.push(outputPath); 127 128 const data = makeReportData({ 129 systemHealth: { 130 errors: [], 131 warnings: [], 132 uptime: '0h', 133 }, 134 }); 135 136 await assert.doesNotReject(generateDailyReport(data, outputPath)); 137 }); 138 139 test('returns string path ending in .pdf', async () => { 140 const outputPath = join(OUTPUT_DIR, `test-daily-return-${Date.now()}.pdf`); 141 generatedFiles.push(outputPath); 142 143 const result = await generateDailyReport(makeReportData(), outputPath); 144 assert.ok(typeof result === 'string', 'should return a string'); 145 assert.ok(result.endsWith('.pdf'), 'should return a PDF path'); 146 }); 147 148 test('uses default output path when none specified', async () => { 149 const result = await generateDailyReport(makeReportData()); 150 generatedFiles.push(result); 151 152 assert.ok(typeof result === 'string', 'should return path string'); 153 assert.ok(result.endsWith('.pdf'), 'should end with .pdf'); 154 assert.ok(existsSync(result), 'file should exist at default path'); 155 }); 156 157 test('handles high coverage value (100%)', async () => { 158 const outputPath = join(OUTPUT_DIR, `test-daily-highcov-${Date.now()}.pdf`); 159 generatedFiles.push(outputPath); 160 161 const data = makeReportData({ 162 codeQuality: { 163 coverage: 100, 164 todosAdded: 0, 165 todosResolved: 5, 166 newDependencies: [], 167 }, 168 }); 169 170 await assert.doesNotReject(generateDailyReport(data, outputPath)); 171 }); 172 173 test('handles many commits and errors', async () => { 174 const outputPath = join(OUTPUT_DIR, `test-daily-many-${Date.now()}.pdf`); 175 generatedFiles.push(outputPath); 176 177 const data = makeReportData({ 178 gitActivity: { 179 commits: Array.from({ length: 20 }, (_, i) => ({ 180 hash: `abc${i}`, 181 message: `feat: change ${i}`, 182 date: '2026-03-08', 183 })), 184 filesChanged: 100, 185 insertions: 5000, 186 deletions: 2000, 187 branches: ['main'], 188 }, 189 systemHealth: { 190 errors: Array.from({ length: 15 }, (_, i) => `Error ${i}`), 191 warnings: Array.from({ length: 10 }, (_, i) => `Warning ${i}`), 192 uptime: '72h', 193 }, 194 }); 195 196 await assert.doesNotReject(generateDailyReport(data, outputPath)); 197 }); 198 });