Skip to main content
Glama
reportGenerator.test.ts18.9 kB
/** * Tests for weekly report generator */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { generateWeeklyReport, formatWeeklyReport } from '../reportGenerator.js'; import { complexNote, bugFixNote, refactoringNote, testingNote, documentationNote, } from '../../__fixtures__/sessionNotes.js'; import type { SessionNote } from '../../types/session.js'; describe('generateWeeklyReport', () => { describe('week boundary calculation', () => { it('calculates week starting Monday', () => { // 2024-01-17 was a Wednesday const wednesday = new Date('2024-01-17T12:00:00Z'); const notes: SessionNote[] = [ { summary: 'Monday work', timestamp: '2024-01-15T10:00:00Z', // Monday of that week }, { summary: 'Sunday work', timestamp: '2024-01-21T10:00:00Z', // Sunday of that week }, ]; const report = generateWeeklyReport(notes, wednesday); expect(report.sessions).toHaveLength(2); }); it('excludes notes before week start', () => { const wednesday = new Date('2024-01-17T12:00:00Z'); const notes: SessionNote[] = [ { summary: 'Previous week', timestamp: '2024-01-14T10:00:00Z', // Sunday of previous week }, { summary: 'This week', timestamp: '2024-01-15T10:00:00Z', // Monday of this week }, ]; const report = generateWeeklyReport(notes, wednesday); expect(report.sessions).toHaveLength(1); expect(report.sessions[0].summary).toBe('This week'); }); it('excludes notes after week end', () => { const wednesday = new Date('2024-01-17T12:00:00Z'); const notes: SessionNote[] = [ { summary: 'This week', timestamp: '2024-01-20T10:00:00Z', // Saturday }, { summary: 'Next week', timestamp: '2024-01-22T10:00:00Z', // Monday of next week }, ]; const report = generateWeeklyReport(notes, wednesday); expect(report.sessions).toHaveLength(1); expect(report.sessions[0].summary).toBe('This week'); }); it('uses current date when week not specified', () => { const now = new Date(); const notes: SessionNote[] = [ { summary: 'Recent work', timestamp: now.toISOString(), }, ]; const report = generateWeeklyReport(notes); expect(report.sessions).toHaveLength(1); }); it('handles Sunday as start date (should go back to previous Monday)', () => { // 2024-01-21 was a Sunday const sunday = new Date('2024-01-21T12:00:00Z'); const notes: SessionNote[] = [ { summary: 'Monday work', timestamp: '2024-01-15T10:00:00Z', }, ]; const report = generateWeeklyReport(notes, sunday); // Should include Monday 1/15 in the week expect(report.sessions).toHaveLength(1); }); }); describe('statistics calculation', () => { it('counts total sessions', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = [ { summary: 'Work 1', timestamp: '2024-01-15T10:00:00Z' }, { summary: 'Work 2', timestamp: '2024-01-16T10:00:00Z' }, { summary: 'Work 3', timestamp: '2024-01-17T10:00:00Z' }, ]; const report = generateWeeklyReport(notes, monday); expect(report.totalSessions).toBe(3); }); it('counts total files changed', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = [ { summary: 'Work 1', timestamp: '2024-01-15T10:00:00Z', fileChanges: [ { path: 'a.ts', type: 'modified' }, { path: 'b.ts', type: 'created' }, ], }, { summary: 'Work 2', timestamp: '2024-01-16T10:00:00Z', fileChanges: [{ path: 'c.ts', type: 'modified' }], }, ]; const report = generateWeeklyReport(notes, monday); expect(report.totalFiles).toBe(3); }); it('counts total commands executed', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = [ { summary: 'Work 1', timestamp: '2024-01-15T10:00:00Z', commands: [ { command: 'npm install' }, { command: 'npm build' }, ], }, { summary: 'Work 2', timestamp: '2024-01-16T10:00:00Z', commands: [{ command: 'npm test' }], }, ]; const report = generateWeeklyReport(notes, monday); expect(report.totalCommands).toBe(3); }); it('handles notes without file changes', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = [ { summary: 'Work 1', timestamp: '2024-01-15T10:00:00Z' }, ]; const report = generateWeeklyReport(notes, monday); expect(report.totalFiles).toBe(0); }); it('handles notes without commands', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = [ { summary: 'Work 1', timestamp: '2024-01-15T10:00:00Z' }, ]; const report = generateWeeklyReport(notes, monday); expect(report.totalCommands).toBe(0); }); }); describe('project breakdown', () => { it('counts sessions by project', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = [ { summary: 'Work', timestamp: '2024-01-15T10:00:00Z', projectName: 'app-a' }, { summary: 'Work', timestamp: '2024-01-16T10:00:00Z', projectName: 'app-a' }, { summary: 'Work', timestamp: '2024-01-17T10:00:00Z', projectName: 'app-b' }, ]; const report = generateWeeklyReport(notes, monday); expect(report.projectBreakdown['app-a']).toBe(2); expect(report.projectBreakdown['app-b']).toBe(1); }); it('uses "Unknown" for notes without project', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = [ { summary: 'Work', timestamp: '2024-01-15T10:00:00Z' }, ]; const report = generateWeeklyReport(notes, monday); expect(report.projectBreakdown['Unknown']).toBe(1); }); }); describe('pattern breakdown', () => { it('counts sessions by pattern', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = [ { ...bugFixNote, timestamp: '2024-01-15T10:00:00Z' }, { ...complexNote, timestamp: '2024-01-16T10:00:00Z' }, { ...refactoringNote, timestamp: '2024-01-17T10:00:00Z' }, ]; const report = generateWeeklyReport(notes, monday); expect(report.patternBreakdown['bug-fix']).toBe(1); expect(report.patternBreakdown['new-feature']).toBe(1); expect(report.patternBreakdown['refactoring']).toBe(1); }); it('initializes all patterns to 0', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = []; const report = generateWeeklyReport(notes, monday); expect(report.patternBreakdown['new-feature']).toBe(0); expect(report.patternBreakdown['bug-fix']).toBe(0); expect(report.patternBreakdown['refactoring']).toBe(0); expect(report.patternBreakdown['documentation']).toBe(0); expect(report.patternBreakdown['configuration']).toBe(0); expect(report.patternBreakdown['testing']).toBe(0); expect(report.patternBreakdown['mixed']).toBe(0); }); }); describe('complexity breakdown', () => { it('counts sessions by complexity', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = [ { summary: 'Simple', timestamp: '2024-01-15T10:00:00Z', analysis: { pattern: 'bug-fix', patternConfidence: 90, complexity: 'simple', fileCount: 20, keyFiles: [], }, }, { summary: 'Moderate', timestamp: '2024-01-16T10:00:00Z', analysis: { pattern: 'new-feature', patternConfidence: 85, complexity: 'moderate', fileCount: 50, keyFiles: [], }, }, { summary: 'Complex', timestamp: '2024-01-17T10:00:00Z', analysis: { pattern: 'refactoring', patternConfidence: 80, complexity: 'complex', fileCount: 75, keyFiles: [], }, }, ]; const report = generateWeeklyReport(notes, monday); expect(report.complexityBreakdown['simple']).toBe(1); expect(report.complexityBreakdown['moderate']).toBe(1); expect(report.complexityBreakdown['complex']).toBe(1); }); it('initializes all complexity levels to 0', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = []; const report = generateWeeklyReport(notes, monday); expect(report.complexityBreakdown['simple']).toBe(0); expect(report.complexityBreakdown['moderate']).toBe(0); expect(report.complexityBreakdown['complex']).toBe(0); }); }); describe('date range', () => { it('includes start and end dates', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = []; const report = generateWeeklyReport(notes, monday); expect(report.startDate).toBeTruthy(); expect(report.endDate).toBeTruthy(); expect(new Date(report.startDate).getTime()).toBeLessThan(new Date(report.endDate).getTime()); }); it('spans exactly 7 days', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = []; const report = generateWeeklyReport(notes, monday); const start = new Date(report.startDate); const end = new Date(report.endDate); const diffDays = (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24); expect(diffDays).toBe(7); }); }); describe('edge cases', () => { it('handles empty notes array', () => { const monday = new Date('2024-01-15T00:00:00Z'); const report = generateWeeklyReport([], monday); expect(report.totalSessions).toBe(0); expect(report.totalFiles).toBe(0); expect(report.totalCommands).toBe(0); expect(report.sessions).toEqual([]); }); it('handles notes without analysis', () => { const monday = new Date('2024-01-15T00:00:00Z'); const notes: SessionNote[] = [ { summary: 'Work', timestamp: '2024-01-15T10:00:00Z' }, ]; const report = generateWeeklyReport(notes, monday); expect(report.totalSessions).toBe(1); }); }); }); describe('formatWeeklyReport', () => { const mockReport = { startDate: '2024-01-15T00:00:00Z', endDate: '2024-01-22T00:00:00Z', totalSessions: 5, totalFiles: 20, totalCommands: 15, projectBreakdown: { 'my-app': 3, 'other-app': 2, }, patternBreakdown: { 'new-feature': 2, 'bug-fix': 2, 'refactoring': 1, 'documentation': 0, 'configuration': 0, 'testing': 0, 'mixed': 0, }, complexityBreakdown: { 'simple': 2, 'moderate': 2, 'complex': 1, }, sessions: [ { ...complexNote, timestamp: '2024-01-20T14:45:00Z' }, { ...bugFixNote, timestamp: '2024-01-18T09:15:00Z' }, ], }; describe('structure', () => { it('includes title', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toContain('# Weekly Coding Report'); }); it('includes date range', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toContain('##'); expect(markdown).toMatch(/\d+\/\d+\/\d+/); // Date format }); it('includes summary section', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toContain('## Summary'); expect(markdown).toContain('**Total Sessions**: 5'); expect(markdown).toContain('**Files Changed**: 20'); expect(markdown).toContain('**Commands Executed**: 15'); }); it('includes projects section', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toContain('## Projects'); expect(markdown).toContain('**my-app**: 3 sessions'); expect(markdown).toContain('**other-app**: 2 sessions'); }); it('includes work patterns section', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toContain('## Work Patterns'); expect(markdown).toMatch(/New Feature.*2 sessions/); expect(markdown).toMatch(/Bug Fix.*2 sessions/); }); it('includes complexity distribution', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toContain('## Complexity Distribution'); expect(markdown).toMatch(/Simple.*2 sessions/); expect(markdown).toMatch(/Moderate.*2 sessions/); expect(markdown).toMatch(/Complex.*1 sessions/); }); it('includes sessions list', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toContain('## Sessions This Week'); expect(markdown).toContain('my-app'); }); }); describe('project section', () => { it('sorts projects by session count', () => { const markdown = formatWeeklyReport(mockReport); const myAppIndex = markdown.indexOf('**my-app**'); const otherAppIndex = markdown.indexOf('**other-app**'); expect(myAppIndex).toBeLessThan(otherAppIndex); }); it('omits projects section when empty', () => { const emptyReport = { ...mockReport, projectBreakdown: {}, }; const markdown = formatWeeklyReport(emptyReport); expect(markdown).not.toContain('## Projects'); }); }); describe('work patterns', () => { it('includes emojis for patterns', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toMatch(/🆕.*New Feature/); expect(markdown).toMatch(/🐛.*Bug Fix/); expect(markdown).toMatch(/♻️.*Refactoring/); }); it('omits patterns with zero sessions', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).not.toContain('Documentation: 0'); expect(markdown).not.toContain('Configuration: 0'); }); it('sorts patterns by count', () => { const report = { ...mockReport, patternBreakdown: { 'new-feature': 5, 'bug-fix': 1, 'refactoring': 0, 'documentation': 0, 'configuration': 0, 'testing': 0, 'mixed': 0, }, }; const markdown = formatWeeklyReport(report); const newFeatureIndex = markdown.indexOf('New Feature'); const bugFixIndex = markdown.indexOf('Bug Fix'); expect(newFeatureIndex).toBeLessThan(bugFixIndex); }); }); describe('complexity distribution', () => { it('includes emojis for complexity levels', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toMatch(/🟢.*Simple/); expect(markdown).toMatch(/🟡.*Moderate/); expect(markdown).toMatch(/🔴.*Complex/); }); it('capitalizes complexity levels', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toContain('Simple'); expect(markdown).toContain('Moderate'); expect(markdown).toContain('Complex'); }); it('omits levels with zero sessions', () => { const report = { ...mockReport, complexityBreakdown: { 'simple': 3, 'moderate': 0, 'complex': 0, }, }; const markdown = formatWeeklyReport(report); expect(markdown).toContain('Simple'); expect(markdown).not.toContain('Moderate: 0'); }); }); describe('sessions list', () => { it('numbers sessions sequentially', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toMatch(/1\./); expect(markdown).toMatch(/2\./); }); it('includes project name', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toContain('**my-app**'); }); it('includes date and time', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toMatch(/Jan \d+, \d{2}:\d{2}/); }); it('includes summary indented', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toMatch(/\n [A-Z]/); // Indented text }); it('uses topic as session title when available', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toContain('OAuth2 Authentication'); }); it('falls back to summary when no topic', () => { const report = { ...mockReport, sessions: [ { summary: 'Work on feature', timestamp: '2024-01-20T10:00:00Z', projectName: 'my-app', }, ], }; const markdown = formatWeeklyReport(report); expect(markdown).toMatch(/Implemented new authentication feature|Work on feature/); }); it('truncates long summaries as title', () => { const report = { ...mockReport, sessions: [ { summary: 'A very long summary that should be truncated '.repeat(5), timestamp: '2024-01-20T10:00:00Z', projectName: 'my-app', }, ], }; const markdown = formatWeeklyReport(report); expect(markdown).toContain('...'); }); it('uses "General" for notes without project', () => { const report = { ...mockReport, sessions: [ { summary: 'Work', timestamp: '2024-01-20T10:00:00Z', }, ], }; const markdown = formatWeeklyReport(report); expect(markdown).toContain('**General**'); }); }); describe('markdown validity', () => { it('produces valid markdown', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toBeTruthy(); expect(markdown.length).toBeGreaterThan(100); }); it('has proper heading hierarchy', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toMatch(/^# /m); expect(markdown).toMatch(/^## /m); }); it('has proper list formatting', () => { const markdown = formatWeeklyReport(mockReport); expect(markdown).toMatch(/^- /m); }); }); });

Latest Blog Posts

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/VoCoufi/second-brain-mcp'

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