@elisra-devops/docgen-data-provider 1.21.0 → 1.23.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 (49) hide show
  1. package/bin/helpers/helper.js.map +1 -1
  2. package/bin/helpers/test/tfs.test.d.ts +1 -0
  3. package/bin/helpers/test/tfs.test.js +613 -0
  4. package/bin/helpers/test/tfs.test.js.map +1 -0
  5. package/bin/helpers/tfs.js +4 -8
  6. package/bin/helpers/tfs.js.map +1 -1
  7. package/bin/modules/GitDataProvider.js.map +1 -1
  8. package/bin/modules/PipelinesDataProvider.js +3 -2
  9. package/bin/modules/PipelinesDataProvider.js.map +1 -1
  10. package/bin/modules/ResultDataProvider.d.ts +199 -17
  11. package/bin/modules/ResultDataProvider.js +611 -195
  12. package/bin/modules/ResultDataProvider.js.map +1 -1
  13. package/bin/modules/TestDataProvider.js +7 -7
  14. package/bin/modules/TestDataProvider.js.map +1 -1
  15. package/bin/modules/TicketsDataProvider.d.ts +1 -1
  16. package/bin/modules/TicketsDataProvider.js +3 -2
  17. package/bin/modules/TicketsDataProvider.js.map +1 -1
  18. package/bin/modules/test/JfrogDataProvider.test.d.ts +1 -0
  19. package/bin/modules/test/JfrogDataProvider.test.js +110 -0
  20. package/bin/modules/test/JfrogDataProvider.test.js.map +1 -0
  21. package/bin/modules/test/ResultDataProvider.test.d.ts +1 -0
  22. package/bin/modules/test/ResultDataProvider.test.js +478 -0
  23. package/bin/modules/test/ResultDataProvider.test.js.map +1 -0
  24. package/bin/modules/test/gitDataProvider.test.js +424 -120
  25. package/bin/modules/test/gitDataProvider.test.js.map +1 -1
  26. package/bin/modules/test/managmentDataProvider.test.js +283 -28
  27. package/bin/modules/test/managmentDataProvider.test.js.map +1 -1
  28. package/bin/modules/test/pipelineDataProvider.test.js +229 -45
  29. package/bin/modules/test/pipelineDataProvider.test.js.map +1 -1
  30. package/bin/modules/test/testDataProvider.test.js +225 -81
  31. package/bin/modules/test/testDataProvider.test.js.map +1 -1
  32. package/bin/modules/test/ticketsDataProvider.test.js +310 -82
  33. package/bin/modules/test/ticketsDataProvider.test.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/helpers/helper.ts +16 -14
  36. package/src/helpers/test/tfs.test.ts +748 -0
  37. package/src/helpers/tfs.ts +4 -8
  38. package/src/modules/GitDataProvider.ts +10 -10
  39. package/src/modules/PipelinesDataProvider.ts +2 -2
  40. package/src/modules/ResultDataProvider.ts +815 -260
  41. package/src/modules/TestDataProvider.ts +8 -8
  42. package/src/modules/TicketsDataProvider.ts +5 -9
  43. package/src/modules/test/JfrogDataProvider.test.ts +171 -0
  44. package/src/modules/test/ResultDataProvider.test.ts +581 -0
  45. package/src/modules/test/gitDataProvider.test.ts +671 -187
  46. package/src/modules/test/managmentDataProvider.test.ts +386 -26
  47. package/src/modules/test/pipelineDataProvider.test.ts +281 -52
  48. package/src/modules/test/testDataProvider.test.ts +307 -105
  49. package/src/modules/test/ticketsDataProvider.test.ts +425 -129
