@elisra-devops/docgen-data-provider 1.22.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.
- package/bin/helpers/helper.js.map +1 -1
- package/bin/helpers/test/tfs.test.d.ts +1 -0
- package/bin/helpers/test/tfs.test.js +613 -0
- package/bin/helpers/test/tfs.test.js.map +1 -0
- package/bin/helpers/tfs.js +1 -1
- package/bin/helpers/tfs.js.map +1 -1
- package/bin/modules/GitDataProvider.js.map +1 -1
- package/bin/modules/PipelinesDataProvider.js +3 -2
- package/bin/modules/PipelinesDataProvider.js.map +1 -1
- package/bin/modules/ResultDataProvider.d.ts +199 -17
- package/bin/modules/ResultDataProvider.js +611 -195
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/modules/TestDataProvider.js +7 -7
- package/bin/modules/TestDataProvider.js.map +1 -1
- package/bin/modules/TicketsDataProvider.d.ts +1 -1
- package/bin/modules/TicketsDataProvider.js +3 -2
- package/bin/modules/TicketsDataProvider.js.map +1 -1
- package/bin/modules/test/JfrogDataProvider.test.d.ts +1 -0
- package/bin/modules/test/JfrogDataProvider.test.js +110 -0
- package/bin/modules/test/JfrogDataProvider.test.js.map +1 -0
- package/bin/modules/test/ResultDataProvider.test.d.ts +1 -0
- package/bin/modules/test/ResultDataProvider.test.js +478 -0
- package/bin/modules/test/ResultDataProvider.test.js.map +1 -0
- package/bin/modules/test/gitDataProvider.test.js +424 -120
- package/bin/modules/test/gitDataProvider.test.js.map +1 -1
- package/bin/modules/test/managmentDataProvider.test.js +283 -28
- package/bin/modules/test/managmentDataProvider.test.js.map +1 -1
- package/bin/modules/test/pipelineDataProvider.test.js +229 -45
- package/bin/modules/test/pipelineDataProvider.test.js.map +1 -1
- package/bin/modules/test/testDataProvider.test.js +225 -81
- package/bin/modules/test/testDataProvider.test.js.map +1 -1
- package/bin/modules/test/ticketsDataProvider.test.js +310 -82
- package/bin/modules/test/ticketsDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/helpers/helper.ts +16 -14
- package/src/helpers/test/tfs.test.ts +748 -0
- package/src/helpers/tfs.ts +1 -1
- package/src/modules/GitDataProvider.ts +10 -10
- package/src/modules/PipelinesDataProvider.ts +2 -2
- package/src/modules/ResultDataProvider.ts +815 -260
- package/src/modules/TestDataProvider.ts +8 -8
- package/src/modules/TicketsDataProvider.ts +5 -9
- package/src/modules/test/JfrogDataProvider.test.ts +171 -0
- package/src/modules/test/ResultDataProvider.test.ts +581 -0
- package/src/modules/test/gitDataProvider.test.ts +671 -187
- package/src/modules/test/managmentDataProvider.test.ts +386 -26
- package/src/modules/test/pipelineDataProvider.test.ts +281 -52
- package/src/modules/test/testDataProvider.test.ts +307 -105
- 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":";;;AAEA,MAAa,SAAS;IAMpB,YAAY,IAAY,EAAE,EAAU,EAAE,MAAc,EAAE,KAAa;QACjE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAZD,8BAYC;AACD,MAAa,SAAS;IAAtB;QAEE,SAAI,GAAkB,IAAI,KAAK,EAAU,CAAC;IAC5C,CAAC;CAAA;AAHD,8BAGC;AAED,MAAa,KAAK;CAOjB;AAPD,sBAOC;AACD,MAAa,KAAK;
|
|
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
|