/ tests / utils / summary-generator.test.js
summary-generator.test.js
  1  /**
  2   * Tests for Summary Generator Utility
  3   * Covers previously untested functions: generatePipelineCompletion,
  4   * generateGradeDistribution, displayProgress, and generateSummary color types.
  5   */
  6  
  7  import { describe, test, beforeEach, afterEach, mock } from 'node:test';
  8  import assert from 'node:assert/strict';
  9  import {
 10    generateSummary,
 11    generateStageCompletion,
 12    generatePipelineCompletion,
 13    generateGradeDistribution,
 14    displayProgress,
 15  } from '../../src/utils/summary-generator.js';
 16  
 17  describe('Summary Generator', () => {
 18    let originalLog;
 19  
 20    beforeEach(() => {
 21      originalLog = console.log;
 22      console.log = mock.fn();
 23    });
 24  
 25    afterEach(() => {
 26      console.log = originalLog;
 27    });
 28  
 29    describe('generateSummary', () => {
 30      test('should output summary with success items', () => {
 31        generateSummary('Test Results', [{ label: 'Passed', value: 10, type: 'success' }]);
 32  
 33        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
 34        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
 35        assert.ok(output.includes('Test Results'), 'should include title');
 36        assert.ok(output.includes('Passed'), 'should include label');
 37      });
 38  
 39      test('should output summary with info items', () => {
 40        generateSummary('Info Summary', [{ label: 'Processed', value: 5, type: 'info' }]);
 41  
 42        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
 43        assert.ok(output.includes('Info Summary'), 'should include title');
 44        assert.ok(output.includes('Processed'), 'should include label');
 45      });
 46  
 47      test('should output summary with warn items', () => {
 48        generateSummary('Warning Summary', [{ label: 'Skipped', value: 3, type: 'warn' }]);
 49  
 50        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
 51        assert.ok(output.includes('Warning Summary'), 'should include title');
 52        assert.ok(output.includes('Skipped'), 'should include label');
 53      });
 54  
 55      test('should output summary with error items', () => {
 56        generateSummary('Error Summary', [{ label: 'Failed', value: 2, type: 'error' }]);
 57  
 58        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
 59        assert.ok(output.includes('Error Summary'), 'should include title');
 60        assert.ok(output.includes('Failed'), 'should include label');
 61      });
 62  
 63      test('should handle unknown type gracefully', () => {
 64        generateSummary('Unknown Type', [{ label: 'Other', value: 1, type: 'unknown' }]);
 65  
 66        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
 67        assert.ok(output.includes('Unknown Type'), 'should include title');
 68        assert.ok(output.includes('Other'), 'should include label');
 69      });
 70  
 71      test('should handle multiple items of different types', () => {
 72        generateSummary('Mixed Summary', [
 73          { label: 'Passed', value: 10, type: 'success' },
 74          { label: 'Info', value: 5, type: 'info' },
 75          { label: 'Warnings', value: 3, type: 'warn' },
 76          { label: 'Errors', value: 1, type: 'error' },
 77        ]);
 78  
 79        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
 80        assert.ok(output.includes('Mixed Summary'), 'should include title');
 81        assert.ok(output.includes('Passed'), 'should include success label');
 82        assert.ok(output.includes('Errors'), 'should include error label');
 83      });
 84    });
 85  
 86    describe('generatePipelineCompletion', () => {
 87      test('should output pipeline completion with stage results', () => {
 88        const stageResults = [
 89          { stage: 'serps', stats: { succeeded: 10, processed: 12, failed: 2 } },
 90          { stage: 'assets', stats: { succeeded: 8, processed: 8, failed: 0 } },
 91        ];
 92  
 93        generatePipelineCompletion(stageResults, 5000);
 94  
 95        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
 96        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
 97        assert.ok(output.includes('serps'), 'should include serps stage');
 98        assert.ok(output.includes('assets'), 'should include assets stage');
 99      });
100  
101      test('should handle single stage result', () => {
102        const stageResults = [{ stage: 'scoring', stats: { succeeded: 5, processed: 5, failed: 0 } }];
103  
104        generatePipelineCompletion(stageResults, 1500);
105  
106        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
107        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
108        assert.ok(output.includes('scoring'), 'should include scoring stage');
109      });
110  
111      test('should handle stage with all failures', () => {
112        const stageResults = [{ stage: 'enrich', stats: { succeeded: 0, processed: 5, failed: 5 } }];
113  
114        generatePipelineCompletion(stageResults, 3000);
115  
116        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
117        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
118        assert.ok(output.includes('enrich'), 'should include enrich stage');
119      });
120  
121      test('should handle empty stage results', () => {
122        generatePipelineCompletion([], 0);
123  
124        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
125      });
126  
127      test('should format duration correctly for long-running pipelines', () => {
128        const stageResults = [
129          { stage: 'proposals', stats: { succeeded: 50, processed: 55, failed: 5 } },
130        ];
131  
132        generatePipelineCompletion(stageResults, 120000);
133  
134        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
135      });
136    });
137  
138    describe('generateGradeDistribution', () => {
139      test('should output grade distribution with multiple grades', () => {
140        const distribution = { 'A+': 5, A: 3, 'B+': 8, B: 12, C: 4, D: 2, E: 1 };
141  
142        generateGradeDistribution(distribution);
143  
144        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
145        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
146        assert.ok(output.includes('A+'), 'should include A+ grade');
147      });
148  
149      test('should output grade distribution with only A grades (success type)', () => {
150        const distribution = { 'A+': 10, A: 5, 'A-': 3 };
151  
152        generateGradeDistribution(distribution);
153  
154        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
155        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
156        assert.ok(output.includes('A+'), 'should include A+ grade');
157        assert.ok(output.includes('A-'), 'should include A- grade');
158      });
159  
160      test('should output grade distribution with F grade (error type)', () => {
161        const distribution = { F: 3 };
162  
163        generateGradeDistribution(distribution);
164  
165        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
166        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
167        assert.ok(output.includes('F'), 'should include F grade');
168      });
169  
170      test('should handle single grade entry', () => {
171        const distribution = { B: 7 };
172  
173        generateGradeDistribution(distribution);
174  
175        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
176        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
177        assert.ok(output.includes('B'), 'should include B grade');
178      });
179  
180      test('should handle empty distribution', () => {
181        generateGradeDistribution({});
182  
183        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
184      });
185  
186      test('should apply correct color types for different grades', () => {
187        // C and D get 'warn' type, E and F get 'error' type
188        const distribution = { 'A+': 1, 'B+': 1, C: 1, D: 1, E: 1, F: 1 };
189  
190        generateGradeDistribution(distribution);
191  
192        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
193      });
194    });
195  
196    describe('displayProgress', () => {
197      test('should display progress bar with percentage and message', () => {
198        displayProgress(5, 10, 'Processing sites');
199  
200        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
201        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
202        assert.ok(output.includes('50%'), 'should include percentage');
203        assert.ok(output.includes('Processing sites'), 'should include message');
204      });
205  
206      test('should display 0% progress', () => {
207        displayProgress(0, 10, 'Starting');
208  
209        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
210        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
211        assert.ok(output.includes('0%'), 'should include 0%');
212        assert.ok(output.includes('Starting'), 'should include message');
213      });
214  
215      test('should display 100% progress', () => {
216        displayProgress(10, 10, 'Complete');
217  
218        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
219        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
220        assert.ok(output.includes('100%'), 'should include 100%');
221        assert.ok(output.includes('Complete'), 'should include message');
222      });
223  
224      test('should display progress with custom message', () => {
225        displayProgress(3, 20, 'Scoring sites');
226  
227        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
228        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
229        assert.ok(output.includes('Scoring sites'), 'should include custom message');
230        assert.ok(output.includes('15%'), 'should include percentage');
231      });
232  
233      test('should handle large numbers', () => {
234        displayProgress(500, 1000, 'Bulk processing');
235  
236        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
237        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
238        assert.ok(output.includes('50%'), 'should include percentage');
239        assert.ok(output.includes('Bulk processing'), 'should include message');
240      });
241    });
242  
243    describe('generateStageCompletion', () => {
244      test('should output stage completion summary', () => {
245        generateStageCompletion('scoring', { succeeded: 10, processed: 12, failed: 2 }, 3000);
246  
247        assert.ok(console.log.mock.calls.length > 0, 'console.log should be called');
248        const output = console.log.mock.calls.map(c => String(c.arguments[0])).join('\n');
249        assert.ok(output.includes('scoring'), 'should include stage name');
250      });
251    });
252  });