/*! * @license * Copyright Squiz Australia Pty Ltd. All Rights Reserved. */ /* eslint-disable @typescript-eslint/no-explicit-any */ // Mock external dependencies before any imports const mockUploadFile = jest.fn(); jest.mock('@squiz/virus-scanner-lib', () => { const originalModule = jest.requireActual('@squiz/virus-scanner-lib'); return { ...originalModule, uploadFile: mockUploadFile, }; }); jest.mock('./utils/manifestValidator'); jest.mock('./utils/uploadInitializer'); jest.mock('./utils/scanWatcher'); jest.mock('./utils/resultPoller'); jest.mock('./utils/cleanup'); const mockMkdtemp = jest.fn(); const mockRm = jest.fn(); jest.mock('fs/promises', () => ({ mkdtemp: mockMkdtemp, rm: mockRm, })); import { AxiosInstance } from 'axios'; import { JobUploadService } from './JobUploadService'; import { cleanupTmpDir } from './utils/cleanup'; import { getAndValidateManifest } from './utils/manifestValidator'; import { pollForResult } from './utils/resultPoller'; import { watchAndWaitForUploadAndScanComplete } from './utils/scanWatcher'; import { initializeUpload } from './utils/uploadInitializer'; const mockGetAndValidateManifest = getAndValidateManifest as jest.MockedFunction; const mockInitializeUpload = initializeUpload as jest.MockedFunction; const mockWatchAndWait = watchAndWaitForUploadAndScanComplete as jest.MockedFunction< typeof watchAndWaitForUploadAndScanComplete >; const mockPollForResult = pollForResult as jest.MockedFunction; const mockCleanupTmpDir = cleanupTmpDir as jest.MockedFunction; describe('JobUploadService', (): void => { let jobUploadService: JobUploadService; const mockAxiosClient = { post: jest.fn(), get: jest.fn(), } as unknown as AxiosInstance; beforeEach(() => { jest.clearAllMocks(); jobUploadService = new JobUploadService(); jest.spyOn(jobUploadService['logger'], 'info').mockImplementation(() => jobUploadService['logger']); jest.spyOn(jobUploadService['logger'], 'error').mockImplementation(() => jobUploadService['logger']); jest.spyOn(jobUploadService['logger'], 'warn').mockImplementation(() => jobUploadService['logger']); jest.spyOn(jobUploadService['logger'], 'debug').mockImplementation(() => jobUploadService['logger']); mockMkdtemp.mockResolvedValue('/tmp/job-upload-abc123'); }); describe('uploadJobFolder', (): void => { const mockFolderPath = '/test/job/folder'; const mockBaseTempDir = '/tmp'; it('should successfully upload job folder with all steps', async (): Promise => { const mockManifest = { getName: () => 'test-job', getVersion: () => '1.0.0', getModel: () => ({ name: 'test-job', version: '1.0.0' }), }; const mockInitialUpload = { zip: '/tmp/job.zip', upload: { id: 'upload-123' }, }; const mockResult = { status: 'successful' }; mockGetAndValidateManifest.mockResolvedValue(mockManifest as any); mockInitializeUpload.mockResolvedValue(mockInitialUpload); mockUploadFile.mockResolvedValue(undefined); mockWatchAndWait.mockResolvedValue(undefined); mockPollForResult.mockResolvedValue(mockResult); mockCleanupTmpDir.mockResolvedValue(undefined); await jobUploadService.uploadJobFolder(mockAxiosClient, mockFolderPath, mockBaseTempDir); expect(mockMkdtemp).toHaveBeenCalledWith('/tmp/job-upload'); expect(mockGetAndValidateManifest).toHaveBeenCalledWith(mockAxiosClient, mockFolderPath); expect(mockInitializeUpload).toHaveBeenCalledWith( mockAxiosClient, mockFolderPath, '/tmp/job-upload-abc123', expect.any(Object), ); expect(mockUploadFile).toHaveBeenCalledWith(mockInitialUpload.upload, mockInitialUpload.zip); expect(mockWatchAndWait).toHaveBeenCalledWith(mockAxiosClient, '/upload-job/status/', 'upload-123'); expect(mockPollForResult).toHaveBeenCalledWith( mockAxiosClient, 'upload-123', mockManifest.getModel(), expect.any(Object), ); expect(mockCleanupTmpDir).toHaveBeenCalledWith('/tmp/job-upload-abc123', expect.any(Object)); }); it('should cleanup temp directory on success', async (): Promise => { const mockManifest = { getModel: () => ({}) }; const mockInitialUpload = { zip: '/tmp/job.zip', upload: { id: 'upload-123' } }; const mockResult = { status: 'successful' }; mockGetAndValidateManifest.mockResolvedValue(mockManifest as any); mockInitializeUpload.mockResolvedValue(mockInitialUpload); mockUploadFile.mockResolvedValue(undefined); mockWatchAndWait.mockResolvedValue(undefined); mockPollForResult.mockResolvedValue(mockResult); mockCleanupTmpDir.mockResolvedValue(undefined); await jobUploadService.uploadJobFolder(mockAxiosClient, mockFolderPath, mockBaseTempDir); expect(mockCleanupTmpDir).toHaveBeenCalledWith('/tmp/job-upload-abc123', expect.any(Object)); }); it('should cleanup temp directory on error', async (): Promise => { const testError = new Error('Test error'); mockGetAndValidateManifest.mockRejectedValue(testError); mockCleanupTmpDir.mockResolvedValue(undefined); await expect(jobUploadService.uploadJobFolder(mockAxiosClient, mockFolderPath, mockBaseTempDir)).rejects.toThrow( 'Test error', ); expect(mockCleanupTmpDir).toHaveBeenCalledWith('/tmp/job-upload-abc123', expect.any(Object)); }); it('should handle unsuccessful upload result', async (): Promise => { const mockManifest = { getModel: () => ({}) }; const mockInitialUpload = { zip: '/tmp/job.zip', upload: { id: 'upload-123' } }; const mockResult = { status: 'failed', message: 'Upload failed' }; mockGetAndValidateManifest.mockResolvedValue(mockManifest as any); mockInitializeUpload.mockResolvedValue(mockInitialUpload); mockUploadFile.mockResolvedValue(undefined); mockWatchAndWait.mockResolvedValue(undefined); mockPollForResult.mockResolvedValue(mockResult); mockCleanupTmpDir.mockResolvedValue(undefined); await jobUploadService.uploadJobFolder(mockAxiosClient, mockFolderPath, mockBaseTempDir); // Should call cleanup twice: once for unsuccessful result, once for normal cleanup expect(mockCleanupTmpDir).toHaveBeenCalledTimes(2); }); it('should handle upload file step failure', async (): Promise => { const mockManifest = { getModel: () => ({}) }; const mockInitialUpload = { zip: '/tmp/job.zip', upload: { id: 'upload-123' } }; const uploadError = new Error('Upload failed'); mockGetAndValidateManifest.mockResolvedValue(mockManifest as any); mockInitializeUpload.mockResolvedValue(mockInitialUpload); mockUploadFile.mockRejectedValue(uploadError); mockCleanupTmpDir.mockResolvedValue(undefined); await expect(jobUploadService.uploadJobFolder(mockAxiosClient, mockFolderPath, mockBaseTempDir)).rejects.toThrow( 'Upload failed', ); expect(mockCleanupTmpDir).toHaveBeenCalledWith('/tmp/job-upload-abc123', expect.any(Object)); }); }); });