@optimizely/ocp-cli 1.2.13 → 1.2.14-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/commands/app/Init.js +1 -1
  2. package/dist/commands/app/Init.js.map +1 -1
  3. package/dist/oo-cli.manifest.json +1 -1
  4. package/package.json +10 -6
  5. package/src/commands/app/Init.ts +1 -1
  6. package/src/test/e2e/__tests__/accounts/accounts.test.ts +120 -0
  7. package/src/test/e2e/__tests__/availability/availability.test.ts +156 -0
  8. package/src/test/e2e/__tests__/directory/directory.test.ts +668 -0
  9. package/src/test/e2e/__tests__/jobs/jobs.test.ts +487 -0
  10. package/src/test/e2e/__tests__/review/review.test.ts +355 -0
  11. package/src/test/e2e/config/fixture-loader.ts +130 -0
  12. package/src/test/e2e/config/setup.ts +29 -0
  13. package/src/test/e2e/config/test-data-config.ts +27 -0
  14. package/src/test/e2e/config/test-data-helpers.ts +23 -0
  15. package/src/test/e2e/fixtures/baselines/accounts/whoami.txt +11 -0
  16. package/src/test/e2e/fixtures/baselines/accounts/whois.txt +4 -0
  17. package/src/test/e2e/fixtures/baselines/directory/info.txt +7 -0
  18. package/src/test/e2e/fixtures/baselines/directory/list.txt +4 -0
  19. package/src/test/e2e/fixtures/baselines/jobs/list.txt +4 -0
  20. package/src/test/e2e/fixtures/baselines/review/list.txt +4 -0
  21. package/src/test/e2e/lib/base-test.ts +150 -0
  22. package/src/test/e2e/lib/command-discovery.ts +324 -0
  23. package/src/test/e2e/utils/baseline-normalizer.ts +79 -0
  24. package/src/test/e2e/utils/cli-executor.ts +349 -0
  25. package/src/test/e2e/utils/command-registry.ts +99 -0
  26. package/src/test/e2e/utils/output-validator.ts +661 -0
  27. package/src/test/setup.ts +3 -1
  28. package/src/test/tsconfig.json +17 -0
  29. package/dist/test/setup.d.ts +0 -0
  30. package/dist/test/setup.js +0 -4
  31. package/dist/test/setup.js.map +0 -1
