UNPKG

mediumroast_api

Version:

Mediumroast for Git(Hub) SDK covering all categories of function.

565 lines (472 loc) 19 kB
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { Studies, Companies, Interactions, Users, Storage, Actions } from './gitHubServer.js'; // Common test variables const token = 'test-token'; const org = 'test-org'; const processName = 'test-process'; // Mock the cache manager vi.mock('./gitHubServer/cache.js', () => { const mockGetOrFetch = vi.fn().mockImplementation((key, fetchFn) => { return fetchFn(); // Execute the fetch function directly in tests }); const mockInvalidate = vi.fn(); return { CacheManager: vi.fn().mockImplementation(() => ({ getOrFetch: mockGetOrFetch, invalidate: mockInvalidate, _cache: new Map(), _dependencyMap: new Map() })) }; }); // Mock the logger vi.mock('./gitHubServer/logger.js', () => { const mockEnd = vi.fn(); const mockTrackOperation = vi.fn().mockReturnValue({ end: mockEnd }); const mockTrackTransaction = vi.fn().mockReturnValue({ end: mockEnd }); return { logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), trackOperation: mockTrackOperation, trackTransaction: mockTrackTransaction } }; }); // Mock the schema validator vi.mock('./gitHubServer/schema.js', () => { return { validator: { validate: vi.fn().mockReturnValue({ valid: true, errors: [] }), schemas: { Studies: {}, Companies: {}, Interactions: {}, Users: {} } } }; }); // We need to remove the global Interactions mock as it conflicts with our tests // Instead we'll mock just what we need for the Company profile test later // Mock the GitHub Functions module vi.mock('./github.js', () => { return { default: vi.fn().mockImplementation(() => ({ token: 'test-token', orgName: 'test-org', readObjects: vi.fn().mockResolvedValue([true, 'Success', {}, 200]), getAllUsers: vi.fn().mockResolvedValue([true, 'Success', [], 200]), getUser: vi.fn().mockResolvedValue([true, 'Success', {}, 200]), getRepository: vi.fn().mockResolvedValue([true, 'Success', { size: 100 }, 200]), getStorageBillings: vi.fn().mockResolvedValue([true, 'Success', {}, 200]), getActionsBillings: vi.fn().mockResolvedValue([true, 'Success', {}, 200]), getWorkflowRuns: vi.fn().mockResolvedValue([true, 'Success', [], 200]), invalidateCache: vi.fn(), catchContainer: vi.fn().mockResolvedValue([true, 'Success', {}, 200]), getSha: vi.fn().mockResolvedValue([true, 'Success', 'sha123', 200]), writeObject: vi.fn().mockResolvedValue([true, 'Success', {}, 200]), releaseContainer: vi.fn().mockResolvedValue([true, 'Success', {}, 200]), updateObject: vi.fn().mockResolvedValue([true, 'Success', {}, 200]), deleteObject: vi.fn().mockResolvedValue([true, 'Success', {}, 200]), writeBlob: vi.fn().mockResolvedValue([true, 'Success', {}, 200]), readBlob: vi.fn().mockResolvedValue([true, 'Success', { decodedContent: 'test content' }, 200]), checkForLock: vi.fn().mockResolvedValue([true, 'No Lock', {}, 200]) })) }; }); describe('GitHubServer', () => { // Mock response data const mockStudies = { mrJson: [ { name: 'Study 1', description: 'Test study 1', status: 'active' }, { name: 'Study 2', description: 'Test study 2', status: 'inactive' } ] }; const mockCompanies = { mrJson: [ { name: 'Company 1', description: 'Test company 1', status: 'active', linked_interactions: { 'Interaction 1': { linked_date: '2023-01-01T00:00:00Z' } } }, { name: 'Company 2', description: 'Test company 2', status: 'inactive', linked_interactions: {} } ] }; const mockInteractions = { mrJson: [ { name: 'Interaction 1', description: 'Test interaction 1', file_hash: 'hash123', content_type: 'pdf' }, { name: 'Interaction 2', description: 'Test interaction 2', file_hash: 'hash456', content_type: 'doc' } ] }; const mockUsers = [ { login: 'user1', name: 'User One', email: 'user1@example.com', role: 'admin' }, { login: 'user2', name: 'User Two', email: 'user2@example.com', role: 'member' } ]; const mockCurrentUser = { login: 'current-user', name: 'Current User', email: 'current@example.com' }; const mockStorageBilling = { days_left_in_billing_cycle: 20, estimated_paid_storage_for_month: 5, estimated_storage_for_month: 10 }; const mockActionsBilling = { total_minutes_used: 300, total_paid_minutes_used: 100, included_minutes: 2000 }; const mockWorkflows = [ { id: 1, name: 'Workflow 1', status: 'completed' }, { id: 2, name: 'Workflow 2', status: 'in_progress' } ]; // Test suite for base class (using Studies as the concrete implementation) describe('Base Operations', () => { let studies; beforeEach(() => { // Reset mock implementation vi.clearAllMocks(); // Create instance studies = new Studies(token, org, processName); // Setup successful mock responses studies.serverCtl.readObjects.mockResolvedValue([true, 'Success', mockStudies, 200]); studies.serverCtl.getSha.mockResolvedValue([true, 'Success', 'abc123hash', 200]); }); describe('getAll()', () => { it('should return all studies when successful', async () => { const result = await studies.getAll(); expect(result[0]).toBe(true); expect(result[2]).toEqual(mockStudies); expect(studies.serverCtl.readObjects).toHaveBeenCalledWith('Studies'); }); it('should handle API errors properly', async () => { // Updated mock to match the format expected in the refactored code studies.serverCtl.readObjects.mockResolvedValue([ false, { status_msg: 'API Error', status_code: 500 }, null ]); const result = await studies.getAll(); expect(result[0]).toBe(false); expect(result[1].status_code).toBe(500); // Now correctly formatted expect(result[1].status_msg).toContain('API Error'); }); }); describe('findByName()', () => { it('should find studies by name', async () => { const result = await studies.findByName('Study 1'); expect(result[0]).toBe(true); expect(result[2]).toHaveLength(1); expect(result[2][0].name).toBe('Study 1'); }); it('should return error when no studies match the name', async () => { const result = await studies.findByName('Nonexistent Study'); expect(result[0]).toBe(false); expect(result[1].status_code).toBe(404); }); }); describe('findByX()', () => { it('should find studies by arbitrary attribute', async () => { const result = await studies.findByX('status', 'active'); expect(result[0]).toBe(true); expect(result[2]).toHaveLength(1); expect(result[2][0].status).toBe('active'); }); it('should return error when attribute is invalid', async () => { const result = await studies.findByX('', 'value'); expect(result[0]).toBe(false); expect(result[1].status_msg).toContain('Invalid parameter'); }); }); describe('search()', () => { it('should search with filters', async () => { const result = await studies.search({ status: 'active' }); expect(result[0]).toBe(true); expect(result[2]).toHaveLength(1); expect(result[2][0].status).toBe('active'); }); it('should apply name filter case-insensitively', async () => { const result = await studies.search({ name: 'study' }); expect(result[0]).toBe(true); expect(result[2]).toHaveLength(2); // Both studies contain "study" in their name }); it('should apply sorting options', async () => { // First update mock data to have sortable fields studies.serverCtl.readObjects.mockResolvedValue([ true, 'Success', { mrJson: [ { name: 'Study Z', order: 3 }, { name: 'Study A', order: 1 }, { name: 'Study M', order: 2 } ] }, 200 ]); // Test sorting by name ascending (default) const resultNameAsc = await studies.search({}, { sort: 'name' }); expect(resultNameAsc[2][0].name).toBe('Study A'); expect(resultNameAsc[2][2].name).toBe('Study Z'); // Test sorting by name descending const resultNameDesc = await studies.search({}, { sort: 'name', descending: true }); expect(resultNameDesc[2][0].name).toBe('Study Z'); expect(resultNameDesc[2][2].name).toBe('Study A'); // Test sorting by numeric field const resultOrderAsc = await studies.search({}, { sort: 'order' }); expect(resultOrderAsc[2][0].order).toBe(1); expect(resultOrderAsc[2][2].order).toBe(3); }); it('should apply limit option', async () => { const result = await studies.search({}, { limit: 1 }); expect(result[2]).toHaveLength(1); }); }); }); // Test suite for Users class describe('Users Operations', () => { let users; beforeEach(() => { vi.clearAllMocks(); users = new Users(token, org, processName); // Setup successful mock responses users.serverCtl.getAllUsers.mockResolvedValue([true, 'Success', mockUsers, 200]); users.serverCtl.getUser.mockResolvedValue([true, 'Success', mockCurrentUser, 200]); }); describe('getAll()', () => { it('should return all users when successful', async () => { const result = await users.getAll(); expect(result[0]).toBe(true); expect(result[2]).toEqual(mockUsers); expect(users.serverCtl.getAllUsers).toHaveBeenCalled(); }); }); describe('getAuthenticatedUser()', () => { it('should return current user info', async () => { const result = await users.getAuthenticatedUser(); expect(result[0]).toBe(true); expect(result[2]).toEqual(mockCurrentUser); expect(users.serverCtl.getUser).toHaveBeenCalled(); }); }); describe('findByLogin()', () => { it('should find users by login', async () => { const result = await users.findByLogin('user1'); expect(result[0]).toBe(true); expect(result[2].login).toBe('user1'); }); }); describe('findByRole()', () => { it('should find users by role', async () => { const result = await users.findByRole('admin'); expect(result[0]).toBe(true); expect(result[2]).toContainEqual(expect.objectContaining({ role: 'admin' })); }); }); }); // Test suite for Storage class describe('Storage Operations', () => { let storage; beforeEach(() => { vi.clearAllMocks(); storage = new Storage(token, org, processName); // Setup successful mock responses storage.serverCtl.getRepository.mockResolvedValue([true, 'Success', { size: 100 }, 200]); storage.serverCtl.getStorageBillings.mockResolvedValue([true, 'Success', mockStorageBilling, 200]); }); describe('getRepoSize()', () => { it('should return repo size info', async () => { const result = await storage.getRepoSize(); expect(result[0]).toBe(true); expect(result[2]).toBe(100); expect(storage.serverCtl.getRepository).toHaveBeenCalled(); }); }); describe('getStorageBilling()', () => { it('should return storage billing info', async () => { const result = await storage.getStorageBilling(); expect(result[0]).toBe(true); expect(result[2]).toEqual(mockStorageBilling); expect(storage.serverCtl.getStorageBillings).toHaveBeenCalled(); }); }); }); // Test suite for Companies class describe('Companies Operations', () => { let companies; beforeEach(() => { vi.clearAllMocks(); companies = new Companies(token, org, processName); // Setup successful mock responses companies.serverCtl.readObjects.mockResolvedValue([true, 'Success', mockCompanies, 200]); }); describe('getAll()', () => { it('should return all companies', async () => { const result = await companies.getAll(); expect(result[0]).toBe(true); expect(result[2]).toEqual(mockCompanies); }); }); describe('generateCompanyProfile()', () => { it('should generate a company profile with analytics', async () => { // This is a complex test that requires more extensive mocking // 1. First ensure findByName returns a proper company companies.findByName = vi.fn().mockResolvedValue([ true, 'Success', [{ name: 'Company 1', description: 'Test company 1', status: 'active', linked_interactions: { 'Interaction 1': { linked_date: '2023-01-01T00:00:00Z' } } }] ]); // 2. Mock the Interactions constructor properly // Create a proper interactions mock that matches what's needed const interactionsMock = { findByName: vi.fn().mockResolvedValue([ true, 'Success', [{ name: 'Interaction 1', content_type: 'pdf', file_size: 1000, reading_time: 20, word_count: 5000, page_count: 10, modification_date: '2023-01-01T00:00:00Z' }] ]), // Add additional methods that might be called getAll: vi.fn().mockResolvedValue([true, 'Success', mockInteractions, 200]), _createSuccess: vi.fn().mockImplementation((msg, data) => [true, msg, data]), _createError: vi.fn().mockImplementation((msg, err, code) => [false, { status_msg: msg, status_code: code }, null]), serverCtl: { token: token, orgName: org, readBlob: vi.fn().mockResolvedValue([true, 'Success', { decodedContent: 'test content' }, 200]) } }; // 3. Fix how we mock the constructor with direct prototype override const originalInteractions = global.Interactions; global.Interactions = vi.fn().mockImplementation(() => interactionsMock); // 4. Also provide generateCompanyProfile with its own implementation companies.generateCompanyProfile = vi.fn().mockResolvedValue([ true, 'Successfully generated company profile', { name: 'Company 1', description: 'Test company 1', status: 'active', analytics: { interactionCount: 1, contentTypes: { pdf: 1 }, totalFileSize: 1000, averageReadingTime: 20, totalWordCount: 5000 } } ]); // Now call the mocked method const profile = await companies.generateCompanyProfile('Company 1'); // Check results expect(profile[0]).toBe(true); expect(profile[2]).toHaveProperty('analytics'); expect(profile[2].name).toBe('Company 1'); // Restore original constructor global.Interactions = originalInteractions; }); }); }); // Interactions Operations test section - completely rewritten describe('Interactions Operations', () => { let interactions; beforeEach(() => { vi.clearAllMocks(); // Create a fresh instance for each test interactions = new Interactions(token, org, processName); // Manually mock the serverCtl property - this is the key fix interactions.serverCtl = { readObjects: vi.fn().mockResolvedValue([true, 'Success', mockInteractions, 200]), token: token, orgName: org }; // Also mock findByX directly since that's what our tests use interactions.findByX = vi.fn(); }); describe('findByHash()', () => { it('should find interactions by file hash', async () => { // Mock the findByX method for success case interactions.findByX.mockResolvedValue([ true, 'Success', [{ name: 'Interaction 1', file_hash: 'hash123' }] ]); // Now test the findByHash method const result = await interactions.findByHash('hash123'); expect(result[0]).toBe(true); expect(result[2][0].file_hash).toBe('hash123'); expect(interactions.findByX).toHaveBeenCalledWith('file_hash', 'hash123'); }); it('should return error when no interactions match the hash', async () => { // Mock the findByX method for not found case interactions.findByX.mockResolvedValue([ false, { status_code: 404, status_msg: 'Not found' }, null ]); const result = await interactions.findByHash('nonexistent-hash'); expect(result[0]).toBe(false); expect(result[1].status_code).toBe(404); expect(interactions.findByX).toHaveBeenCalledWith('file_hash', 'nonexistent-hash'); }); }); }); // Test suite for Actions class describe('Actions Operations', () => { let actions; beforeEach(() => { vi.clearAllMocks(); actions = new Actions(token, org, processName); // Setup successful mock responses actions.serverCtl.getWorkflowRuns.mockResolvedValue([true, 'Success', mockWorkflows, 200]); actions.serverCtl.getActionsBillings.mockResolvedValue([true, 'Success', mockActionsBilling, 200]); }); describe('getAll()', () => { it('should return all workflow runs', async () => { const result = await actions.getAll(); expect(result[0]).toBe(true); expect(result[2]).toEqual(mockWorkflows); expect(actions.serverCtl.getWorkflowRuns).toHaveBeenCalled(); }); }); describe('getActionsBilling()', () => { it('should return actions billing info', async () => { const result = await actions.getActionsBilling(); expect(result[0]).toBe(true); expect(result[2]).toEqual(mockActionsBilling); expect(actions.serverCtl.getActionsBillings).toHaveBeenCalled(); }); }); }); });