Skip to main content
Glama
adrs-discovered-resource.test.ts12.1 kB
/** * Unit tests for adrs-discovered-resource.ts * Tests ADR discovery, filtering, and caching */ import { URLSearchParams } from 'url'; import { describe, it, expect, beforeAll, _beforeEach, _afterEach, _jest } from 'vitest'; // Mock conditional-request (needed for generateETag) vi.mock('../../src/utils/conditional-request.js', () => ({ generateStrongETag: vi.fn((data: any) => `etag-${JSON.stringify(data).length}`), })); // Mock ResourceCache const mockCacheGet = vi.fn(); const mockCacheSet = vi.fn(); vi.mock('../../src/resources/resource-cache.js', () => ({ resourceCache: { get: mockCacheGet, set: mockCacheSet, }, generateETag: (data: any) => `etag-${JSON.stringify(data).length}`, ResourceCache: vi.fn(), })); // Mock adr-discovery const mockDiscoverAdrsInDirectory = vi.fn(); vi.mock('../../src/utils/adr-discovery.js', () => ({ discoverAdrsInDirectory: mockDiscoverAdrsInDirectory, })); describe('ADRs Discovered Resource', () => { let generateAdrsDiscoveredResource: any; beforeAll(async () => { const module = await import('../../src/resources/adrs-discovered-resource.js'); generateAdrsDiscoveredResource = module.generateAdrsDiscoveredResource; }); beforeEach(() => { vi.clearAllMocks(); // Default: no cache hit mockCacheGet.mockResolvedValue(null); // Mock ADR discovery result mockDiscoverAdrsInDirectory.mockResolvedValue({ directory: '/project/docs/adrs', totalAdrs: 3, adrs: [ { filename: '001-use-postgresql.md', title: 'Use PostgreSQL for primary database', status: 'accepted', date: '2025-01-15', path: '/project/docs/adrs/001-use-postgresql.md', metadata: { number: '001', category: 'database', tags: ['postgresql', 'database'], }, }, { filename: '002-api-design.md', title: 'RESTful API Design', status: 'accepted', date: '2025-01-20', path: '/project/docs/adrs/002-api-design.md', metadata: { number: '002', category: 'api', tags: ['rest', 'api'], }, }, { filename: '003-caching-strategy.md', title: 'Redis Caching Strategy', status: 'proposed', date: '2025-02-01', path: '/project/docs/adrs/003-caching-strategy.md', metadata: { number: '003', category: 'infrastructure', tags: ['redis', 'caching'], }, }, ], summary: { byStatus: { accepted: 2, proposed: 1, }, byCategory: { database: 1, api: 1, infrastructure: 1, }, }, recommendations: ['Consider reviewing proposed ADRs', '1 ADR needs decision'], }); }); afterEach(() => { vi.restoreAllMocks(); }); describe('Basic Resource Generation', () => { it('should generate ADR discovery results with default parameters', async () => { const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); expect(result).toBeDefined(); expect(result.data).toBeDefined(); expect(result.data.directory).toBe('/project/docs/adrs'); expect(result.data.totalAdrs).toBe(3); expect(result.data.adrs).toHaveLength(3); expect(result.contentType).toBe('application/json'); }); it('should include timestamp in response', async () => { const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); expect(result.data.timestamp).toBeDefined(); expect(new Date(result.data.timestamp).getTime()).toBeGreaterThan(0); }); it('should call discoverAdrsInDirectory with default options', async () => { await generateAdrsDiscoveredResource({}, new URLSearchParams()); expect(mockDiscoverAdrsInDirectory).toHaveBeenCalledWith( expect.stringContaining('docs/adrs'), expect.any(String), { includeContent: false, includeTimeline: false, } ); }); }); describe('Query Parameter Handling', () => { it('should use custom projectPath when provided', async () => { const customPath = '/custom/project'; await generateAdrsDiscoveredResource({}, new URLSearchParams(`projectPath=${customPath}`)); expect(mockDiscoverAdrsInDirectory).toHaveBeenCalledWith( expect.stringContaining(customPath), customPath, expect.any(Object) ); }); it('should use custom adrDirectory when provided', async () => { await generateAdrsDiscoveredResource({}, new URLSearchParams('adrDirectory=decisions')); expect(mockDiscoverAdrsInDirectory).toHaveBeenCalledWith( expect.stringContaining('decisions'), expect.any(String), expect.any(Object) ); }); it('should include content when requested', async () => { await generateAdrsDiscoveredResource({}, new URLSearchParams('includeContent=true')); expect(mockDiscoverAdrsInDirectory).toHaveBeenCalledWith( expect.any(String), expect.any(String), { includeContent: true, includeTimeline: false, } ); }); it('should include timeline when requested', async () => { await generateAdrsDiscoveredResource({}, new URLSearchParams('includeTimeline=true')); expect(mockDiscoverAdrsInDirectory).toHaveBeenCalledWith( expect.any(String), expect.any(String), { includeContent: false, includeTimeline: true, } ); }); }); describe('ADR Data Structure', () => { it('should include basic ADR metadata', async () => { const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); const adr = result.data.adrs[0]; expect(adr.filename).toBe('001-use-postgresql.md'); expect(adr.title).toBe('Use PostgreSQL for primary database'); expect(adr.status).toBe('accepted'); expect(adr.date).toBe('2025-01-15'); expect(adr.path).toBe('/project/docs/adrs/001-use-postgresql.md'); }); it('should include metadata fields', async () => { const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); const adr = result.data.adrs[0]; expect(adr.number).toBe('001'); expect(adr.category).toBe('database'); expect(adr.tags).toEqual(['postgresql', 'database']); }); it('should exclude full content by default', async () => { const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); const adr = result.data.adrs[0]; expect(adr).not.toHaveProperty('content'); }); it('should include all discovered ADRs', async () => { const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); expect(result.data.adrs).toHaveLength(3); expect(result.data.adrs[0].filename).toBe('001-use-postgresql.md'); expect(result.data.adrs[1].filename).toBe('002-api-design.md'); expect(result.data.adrs[2].filename).toBe('003-caching-strategy.md'); }); }); describe('Summary Statistics', () => { it('should include summary with status breakdown', async () => { const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); expect(result.data.summary.byStatus).toEqual({ accepted: 2, proposed: 1, }); }); it('should include summary with category breakdown', async () => { const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); expect(result.data.summary.byCategory).toEqual({ database: 1, api: 1, infrastructure: 1, }); }); it('should include recommendations', async () => { const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); expect(result.data.recommendations).toHaveLength(2); expect(result.data.recommendations[0]).toBe('Consider reviewing proposed ADRs'); }); }); describe('Empty Results', () => { it('should handle no ADRs found', async () => { mockDiscoverAdrsInDirectory.mockResolvedValue({ directory: '/project/docs/adrs', totalAdrs: 0, adrs: [], summary: { byStatus: {}, byCategory: {}, }, recommendations: ['No ADRs found. Consider creating your first ADR.'], }); const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); expect(result.data.totalAdrs).toBe(0); expect(result.data.adrs).toHaveLength(0); }); }); describe('Caching', () => { it('should return cached result when available', async () => { const cachedResult = { data: { directory: '/cached/adrs', totalAdrs: 5, adrs: [], summary: { byStatus: {}, byCategory: {} }, recommendations: [], timestamp: new Date().toISOString(), }, contentType: 'application/json', cacheKey: 'adrs-discovered', }; mockCacheGet.mockResolvedValue(cachedResult); const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); expect(result).toBe(cachedResult); expect(mockDiscoverAdrsInDirectory).not.toHaveBeenCalled(); }); it('should cache result after generation', async () => { await generateAdrsDiscoveredResource({}, new URLSearchParams()); expect(mockCacheSet).toHaveBeenCalledWith( 'adrs-discovered', expect.objectContaining({ data: expect.any(Object), contentType: 'application/json', cacheKey: 'adrs-discovered', ttl: 120, }), 120 ); }); it('should use project-specific cache key for custom paths', async () => { await generateAdrsDiscoveredResource({}, new URLSearchParams('projectPath=/custom')); expect(mockCacheSet).toHaveBeenCalledWith('adrs-discovered:/custom', expect.anything(), 120); }); it('should use 2-minute TTL for occasionally changing data', async () => { const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); expect(result.ttl).toBe(120); }); }); describe('Error Handling', () => { it('should throw RESOURCE_NOT_FOUND when directory not found', async () => { mockDiscoverAdrsInDirectory.mockRejectedValue(new Error('Directory not found')); await expect(generateAdrsDiscoveredResource({}, new URLSearchParams())).rejects.toThrow( 'ADR directory not found' ); }); it('should throw RESOURCE_GENERATION_ERROR on discovery failure', async () => { mockDiscoverAdrsInDirectory.mockRejectedValue(new Error('Parse error')); await expect(generateAdrsDiscoveredResource({}, new URLSearchParams())).rejects.toThrow( 'Failed to discover ADRs' ); }); }); describe('Response Structure', () => { it('should return properly structured ResourceGenerationResult', async () => { const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); expect(result).toMatchObject({ data: expect.objectContaining({ directory: expect.any(String), totalAdrs: expect.any(Number), adrs: expect.any(Array), summary: expect.any(Object), recommendations: expect.any(Array), timestamp: expect.any(String), }), contentType: 'application/json', lastModified: expect.any(String), cacheKey: expect.any(String), ttl: 120, etag: expect.any(String), }); }); it('should generate valid etag', async () => { const result = await generateAdrsDiscoveredResource({}, new URLSearchParams()); expect(result.etag).toBeDefined(); expect(typeof result.etag).toBe('string'); expect(result.etag.length).toBeGreaterThan(0); }); }); });

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

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