@@ -0,0 +1,487 @@
1
+ import { BaseE2ETest } from '../../lib/base-test';
2
+ import { testData } from '../../config/test-data-helpers';
3
+
4
+ class JobsE2ETest extends BaseE2ETest {
5
+ // Jobs commands
6
+ async runListCommand(
7
+ appId: string,
8
+ options?: {
9
+ version?: string[];
10
+ trackerId?: string[];
11
+ function?: string[];
12
+ status?: string[];
13
+ minDuration?: string;
14
+ sortBy?: string;
15
+ sortDirection?: string;
16
+ limit?: number;
17
+ columns?: string;
18
+ from?: string;
19
+ to?: string;
20
+ availability?: string;
21
+ }
22
+ ) {
23
+ const args = ['jobs', 'list', appId];
24
+
25
+ if (options?.version) {
26
+ options.version.forEach(v => args.push(`--version=${v}`));
27
+ }
28
+ if (options?.trackerId) {
29
+ options.trackerId.forEach(t => args.push(`--trackerId=${t}`));
30
+ }
31
+ if (options?.function) {
32
+ options.function.forEach(f => args.push(`--function=${f}`));
33
+ }
34
+ if (options?.status) {
35
+ options.status.forEach(s => args.push(`--status=${s}`));
36
+ }
37
+ if (options?.minDuration) {
38
+ args.push(`--minDuration=${options.minDuration}`);
39
+ }
40
+ if (options?.sortBy) {
41
+ args.push(`--sortBy=${options.sortBy}`);
42
+ }
43
+ if (options?.sortDirection) {
44
+ args.push(`--sortDirection=${options.sortDirection}`);
45
+ }
46
+ if (options?.limit) {
47
+ args.push(`--limit=${options.limit}`);
48
+ }
49
+ if (options?.columns) {
50
+ args.push(`--columns=${options.columns}`);
51
+ }
52
+ if (options?.from) {
53
+ args.push(`--from=${options.from}`);
54
+ }
55
+ if (options?.to) {
56
+ args.push(`--to=${options.to}`);
57
+ }
58
+ if (options?.availability) {
59
+ args.push(`-a=${options.availability}`);
60
+ }
61
+
62
+ return this.execute(args);
63
+ }
64
+
65
+ async runStatusCommand(jobId: string, availability?: string) {
66
+ const args = ['jobs', 'status', jobId];
67
+ if (availability) {
68
+ args.push(`-a=${availability}`);
69
+ }
70
+ return this.execute(args);
71
+ }
72
+
73
+ async runStatusCommandWithoutJobId() {
74
+ return this.execute(['jobs', 'status']);
75
+ }
76
+
77
+ // Public wrapper methods to access protected assertions
78
+ public checkSuccess(result: any) {
79
+ this.assertSuccess(result);
80
+ }
81
+
82
+ public checkFailure(result: any) {
83
+ this.assertFailure(result);
84
+ }
85
+
86
+ public checkOutputContains(result: any, text: string) {
87
+ this.assertOutputContains(result, text);
88
+ }
89
+
90
+ public checkOutputMatchesBaseline(result: any, baselinePath: string, maxLines?: number) {
91
+ this.assertOutputMatchesBaseline(result, baselinePath, maxLines);
92
+ }
93
+
94
+ public clearHistory() {
95
+ this.cliExecutor.clearProcessHistory();
96
+ }
97
+
98
+ public executePublic(args: string[], options?: any) {
99
+ return this.execute(args, options);
100
+ }
101
+ }
102
+
103
+ describe('Jobs Commands E2E Tests', () => {
104
+ let testInstance: JobsE2ETest;
105
+
106
+ beforeAll(() => {
107
+ testInstance = new JobsE2ETest();
108
+ });
109
+
110
+ afterAll(async () => {
111
+ if (testInstance) {
112
+ await testInstance.cleanup();
113
+ }
114
+ });
115
+
116
+ afterEach(() => {
117
+ // Clean up process history after each test
118
+ if (testInstance) {
119
+ testInstance.clearHistory();
120
+ }
121
+ });
122
+
123
+ describe('jobs list', () => {
124
+ it('should list jobs for a valid app ID', async () => {
125
+ const result = await testInstance.runListCommand(testData.appId);
126
+
127
+ expect(result.exitCode).toBe(0);
128
+ expect(result.stdout).toBeTruthy();
129
+
130
+ // Check baseline format Headers + Table header + First job output
131
+ testInstance.checkOutputMatchesBaseline(result, 'jobs/list.txt', 4);
132
+ }, 15000);
133
+
134
+ it('should work with version filtering', async () => {
135
+ const result = await testInstance.runListCommand(testData.appId, {
136
+ version: [testData.appVersion]
137
+ });
138
+
139
+ expect(result.exitCode).toBe(0);
140
+ expect(result.stdout).toBeTruthy();
141
+ }, 15000);
142
+
143
+ it('should work with tracker ID filtering', async () => {
144
+ const result = await testInstance.runListCommand(testData.appId, {
145
+ trackerId: [testData.trackerId]
146
+ });
147
+
148
+ if (result.exitCode === 0) {
149
+ expect(result.stdout).toBeTruthy();
150
+ } else {
151
+ // Should fail gracefully with informative message when no jobs found
152
+ expect(`${result.stdout} - ${result.stderr}`).toMatch(/No jobs found/);
153
+ }
154
+ }, 15000);
155
+
156
+ it('should work with function filtering', async () => {
157
+ const result = await testInstance.runListCommand(testData.appId, {
158
+ function: ['foo']
159
+ });
160
+
161
+ expect(result.exitCode).toBe(0);
162
+ expect(result.stdout).toBeTruthy();
163
+ }, 15000);
164
+
165
+ it('should work with status filtering', async () => {
166
+ const result = await testInstance.runListCommand(testData.appId, {
167
+ status: ['COMPLETE', 'RUNNING']
168
+ });
169
+
170
+ expect(result.exitCode).toBe(0);
171
+ expect(result.stdout).toBeTruthy();
172
+ }, 15000);
173
+
174
+ it('should work with multiple filters combined', async () => {
175
+ const result = await testInstance.runListCommand(testData.appId, {
176
+ version: [testData.appVersion],
177
+ status: ['COMPLETE'],
178
+ limit: 10
179
+ });
180
+
181
+ expect(result.exitCode).toBe(0);
182
+ expect(result.stdout).toBeTruthy();
183
+ }, 15000);
184
+
185
+ it('should work with date range filtering', async () => {
186
+ const result = await testInstance.runListCommand(testData.appId, {
187
+ from: '30d',
188
+ to: '1h'
189
+ });
190
+
191
+ // May return success or failure depending on if jobs exist in range
192
+ expect(result.exitCode).toBeDefined();
193
+ expect(result.stdout).toBeTruthy();
194
+ }, 15000);
195
+
196
+ it('should work with duration filtering', async () => {
197
+ const result = await testInstance.runListCommand(testData.appId, {
198
+ minDuration: '1s'
199
+ });
200
+
201
+ // May return success or failure depending on if jobs exist with that duration
202
+ expect(result.exitCode).toBeDefined();
203
+ expect(result.stdout).toBeTruthy();
204
+ }, 15000);
205
+
206
+ it('should work with column selection', async () => {
207
+ const result = await testInstance.runListCommand(testData.appId, {
208
+ columns: 'id,status,createdAt'
209
+ });
210
+
211
+ expect(result.exitCode).toBe(0);
212
+ expect(result.stdout).toBeTruthy();
213
+
214
+ // Should only contain specified columns
215
+ expect(result.stdout).toMatch(/Job ID/);
216
+ expect(result.stdout).toMatch(/Status/);
217
+ expect(result.stdout).toMatch(/Created At/);
218
+ }, 15000);
219
+
220
+ it('should work with sorting options', async () => {
221
+ const result = await testInstance.runListCommand(testData.appId, {
222
+ sortBy: 'createdAt',
223
+ sortDirection: 'asc'
224
+ });
225
+
226
+ expect(result.exitCode).toBe(0);
227
+ expect(result.stdout).toBeTruthy();
228
+ }, 15000);
229
+
230
+ it('should work with limit parameter', async () => {
231
+ const result = await testInstance.runListCommand(testData.appId, {
232
+ limit: 5
233
+ });
234
+
235
+ expect(result.exitCode).toBe(0);
236
+ expect(result.stdout).toBeTruthy();
237
+ }, 15000);
238
+
239
+ it('should work with availability zone parameter', async () => {
240
+ const result = await testInstance.runListCommand(testData.appId, {
241
+ availability: 'us'
242
+ });
243
+
244
+ expect(result.exitCode).toBe(0);
245
+ expect(result.stdout).toBeTruthy();
246
+ }, 15000);
247
+
248
+ it('should fail when app ID parameter is missing', async () => {
249
+ const result = await testInstance.executePublic(['jobs', 'list']);
250
+
251
+ expect(result.exitCode).not.toBe(0);
252
+ expect(result.stderr).toBeTruthy();
253
+ });
254
+
255
+ it('should handle invalid duration format gracefully', async () => {
256
+ const result = await testInstance.runListCommand(testData.appId, {
257
+ minDuration: 'invalid-duration'
258
+ });
259
+
260
+ expect(result.exitCode).not.toBe(0);
261
+ expect(result.stderr).toBeTruthy();
262
+ expect(result.stderr).toMatch(/Invalid.*minDuration/);
263
+ });
264
+
265
+ it('should handle invalid sort column gracefully', async () => {
266
+ const result = await testInstance.runListCommand(testData.appId, {
267
+ sortBy: 'invalidColumn'
268
+ });
269
+
270
+ expect(result.exitCode).not.toBe(0);
271
+ expect(result.stderr).toBeTruthy();
272
+ });
273
+
274
+ it('should handle invalid sort direction gracefully', async () => {
275
+ const result = await testInstance.runListCommand(testData.appId, {
276
+ sortDirection: 'invalid'
277
+ });
278
+
279
+ expect(result.exitCode).not.toBe(0);
280
+ expect(result.stderr).toBeTruthy();
281
+ });
282
+
283
+ it('should handle invalid status values gracefully', async () => {
284
+ const result = await testInstance.runListCommand(testData.appId, {
285
+ status: ['INVALID_STATUS']
286
+ });
287
+
288
+ expect(result.exitCode).not.toBe(0);
289
+ expect(result.stderr).toBeTruthy();
290
+ });
291
+
292
+ it('should complete within reasonable time', async () => {
293
+ const result = await testInstance.runListCommand(testData.appId);
294
+
295
+ expect(result.executionTime).toBeLessThan(20000); // 20 seconds
296
+ expect(result.timedOut).toBe(false);
297
+ }, 25000);
298
+ });
299
+
300
+ describe('jobs status', () => {
301
+ it('should return status for valid job ID', async () => {
302
+ // First get a job ID from the list
303
+ const listResult = await testInstance.runListCommand(testData.appId, { limit: 1 });
304
+
305
+ if (listResult.exitCode === 0 && listResult.stdout.includes('No jobs found')) {
306
+ // Skip test if no jobs found
307
+ expect(true).toBe(true);
308
+ return;
309
+ }
310
+
311
+ expect(listResult.exitCode).toBe(0);
312
+
313
+ // Extract a job ID from the output (simplified approach)
314
+ const lines = listResult.stdout.split('\n');
315
+ const dataLine = lines.find(line => line.includes('-') && !line.includes('Job ID'));
316
+
317
+ if (!dataLine) {
318
+ // Skip test if no job data found
319
+ expect(true).toBe(true);
320
+ return;
321
+ }
322
+
323
+ const jobId = dataLine.trim().split(/\s+/)[0];
324
+
325
+ if (jobId && jobId !== '-' && jobId.length > 5) {
326
+ const result = await testInstance.runStatusCommand(jobId);
327
+
328
+ expect(result.exitCode).toBe(0);
329
+ expect(result.stdout).toBeTruthy();
330
+
331
+ // Check for status information
332
+ expect(result.stdout).toMatch(/Id/);
333
+ expect(result.stdout).toMatch(/App Id/);
334
+ expect(result.stdout).toMatch(/Version/);
335
+ expect(result.stdout).toMatch(/Function/);
336
+ expect(result.stdout).toMatch(/Status/);
337
+ } else {
338
+ // Skip test if no valid job ID found
339
+ expect(true).toBe(true);
340
+ }
341
+ }, 25000);
342
+
343
+ it('should work with availability zone parameter', async () => {
344
+ // Get a job ID first
345
+ const listResult = await testInstance.runListCommand(testData.appId, { limit: 1 });
346
+
347
+ if (listResult.exitCode === 0 && listResult.stdout.includes('No jobs found')) {
348
+ expect(true).toBe(true);
349
+ return;
350
+ }
351
+
352
+ const lines = listResult.stdout.split('\n');
353
+ const dataLine = lines.find(line => line.includes('-') && !line.includes('Job ID'));
354
+
355
+ if (!dataLine) {
356
+ expect(true).toBe(true);
357
+ return;
358
+ }
359
+
360
+ const jobId = dataLine.trim().split(/\s+/)[0];
361
+
362
+ if (jobId && jobId !== '-' && jobId.length > 5) {
363
+ const result = await testInstance.runStatusCommand(jobId, 'us');
364
+
365
+ expect(result.exitCode).toBe(0);
366
+ expect(result.stdout).toBeTruthy();
367
+ } else {
368
+ expect(true).toBe(true);
369
+ }
370
+ }, 25000);
371
+
372
+ it('should handle non-existent job ID gracefully', async () => {
373
+ const result = await testInstance.runStatusCommand('nonexistent-job-id');
374
+
375
+ expect(result.exitCode).not.toBe(0);
376
+ expect(result.stderr).toBeTruthy();
377
+ }, 15000);
378
+
379
+ it('should fail when job ID parameter is missing', async () => {
380
+ const result = await testInstance.runStatusCommandWithoutJobId();
381
+
382
+ expect(result.exitCode).not.toBe(0);
383
+ expect(result.stderr).toBeTruthy();
384
+ expect(result.stderr).toMatch(/Job ID is required/);
385
+ });
386
+
387
+ it('should complete within reasonable time', async () => {
388
+ // Use a dummy job ID for timing test
389
+ const result = await testInstance.runStatusCommand('dummy-job-id');
390
+
391
+ expect(result.executionTime).toBeLessThan(10000); // 10 seconds
392
+ expect(result.timedOut).toBe(false);
393
+ }, 15000);
394
+ });
395
+
396
+ describe('Error Scenarios', () => {
397
+ it('should handle malformed availability zone parameter', async () => {
398
+ const result = await testInstance.runListCommand(testData.appId, { availability: 'invalid-zone' });
399
+ expect(result.exitCode).toBeGreaterThan(0);
400
+ }, 15000);
401
+ });
402
+
403
+ describe('Performance Tests', () => {
404
+ it('should complete jobs list within reasonable time', async () => {
405
+ const result = await testInstance.runListCommand(testData.appId);
406
+
407
+ expect(result.executionTime).toBeLessThan(20000); // 20 seconds
408
+ expect(result.executionTime).toBeGreaterThan(0);
409
+ }, 25000);
410
+
411
+ it('should complete jobs status within reasonable time', async () => {
412
+ const result = await testInstance.runStatusCommand('dummy-job-id');
413
+
414
+ expect(result.executionTime).toBeLessThan(10000); // 10 seconds
415
+ expect(result.executionTime).toBeGreaterThan(0);
416
+ }, 15000);
417
+
418
+ it('should handle large result sets efficiently', async () => {
419
+ const result = await testInstance.runListCommand(testData.appId, {
420
+ limit: 100,
421
+ from: '30d'
422
+ });
423
+
424
+ expect(result.executionTime).toBeLessThan(30000); // 30 seconds
425
+ expect(result.timedOut).toBe(false);
426
+ }, 35000);
427
+ });
428
+
429
+ describe('Advanced Filtering and Sorting', () => {
430
+ it('should work with ISO date strings', async () => {
431
+ const now = new Date();
432
+ const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
433
+
434
+ const result = await testInstance.runListCommand(testData.appId, {
435
+ from: oneWeekAgo.toISOString(),
436
+ to: now.toISOString()
437
+ });
438
+
439
+ expect(result.exitCode).toBe(0);
440
+ expect(result.stdout).toBeTruthy();
441
+ }, 15000);
442
+
443
+ it('should work with epoch timestamps', async () => {
444
+ const now = Date.now();
445
+ const oneWeekAgo = now - (7 * 24 * 60 * 60 * 1000);
446
+
447
+ const result = await testInstance.runListCommand(testData.appId, {
448
+ from: oneWeekAgo.toString(),
449
+ to: now.toString()
450
+ });
451
+
452
+ // May return success or failure depending on data availability
453
+ expect(result.exitCode).toBeDefined();
454
+ expect(result.stdout).toBeTruthy();
455
+ }, 15000);
456
+
457
+ it('should sort by different columns', async () => {
458
+ const sortColumns = ['id', 'status', 'createdAt', 'updatedAt'];
459
+
460
+ for (const column of sortColumns) {
461
+ const result = await testInstance.runListCommand(testData.appId, {
462
+ sortBy: column,
463
+ limit: 5
464
+ });
465
+
466
+ expect(result.exitCode).toBe(0);
467
+ expect(result.stdout).toBeTruthy();
468
+ }
469
+ }, 60000);
470
+
471
+ it('should work with complex multi-parameter filtering', async () => {
472
+ const result = await testInstance.runListCommand(testData.appId, {
473
+ version: [testData.appVersion],
474
+ function: ['foo'],
475
+ from: '30d',
476
+ sortBy: 'updatedAt',
477
+ sortDirection: 'desc',
478
+ limit: 20,
479
+ columns: 'id,status,function,createdAt,duration'
480
+ });
481
+
482
+ // May return success or failure depending on data availability
483
+ expect(result.exitCode).toBeDefined();
484
+ expect(result.stdout).toBeTruthy();
485
+ }, 20000);
486
+ });
487
+ });