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 });