Skip to main content
Glama

mcp-adr-analysis-server

by tosin2013
release-readiness-detector.test.ts22.4 kB
/** * Test suite for release readiness detector */ import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals'; import { join } from 'path'; // Mock file system operations const mockReadFileSync = jest.fn(); const mockWriteFileSync = jest.fn(); const mockMkdirSync = jest.fn(); const mockRmSync = jest.fn(); const mockExistsSync = jest.fn(); const mockStatSync = jest.fn(); jest.unstable_mockModule('fs', () => ({ readFileSync: mockReadFileSync, writeFileSync: mockWriteFileSync, mkdirSync: mockMkdirSync, rmSync: mockRmSync, existsSync: mockExistsSync, statSync: mockStatSync, })); // Mock child_process for git commands const mockExecSync = jest.fn(); jest.unstable_mockModule('child_process', () => ({ execSync: mockExecSync, })); describe('Release Readiness Detector', () => { let testDir: string; beforeEach(() => { testDir = '/tmp/test-release-readiness'; jest.clearAllMocks(); // Setup default mocks mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(''); mockExecSync.mockReturnValue(''); mockStatSync.mockReturnValue({ size: 1024 } as any); }); afterEach(() => { jest.restoreAllMocks(); }); describe('TODO.md Analysis', () => { it('should parse TODO.md sections correctly', async () => { const todoContent = `# Project TODO ## Core Features - [x] Implement user authentication - [x] Add database integration - [ ] Create REST API endpoints (high priority) - [ ] Add input validation ## Testing - [x] Unit tests for auth - [ ] Integration tests (critical) - [ ] E2E tests ## Documentation - [ ] API documentation - [ ] User guide (low priority) `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, todoPath: join(testDir, 'TODO.md'), }); expect(result.milestones).toHaveLength(3); // Check Core Features milestone const coreFeatures = result.milestones.find(m => m.name === 'Core Features'); expect(coreFeatures).toBeDefined(); expect(coreFeatures!.total).toBe(4); expect(coreFeatures!.completed).toBe(2); expect(coreFeatures!.completionRate).toBe(0.5); expect(coreFeatures!.criticalTodos).toBe(1); // high priority item // Check Testing milestone const testing = result.milestones.find(m => m.name === 'Testing'); expect(testing).toBeDefined(); expect(testing!.total).toBe(3); expect(testing!.completed).toBe(1); expect(testing!.criticalTodos).toBe(1); // critical item }); it('should detect critical TODO items correctly', async () => { const todoContent = `# Project TODO ## Release Blockers - [ ] Fix security vulnerability (critical) - [ ] Resolve performance issue (high priority) - [ ] Update dependencies (blocker) - [ ] Add logging (medium priority) `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, maxCriticalTodos: 0, }); expect(result.isReady).toBe(false); expect(result.milestones[0]?.criticalTodos).toBe(3); // critical, high, blocker const criticalBlockers = result.blockers.filter(b => b.type === 'critical-todos'); expect(criticalBlockers).toHaveLength(1); expect(criticalBlockers[0]?.severity).toBe('error'); }); it('should handle empty TODO.md correctly', async () => { const todoContent = `# Project TODO No TODO items yet. `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); expect(result.milestones).toHaveLength(0); expect(result.isReady).toBe(true); // No TODOs means ready expect(result.score).toBe(1.0); }); it('should handle missing TODO.md file', async () => { mockExistsSync.mockReturnValue(false); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); expect(result.milestones).toHaveLength(0); expect(result.isReady).toBe(true); // No TODO file means ready expect(result.score).toBe(1.0); }); }); describe('Completion Rate Analysis', () => { it('should calculate completion rates correctly', async () => { const todoContent = `# Project TODO ## Feature A - [x] Task 1 - [x] Task 2 - [x] Task 3 - [x] Task 4 - [ ] Task 5 ## Feature B - [x] Task 1 - [ ] Task 2 `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, minCompletionRate: 0.8, }); // Feature A: 4/5 = 0.8 (80%) const featureA = result.milestones.find(m => m.name === 'Feature A'); expect(featureA!.completionRate).toBe(0.8); // Feature B: 1/2 = 0.5 (50%) const featureB = result.milestones.find(m => m.name === 'Feature B'); expect(featureB!.completionRate).toBe(0.5); // Overall should not be ready due to Feature B expect(result.isReady).toBe(false); }); it('should apply different completion thresholds', async () => { const todoContent = `# Project TODO ## Feature - [x] Task 1 - [x] Task 2 - [x] Task 3 - [ ] Task 4 - [ ] Task 5 `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); // Test with 60% threshold const result60 = await analyzeReleaseReadiness({ projectPath: testDir, minCompletionRate: 0.6, }); expect(result60.isReady).toBe(true); // 3/5 = 0.6, meets threshold // Test with 80% threshold const result80 = await analyzeReleaseReadiness({ projectPath: testDir, minCompletionRate: 0.8, }); expect(result80.isReady).toBe(false); // 3/5 = 0.6, doesn't meet threshold }); }); describe('Release Scoring Algorithm', () => { it('should score perfect completion correctly', async () => { const todoContent = `# Project TODO ## Feature A - [x] Task 1 - [x] Task 2 ## Feature B - [x] Task 1 - [x] Task 2 `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); mockExecSync.mockReturnValue(''); // Clean git status const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); expect(result.score).toBe(1.0); expect(result.isReady).toBe(true); expect(result.confidence).toBeGreaterThan(0.8); }); it('should penalize critical todos heavily', async () => { const todoContent = `# Project TODO ## Feature A - [x] Task 1 - [x] Task 2 - [x] Task 3 - [x] Task 4 - [ ] Critical bug fix (critical) `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, maxCriticalTodos: 0, }); expect(result.score).toBeLessThan(0.5); // Should be heavily penalized expect(result.isReady).toBe(false); }); it('should factor in different severity blockers', async () => { const todoContent = `# Project TODO ## Feature A - [x] Task 1 - [x] Task 2 - [x] Task 3 - [x] Task 4 - [ ] Minor improvement `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); // Mock git commands to simulate warnings mockExecSync .mockReturnValueOnce('M file1.js\n?? debug_temp.js') // Uncommitted changes .mockReturnValueOnce('abc123 WIP: temp fix\ndef456 debug: testing'); // Concerning commits const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); // Should have warning-level blockers but still be mostly ready expect(result.score).toBeLessThan(1.0); expect(result.score).toBeGreaterThan(0.6); const warningBlockers = result.blockers.filter(b => b.severity === 'warning'); expect(warningBlockers.length).toBeGreaterThan(0); }); }); describe('Git History Analysis', () => { it('should detect concerning commit patterns', async () => { mockExistsSync.mockReturnValue(false); // No TODO.md mockExecSync .mockReturnValueOnce( 'abc123 debug: temp fix\ndef456 WIP: work in progress\n789abc fixme: quick hack' ) // Recent commits .mockReturnValueOnce(''); // Clean working directory const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); const unstableBlockers = result.blockers.filter(b => b.type === 'unstable-code'); expect(unstableBlockers.length).toBeGreaterThan(0); expect(unstableBlockers[0]?.severity).toBe('warning'); expect(unstableBlockers[0]?.message).toContain('concerning patterns'); }); it('should detect uncommitted changes', async () => { mockExistsSync.mockReturnValue(false); // No TODO.md mockExecSync .mockReturnValueOnce('abc123 feat: add new feature') // Clean commits .mockReturnValueOnce('M file1.js\n?? file2.js'); // Uncommitted changes const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); const unstableBlockers = result.blockers.filter(b => b.type === 'unstable-code'); expect(unstableBlockers.some(b => b.message.includes('Uncommitted changes'))).toBe(true); }); it('should handle git command failures gracefully', async () => { mockExistsSync.mockReturnValue(false); // No TODO.md mockExecSync.mockImplementation(() => { throw new Error('git: command not found'); }); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); // Should still work but with info-level blocker expect(result.blockers.some(b => b.severity === 'info')).toBe(true); expect(result.score).toBeGreaterThan(0); // Should not completely fail }); }); describe('Project State Analysis', () => { it('should detect test configuration', async () => { mockExistsSync.mockImplementation((path: any) => { if (String(path).includes('TODO.md')) return false; if (String(path).includes('package.json')) return true; return false; }); const packageJsonContent = JSON.stringify({ name: 'test-project', scripts: { test: 'jest', build: 'tsc', }, }); mockReadFileSync.mockReturnValue(packageJsonContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); const testBlockers = result.blockers.filter(b => b.type === 'test-failures'); expect(testBlockers.length).toBeGreaterThan(0); expect(testBlockers[0]?.message).toContain('Tests should be run'); }); it('should handle missing package.json', async () => { mockExistsSync.mockReturnValue(false); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); // Should work without package.json expect(result.score).toBeGreaterThan(0); }); }); describe('Recommendation Generation', () => { it('should provide appropriate recommendations for ready projects', async () => { const todoContent = `# Project TODO ## Feature A - [x] Task 1 - [x] Task 2 - [x] Task 3 `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); mockExecSync.mockReturnValue(''); // Clean git const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); expect(result.recommendations).toContain('✅ Project appears ready for release'); expect(result.recommendations).toContain('🚀 Consider creating a release candidate'); }); it('should provide specific recommendations for blockers', async () => { const todoContent = `# Project TODO ## Feature A - [x] Task 1 - [ ] Critical bug fix (critical) - [ ] Performance optimization (high priority) `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, maxCriticalTodos: 0, }); expect(result.recommendations).toContain('❌ Project is not ready for release'); expect(result.recommendations).toContain('🚨 Critical blockers must be resolved:'); expect(result.recommendations.some(r => r.includes('Critical bug fix'))).toBe(true); }); it('should provide milestone-specific recommendations', async () => { const todoContent = `# Project TODO ## Core Features - [x] Task 1 - [x] Task 2 - [ ] Task 3 - [ ] Task 4 - [ ] Task 5 ## Testing - [x] Task 1 - [ ] Task 2 `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, minCompletionRate: 0.8, }); expect(result.recommendations).toContain('📈 Focus on completing these milestones:'); expect(result.recommendations.some(r => r.includes('Core Features'))).toBe(true); expect(result.recommendations.some(r => r.includes('Testing'))).toBe(true); }); }); describe('Release Types', () => { it('should handle different release types appropriately', async () => { const todoContent = `# Project TODO ## Feature A - [x] Task 1 - [x] Task 2 - [ ] Task 3 (minor enhancement) `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); // Patch release should be more lenient const patchResult = await analyzeReleaseReadiness({ projectPath: testDir, releaseType: 'patch', minCompletionRate: 0.6, }); // Major release should be more strict const majorResult = await analyzeReleaseReadiness({ projectPath: testDir, releaseType: 'major', minCompletionRate: 0.9, }); expect(patchResult.isReady).toBe(true); expect(majorResult.isReady).toBe(false); }); }); describe('Integration with MCP Tools', () => { it('should integrate with manage_todo tool', async () => { // Mock the manage_todo tool - currently disabled in implementation // const mockManageTodo = jest.fn().mockResolvedValue({ // status: 'success', // data: { // completionRate: 0.85, // criticalTodos: 1 // } // }); // jest.doMock('../../src/tools/manage-todo-tool.js', () => ({ // manageTodo: mockManageTodo // })); const { integrateWithMcpTools } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await integrateWithMcpTools(testDir); // expect(mockManageTodo).toHaveBeenCalledWith({ // action: 'analyze_progress', // projectPath: testDir // }); expect(result).toBeDefined(); }); it('should fallback gracefully when MCP tools fail', async () => { // Mock the manage_todo tool to fail - currently disabled in implementation // jest.doMock('../../src/tools/manage-todo-tool.js', () => ({ // manageTodo: jest.fn().mockRejectedValue(new Error('Tool not available')) // })); const { integrateWithMcpTools } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await integrateWithMcpTools(testDir); // Should still work with fallback analysis expect(result).toBeDefined(); expect(result.score).toBeGreaterThanOrEqual(0); }); }); describe('Edge Cases and Error Handling', () => { it('should handle malformed TODO.md gracefully', async () => { const malformedContent = `# Project TODO ## Feature A - [x] Task 1 - [INVALID] Task 2 - Task 3 without checkbox - [x Task 4 missing bracket `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(malformedContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); // Should parse what it can and continue expect(result.milestones.length).toBeGreaterThan(0); expect(result.score).toBeGreaterThanOrEqual(0); }); it('should handle very large TODO.md files', async () => { // Create a large TODO.md content let largeContent = '# Project TODO\n\n'; for (let i = 0; i < 100; i++) { largeContent += `## Feature ${i}\n`; for (let j = 0; j < 50; j++) { largeContent += `- [${j % 2 === 0 ? 'x' : ' '}] Task ${j}\n`; } } mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(largeContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); expect(result.milestones).toHaveLength(100); expect(result.score).toBeGreaterThanOrEqual(0); }); it('should handle file system errors gracefully', async () => { mockExistsSync.mockImplementation(() => { throw new Error('File system error'); }); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); // Should have error blocker but not completely fail expect(result.blockers.some(b => b.type === 'unstable-code')).toBe(true); expect(result.score).toBeGreaterThanOrEqual(0); }); }); describe('Summary Generation', () => { it('should generate comprehensive summaries', async () => { const todoContent = `# Project TODO ## Feature A - [x] Task 1 - [x] Task 2 - [ ] Task 3 (critical) ## Feature B - [x] Task 1 - [x] Task 2 `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); expect(result.summary).toContain('Release Readiness:'); expect(result.summary).toContain('**Score**:'); expect(result.summary).toContain('Milestone Status'); expect(result.summary).toContain('Feature A'); expect(result.summary).toContain('Feature B'); }); it('should format percentages correctly', async () => { const todoContent = `# Project TODO ## Feature A - [x] Task 1 - [x] Task 2 - [x] Task 3 - [ ] Task 4 - [ ] Task 5 - [ ] Task 6 `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); // 3/6 = 50.0% expect(result.summary).toContain('50.0%'); }); }); describe('Performance Tests', () => { it('should complete analysis within reasonable time', async () => { const todoContent = `# Project TODO ## Feature A - [x] Task 1 - [x] Task 2 - [ ] Task 3 `; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(todoContent); mockExecSync.mockReturnValue(''); const { analyzeReleaseReadiness } = await import( '../../src/utils/release-readiness-detector.js' ); const startTime = Date.now(); const result = await analyzeReleaseReadiness({ projectPath: testDir, }); const duration = Date.now() - startTime; expect(duration).toBeLessThan(1000); // Should complete within 1 second expect(result).toBeDefined(); }); }); });

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/tosin2013/mcp-adr-analysis-server'

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