@optimizely/ocp-cli 1.2.13 → 1.2.14
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.
- package/dist/commands/app/Init.js +1 -1
- package/dist/commands/app/Init.js.map +1 -1
- package/dist/oo-cli.manifest.json +1 -1
- package/package.json +10 -6
- package/src/commands/app/Init.ts +1 -1
- package/src/test/e2e/__tests__/accounts/accounts.test.ts +120 -0
- package/src/test/e2e/__tests__/availability/availability.test.ts +156 -0
- package/src/test/e2e/__tests__/directory/directory.test.ts +668 -0
- package/src/test/e2e/__tests__/jobs/jobs.test.ts +487 -0
- package/src/test/e2e/__tests__/review/review.test.ts +355 -0
- package/src/test/e2e/config/fixture-loader.ts +130 -0
- package/src/test/e2e/config/setup.ts +29 -0
- package/src/test/e2e/config/test-data-config.ts +27 -0
- package/src/test/e2e/config/test-data-helpers.ts +23 -0
- package/src/test/e2e/fixtures/baselines/accounts/whoami.txt +11 -0
- package/src/test/e2e/fixtures/baselines/accounts/whois.txt +4 -0
- package/src/test/e2e/fixtures/baselines/directory/info.txt +7 -0
- package/src/test/e2e/fixtures/baselines/directory/list.txt +4 -0
- package/src/test/e2e/fixtures/baselines/jobs/list.txt +4 -0
- package/src/test/e2e/fixtures/baselines/review/list.txt +4 -0
- package/src/test/e2e/lib/base-test.ts +150 -0
- package/src/test/e2e/lib/command-discovery.ts +324 -0
- package/src/test/e2e/utils/baseline-normalizer.ts +79 -0
- package/src/test/e2e/utils/cli-executor.ts +349 -0
- package/src/test/e2e/utils/command-registry.ts +99 -0
- package/src/test/e2e/utils/output-validator.ts +661 -0
- package/src/test/setup.ts +3 -1
- package/src/test/tsconfig.json +17 -0
- package/dist/test/setup.d.ts +0 -0
- package/dist/test/setup.js +0 -4
- package/dist/test/setup.js.map +0 -1
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { BaseE2ETest } from '../../lib/base-test';
|
|
2
|
+
|
|
3
|
+
class ReviewE2ETest extends BaseE2ETest {
|
|
4
|
+
// Review commands
|
|
5
|
+
async runListCommand(appId?: string) {
|
|
6
|
+
const args = ['review', 'list'];
|
|
7
|
+
if (appId) {
|
|
8
|
+
args.push(appId);
|
|
9
|
+
}
|
|
10
|
+
return this.execute(args);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async runOpenCommand(appVersion: string) {
|
|
14
|
+
return this.execute(['review', 'open', appVersion]);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async runOpenCommandWithoutVersion() {
|
|
18
|
+
return this.execute(['review', 'open']);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Public wrapper methods to access protected assertions
|
|
22
|
+
public checkSuccess(result: any) {
|
|
23
|
+
this.assertSuccess(result);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public checkFailure(result: any) {
|
|
27
|
+
this.assertFailure(result);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public checkOutputContains(result: any, text: string) {
|
|
31
|
+
this.assertOutputContains(result, text);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public checkOutputMatchesBaseline(result: any, baselinePath: string, maxLines?: number) {
|
|
35
|
+
this.assertOutputMatchesBaseline(result, baselinePath, maxLines);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public clearHistory() {
|
|
39
|
+
this.cliExecutor.clearProcessHistory();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public executePublic(args: string[], options?: any) {
|
|
43
|
+
return this.execute(args, options);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('Review Commands E2E Tests', () => {
|
|
48
|
+
let testInstance: ReviewE2ETest;
|
|
49
|
+
|
|
50
|
+
beforeAll(() => {
|
|
51
|
+
testInstance = new ReviewE2ETest();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterAll(async () => {
|
|
55
|
+
if (testInstance) {
|
|
56
|
+
await testInstance.cleanup();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
// Clean up process history after each test
|
|
62
|
+
if (testInstance) {
|
|
63
|
+
testInstance.clearHistory();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('review list', () => {
|
|
68
|
+
it('should list all reviews when no app ID is provided', async () => {
|
|
69
|
+
const result = await testInstance.runListCommand();
|
|
70
|
+
|
|
71
|
+
expect(result.exitCode).toBe(0);
|
|
72
|
+
expect(result.stdout).toBeTruthy();
|
|
73
|
+
|
|
74
|
+
// Check baseline format (first 5 lines - header + 1 entry)
|
|
75
|
+
testInstance.checkOutputMatchesBaseline(result, 'review/list.txt', 4);
|
|
76
|
+
}, 15000);
|
|
77
|
+
|
|
78
|
+
it('should filter reviews by app ID when provided', async () => {
|
|
79
|
+
const result = await testInstance.runListCommand('ocp_shakedown');
|
|
80
|
+
|
|
81
|
+
expect(result.exitCode).toBe(0);
|
|
82
|
+
expect(result.stdout).toBeTruthy();
|
|
83
|
+
|
|
84
|
+
// Check for table headers
|
|
85
|
+
expect(result.stdout).toMatch(/App ID/);
|
|
86
|
+
expect(result.stdout).toMatch(/Version/);
|
|
87
|
+
expect(result.stdout).toMatch(/Review Status/);
|
|
88
|
+
expect(result.stdout).toMatch(/Created At/);
|
|
89
|
+
expect(result.stdout).toMatch(/Updated At/);
|
|
90
|
+
|
|
91
|
+
// If there are results, they should only be for the specified app
|
|
92
|
+
const lines = result.stdout.split('\n');
|
|
93
|
+
const dataLines = lines.filter(line =>
|
|
94
|
+
line.trim() &&
|
|
95
|
+
!line.includes('App ID') &&
|
|
96
|
+
!line.includes('---') &&
|
|
97
|
+
line.includes('ocp_shakedown')
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Each data line should contain the app ID we filtered for
|
|
101
|
+
dataLines.forEach(line => {
|
|
102
|
+
if (line.trim()) {
|
|
103
|
+
expect(line).toMatch(/ocp_shakedown/);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}, 15000);
|
|
107
|
+
|
|
108
|
+
it('should handle non-existent app ID gracefully', async () => {
|
|
109
|
+
const result = await testInstance.runListCommand('nonexistent-app-id');
|
|
110
|
+
|
|
111
|
+
expect(result.exitCode).toBe(0);
|
|
112
|
+
expect(result.stdout).toBeTruthy();
|
|
113
|
+
|
|
114
|
+
// Should still show headers even with no results
|
|
115
|
+
expect(result.stdout).toMatch(/App ID/);
|
|
116
|
+
expect(result.stdout).toMatch(/Version/);
|
|
117
|
+
expect(result.stdout).toMatch(/Review Status/);
|
|
118
|
+
}, 15000);
|
|
119
|
+
|
|
120
|
+
it('should display review statuses correctly', async () => {
|
|
121
|
+
const result = await testInstance.runListCommand();
|
|
122
|
+
|
|
123
|
+
expect(result.exitCode).toBe(0);
|
|
124
|
+
expect(result.stdout).toBeTruthy();
|
|
125
|
+
|
|
126
|
+
// Should contain valid review statuses
|
|
127
|
+
const validStatuses = ['IN_REVIEW', 'APPROVED', 'REJECTED'];
|
|
128
|
+
const containsValidStatus = validStatuses.some(status =>
|
|
129
|
+
result.stdout.includes(status)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// If there are any reviews, they should have valid statuses
|
|
133
|
+
const hasDataLines = result.stdout.split('\n').some(line =>
|
|
134
|
+
line.trim() &&
|
|
135
|
+
!line.includes('App ID') &&
|
|
136
|
+
!line.includes('---') &&
|
|
137
|
+
line.includes('.') // Likely contains version numbers
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (hasDataLines) {
|
|
141
|
+
expect(containsValidStatus).toBe(true);
|
|
142
|
+
}
|
|
143
|
+
}, 15000);
|
|
144
|
+
|
|
145
|
+
it('should complete within reasonable time', async () => {
|
|
146
|
+
const result = await testInstance.runListCommand();
|
|
147
|
+
|
|
148
|
+
expect(result.executionTime).toBeLessThan(15000); // 15 seconds
|
|
149
|
+
expect(result.timedOut).toBe(false);
|
|
150
|
+
}, 20000);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('review open', () => {
|
|
154
|
+
it('should fail when app version parameter is missing', async () => {
|
|
155
|
+
const result = await testInstance.runOpenCommandWithoutVersion();
|
|
156
|
+
|
|
157
|
+
expect(result.exitCode).not.toBe(0);
|
|
158
|
+
expect(result.stderr).toBeTruthy();
|
|
159
|
+
expect(result.stderr).toMatch(/Missing required parameter.*appVersion/);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should handle non-existent app version gracefully', async () => {
|
|
163
|
+
const result = await testInstance.runOpenCommand('nonexistent-app@1.0.0');
|
|
164
|
+
|
|
165
|
+
expect(result.exitCode).not.toBe(0);
|
|
166
|
+
expect(result.stderr).toBeTruthy();
|
|
167
|
+
}, 15000);
|
|
168
|
+
|
|
169
|
+
it('should handle malformed app version parameter', async () => {
|
|
170
|
+
const result = await testInstance.runOpenCommand('invalid-format');
|
|
171
|
+
|
|
172
|
+
expect(result.exitCode).not.toBe(0);
|
|
173
|
+
expect(result.stderr).toBeTruthy();
|
|
174
|
+
}, 15000);
|
|
175
|
+
|
|
176
|
+
it('should complete within reasonable time', async () => {
|
|
177
|
+
// Use a dummy app version for timing test
|
|
178
|
+
const result = await testInstance.runOpenCommand('dummy-app@1.0.0');
|
|
179
|
+
|
|
180
|
+
expect(result.executionTime).toBeLessThan(10000); // 10 seconds
|
|
181
|
+
expect(result.timedOut).toBe(false);
|
|
182
|
+
}, 15000);
|
|
183
|
+
|
|
184
|
+
it('should attempt to open valid review URL', async () => {
|
|
185
|
+
// Get a real app version from the review list first
|
|
186
|
+
const listResult = await testInstance.runListCommand();
|
|
187
|
+
|
|
188
|
+
if (listResult.exitCode === 0 && listResult.stdout) {
|
|
189
|
+
const lines = listResult.stdout.split('\n');
|
|
190
|
+
const dataLine = lines.find(line =>
|
|
191
|
+
line.trim() &&
|
|
192
|
+
!line.includes('App ID') &&
|
|
193
|
+
!line.includes('---') &&
|
|
194
|
+
line.includes('.') // Likely contains version numbers
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
if (dataLine) {
|
|
198
|
+
const parts = dataLine.trim().split(/\s+/);
|
|
199
|
+
if (parts.length >= 2) {
|
|
200
|
+
const appId = parts[0];
|
|
201
|
+
const version = parts[1];
|
|
202
|
+
const appVersion = `${appId}@${version}`;
|
|
203
|
+
|
|
204
|
+
const result = await testInstance.runOpenCommand(appVersion);
|
|
205
|
+
|
|
206
|
+
// Command might succeed (if review exists) or fail (if not found)
|
|
207
|
+
expect(result.exitCode).toBeDefined();
|
|
208
|
+
|
|
209
|
+
if (result.exitCode === 0) {
|
|
210
|
+
// If successful, should mention opening or URL
|
|
211
|
+
expect(result.stdout).toMatch(/Review URL|Opened review|url/i);
|
|
212
|
+
} else {
|
|
213
|
+
// If failed, should have error message
|
|
214
|
+
expect(result.stderr).toBeTruthy();
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
// Skip test if no valid app version found
|
|
218
|
+
expect(true).toBe(true);
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
// Skip test if no data found
|
|
222
|
+
expect(true).toBe(true);
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
// Skip test if list command failed
|
|
226
|
+
expect(true).toBe(true);
|
|
227
|
+
}
|
|
228
|
+
}, 25000);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('Error Scenarios', () => {
|
|
232
|
+
it('should handle malformed command gracefully', async () => {
|
|
233
|
+
const result = await testInstance.executePublic(['review', 'invalid-command']);
|
|
234
|
+
|
|
235
|
+
expect(result.exitCode).not.toBe(0);
|
|
236
|
+
expect(result.stderr).toBeTruthy();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should handle unexpected parameters for list command', async () => {
|
|
240
|
+
const result = await testInstance.executePublic(['review', 'list', 'param1', 'param2']);
|
|
241
|
+
expect(result.exitCode).not.toBe(0);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('Performance Tests', () => {
|
|
246
|
+
it('should complete review list within reasonable time', async () => {
|
|
247
|
+
const result = await testInstance.runListCommand();
|
|
248
|
+
|
|
249
|
+
expect(result.executionTime).toBeLessThan(15000); // 15 seconds
|
|
250
|
+
expect(result.executionTime).toBeGreaterThan(0);
|
|
251
|
+
}, 20000);
|
|
252
|
+
|
|
253
|
+
it('should complete review open within reasonable time', async () => {
|
|
254
|
+
const result = await testInstance.runOpenCommand('dummy-app@1.0.0');
|
|
255
|
+
|
|
256
|
+
expect(result.executionTime).toBeLessThan(10000); // 10 seconds
|
|
257
|
+
expect(result.executionTime).toBeGreaterThan(0);
|
|
258
|
+
}, 15000);
|
|
259
|
+
|
|
260
|
+
it('should handle multiple concurrent list requests', async () => {
|
|
261
|
+
const promises = Array(3).fill(null).map(() => testInstance.runListCommand());
|
|
262
|
+
const results = await Promise.all(promises);
|
|
263
|
+
|
|
264
|
+
results.forEach(result => {
|
|
265
|
+
expect(result.exitCode).toBe(0);
|
|
266
|
+
expect(result.stdout).toBeTruthy();
|
|
267
|
+
expect(result.executionTime).toBeLessThan(20000);
|
|
268
|
+
});
|
|
269
|
+
}, 30000);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('Output Validation', () => {
|
|
273
|
+
it('should format output properly', async () => {
|
|
274
|
+
const result = await testInstance.runListCommand();
|
|
275
|
+
|
|
276
|
+
expect(result.exitCode).toBe(0);
|
|
277
|
+
expect(result.stdout).toBeTruthy();
|
|
278
|
+
|
|
279
|
+
// Output should be tabular format
|
|
280
|
+
const lines = result.stdout.trim().split('\n').filter(line => line.trim());
|
|
281
|
+
expect(lines.length).toBeGreaterThanOrEqual(1); // At least header
|
|
282
|
+
|
|
283
|
+
// Header line should contain all expected columns
|
|
284
|
+
const headerLine = lines.find(line =>
|
|
285
|
+
line.includes('App ID') &&
|
|
286
|
+
line.includes('Version') &&
|
|
287
|
+
line.includes('Review Status')
|
|
288
|
+
);
|
|
289
|
+
expect(headerLine).toBeTruthy();
|
|
290
|
+
}, 15000);
|
|
291
|
+
|
|
292
|
+
it('should not contain sensitive information in output', async () => {
|
|
293
|
+
const result = await testInstance.runListCommand();
|
|
294
|
+
|
|
295
|
+
expect(result.exitCode).toBe(0);
|
|
296
|
+
expect(result.stdout).toBeTruthy();
|
|
297
|
+
|
|
298
|
+
// Output should not contain sensitive data patterns
|
|
299
|
+
expect(result.stdout).not.toMatch(/password/i);
|
|
300
|
+
expect(result.stdout).not.toMatch(/token/i);
|
|
301
|
+
expect(result.stdout).not.toMatch(/secret/i);
|
|
302
|
+
expect(result.stdout).not.toMatch(/api.*key/i);
|
|
303
|
+
}, 10000);
|
|
304
|
+
|
|
305
|
+
it('should handle empty results gracefully', async () => {
|
|
306
|
+
const result = await testInstance.runListCommand('definitely-nonexistent-app-12345');
|
|
307
|
+
|
|
308
|
+
expect(result.exitCode).toBe(0);
|
|
309
|
+
expect(result.stdout).toBeTruthy();
|
|
310
|
+
|
|
311
|
+
// Should still show headers
|
|
312
|
+
expect(result.stdout).toMatch(/App ID/);
|
|
313
|
+
expect(result.stdout).toMatch(/Version/);
|
|
314
|
+
expect(result.stdout).toMatch(/Review Status/);
|
|
315
|
+
}, 15000);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('Data Consistency', () => {
|
|
319
|
+
it('should return consistent results across multiple calls', async () => {
|
|
320
|
+
const result1 = await testInstance.runListCommand();
|
|
321
|
+
const result2 = await testInstance.runListCommand();
|
|
322
|
+
|
|
323
|
+
expect(result1.exitCode).toBe(result2.exitCode);
|
|
324
|
+
expect(result1.exitCode).toBe(0);
|
|
325
|
+
|
|
326
|
+
// Results should be consistent (allowing for minor timing differences)
|
|
327
|
+
const lines1 = result1.stdout.split('\n').filter(line => line.trim());
|
|
328
|
+
const lines2 = result2.stdout.split('\n').filter(line => line.trim());
|
|
329
|
+
|
|
330
|
+
// Should have same number of header lines at minimum
|
|
331
|
+
expect(lines1.length).toBeGreaterThan(0);
|
|
332
|
+
expect(lines2.length).toBeGreaterThan(0);
|
|
333
|
+
}, 25000);
|
|
334
|
+
|
|
335
|
+
it('should filter results correctly when app ID is provided', async () => {
|
|
336
|
+
const allResult = await testInstance.runListCommand();
|
|
337
|
+
const filteredResult = await testInstance.runListCommand('ocp_shakedown');
|
|
338
|
+
|
|
339
|
+
expect(allResult.exitCode).toBe(0);
|
|
340
|
+
expect(filteredResult.exitCode).toBe(0);
|
|
341
|
+
|
|
342
|
+
// Filtered results should be subset of all results (or empty)
|
|
343
|
+
const filteredLines = filteredResult.stdout.split('\n').filter(line =>
|
|
344
|
+
line.trim() && !line.includes('App ID') && !line.includes('---') && line.includes('ocp_shakedown')
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
// Each filtered line should be for the specified app
|
|
348
|
+
filteredLines.forEach(line => {
|
|
349
|
+
if (line.trim()) {
|
|
350
|
+
expect(line).toMatch(/ocp_shakedown/);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}, 25000);
|
|
354
|
+
});
|
|
355
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
|
|
4
|
+
export interface TestDataFixture {
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
data: any;
|
|
8
|
+
type: 'json' | 'yaml' | 'text' | 'binary';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface FixtureLoader {
|
|
12
|
+
loadFixture(name: string): TestDataFixture;
|
|
13
|
+
loadFixtures(pattern: string): TestDataFixture[];
|
|
14
|
+
getFixturePath(name: string): string;
|
|
15
|
+
fixtureExists(name: string): boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class FileSystemFixtureLoader implements FixtureLoader {
|
|
19
|
+
private readonly fixturesPath: string;
|
|
20
|
+
private readonly cache: Map<string, TestDataFixture> = new Map();
|
|
21
|
+
|
|
22
|
+
constructor(fixturesPath?: string) {
|
|
23
|
+
this.fixturesPath = fixturesPath || join(__dirname, '../fixtures');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
loadFixture(name: string): TestDataFixture {
|
|
27
|
+
// Check cache first
|
|
28
|
+
if (this.cache.has(name)) {
|
|
29
|
+
return this.cache.get(name)!;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const fixturePath = this.getFixturePath(name);
|
|
33
|
+
|
|
34
|
+
if (!this.fixtureExists(name)) {
|
|
35
|
+
throw new Error(`Fixture not found: ${name} at path ${fixturePath}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const content = readFileSync(fixturePath, 'utf-8');
|
|
40
|
+
const fixture = this.parseFixtureContent(name, content);
|
|
41
|
+
|
|
42
|
+
// Cache the fixture
|
|
43
|
+
this.cache.set(name, fixture);
|
|
44
|
+
|
|
45
|
+
return fixture;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new Error(`Failed to load fixture ${name}: ${error}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
loadFixtures(_pattern: string): TestDataFixture[] {
|
|
52
|
+
// For now, implement basic pattern matching
|
|
53
|
+
// In a real implementation, you might use glob patterns
|
|
54
|
+
const fixtures: TestDataFixture[] = [];
|
|
55
|
+
|
|
56
|
+
// This is a simplified implementation
|
|
57
|
+
// You could extend this to use glob patterns or other matching logic
|
|
58
|
+
return fixtures;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getFixturePath(name: string): string {
|
|
62
|
+
// Support different file extensions
|
|
63
|
+
const extensions = ['.json', '.yaml', '.yml', '.txt', '.xml'];
|
|
64
|
+
|
|
65
|
+
for (const ext of extensions) {
|
|
66
|
+
const fullPath = join(this.fixturesPath, `${name}${ext}`);
|
|
67
|
+
if (existsSync(fullPath)) {
|
|
68
|
+
return fullPath;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Default to .json if no extension provided
|
|
73
|
+
return join(this.fixturesPath, name.includes('.') ? name : `${name}.json`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fixtureExists(name: string): boolean {
|
|
77
|
+
return existsSync(this.getFixturePath(name));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private parseFixtureContent(name: string, content: string): TestDataFixture {
|
|
81
|
+
const fixturePath = this.getFixturePath(name);
|
|
82
|
+
const extension = fixturePath.split('.').pop()?.toLowerCase();
|
|
83
|
+
|
|
84
|
+
let data: any;
|
|
85
|
+
let type: TestDataFixture['type'];
|
|
86
|
+
|
|
87
|
+
switch (extension) {
|
|
88
|
+
case 'json':
|
|
89
|
+
try {
|
|
90
|
+
data = JSON.parse(content);
|
|
91
|
+
type = 'json';
|
|
92
|
+
} catch (error) {
|
|
93
|
+
throw new Error(`Invalid JSON in fixture ${name}: ${error}`);
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
|
|
97
|
+
case 'yaml':
|
|
98
|
+
case 'yml':
|
|
99
|
+
// For YAML parsing, you might want to add a YAML library like 'js-yaml'
|
|
100
|
+
// For now, treat as text
|
|
101
|
+
data = content;
|
|
102
|
+
type = 'yaml';
|
|
103
|
+
break;
|
|
104
|
+
|
|
105
|
+
case 'txt':
|
|
106
|
+
case 'md':
|
|
107
|
+
data = content;
|
|
108
|
+
type = 'text';
|
|
109
|
+
break;
|
|
110
|
+
|
|
111
|
+
default:
|
|
112
|
+
data = content;
|
|
113
|
+
type = 'text';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
name,
|
|
118
|
+
data,
|
|
119
|
+
type,
|
|
120
|
+
description: `Test fixture: ${name}`
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
clearCache(): void {
|
|
125
|
+
this.cache.clear();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Global fixture loader instance
|
|
130
|
+
export const fixtureLoader = new FileSystemFixtureLoader();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { afterEach, vi } from 'vitest';
|
|
3
|
+
import { CLIExecutor } from '../utils/cli-executor';
|
|
4
|
+
|
|
5
|
+
// Global test configuration - just the essentials
|
|
6
|
+
const TEST_CONFIG = {
|
|
7
|
+
CLI_PATH: join(__dirname, '../../../../bin/ocp.js'),
|
|
8
|
+
FIXTURES_PATH: join(__dirname, '../fixtures'),
|
|
9
|
+
BASELINES_PATH: join(__dirname, '../fixtures/baselines'),
|
|
10
|
+
DEFAULT_TIMEOUT: 20000,
|
|
11
|
+
LONG_TIMEOUT: 30000
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Make test config globally available
|
|
15
|
+
(global as any).TEST_CONFIG = TEST_CONFIG;
|
|
16
|
+
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
new CLIExecutor().execute(['env', 'set', 'staging']);
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
// Cleanup after each test
|
|
23
|
+
vi.clearAllMocks();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Global error handler for unhandled rejections
|
|
27
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
28
|
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
29
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Data Configuration
|
|
3
|
+
*
|
|
4
|
+
* Centralized configuration for test data parameters used across E2E tests.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface TestDataConfig {
|
|
8
|
+
appId: string;
|
|
9
|
+
appVersion: string;
|
|
10
|
+
trackerId: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Default test data configuration
|
|
15
|
+
*/
|
|
16
|
+
export const TEST_DATA: TestDataConfig = {
|
|
17
|
+
appId: process.env.OCP_TEST_APP_ID || "hub_shakedown",
|
|
18
|
+
appVersion: process.env.OCP_TEST_APP_VERSION || "2.2.0",
|
|
19
|
+
trackerId: process.env.OCP_TEST_TRACKER_ID || "Z6jCndly_E2WzpvDkcKBKA",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the current test data configuration
|
|
24
|
+
*/
|
|
25
|
+
export function getTestData(): TestDataConfig {
|
|
26
|
+
return TEST_DATA;
|
|
27
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Data Helper Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { TEST_DATA } from './test-data-config';
|
|
6
|
+
|
|
7
|
+
export const testData = {
|
|
8
|
+
get appId(): string {
|
|
9
|
+
return TEST_DATA.appId;
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
get appVersion(): string {
|
|
13
|
+
return TEST_DATA.appVersion;
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
get trackerId(): string {
|
|
17
|
+
return TEST_DATA.trackerId;
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
get appVersionString(): string {
|
|
21
|
+
return `${TEST_DATA.appId}@${TEST_DATA.appVersion}`;
|
|
22
|
+
}
|
|
23
|
+
};
|