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