import { BaseE2ETest } from '../../lib/base-test'; import { testData } from '../../config/test-data-helpers'; class JobsE2ETest extends BaseE2ETest { // Jobs commands async runListCommand( appId: string, options?: { version?: string[]; trackerId?: string[]; function?: string[]; status?: string[]; minDuration?: string; sortBy?: string; sortDirection?: string; limit?: number; columns?: string; from?: string; to?: string; availability?: string; } ) { const args = ['jobs', 'list', appId]; if (options?.version) { options.version.forEach(v => args.push(`--version=${v}`)); } if (options?.trackerId) { options.trackerId.forEach(t => args.push(`--trackerId=${t}`)); } if (options?.function) { options.function.forEach(f => args.push(`--function=${f}`)); } if (options?.status) { options.status.forEach(s => args.push(`--status=${s}`)); } if (options?.minDuration) { args.push(`--minDuration=${options.minDuration}`); } if (options?.sortBy) { args.push(`--sortBy=${options.sortBy}`); } if (options?.sortDirection) { args.push(`--sortDirection=${options.sortDirection}`); } if (options?.limit) { args.push(`--limit=${options.limit}`); } if (options?.columns) { args.push(`--columns=${options.columns}`); } if (options?.from) { args.push(`--from=${options.from}`); } if (options?.to) { args.push(`--to=${options.to}`); } if (options?.availability) { args.push(`-a=${options.availability}`); } return this.execute(args); } async runStatusCommand(jobId: string, availability?: string) { const args = ['jobs', 'status', jobId]; if (availability) { args.push(`-a=${availability}`); } return this.execute(args); } async runStatusCommandWithoutJobId() { return this.execute(['jobs', 'status']); } async runTriggerCommand(appId: string, jobId: string, trackerId: string, availability?: string) { const args = ['jobs', 'trigger', appId, jobId, trackerId]; if (availability) { args.push(`-a=${availability}`); } return this.execute(args); } // Public wrapper methods to access protected assertions public checkSuccess(result: any) { this.assertSuccess(result); } public checkFailure(result: any) { this.assertFailure(result); } public checkOutputContains(result: any, text: string) { this.assertOutputContains(result, text); } public checkOutputMatchesBaseline(result: any, baselinePath: string, maxLines?: number) { this.assertOutputMatchesBaseline(result, baselinePath, maxLines); } public clearHistory() { this.cliExecutor.clearProcessHistory(); } public executePublic(args: string[], options?: any) { return this.execute(args, options); } async getInstalledVersion(appId: string, trackerId: string): Promise { const result = await this.execute(['directory', 'list-installs', appId]); if (result.exitCode !== 0) { return null; } // Parse the output to find the version for the given trackerId const lines = result.stdout.split('\n'); for (const line of lines) { if (line.includes(trackerId)) { // Format: Tracker Version Created At Updated At const parts = line.trim().split(/\s{2,}/); if (parts.length >= 2) { return parts[1]; // Version is the second column } } } return null; } } describe('Jobs Commands E2E Tests', () => { let testInstance: JobsE2ETest; beforeAll(() => { testInstance = new JobsE2ETest(); }); afterAll(async () => { if (testInstance) { await testInstance.cleanup(); } }); afterEach(() => { // Clean up process history after each test if (testInstance) { testInstance.clearHistory(); } }); describe('jobs trigger', () => { it('should trigger job', async () => { const result = await testInstance.runTriggerCommand(testData.appId, testData.jobId, testData.trackerId); testInstance.checkSuccess(result); const listResult = await testInstance.runListCommand(testData.appId, { trackerId: [testData.trackerId], function: [testData.jobId] }); testInstance.checkSuccess(listResult); }); }); describe('jobs list', () => { it('should list jobs for a valid app ID', async () => { const result = await testInstance.runListCommand(testData.appId); expect(result.exitCode).toBe(0); expect(result.stdout).toBeTruthy(); // Check baseline format Headers + Table header + First job output testInstance.checkOutputMatchesBaseline(result, 'jobs/list.txt', 4); }, 15000); it('should work with version filtering', async () => { // Get the currently installed version for the test installation const installedVersion = await testInstance.getInstalledVersion(testData.appId, testData.trackerId); expect(installedVersion).not.toBeNull(); const result = await testInstance.runListCommand(testData.appId, { version: [installedVersion!] }); expect(result.exitCode).toBe(0); expect(result.stdout).toBeTruthy(); }, 15000); it('should work with tracker ID filtering', async () => { const result = await testInstance.runListCommand(testData.appId, { trackerId: [testData.trackerId] }); if (result.exitCode === 0) { expect(result.stdout).toBeTruthy(); } else { // Should fail gracefully with informative message when no jobs found expect(`${result.stdout} - ${result.stderr}`).toMatch(/No jobs found/); } }, 15000); it('should work with function filtering', async () => { const result = await testInstance.runListCommand(testData.appId, { function: [testData.jobId] }); expect(result.exitCode).toBe(0); expect(result.stdout).toBeTruthy(); }, 15000); it('should work with status filtering', async () => { const result = await testInstance.runListCommand(testData.appId, { status: ['COMPLETE', 'RUNNING'] }); expect(result.exitCode).toBe(0); expect(result.stdout).toBeTruthy(); }, 15000); it('should work with multiple filters combined', async () => { // Get the currently installed version for the test installation const installedVersion = await testInstance.getInstalledVersion(testData.appId, testData.trackerId); expect(installedVersion).not.toBeNull(); const result = await testInstance.runListCommand(testData.appId, { version: [installedVersion!], status: ['COMPLETE'], limit: 10 }); expect(result.exitCode).toBe(0); expect(result.stdout).toBeTruthy(); }, 15000); it('should work with date range filtering', async () => { const result = await testInstance.runListCommand(testData.appId, { from: '30d', to: '1h' }); // May return success or failure depending on if jobs exist in range expect(result.exitCode).toBeDefined(); expect(result.stdout).toBeTruthy(); }, 15000); it('should work with duration filtering', async () => { const result = await testInstance.runListCommand(testData.appId, { minDuration: '1s' }); // May return success or failure depending on if jobs exist with that duration expect(result.exitCode).toBeDefined(); expect(result.stdout).toBeTruthy(); }, 15000); it('should work with column selection', async () => { const result = await testInstance.runListCommand(testData.appId, { columns: 'id,status,createdAt' }); expect(result.exitCode).toBe(0); expect(result.stdout).toBeTruthy(); // Should only contain specified columns expect(result.stdout).toMatch(/Job ID/); expect(result.stdout).toMatch(/Status/); expect(result.stdout).toMatch(/Created At/); }, 15000); it('should work with sorting options', async () => { const result = await testInstance.runListCommand(testData.appId, { sortBy: 'createdAt', sortDirection: 'asc' }); expect(result.exitCode).toBe(0); expect(result.stdout).toBeTruthy(); }, 15000); it('should work with limit parameter', async () => { const result = await testInstance.runListCommand(testData.appId, { limit: 5 }); expect(result.exitCode).toBe(0); expect(result.stdout).toBeTruthy(); }, 15000); it('should work with availability zone parameter', async () => { const result = await testInstance.runListCommand(testData.appId, { availability: 'us' }); expect(result.exitCode).toBe(0); expect(result.stdout).toBeTruthy(); }, 15000); it('should fail when app ID parameter is missing', async () => { const result = await testInstance.executePublic(['jobs', 'list']); expect(result.exitCode).not.toBe(0); expect(result.stderr).toBeTruthy(); }); it('should handle invalid duration format gracefully', async () => { const result = await testInstance.runListCommand(testData.appId, { minDuration: 'invalid-duration' }); expect(result.exitCode).not.toBe(0); expect(result.stderr).toBeTruthy(); expect(result.stderr).toMatch(/Invalid.*minDuration/); }); it('should handle invalid sort column gracefully', async () => { const result = await testInstance.runListCommand(testData.appId, { sortBy: 'invalidColumn' }); expect(result.exitCode).not.toBe(0); expect(result.stderr).toBeTruthy(); }); it('should handle invalid sort direction gracefully', async () => { const result = await testInstance.runListCommand(testData.appId, { sortDirection: 'invalid' }); expect(result.exitCode).not.toBe(0); expect(result.stderr).toBeTruthy(); }); it('should handle invalid status values gracefully', async () => { const result = await testInstance.runListCommand(testData.appId, { status: ['INVALID_STATUS'] }); expect(result.exitCode).not.toBe(0); expect(result.stderr).toBeTruthy(); }); it('should complete within reasonable time', async () => { const result = await testInstance.runListCommand(testData.appId); expect(result.executionTime).toBeLessThan(20000); // 20 seconds expect(result.timedOut).toBe(false); }, 25000); }); describe('jobs status', () => { it('should return status for valid job ID', async () => { // First get a job ID from the list const listResult = await testInstance.runListCommand(testData.appId, { limit: 1 }); if (listResult.exitCode === 0 && listResult.stdout.includes('No jobs found')) { // Skip test if no jobs found expect(true).toBe(true); return; } expect(listResult.exitCode).toBe(0); // Extract a job ID from the output (simplified approach) const lines = listResult.stdout.split('\n'); const dataLine = lines.find(line => line.includes('-') && !line.includes('Job ID')); if (!dataLine) { // Skip test if no job data found expect(true).toBe(true); return; } const jobId = dataLine.trim().split(/\s+/)[0]; if (jobId && jobId !== '-' && jobId.length > 5) { const result = await testInstance.runStatusCommand(jobId); expect(result.exitCode).toBe(0); expect(result.stdout).toBeTruthy(); // Check for status information expect(result.stdout).toMatch(/Id/); expect(result.stdout).toMatch(/App Id/); expect(result.stdout).toMatch(/Version/); expect(result.stdout).toMatch(/Function/); expect(result.stdout).toMatch(/Status/); } else { // Skip test if no valid job ID found expect(true).toBe(true); } }, 25000); it('should work with availability zone parameter', async () => { // Get a job ID first const listResult = await testInstance.runListCommand(testData.appId, { limit: 1 }); if (listResult.exitCode === 0 && listResult.stdout.includes('No jobs found')) { expect(true).toBe(true); return; } const lines = listResult.stdout.split('\n'); const dataLine = lines.find(line => line.includes('-') && !line.includes('Job ID')); if (!dataLine) { expect(true).toBe(true); return; } const jobId = dataLine.trim().split(/\s+/)[0]; if (jobId && jobId !== '-' && jobId.length > 5) { const result = await testInstance.runStatusCommand(jobId, 'us'); expect(result.exitCode).toBe(0); expect(result.stdout).toBeTruthy(); } else { expect(true).toBe(true); } }, 25000); it('should handle non-existent job ID gracefully', async () => { const result = await testInstance.runStatusCommand('nonexistent-job-id'); expect(result.exitCode).not.toBe(0); expect(result.stderr).toBeTruthy(); }, 15000); it('should fail when job ID parameter is missing', async () => { const result = await testInstance.runStatusCommandWithoutJobId(); expect(result.exitCode).not.toBe(0); expect(result.stderr).toBeTruthy(); expect(result.stderr).toMatch(/Job ID is required/); }); it('should complete within reasonable time', async () => { // Use a dummy job ID for timing test const result = await testInstance.runStatusCommand('dummy-job-id'); expect(result.executionTime).toBeLessThan(10000); // 10 seconds expect(result.timedOut).toBe(false); }, 15000); }); describe('Error Scenarios', () => { it('should handle malformed availability zone parameter', async () => { const result = await testInstance.runListCommand(testData.appId, { availability: 'invalid-zone' }); expect(result.exitCode).toBeGreaterThan(0); }, 15000); }); describe('Performance Tests', () => { it('should complete jobs list within reasonable time', async () => { const result = await testInstance.runListCommand(testData.appId); expect(result.executionTime).toBeLessThan(20000); // 20 seconds expect(result.executionTime).toBeGreaterThan(0); }, 25000); it('should complete jobs status within reasonable time', async () => { const result = await testInstance.runStatusCommand('dummy-job-id'); expect(result.executionTime).toBeLessThan(10000); // 10 seconds expect(result.executionTime).toBeGreaterThan(0); }, 15000); it('should handle large result sets efficiently', async () => { const result = await testInstance.runListCommand(testData.appId, { limit: 100, from: '30d' }); expect(result.executionTime).toBeLessThan(30000); // 30 seconds expect(result.timedOut).toBe(false); }, 35000); }); describe('Advanced Filtering and Sorting', () => { it('should work with ISO date strings', async () => { const now = new Date(); const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const result = await testInstance.runListCommand(testData.appId, { from: oneWeekAgo.toISOString(), to: now.toISOString() }); expect(result.exitCode).toBe(0); expect(result.stdout).toBeTruthy(); }, 15000); it('should work with epoch timestamps', async () => { const now = Date.now(); const oneWeekAgo = now - (7 * 24 * 60 * 60 * 1000); const result = await testInstance.runListCommand(testData.appId, { from: oneWeekAgo.toString(), to: now.toString() }); // May return success or failure depending on data availability expect(result.exitCode).toBeDefined(); expect(result.stdout).toBeTruthy(); }, 15000); it('should sort by different columns', async () => { const sortColumns = ['id', 'status', 'createdAt', 'updatedAt']; for (const column of sortColumns) { const result = await testInstance.runListCommand(testData.appId, { sortBy: column, limit: 5 }); expect(result.exitCode).toBe(0); expect(result.stdout).toBeTruthy(); } }, 60000); it('should work with complex multi-parameter filtering', async () => { // Get the currently installed version for the test installation const installedVersion = await testInstance.getInstalledVersion(testData.appId, testData.trackerId); expect(installedVersion).not.toBeNull(); const result = await testInstance.runListCommand(testData.appId, { version: [installedVersion!], function: ['foo'], from: '30d', sortBy: 'updatedAt', sortDirection: 'desc', limit: 20, columns: 'id,status,function,createdAt,duration' }); // May return success or failure depending on data availability expect(result.exitCode).toBeDefined(); expect(result.stdout).toBeTruthy(); }, 20000); }); });