/** * API Client Integration Tests * * Tests the complete API client layer integration, verifying: * - Import structure (all APIs accessible) * - Error handling (ApiError factory functions) * - Mock API client setup * - Basic smoke tests for each module * * @layer Infrastructure - API Client */ import { describe, it, expect } from 'vitest'; import { authApi } from '../auth'; import { TenantsApi } from '../tenant'; import { companiesApi } from '../company'; import { integrationKeysApi } from '../integration-keys'; import { InfoCenterApi } from '../info-center'; import { uploadApi } from '../upload'; import { ApiError, ApiErrorCode, createNetworkError, createAuthError, createValidationError, createNotFoundError, createForbiddenError, createConflictError, createServerError, createTimeoutError, } from '../shared/errors'; import { PaginationMeta, PaginatedResponse, ApiResponse, } from '../shared/types'; describe('API Client Integration Tests', () => { describe('Import Structure', () => { it('should import auth API', () => { expect(authApi).toBeDefined(); expect(authApi.login).toBeDefined(); expect(authApi.register).toBeDefined(); expect(authApi.refreshToken).toBeDefined(); expect(authApi.logout).toBeDefined(); expect(authApi.getMe).toBeDefined(); expect(authApi.getSessions).toBeDefined(); expect(authApi.revokeSession).toBeDefined(); }); it('should import tenant API', () => { expect(TenantsApi).toBeDefined(); // TenantsApi is a class, needs instantiation }); it('should import company API', () => { expect(companiesApi).toBeDefined(); expect(companiesApi.createCompany).toBeDefined(); expect(companiesApi.listCompanies).toBeDefined(); expect(companiesApi.getCompany).toBeDefined(); expect(companiesApi.updateCompany).toBeDefined(); expect(companiesApi.deleteCompany).toBeDefined(); }); it('should import integration keys API', () => { expect(integrationKeysApi).toBeDefined(); expect(integrationKeysApi.listKeys).toBeDefined(); expect(integrationKeysApi.getStats).toBeDefined(); }); it('should import info center API', () => { expect(InfoCenterApi).toBeDefined(); // InfoCenterApi is a class, needs instantiation }); it('should import upload API', () => { expect(uploadApi).toBeDefined(); expect(uploadApi.uploadFile).toBeDefined(); expect(uploadApi.deleteFile).toBeDefined(); }); it('should import shared types', () => { // Test that types are defined (type-level check) const meta: PaginationMeta = { page: 1, limit: 10, total: 100, totalPages: 10, }; expect(meta).toBeDefined(); const paginated: PaginatedResponse = { items: ['item1'], meta, }; expect(paginated).toBeDefined(); const response: ApiResponse = { data: 'test', }; expect(response).toBeDefined(); }); it('should import error types', () => { expect(ApiError).toBeDefined(); expect(ApiErrorCode).toBeDefined(); }); }); describe('Error Handling', () => { it('should create network error', () => { const error = createNetworkError('Connection failed'); expect(error).toBeInstanceOf(ApiError); expect(error.code).toBe(ApiErrorCode.NETWORK_ERROR); expect(error.message).toBe('Connection failed'); expect(error.statusCode).toBeUndefined(); expect(error.isNetworkError()).toBe(true); }); it('should create auth error', () => { const error = createAuthError('Unauthorized', 401); expect(error).toBeInstanceOf(ApiError); expect(error.code).toBe(ApiErrorCode.AUTH_ERROR); expect(error.message).toBe('Unauthorized'); expect(error.statusCode).toBe(401); expect(error.isAuthError()).toBe(true); }); it('should create validation error', () => { const details = { field: 'email', message: 'Invalid email' }; const error = createValidationError('Validation failed', details); expect(error).toBeInstanceOf(ApiError); expect(error.code).toBe(ApiErrorCode.VALIDATION_ERROR); expect(error.message).toBe('Validation failed'); expect(error.statusCode).toBe(400); expect(error.details).toEqual(details); expect(error.isValidationError()).toBe(true); }); it('should create not found error', () => { const error = createNotFoundError('Resource not found'); expect(error).toBeInstanceOf(ApiError); expect(error.code).toBe(ApiErrorCode.NOT_FOUND); expect(error.statusCode).toBe(404); }); it('should create forbidden error', () => { const error = createForbiddenError('Access forbidden'); expect(error).toBeInstanceOf(ApiError); expect(error.code).toBe(ApiErrorCode.FORBIDDEN); expect(error.statusCode).toBe(403); }); it('should create conflict error', () => { const error = createConflictError('Resource conflict'); expect(error).toBeInstanceOf(ApiError); expect(error.code).toBe(ApiErrorCode.CONFLICT); expect(error.statusCode).toBe(409); }); it('should create server error', () => { const error = createServerError('Internal server error', 500); expect(error).toBeInstanceOf(ApiError); expect(error.code).toBe(ApiErrorCode.SERVER_ERROR); expect(error.statusCode).toBe(500); expect(error.isServerError()).toBe(true); }); it('should create timeout error', () => { const error = createTimeoutError('Request timeout'); expect(error).toBeInstanceOf(ApiError); expect(error.code).toBe(ApiErrorCode.TIMEOUT); expect(error.statusCode).toBe(408); }); it('should convert error to JSON', () => { const error = createValidationError('Validation failed', { field: 'email', }); const json = error.toJSON(); expect(json).toEqual({ name: 'ApiError', message: 'Validation failed', code: ApiErrorCode.VALIDATION_ERROR, statusCode: 400, details: { field: 'email' }, }); }); it('should check error types correctly', () => { const authError = createAuthError(); expect(authError.isAuthError()).toBe(true); expect(authError.isValidationError()).toBe(false); expect(authError.isNetworkError()).toBe(false); expect(authError.isServerError()).toBe(false); const validationError = createValidationError(); expect(validationError.isValidationError()).toBe(true); expect(validationError.isAuthError()).toBe(false); const networkError = createNetworkError(); expect(networkError.isNetworkError()).toBe(true); expect(networkError.isAuthError()).toBe(false); const serverError = createServerError(); expect(serverError.isServerError()).toBe(true); expect(serverError.isAuthError()).toBe(false); }); }); describe('API Module Smoke Tests', () => { describe('Auth API', () => { it('should have correct method signatures', () => { expect(typeof authApi.login).toBe('function'); expect(typeof authApi.register).toBe('function'); expect(typeof authApi.refreshToken).toBe('function'); expect(typeof authApi.logout).toBe('function'); expect(typeof authApi.getMe).toBe('function'); expect(typeof authApi.getSessions).toBe('function'); expect(typeof authApi.revokeSession).toBe('function'); }); }); describe('Company API', () => { it('should have correct method signatures', () => { expect(typeof companiesApi.createCompany).toBe('function'); expect(typeof companiesApi.listCompanies).toBe('function'); expect(typeof companiesApi.getCompany).toBe('function'); expect(typeof companiesApi.updateCompany).toBe('function'); expect(typeof companiesApi.deleteCompany).toBe('function'); }); }); describe('Integration Keys API', () => { it('should have correct method signatures', () => { expect(typeof integrationKeysApi.listKeys).toBe('function'); expect(typeof integrationKeysApi.getStats).toBe('function'); }); }); describe('Upload API', () => { it('should have correct method signatures', () => { expect(typeof uploadApi.uploadFile).toBe('function'); expect(typeof uploadApi.deleteFile).toBe('function'); }); }); }); describe('Error Integration', () => { it('should handle ApiError instances correctly', () => { const error = new ApiError( 'Test error', ApiErrorCode.VALIDATION_ERROR, 400, new Error('Original error'), { field: 'email' } ); expect(error.name).toBe('ApiError'); expect(error.message).toBe('Test error'); expect(error.code).toBe(ApiErrorCode.VALIDATION_ERROR); expect(error.statusCode).toBe(400); expect(error.originalError).toBeDefined(); expect(error.details).toEqual({ field: 'email' }); }); it('should maintain error stack trace', () => { const error = createNotFoundError('Not found'); expect(error.stack).toBeDefined(); }); it('should use default error messages', () => { const networkError = createNetworkError(); expect(networkError.message).toBe( 'Network error: Unable to connect to server' ); const authError = createAuthError(); expect(authError.message).toBe('Authentication failed'); const validationError = createValidationError(); expect(validationError.message).toBe('Validation failed'); const notFoundError = createNotFoundError(); expect(notFoundError.message).toBe('Resource not found'); const forbiddenError = createForbiddenError(); expect(forbiddenError.message).toBe('Access forbidden'); const conflictError = createConflictError(); expect(conflictError.message).toBe('Resource conflict'); const serverError = createServerError(); expect(serverError.message).toBe('Internal server error'); const timeoutError = createTimeoutError(); expect(timeoutError.message).toBe('Request timeout'); }); }); describe('Type Safety', () => { it('should enforce PaginationMeta structure', () => { const meta: PaginationMeta = { page: 1, limit: 10, total: 100, totalPages: 10, hasNext: true, hasPrevious: false, }; expect(meta.page).toBe(1); expect(meta.limit).toBe(10); expect(meta.total).toBe(100); expect(meta.totalPages).toBe(10); expect(meta.hasNext).toBe(true); expect(meta.hasPrevious).toBe(false); }); it('should enforce PaginatedResponse structure', () => { const response: PaginatedResponse<{ id: string; name: string }> = { items: [ { id: '1', name: 'Item 1' }, { id: '2', name: 'Item 2' }, ], meta: { page: 1, limit: 10, total: 2, totalPages: 1, }, }; expect(response.items).toHaveLength(2); expect(response.meta.page).toBe(1); }); it('should enforce ApiResponse structure', () => { const response: ApiResponse = { data: 'test', meta: { requestId: '123' }, message: 'Success', }; expect(response.data).toBe('test'); expect(response.meta?.requestId).toBe('123'); expect(response.message).toBe('Success'); }); }); describe('Module Independence', () => { it('should allow importing individual modules', async () => { // Each module should be independently importable const { authApi: authModule } = await import('../auth'); expect(authModule).toBeDefined(); const { companiesApi: companyModule } = await import('../company'); expect(companyModule).toBeDefined(); const { integrationKeysApi: keysModule } = await import( '../integration-keys' ); expect(keysModule).toBeDefined(); const { uploadApi: uploadModule } = await import('../upload'); expect(uploadModule).toBeDefined(); }); it('should not expose mappers publicly', async () => { // Mappers should not be exported from barrel files const authExports = await import('../auth'); expect(authExports).toHaveProperty('authApi'); // Mappers should NOT be in exports expect(authExports).not.toHaveProperty('AuthRequestMapper'); expect(authExports).not.toHaveProperty('AuthResponseMapper'); const companyExports = await import('../company'); expect(companyExports).toHaveProperty('companiesApi'); expect(companyExports).not.toHaveProperty('CompanyRequestMapper'); expect(companyExports).not.toHaveProperty('CompanyResponseMapper'); }); }); describe('Error Code Enum', () => { it('should have all expected error codes', () => { expect(ApiErrorCode.NETWORK_ERROR).toBe('NETWORK_ERROR'); expect(ApiErrorCode.AUTH_ERROR).toBe('AUTH_ERROR'); expect(ApiErrorCode.VALIDATION_ERROR).toBe('VALIDATION_ERROR'); expect(ApiErrorCode.NOT_FOUND).toBe('NOT_FOUND'); expect(ApiErrorCode.FORBIDDEN).toBe('FORBIDDEN'); expect(ApiErrorCode.CONFLICT).toBe('CONFLICT'); expect(ApiErrorCode.SERVER_ERROR).toBe('SERVER_ERROR'); expect(ApiErrorCode.TIMEOUT).toBe('TIMEOUT'); expect(ApiErrorCode.UNKNOWN).toBe('UNKNOWN'); }); it('should have exactly 9 error codes', () => { const codes = Object.values(ApiErrorCode); expect(codes).toHaveLength(9); }); }); });