@@ -1 +1 @@
1
- {"version":3,"file":"helper.js","sourceRoot":"","sources":["../../src/helpers/helper.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"helper.js","sourceRoot":"","sources":["../../src/helpers/helper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,613 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // First, mock axios BEFORE importing TFSServices
4
+ const axios_1 = require("axios");
5
+ jest.mock('axios');
6
+ // Set up the mock axios instance that axios.create will return
7
+ const mockAxiosInstance = {
8
+ request: jest.fn()
9
+ };
10
+ axios_1.default.create.mockReturnValue(mockAxiosInstance);
11
+ // NOW import TFSServices (it will use our mock)
12
+ const tfs_1 = require("../tfs");
13
+ const logger_1 = require("../../utils/logger");
14
+ // Mock logger
15
+ jest.mock('../../utils/logger');
16
+ describe('TFSServices', () => {
17
+ // Store the original implementation of random to restore it later
18
+ const originalRandom = Math.random;
19
+ beforeEach(() => {
20
+ jest.clearAllMocks();
21
+ // Mock Math.random to return a predictable value for tests with retry
22
+ Math.random = jest.fn().mockReturnValue(0.5);
23
+ });
24
+ afterEach(() => {
25
+ // Restore the original Math.random implementation
26
+ Math.random = originalRandom;
27
+ });
28
+ describe('downloadZipFile', () => {
29
+ it('should download a zip file successfully', async () => {
30
+ // Arrange
31
+ const url = 'https://example.com/file.zip';
32
+ const pat = 'token123';
33
+ const mockResponse = { data: Buffer.from('zip-file-content') };
34
+ // Configure mock response
35
+ mockAxiosInstance.request.mockResolvedValueOnce(mockResponse);
36
+ // Act
37
+ const result = await tfs_1.TFSServices.downloadZipFile(url, pat);
38
+ // Assert
39
+ expect(result).toEqual(mockResponse);
40
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith({
41
+ url,
42
+ headers: { 'Content-Type': 'application/zip' },
43
+ auth: { username: '', password: pat }
44
+ });
45
+ });
46
+ it('should log and throw error when download fails', async () => {
47
+ // Arrange
48
+ const url = 'https://example.com/file.zip';
49
+ const pat = 'token123';
50
+ const mockError = new Error('Network error');
51
+ // Configure mock to throw error
52
+ mockAxiosInstance.request.mockRejectedValueOnce(mockError);
53
+ // Act & Assert
54
+ await expect(tfs_1.TFSServices.downloadZipFile(url, pat)).rejects.toThrow();
55
+ expect(logger_1.default.error).toHaveBeenCalledWith(`error download zip file , url : ${url}`);
56
+ });
57
+ });
58
+ describe('fetchAzureDevOpsImageAsBase64', () => {
59
+ it('should fetch and convert image to base64', async () => {
60
+ // Arrange
61
+ const url = 'https://example.com/image.png';
62
+ const pat = 'token123';
63
+ const mockResponse = {
64
+ data: Buffer.from('image-data'),
65
+ headers: { 'content-type': 'image/png' }
66
+ };
67
+ // Configure mock response
68
+ mockAxiosInstance.request.mockResolvedValueOnce(mockResponse);
69
+ // Act
70
+ const result = await tfs_1.TFSServices.fetchAzureDevOpsImageAsBase64(url, pat);
71
+ // Assert
72
+ expect(result).toEqual('data:image/png;base64,aW1hZ2UtZGF0YQ==');
73
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
74
+ url,
75
+ method: 'get',
76
+ auth: { username: '', password: pat },
77
+ responseType: 'arraybuffer'
78
+ }));
79
+ });
80
+ it('should handle errors and retry for retryable errors', async () => {
81
+ // Arrange
82
+ const url = 'https://example.com/image.png';
83
+ const pat = 'token123';
84
+ // Create a rate limit error (retry-eligible)
85
+ const rateLimitError = new Error('Rate limit exceeded');
86
+ rateLimitError.response = { status: 429 };
87
+ // Configure mock to fail once then succeed
88
+ mockAxiosInstance.request
89
+ .mockRejectedValueOnce(rateLimitError)
90
+ .mockResolvedValueOnce({
91
+ data: Buffer.from('image-data'),
92
+ headers: { 'content-type': 'image/png' }
93
+ });
94
+ // Mock setTimeout to execute immediately
95
+ jest.spyOn(global, 'setTimeout').mockImplementation((fn) => {
96
+ fn();
97
+ return {};
98
+ });
99
+ // Act
100
+ const result = await tfs_1.TFSServices.fetchAzureDevOpsImageAsBase64(url, pat);
101
+ // Assert
102
+ expect(result).toEqual('data:image/png;base64,aW1hZ2UtZGF0YQ==');
103
+ expect(mockAxiosInstance.request).toHaveBeenCalledTimes(2);
104
+ expect(logger_1.default.warn).toHaveBeenCalled();
105
+ });
106
+ });
107
+ describe('getItemContent', () => {
108
+ it('should get item content successfully with GET request', async () => {
109
+ // Arrange
110
+ const url = 'https://example.com/api/item';
111
+ const pat = 'token123';
112
+ const mockResponse = { data: { id: 123, name: 'Test Item' } };
113
+ // Configure mock response
114
+ mockAxiosInstance.request.mockResolvedValueOnce(mockResponse);
115
+ // Act
116
+ const result = await tfs_1.TFSServices.getItemContent(url, pat);
117
+ // Assert
118
+ expect(result).toEqual(mockResponse.data);
119
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
120
+ url: url.replace(/ /g, '%20'),
121
+ method: 'get',
122
+ auth: { username: '', password: pat },
123
+ timeout: 10000 // Verify the actual timeout value is 10000ms
124
+ }));
125
+ });
126
+ it('should fail when request times out', async () => {
127
+ // Arrange
128
+ const url = 'https://example.com/api/slow-item';
129
+ const pat = 'token123';
130
+ // Create a timeout error
131
+ const timeoutError = new Error('timeout of 1000ms exceeded');
132
+ timeoutError.name = 'TimeoutError';
133
+ timeoutError.code = 'ECONNABORTED';
134
+ // Configure mock to simulate timeout (will retry 3 times by default)
135
+ mockAxiosInstance.request
136
+ .mockRejectedValueOnce(timeoutError)
137
+ .mockRejectedValueOnce(timeoutError)
138
+ .mockRejectedValueOnce(timeoutError);
139
+ // Mock setTimeout to execute immediately for faster tests
140
+ jest.spyOn(global, 'setTimeout').mockImplementation((fn) => {
141
+ fn();
142
+ return {};
143
+ });
144
+ // Act & Assert
145
+ await expect(tfs_1.TFSServices.getItemContent(url, pat)).rejects.toThrow('timeout');
146
+ expect(mockAxiosInstance.request).toHaveBeenCalledTimes(3); // Initial + 2 retries
147
+ expect(logger_1.default.warn).toHaveBeenCalledTimes(2); // Two retry warnings
148
+ });
149
+ it('should fail when network connection fails', async () => {
150
+ // Arrange
151
+ const url = 'https://example.com/api/item';
152
+ const pat = 'token123';
153
+ // Create different network errors
154
+ const connectionResetError = new Error('socket hang up');
155
+ connectionResetError.code = 'ECONNRESET';
156
+ const connectionRefusedError = new Error('connect ECONNREFUSED');
157
+ connectionRefusedError.code = 'ECONNREFUSED';
158
+ // Configure mock to simulate different network failures on each retry
159
+ mockAxiosInstance.request
160
+ .mockRejectedValueOnce(connectionResetError)
161
+ .mockRejectedValueOnce(connectionRefusedError)
162
+ .mockRejectedValueOnce(connectionResetError);
163
+ // Mock setTimeout to execute immediately for faster tests
164
+ jest.spyOn(global, 'setTimeout').mockImplementation((fn) => {
165
+ fn();
166
+ return {};
167
+ });
168
+ // Act & Assert
169
+ await expect(tfs_1.TFSServices.getItemContent(url, pat)).rejects.toThrow();
170
+ expect(mockAxiosInstance.request).toHaveBeenCalledTimes(2); // Initial + 2 retries
171
+ expect(logger_1.default.error).toHaveBeenCalled(); // Should log detailed error
172
+ });
173
+ it('should handle DNS resolution failures', async () => {
174
+ // Arrange
175
+ const url = 'https://nonexistent-domain.example.com/api/item';
176
+ const pat = 'token123';
177
+ // Create DNS resolution error
178
+ const dnsError = new Error('getaddrinfo ENOTFOUND nonexistent-domain.example.com');
179
+ dnsError.code = 'ENOTFOUND';
180
+ // Configure mock to simulate DNS failure
181
+ mockAxiosInstance.request.mockRejectedValue(dnsError);
182
+ // Act & Assert
183
+ await expect(tfs_1.TFSServices.getItemContent(url, pat)).rejects.toThrow('ENOTFOUND');
184
+ expect(mockAxiosInstance.request).toHaveBeenCalledTimes(2); // Should retry DNS failures too
185
+ });
186
+ it('should handle spaces in URL by replacing them with %20', async () => {
187
+ // Arrange
188
+ const url = 'https://example.com/api/item with spaces';
189
+ const pat = 'token123';
190
+ const mockResponse = { data: { id: 123, name: 'Test Item' } };
191
+ // Configure mock response
192
+ mockAxiosInstance.request.mockResolvedValueOnce(mockResponse);
193
+ // Act
194
+ await tfs_1.TFSServices.getItemContent(url, pat);
195
+ // Assert
196
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({ url: 'https://example.com/api/item%20with%20spaces' }));
197
+ });
198
+ });
199
+ describe('getJfrogRequest', () => {
200
+ it('should make a successful GET request to JFrog', async () => {
201
+ // Arrange
202
+ const url = 'https://jfrog.example.com/api/artifacts';
203
+ const headers = { Authorization: 'Bearer token123' };
204
+ const mockResponse = { data: { artifacts: [{ name: 'artifact1' }] } };
205
+ // Configure mock response
206
+ mockAxiosInstance.request.mockResolvedValueOnce(mockResponse);
207
+ // Act
208
+ const result = await tfs_1.TFSServices.getJfrogRequest(url, headers);
209
+ // Assert
210
+ expect(result).toEqual(mockResponse.data);
211
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith({
212
+ url,
213
+ method: 'GET',
214
+ headers
215
+ });
216
+ });
217
+ it('should handle errors from JFrog API', async () => {
218
+ // Arrange
219
+ const url = 'https://jfrog.example.com/api/artifacts';
220
+ const headers = { Authorization: 'Bearer token123' };
221
+ const mockError = new Error('JFrog API error');
222
+ // Configure mock to throw error
223
+ mockAxiosInstance.request.mockRejectedValueOnce(mockError);
224
+ // Act & Assert
225
+ await expect(tfs_1.TFSServices.getJfrogRequest(url, headers)).rejects.toThrow();
226
+ expect(logger_1.default.error).toHaveBeenCalled();
227
+ });
228
+ });
229
+ describe('postRequest', () => {
230
+ it('should make a successful POST request', async () => {
231
+ // Arrange
232
+ const url = 'https://example.com/api/resource';
233
+ const pat = 'token123';
234
+ const data = { name: 'New Resource' };
235
+ const mockResponse = { data: { id: 123, name: 'New Resource' } };
236
+ // Configure mock response
237
+ mockAxiosInstance.request.mockResolvedValueOnce(mockResponse);
238
+ // Act
239
+ const result = await tfs_1.TFSServices.postRequest(url, pat, 'post', data);
240
+ // Assert
241
+ expect(result).toEqual(mockResponse);
242
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith({
243
+ url,
244
+ method: 'post',
245
+ auth: { username: '', password: pat },
246
+ data,
247
+ headers: { headers: { 'Content-Type': 'application/json' } }
248
+ });
249
+ });
250
+ it('should work with custom headers and methods', async () => {
251
+ // Arrange
252
+ const url = 'https://example.com/api/resource';
253
+ const pat = 'token123';
254
+ const data = { name: 'Update Resource' };
255
+ const customHeaders = { 'Content-Type': 'application/xml' };
256
+ const mockResponse = { data: { id: 123, name: 'Updated Resource' } };
257
+ // Configure mock response
258
+ mockAxiosInstance.request.mockResolvedValueOnce(mockResponse);
259
+ // Act
260
+ const result = await tfs_1.TFSServices.postRequest(url, pat, 'put', data, customHeaders);
261
+ // Assert
262
+ expect(result).toEqual(mockResponse);
263
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith({
264
+ url,
265
+ method: 'put',
266
+ auth: { username: '', password: pat },
267
+ data,
268
+ headers: customHeaders
269
+ });
270
+ });
271
+ it('should handle errors in POST requests', async () => {
272
+ // Arrange
273
+ const url = 'https://example.com/api/resource';
274
+ const pat = 'token123';
275
+ const data = { name: 'New Resource' };
276
+ const mockError = new Error('Validation error');
277
+ mockError.response = { status: 400, data: { message: 'Invalid data' } };
278
+ // Configure mock to throw error
279
+ mockAxiosInstance.request.mockRejectedValueOnce(mockError);
280
+ // Act & Assert
281
+ await expect(tfs_1.TFSServices.postRequest(url, pat, 'post', data)).rejects.toThrow();
282
+ expect(logger_1.default.error).toHaveBeenCalled();
283
+ });
284
+ });
285
+ describe('executeWithRetry', () => {
286
+ it('should retry on network timeouts', async () => {
287
+ // Arrange
288
+ const url = 'https://example.com/api/slow-resource';
289
+ const pat = 'token123';
290
+ // Create a timeout error
291
+ const timeoutError = new Error('timeout of 10000ms exceeded');
292
+ // Configure mock to fail with timeout then succeed
293
+ mockAxiosInstance.request
294
+ .mockRejectedValueOnce(timeoutError)
295
+ .mockResolvedValueOnce({ data: { success: true } });
296
+ // Mock setTimeout to execute immediately
297
+ jest.spyOn(global, 'setTimeout').mockImplementation((fn) => {
298
+ fn();
299
+ return {};
300
+ });
301
+ // Act
302
+ const result = await tfs_1.TFSServices.getItemContent(url, pat);
303
+ // Assert
304
+ expect(result).toEqual({ success: true });
305
+ expect(mockAxiosInstance.request).toHaveBeenCalledTimes(2);
306
+ expect(logger_1.default.warn).toHaveBeenCalled();
307
+ });
308
+ it('should retry on server errors (5xx)', async () => {
309
+ // Arrange
310
+ const url = 'https://example.com/api/unstable-resource';
311
+ const pat = 'token123';
312
+ // Create a 503 error
313
+ const serverError = new Error('Service unavailable');
314
+ serverError.response = { status: 503 };
315
+ // Configure mock to fail with server error then succeed
316
+ mockAxiosInstance.request
317
+ .mockRejectedValueOnce(serverError)
318
+ .mockResolvedValueOnce({ data: { success: true } });
319
+ // Mock setTimeout to execute immediately
320
+ jest.spyOn(global, 'setTimeout').mockImplementation((fn) => {
321
+ fn();
322
+ return {};
323
+ });
324
+ // Act
325
+ const result = await tfs_1.TFSServices.getItemContent(url, pat);
326
+ // Assert
327
+ expect(result).toEqual({ success: true });
328
+ expect(mockAxiosInstance.request).toHaveBeenCalledTimes(2);
329
+ });
330
+ it('should give up after max retry attempts', async () => {
331
+ // Arrange
332
+ const url = 'https://example.com/api/failing-resource';
333
+ const pat = 'token123';
334
+ // Create a persistent server error
335
+ const serverError = new Error('Service unavailable');
336
+ serverError.response = { status: 503 };
337
+ // Configure mock to always fail with server error
338
+ mockAxiosInstance.request.mockRejectedValue(serverError);
339
+ // Mock setTimeout to execute immediately
340
+ jest.spyOn(global, 'setTimeout').mockImplementation((fn) => {
341
+ fn();
342
+ return {};
343
+ });
344
+ // Act & Assert
345
+ await expect(tfs_1.TFSServices.getItemContent(url, pat)).rejects.toThrow();
346
+ // Should try original request + retries up to maxAttempts (3)
347
+ expect(mockAxiosInstance.request).toHaveBeenCalledTimes(3);
348
+ });
349
+ });
350
+ // Add these test cases to the existing test file
351
+ describe('Stress testing', () => {
352
+ describe('getItemContent - Sequential large data requests', () => {
353
+ it('should handle multiple sequential requests with large datasets', async () => {
354
+ // Arrange
355
+ const url = 'https://example.com/api/large-dataset';
356
+ const pat = 'token123';
357
+ // Create 5 large responses of different sizes
358
+ const responses = Array(5).fill(0).map((_, i) => {
359
+ // Create increasingly large responses (500KB, 1MB, 1.5MB, 2MB, 2.5MB)
360
+ const size = 25000 * (i + 1);
361
+ return {
362
+ data: {
363
+ items: Array(size).fill(0).map((_, j) => ({
364
+ id: j,
365
+ name: `Item ${j}`,
366
+ description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
367
+ data: `value-${j}-${Math.random().toString(36).substring(2, 15)}`
368
+ }))
369
+ }
370
+ };
371
+ });
372
+ // Configure the mock to return the different responses in sequence
373
+ for (const response of responses) {
374
+ mockAxiosInstance.request.mockResolvedValueOnce(response);
375
+ }
376
+ // Act - Make multiple sequential requests
377
+ const results = [];
378
+ for (let i = 0; i < responses.length; i++) {
379
+ const result = await tfs_1.TFSServices.getItemContent(`${url}/${i}`, pat);
380
+ results.push(result);
381
+ }
382
+ // Assert
383
+ expect(results.length).toBe(responses.length);
384
+ for (let i = 0; i < results.length; i++) {
385
+ expect(results[i].items.length).toBe(responses[i].data.items.length);
386
+ }
387
+ expect(mockAxiosInstance.request).toHaveBeenCalledTimes(responses.length);
388
+ });
389
+ it('should handle sequential requests with mixed success/failure patterns', async () => {
390
+ // Arrange
391
+ const url = 'https://example.com/api/sequential';
392
+ const pat = 'token123';
393
+ // Set up a sequence of responses/errors
394
+ // 1. Success
395
+ // 2. Rate limit error (429) - should retry and succeed
396
+ // 3. Success
397
+ // 4. Server error (503) - should retry and succeed
398
+ // 5. Success
399
+ const rateLimitError = new Error('Rate limit exceeded');
400
+ rateLimitError.response = { status: 429 };
401
+ const serverError = new Error('Server error');
402
+ serverError.response = { status: 503 };
403
+ // Response 1
404
+ mockAxiosInstance.request.mockResolvedValueOnce({
405
+ data: { id: 1, success: true }
406
+ });
407
+ // Response 2 - fails with rate limit first, then succeeds
408
+ mockAxiosInstance.request
409
+ .mockRejectedValueOnce(rateLimitError)
410
+ .mockResolvedValueOnce({
411
+ data: { id: 2, success: true }
412
+ });
413
+ // Response 3
414
+ mockAxiosInstance.request.mockResolvedValueOnce({
415
+ data: { id: 3, success: true }
416
+ });
417
+ // Response 4 - fails with server error first, then succeeds
418
+ mockAxiosInstance.request
419
+ .mockRejectedValueOnce(serverError)
420
+ .mockResolvedValueOnce({
421
+ data: { id: 4, success: true }
422
+ });
423
+ // Response 5
424
+ mockAxiosInstance.request.mockResolvedValueOnce({
425
+ data: { id: 5, success: true }
426
+ });
427
+ // Mock setTimeout to execute immediately for retry delay
428
+ jest.spyOn(global, 'setTimeout').mockImplementation((fn) => {
429
+ fn();
430
+ return {};
431
+ });
432
+ // Act - Make sequential requests
433
+ const results = [];
434
+ for (let i = 1; i <= 5; i++) {
435
+ const result = await tfs_1.TFSServices.getItemContent(`${url}/${i}`, pat);
436
+ results.push(result);
437
+ }
438
+ // Assert
439
+ expect(results.length).toBe(5);
440
+ expect(results.map(r => r.id)).toEqual([1, 2, 3, 4, 5]);
441
+ // Check total number of requests (5 successful responses + 2 retries)
442
+ expect(mockAxiosInstance.request).toHaveBeenCalledTimes(7);
443
+ });
444
+ });
445
+ describe('postRequest - Sequential large data submissions', () => {
446
+ it('should handle multiple sequential POST requests with large payloads', async () => {
447
+ // Arrange
448
+ const url = 'https://example.com/api/resource';
449
+ const pat = 'token123';
450
+ // Create 5 increasingly large payloads
451
+ const payloads = Array(5).fill(0).map((_, i) => {
452
+ // Create payloads of increasing size (100KB, 200KB, 300KB, 400KB, 500KB)
453
+ const size = 5000 * (i + 1);
454
+ return {
455
+ name: `Resource ${i + 1}`,
456
+ description: `Large resource ${i + 1}`,
457
+ items: Array(size).fill(0).map((_, j) => ({
458
+ id: j,
459
+ value: `item-${j}-${Math.random().toString(36).substring(2, 15)}`,
460
+ timestamp: new Date().toISOString()
461
+ }))
462
+ };
463
+ });
464
+ // Set up mock responses
465
+ for (let i = 0; i < payloads.length; i++) {
466
+ mockAxiosInstance.request.mockResolvedValueOnce({
467
+ data: { id: i + 1, status: 'created', itemCount: payloads[i].items.length }
468
+ });
469
+ }
470
+ // Act - Make multiple sequential POST requests
471
+ const results = [];
472
+ for (let i = 0; i < payloads.length; i++) {
473
+ const result = await tfs_1.TFSServices.postRequest(url, pat, 'post', payloads[i]);
474
+ results.push(result);
475
+ }
476
+ // Assert
477
+ expect(results.length).toBe(payloads.length);
478
+ for (let i = 0; i < results.length; i++) {
479
+ expect(results[i].data.id).toBe(i + 1);
480
+ expect(results[i].data.itemCount).toBe(payloads[i].items.length);
481
+ }
482
+ expect(mockAxiosInstance.request).toHaveBeenCalledTimes(payloads.length);
483
+ });
484
+ it('should handle a mix of success and failure during sequential POST operations', async () => {
485
+ // Arrange
486
+ const url = 'https://example.com/api/resource';
487
+ const pat = 'token123';
488
+ // Create test data
489
+ const payloads = Array(5).fill(0).map((_, i) => ({ name: `Resource ${i + 1}` }));
490
+ // Validation error
491
+ const validationError = new Error('Validation error');
492
+ validationError.response = {
493
+ status: 400,
494
+ data: { message: 'Invalid data', details: 'Field X is required' }
495
+ };
496
+ // Server error
497
+ const serverError = new Error('Server error');
498
+ serverError.response = { status: 500 };
499
+ // Configure mock responses/errors for each request
500
+ // 1. Success
501
+ mockAxiosInstance.request.mockResolvedValueOnce({
502
+ data: { id: 1, status: 'created' }
503
+ });
504
+ // 2. Validation error
505
+ mockAxiosInstance.request.mockRejectedValueOnce(validationError);
506
+ // 3. Success
507
+ mockAxiosInstance.request.mockResolvedValueOnce({
508
+ data: { id: 3, status: 'created' }
509
+ });
510
+ // 4. Server error
511
+ mockAxiosInstance.request.mockRejectedValueOnce(serverError);
512
+ // 5. Success
513
+ mockAxiosInstance.request.mockResolvedValueOnce({
514
+ data: { id: 5, status: 'created' }
515
+ });
516
+ // Act & Assert
517
+ // Request 1 - should succeed
518
+ const result1 = await tfs_1.TFSServices.postRequest(url, pat, 'post', payloads[0]);
519
+ expect(result1.data.id).toBe(1);
520
+ // Request 2 - should fail with validation error
521
+ await expect(tfs_1.TFSServices.postRequest(url, pat, 'post', payloads[1]))
522
+ .rejects.toThrow('Validation error');
523
+ // Request 3 - should succeed despite previous failure
524
+ const result3 = await tfs_1.TFSServices.postRequest(url, pat, 'post', payloads[2]);
525
+ expect(result3.data.id).toBe(3);
526
+ // Request 4 - should fail with server error
527
+ await expect(tfs_1.TFSServices.postRequest(url, pat, 'post', payloads[3]))
528
+ .rejects.toThrow('Server error');
529
+ // Request 5 - should succeed despite previous failure
530
+ const result5 = await tfs_1.TFSServices.postRequest(url, pat, 'post', payloads[4]);
531
+ expect(result5.data.id).toBe(5);
532
+ // Verify all requests were made
533
+ expect(mockAxiosInstance.request).toHaveBeenCalledTimes(5);
534
+ });
535
+ it('should handle large payload with many nested objects', async () => {
536
+ // Arrange
537
+ const url = 'https://example.com/api/complex-resource';
538
+ const pat = 'token123';
539
+ // Create a deeply nested object structure
540
+ const createNestedObject = (depth, breadth, current = 0) => {
541
+ if (current >= depth) {
542
+ return { value: `leaf-${Math.random()}` };
543
+ }
544
+ const children = {};
545
+ for (let i = 0; i < breadth; i++) {
546
+ children[`child-${current}-${i}`] = createNestedObject(depth, breadth, current + 1);
547
+ }
548
+ return {
549
+ id: `node-${current}-${Math.random()}`,
550
+ level: current,
551
+ children
552
+ };
553
+ };
554
+ // Create a complex payload with depth 5 and breadth 5 (5^5 = 3,125 nodes)
555
+ const complexPayload = {
556
+ name: 'Complex Resource',
557
+ type: 'hierarchical',
558
+ rootNode: createNestedObject(5, 5)
559
+ };
560
+ // Configure mock response
561
+ mockAxiosInstance.request.mockResolvedValueOnce({
562
+ data: { id: 123, status: 'created', complexity: 'high' }
563
+ });
564
+ // Act
565
+ const result = await tfs_1.TFSServices.postRequest(url, pat, 'post', complexPayload);
566
+ // Assert
567
+ expect(result.data.id).toBe(123);
568
+ expect(result.data.status).toBe('created');
569
+ // Verify the request was made with the complex payload
570
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
571
+ url,
572
+ method: 'post',
573
+ data: complexPayload
574
+ }));
575
+ });
576
+ });
577
+ describe('High volume sequential operations', () => {
578
+ it('should handle a high volume of sequential API calls', async () => {
579
+ // Arrange
580
+ const url = 'https://example.com/api/items';
581
+ const pat = 'token123';
582
+ const requestCount = 50; // Make 50 sequential requests
583
+ // Configure mock responses for all requests
584
+ for (let i = 0; i < requestCount; i++) {
585
+ mockAxiosInstance.request.mockResolvedValueOnce({
586
+ data: { id: i, success: true, timestamp: new Date().toISOString() }
587
+ });
588
+ }
589
+ // Act
590
+ const results = [];
591
+ for (let i = 0; i < requestCount; i++) {
592
+ // Alternate between GET and POST requests
593
+ if (i % 2 === 0) {
594
+ const result = await tfs_1.TFSServices.getItemContent(`${url}/${i}`, pat);
595
+ results.push(result);
596
+ }
597
+ else {
598
+ const result = await tfs_1.TFSServices.postRequest(url, pat, 'post', { itemId: i });
599
+ results.push(result.data);
600
+ }
601
+ }
602
+ // Assert
603
+ expect(results.length).toBe(requestCount);
604
+ for (let i = 0; i < requestCount; i++) {
605
+ expect(results[i].id).toBe(i);
606
+ expect(results[i].success).toBe(true);
607
+ }
608
+ expect(mockAxiosInstance.request).toHaveBeenCalledTimes(requestCount);
609
+ });
610
+ });
611
+ });
612
+ });
613
+ //# sourceMappingURL=tfs.test.js.map