Skip to main content
Glama
enhanced-reporting-client.test.tsโ€ข13.6 kB
import { describe, it } from 'node:test'; import { strict as assert } from 'node:assert'; /** * Unit tests for enhanced reporting client functionality * * Tests the following functionality: * - ZebrunnerReportingClient milestone methods * - ZebrunnerReportingClient project discovery methods * - Enhanced authentication and error handling */ describe('Enhanced Reporting Client Unit Tests', () => { describe('Milestone Methods', () => { it('should validate getMilestones method parameters', () => { const validOptions = { page: 1, pageSize: 10, completed: false as boolean | 'all' }; assert.ok(typeof validOptions.page === 'number', 'page should be number'); assert.ok(validOptions.page >= 1, 'page should be 1-based'); assert.ok(typeof validOptions.pageSize === 'number', 'pageSize should be number'); assert.ok(validOptions.pageSize > 0 && validOptions.pageSize <= 100, 'pageSize should be reasonable'); assert.ok( typeof validOptions.completed === 'boolean' || validOptions.completed === 'all', 'completed should be boolean or "all"' ); }); it('should validate milestone API URL construction', () => { const projectId = 7; const page = 2; const pageSize = 20; const completed = true; const expectedUrl = `/api/reporting/v1/milestones?projectId=${projectId}&page=${page}&pageSize=${pageSize}&completed=${completed}`; assert.ok(expectedUrl.includes(`projectId=${projectId}`), 'should include projectId'); assert.ok(expectedUrl.includes(`page=${page}`), 'should include page'); assert.ok(expectedUrl.includes(`pageSize=${pageSize}`), 'should include pageSize'); assert.ok(expectedUrl.includes(`completed=${completed}`), 'should include completed filter'); }); it('should handle "all" completed parameter correctly', () => { const projectId = 7; const page = 1; const pageSize = 10; const completed = 'all'; // When completed is 'all', it should not be added to URL const baseUrl = `/api/reporting/v1/milestones?projectId=${projectId}&page=${page}&pageSize=${pageSize}`; // Simulate the logic: only add completed parameter if not 'all' const finalUrl = completed !== 'all' ? `${baseUrl}&completed=${completed}` : baseUrl; assert.ok(!finalUrl.includes('completed='), 'should not include completed parameter when "all"'); assert.ok(finalUrl.includes(`projectId=${projectId}`), 'should still include other parameters'); }); it('should validate milestone response parsing', () => { const mockApiResponse = { items: [ { id: 556, name: '25.39.0', completed: false, description: null, projectId: 7, dueDate: '2025-09-30T22:00:00Z', startDate: '2025-09-23T22:00:00Z' } ], _meta: { total: 1, totalPages: 1 } }; // Validate that the response matches expected schema assert.ok(Array.isArray(mockApiResponse.items), 'should have items array'); assert.ok(mockApiResponse._meta, 'should have _meta object'); const milestone = mockApiResponse.items[0]; assert.ok(typeof milestone.id === 'number', 'milestone id should be number'); assert.ok(typeof milestone.name === 'string', 'milestone name should be string'); assert.ok(typeof milestone.completed === 'boolean', 'completed should be boolean'); assert.ok(milestone.description === null || typeof milestone.description === 'string', 'description should be string or null'); assert.ok(milestone.dueDate === null || typeof milestone.dueDate === 'string', 'dueDate should be string or null'); assert.ok(milestone.startDate === null || typeof milestone.startDate === 'string', 'startDate should be string or null'); }); }); describe('Project Discovery Methods', () => { it('should validate getAvailableProjects method parameters', () => { const validOptions = { starred: true, publiclyAccessible: false, extraFields: ['starred', 'publiclyAccessible'] }; assert.ok(typeof validOptions.starred === 'boolean' || validOptions.starred === undefined, 'starred should be boolean or undefined'); assert.ok(typeof validOptions.publiclyAccessible === 'boolean' || validOptions.publiclyAccessible === undefined, 'publiclyAccessible should be boolean or undefined'); assert.ok(Array.isArray(validOptions.extraFields), 'extraFields should be array'); validOptions.extraFields.forEach(field => { assert.ok(typeof field === 'string', 'extraFields items should be strings'); }); }); it('should validate project API URL construction', () => { const extraFields = ['starred', 'publiclyAccessible']; const expectedUrl = `/api/projects/v1/projects?extraFields=${extraFields.join(',')}`; assert.ok(expectedUrl.includes('/api/projects/v1/projects'), 'should use correct base path'); assert.ok(expectedUrl.includes('extraFields='), 'should include extraFields parameter'); assert.ok(expectedUrl.includes('starred'), 'should include starred in extraFields'); assert.ok(expectedUrl.includes('publiclyAccessible'), 'should include publiclyAccessible in extraFields'); }); it('should validate project response parsing', () => { const mockApiResponse = { items: [ { id: 7, name: 'Android', key: 'MCP', logoUrl: '/files/18b4939f-37e9-0576-8c73-478c7095192e', createdAt: '2023-09-11T17:43:13.337691Z', leadId: 26, starred: true, publiclyAccessible: true, deleted: false } ] }; assert.ok(Array.isArray(mockApiResponse.items), 'should have items array'); const project = mockApiResponse.items[0]; assert.ok(typeof project.id === 'number', 'project id should be number'); assert.ok(typeof project.name === 'string', 'project name should be string'); assert.ok(typeof project.key === 'string', 'project key should be string'); assert.ok(typeof project.starred === 'boolean', 'starred should be boolean'); assert.ok(typeof project.publiclyAccessible === 'boolean', 'publiclyAccessible should be boolean'); assert.ok(typeof project.deleted === 'boolean', 'deleted should be boolean'); assert.ok(project.leadId === null || typeof project.leadId === 'number', 'leadId should be number or null'); }); it('should validate client-side filtering logic', () => { const mockProjects = [ { id: 1, name: 'Project 1', key: 'PROJ1', starred: true, publiclyAccessible: true, deleted: false }, { id: 2, name: 'Project 2', key: 'PROJ2', starred: false, publiclyAccessible: true, deleted: false }, { id: 3, name: 'Project 3', key: 'PROJ3', starred: true, publiclyAccessible: false, deleted: false }, { id: 4, name: 'Deleted Project', key: 'DEL', starred: false, publiclyAccessible: true, deleted: true } ]; // Simulate client-side filtering logic const applyFilters = (projects: any[], starred?: boolean, publiclyAccessible?: boolean) => { let filtered = projects.filter(p => !p.deleted); // Always exclude deleted if (starred !== undefined) { filtered = filtered.filter(p => p.starred === starred); } if (publiclyAccessible !== undefined) { filtered = filtered.filter(p => p.publiclyAccessible === publiclyAccessible); } return filtered; }; // Test no filters const noFilters = applyFilters(mockProjects); assert.equal(noFilters.length, 3, 'should exclude deleted projects by default'); // Test starred filter const starredOnly = applyFilters(mockProjects, true); assert.equal(starredOnly.length, 2, 'should filter by starred status'); // Test public filter const publicOnly = applyFilters(mockProjects, undefined, true); assert.equal(publicOnly.length, 2, 'should filter by public accessibility'); // Test combined filters const starredAndPublic = applyFilters(mockProjects, true, true); assert.equal(starredAndPublic.length, 1, 'should apply multiple filters'); }); it('should validate getProjectsLimit method', () => { const mockLimitResponse = { data: { limit: 50, currentTotal: 12 } }; assert.ok(mockLimitResponse.data, 'should have data object'); assert.ok(typeof mockLimitResponse.data.limit === 'number', 'limit should be number'); assert.ok(typeof mockLimitResponse.data.currentTotal === 'number', 'currentTotal should be number'); assert.ok(mockLimitResponse.data.limit > 0, 'limit should be positive'); assert.ok(mockLimitResponse.data.currentTotal >= 0, 'currentTotal should be non-negative'); }); }); describe('Authentication and Error Handling', () => { it('should validate Bearer token authentication', () => { const mockConfig = { baseUrl: 'https://test.zebrunner.com', accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', timeout: 30000, debug: false }; assert.ok(mockConfig.baseUrl, 'should have baseUrl'); assert.ok(mockConfig.accessToken, 'should have accessToken'); assert.ok(mockConfig.accessToken.length > 20, 'accessToken should be substantial length'); assert.ok(typeof mockConfig.timeout === 'number', 'timeout should be number'); assert.ok(typeof mockConfig.debug === 'boolean', 'debug should be boolean'); }); it('should validate error response handling', () => { const mockErrorResponses = [ { status: 401, message: 'Authentication failed' }, { status: 404, message: 'Project not found' }, { status: 403, message: 'Insufficient permissions' }, { status: 500, message: 'Internal server error' } ]; mockErrorResponses.forEach(errorResponse => { assert.ok(typeof errorResponse.status === 'number', 'error status should be number'); assert.ok(typeof errorResponse.message === 'string', 'error message should be string'); assert.ok(errorResponse.status >= 400, 'should be HTTP error status'); }); }); it('should validate error message enhancement', () => { const enhanceError = (status: number, message: string) => { switch (status) { case 401: return `Authentication failed: ${message}`; case 404: return `Resource not found: ${message}`; case 403: return `Access denied: ${message}`; default: return `Request failed: ${message}`; } }; assert.equal( enhanceError(401, 'Invalid token'), 'Authentication failed: Invalid token', 'should enhance 401 errors' ); assert.equal( enhanceError(404, 'Project not found'), 'Resource not found: Project not found', 'should enhance 404 errors' ); assert.equal( enhanceError(500, 'Server error'), 'Request failed: Server error', 'should handle other errors' ); }); }); describe('Response Validation and Parsing', () => { it('should validate response structure handling', () => { const mockResponses = [ { data: { items: [] } }, // Wrapped in data { items: [] }, // Direct response { response: { data: { items: [] } } } // Nested response ]; const extractData = (response: any) => { return response.data || response; }; mockResponses.forEach(response => { const extractedData = extractData(response); assert.ok(extractedData, 'should extract data from various response structures'); }); }); it('should validate schema parsing error handling', () => { const mockInvalidResponses = [ null, undefined, { items: 'not an array' }, { _meta: 'not an object' } ]; const validateResponse = (data: any) => { if (!data) return false; if (!Array.isArray(data.items)) return false; return true; }; mockInvalidResponses.forEach(response => { const isValid = validateResponse(response); assert.equal(isValid, false, 'should reject invalid response structures'); }); }); it('should validate successful response parsing', () => { const mockValidResponse = { items: [ { id: 1, name: 'Test', key: 'TEST' } ], _meta: { total: 1, totalPages: 1 } }; const validateResponse = (data: any) => { if (!data) return false; if (!Array.isArray(data.items)) return false; if (data._meta && typeof data._meta.total !== 'number') return false; return true; }; const isValid = validateResponse(mockValidResponse); assert.equal(isValid, true, 'should accept valid response structures'); }); }); });

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/maksimsarychau/mcp-zebrunner'

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