/ tests / reports / daily-progress-report-generator.test.js
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  });