@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
|
@@ -0,0 +1,1038 @@
|
|
|
1
|
+
import { PipelineRun, Repository, ResourceRepository } from '../../models/tfs-data';
|
|
2
|
+
import { TFSServices } from '../../helpers/tfs';
|
|
3
|
+
import PipelinesDataProvider from '../../modules/PipelinesDataProvider';
|
|
4
|
+
import GitDataProvider from '../../modules/GitDataProvider';
|
|
5
|
+
import logger from '../../utils/logger';
|
|
6
|
+
|
|
7
|
+
jest.mock('../../helpers/tfs');
|
|
8
|
+
jest.mock('../../utils/logger');
|
|
9
|
+
jest.mock('../../modules/GitDataProvider');
|
|
10
|
+
|
|
11
|
+
describe('PipelinesDataProvider', () => {
|
|
12
|
+
let pipelinesDataProvider: PipelinesDataProvider;
|
|
13
|
+
const mockOrgUrl = 'https://dev.azure.com/orgname/';
|
|
14
|
+
const mockToken = 'mock-token';
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
pipelinesDataProvider = new PipelinesDataProvider(mockOrgUrl, mockToken);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('isMatchingPipeline', () => {
|
|
22
|
+
// Create test method to access private method
|
|
23
|
+
const invokeIsMatchingPipeline = (
|
|
24
|
+
fromPipeline: PipelineRun,
|
|
25
|
+
targetPipeline: PipelineRun,
|
|
26
|
+
searchPrevPipelineFromDifferentCommit: boolean
|
|
27
|
+
): boolean => {
|
|
28
|
+
return (pipelinesDataProvider as any).isMatchingPipeline(
|
|
29
|
+
fromPipeline,
|
|
30
|
+
targetPipeline,
|
|
31
|
+
searchPrevPipelineFromDifferentCommit
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
it('should return false when repository IDs are different', () => {
|
|
36
|
+
// Arrange
|
|
37
|
+
const fromPipeline = {
|
|
38
|
+
resources: {
|
|
39
|
+
repositories: {
|
|
40
|
+
'0': {
|
|
41
|
+
self: {
|
|
42
|
+
repository: { id: 'repo1' },
|
|
43
|
+
version: 'v1',
|
|
44
|
+
refName: 'refs/heads/main',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
} as unknown as PipelineRun;
|
|
50
|
+
|
|
51
|
+
const targetPipeline = {
|
|
52
|
+
resources: {
|
|
53
|
+
repositories: {
|
|
54
|
+
'0': {
|
|
55
|
+
self: {
|
|
56
|
+
repository: { id: 'repo2' },
|
|
57
|
+
version: 'v1',
|
|
58
|
+
refName: 'refs/heads/main',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
} as unknown as PipelineRun;
|
|
64
|
+
|
|
65
|
+
// Act
|
|
66
|
+
const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline, false);
|
|
67
|
+
|
|
68
|
+
// Assert
|
|
69
|
+
expect(result).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should return true when versions are the same and searchPrevPipelineFromDifferentCommit is false', () => {
|
|
73
|
+
// Arrange
|
|
74
|
+
const fromPipeline = {
|
|
75
|
+
resources: {
|
|
76
|
+
repositories: {
|
|
77
|
+
'0': {
|
|
78
|
+
self: {
|
|
79
|
+
repository: { id: 'repo1' },
|
|
80
|
+
version: 'v1',
|
|
81
|
+
refName: 'refs/heads/main',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
} as unknown as PipelineRun;
|
|
87
|
+
|
|
88
|
+
const targetPipeline = {
|
|
89
|
+
resources: {
|
|
90
|
+
repositories: {
|
|
91
|
+
'0': {
|
|
92
|
+
self: {
|
|
93
|
+
repository: { id: 'repo1' },
|
|
94
|
+
version: 'v1',
|
|
95
|
+
refName: 'refs/heads/main',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
} as unknown as PipelineRun;
|
|
101
|
+
|
|
102
|
+
// Act
|
|
103
|
+
const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline, false);
|
|
104
|
+
|
|
105
|
+
// Assert
|
|
106
|
+
expect(result).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should return false when versions are the same and searchPrevPipelineFromDifferentCommit is true', () => {
|
|
110
|
+
// Arrange
|
|
111
|
+
const fromPipeline = {
|
|
112
|
+
resources: {
|
|
113
|
+
repositories: {
|
|
114
|
+
'0': {
|
|
115
|
+
self: {
|
|
116
|
+
repository: { id: 'repo1' },
|
|
117
|
+
version: 'v1',
|
|
118
|
+
refName: 'refs/heads/main',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
} as unknown as PipelineRun;
|
|
124
|
+
|
|
125
|
+
const targetPipeline = {
|
|
126
|
+
resources: {
|
|
127
|
+
repositories: {
|
|
128
|
+
'0': {
|
|
129
|
+
self: {
|
|
130
|
+
repository: { id: 'repo1' },
|
|
131
|
+
version: 'v1',
|
|
132
|
+
refName: 'refs/heads/main',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
} as unknown as PipelineRun;
|
|
138
|
+
|
|
139
|
+
// Act
|
|
140
|
+
const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline, true);
|
|
141
|
+
|
|
142
|
+
// Assert
|
|
143
|
+
expect(result).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should return true when refNames match but versions differ', () => {
|
|
147
|
+
// Arrange
|
|
148
|
+
const fromPipeline = {
|
|
149
|
+
resources: {
|
|
150
|
+
repositories: {
|
|
151
|
+
'0': {
|
|
152
|
+
self: {
|
|
153
|
+
repository: { id: 'repo1' },
|
|
154
|
+
version: 'v1',
|
|
155
|
+
refName: 'refs/heads/main',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
} as unknown as PipelineRun;
|
|
161
|
+
|
|
162
|
+
const targetPipeline = {
|
|
163
|
+
resources: {
|
|
164
|
+
repositories: {
|
|
165
|
+
'0': {
|
|
166
|
+
self: {
|
|
167
|
+
repository: { id: 'repo1' },
|
|
168
|
+
version: 'v2',
|
|
169
|
+
refName: 'refs/heads/main',
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
} as unknown as PipelineRun;
|
|
175
|
+
|
|
176
|
+
// Act
|
|
177
|
+
const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline, true);
|
|
178
|
+
|
|
179
|
+
// Assert
|
|
180
|
+
expect(result).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should use __designer_repo when self is not available', () => {
|
|
184
|
+
// Arrange
|
|
185
|
+
const fromPipeline = {
|
|
186
|
+
resources: {
|
|
187
|
+
repositories: {
|
|
188
|
+
__designer_repo: {
|
|
189
|
+
repository: { id: 'repo1' },
|
|
190
|
+
version: 'v1',
|
|
191
|
+
refName: 'refs/heads/main',
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
} as unknown as PipelineRun;
|
|
196
|
+
|
|
197
|
+
const targetPipeline = {
|
|
198
|
+
resources: {
|
|
199
|
+
repositories: {
|
|
200
|
+
__designer_repo: {
|
|
201
|
+
repository: { id: 'repo1' },
|
|
202
|
+
version: 'v1',
|
|
203
|
+
refName: 'refs/heads/main',
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
} as unknown as PipelineRun;
|
|
208
|
+
|
|
209
|
+
// Act
|
|
210
|
+
const result = invokeIsMatchingPipeline(fromPipeline, targetPipeline, false);
|
|
211
|
+
|
|
212
|
+
// Assert
|
|
213
|
+
expect(result).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('getPipelineRunDetails', () => {
|
|
218
|
+
it('should call TFSServices.getItemContent with correct parameters', async () => {
|
|
219
|
+
// Arrange
|
|
220
|
+
const projectName = 'project1';
|
|
221
|
+
const pipelineId = 123;
|
|
222
|
+
const runId = 456;
|
|
223
|
+
const mockResponse = { id: runId, resources: {} };
|
|
224
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
225
|
+
|
|
226
|
+
// Act
|
|
227
|
+
const result = await pipelinesDataProvider.getPipelineRunDetails(projectName, pipelineId, runId);
|
|
228
|
+
|
|
229
|
+
// Assert
|
|
230
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
231
|
+
`${mockOrgUrl}${projectName}/_apis/pipelines/${pipelineId}/runs/${runId}`,
|
|
232
|
+
mockToken
|
|
233
|
+
);
|
|
234
|
+
expect(result).toEqual(mockResponse);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('GetPipelineRunHistory', () => {
|
|
239
|
+
it('should return filtered pipeline run history', async () => {
|
|
240
|
+
// Arrange
|
|
241
|
+
const projectName = 'project1';
|
|
242
|
+
const pipelineId = '123';
|
|
243
|
+
const mockResponse = {
|
|
244
|
+
value: [
|
|
245
|
+
{ id: 1, result: 'succeeded' },
|
|
246
|
+
{ id: 2, result: 'failed' },
|
|
247
|
+
{ id: 3, result: 'canceled' },
|
|
248
|
+
{ id: 4, result: 'succeeded' },
|
|
249
|
+
],
|
|
250
|
+
};
|
|
251
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
252
|
+
|
|
253
|
+
// Act
|
|
254
|
+
const result = await pipelinesDataProvider.GetPipelineRunHistory(projectName, pipelineId);
|
|
255
|
+
|
|
256
|
+
// Assert
|
|
257
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
258
|
+
`${mockOrgUrl}${projectName}/_apis/pipelines/${pipelineId}/runs`,
|
|
259
|
+
mockToken,
|
|
260
|
+
'get',
|
|
261
|
+
null,
|
|
262
|
+
null
|
|
263
|
+
);
|
|
264
|
+
expect(result).toEqual({
|
|
265
|
+
count: 4, // Note: Current filter logic keeps all runs where result is not 'failed' AND not 'canceled'
|
|
266
|
+
value: mockResponse.value,
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should handle API errors gracefully', async () => {
|
|
271
|
+
// Arrange
|
|
272
|
+
const projectName = 'project1';
|
|
273
|
+
const pipelineId = '123';
|
|
274
|
+
const expectedError = new Error('API error');
|
|
275
|
+
(TFSServices.getItemContent as jest.Mock).mockRejectedValueOnce(expectedError);
|
|
276
|
+
|
|
277
|
+
// Act
|
|
278
|
+
const result = await pipelinesDataProvider.GetPipelineRunHistory(projectName, pipelineId);
|
|
279
|
+
|
|
280
|
+
// Assert
|
|
281
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
282
|
+
`${mockOrgUrl}${projectName}/_apis/pipelines/${pipelineId}/runs`,
|
|
283
|
+
mockToken,
|
|
284
|
+
'get',
|
|
285
|
+
null,
|
|
286
|
+
null
|
|
287
|
+
);
|
|
288
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
289
|
+
`Could not fetch Pipeline Run History: ${expectedError.message}`
|
|
290
|
+
);
|
|
291
|
+
expect(result).toBeUndefined();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should return response when value is undefined', async () => {
|
|
295
|
+
// Arrange
|
|
296
|
+
const projectName = 'project1';
|
|
297
|
+
const pipelineId = '123';
|
|
298
|
+
const mockResponse = { count: 0 };
|
|
299
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
300
|
+
|
|
301
|
+
// Act
|
|
302
|
+
const result = await pipelinesDataProvider.GetPipelineRunHistory(projectName, pipelineId);
|
|
303
|
+
|
|
304
|
+
// Assert
|
|
305
|
+
expect(result).toEqual(mockResponse);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe('getPipelineBuildByBuildId', () => {
|
|
310
|
+
it('should fetch pipeline build by build ID', async () => {
|
|
311
|
+
// Arrange
|
|
312
|
+
const projectName = 'project1';
|
|
313
|
+
const buildId = 123;
|
|
314
|
+
const mockResponse = { id: buildId, buildNumber: '20231201.1' };
|
|
315
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
316
|
+
|
|
317
|
+
// Act
|
|
318
|
+
const result = await pipelinesDataProvider.getPipelineBuildByBuildId(projectName, buildId);
|
|
319
|
+
|
|
320
|
+
// Assert
|
|
321
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
322
|
+
`${mockOrgUrl}${projectName}/_apis/build/builds/${buildId}`,
|
|
323
|
+
mockToken,
|
|
324
|
+
'get'
|
|
325
|
+
);
|
|
326
|
+
expect(result).toEqual(mockResponse);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe('TriggerBuildById', () => {
|
|
331
|
+
it('should trigger a build with parameters', async () => {
|
|
332
|
+
// Arrange
|
|
333
|
+
const projectName = 'project1';
|
|
334
|
+
const buildDefId = '456';
|
|
335
|
+
const parameters = '{"Test":"123"}';
|
|
336
|
+
const mockResponse = { id: 789, status: 'queued' };
|
|
337
|
+
(TFSServices.postRequest as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
338
|
+
|
|
339
|
+
// Act
|
|
340
|
+
const result = await pipelinesDataProvider.TriggerBuildById(projectName, buildDefId, parameters);
|
|
341
|
+
|
|
342
|
+
// Assert
|
|
343
|
+
expect(TFSServices.postRequest).toHaveBeenCalledWith(
|
|
344
|
+
`${mockOrgUrl}${projectName}/_apis/build/builds?api-version=5.0`,
|
|
345
|
+
mockToken,
|
|
346
|
+
'post',
|
|
347
|
+
{
|
|
348
|
+
definition: { id: buildDefId },
|
|
349
|
+
parameters: parameters,
|
|
350
|
+
},
|
|
351
|
+
null
|
|
352
|
+
);
|
|
353
|
+
expect(result).toEqual(mockResponse);
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
describe('GetArtifactByBuildId', () => {
|
|
358
|
+
it('should return empty response when no artifacts exist', async () => {
|
|
359
|
+
// Arrange
|
|
360
|
+
const projectName = 'project1';
|
|
361
|
+
const buildId = '123';
|
|
362
|
+
const artifactName = 'drop';
|
|
363
|
+
const mockResponse = { count: 0 };
|
|
364
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
365
|
+
|
|
366
|
+
// Act
|
|
367
|
+
const result = await pipelinesDataProvider.GetArtifactByBuildId(projectName, buildId, artifactName);
|
|
368
|
+
|
|
369
|
+
// Assert
|
|
370
|
+
expect(result).toEqual(mockResponse);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('should download artifact when it exists', async () => {
|
|
374
|
+
// Arrange
|
|
375
|
+
const projectName = 'project1';
|
|
376
|
+
const buildId = '123';
|
|
377
|
+
const artifactName = 'drop';
|
|
378
|
+
const mockArtifactsResponse = { count: 1 };
|
|
379
|
+
const mockArtifactResponse = {
|
|
380
|
+
resource: { downloadUrl: 'https://example.com/download' },
|
|
381
|
+
};
|
|
382
|
+
const mockDownloadResult = { data: Buffer.from('zip content') };
|
|
383
|
+
|
|
384
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
385
|
+
.mockResolvedValueOnce(mockArtifactsResponse)
|
|
386
|
+
.mockResolvedValueOnce(mockArtifactResponse);
|
|
387
|
+
(TFSServices.downloadZipFile as jest.Mock).mockResolvedValueOnce(mockDownloadResult);
|
|
388
|
+
|
|
389
|
+
// Act
|
|
390
|
+
const result = await pipelinesDataProvider.GetArtifactByBuildId(projectName, buildId, artifactName);
|
|
391
|
+
|
|
392
|
+
// Assert
|
|
393
|
+
expect(TFSServices.downloadZipFile).toHaveBeenCalledWith('https://example.com/download', mockToken);
|
|
394
|
+
expect(result).toEqual(mockDownloadResult);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('should throw error when artifact fetch fails', async () => {
|
|
398
|
+
// Arrange
|
|
399
|
+
const projectName = 'project1';
|
|
400
|
+
const buildId = '123';
|
|
401
|
+
const artifactName = 'drop';
|
|
402
|
+
const mockError = new Error('Artifact not found');
|
|
403
|
+
(TFSServices.getItemContent as jest.Mock).mockRejectedValueOnce(mockError);
|
|
404
|
+
|
|
405
|
+
// Act & Assert
|
|
406
|
+
await expect(
|
|
407
|
+
pipelinesDataProvider.GetArtifactByBuildId(projectName, buildId, artifactName)
|
|
408
|
+
).rejects.toThrow();
|
|
409
|
+
expect(logger.error).toHaveBeenCalled();
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe('getPipelineStageName', () => {
|
|
414
|
+
it('should return the matching Stage record', async () => {
|
|
415
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({
|
|
416
|
+
records: [
|
|
417
|
+
{ type: 'Stage', name: 'Deploy', state: 'completed', result: 'succeeded' },
|
|
418
|
+
{ type: 'Job', name: 'Job1' },
|
|
419
|
+
],
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const record = await (pipelinesDataProvider as any).getPipelineStageName(123, 'project1', 'Deploy');
|
|
423
|
+
expect(record).toEqual(expect.objectContaining({ type: 'Stage', name: 'Deploy' }));
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('should return undefined when no matching stage exists', async () => {
|
|
427
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({
|
|
428
|
+
records: [{ type: 'Stage', name: 'Build', state: 'completed', result: 'succeeded' }],
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const record = await (pipelinesDataProvider as any).getPipelineStageName(123, 'project1', 'Deploy');
|
|
432
|
+
expect(record).toBeUndefined();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('should return undefined on fetch error and log', async () => {
|
|
436
|
+
(TFSServices.getItemContent as jest.Mock).mockRejectedValueOnce(new Error('boom'));
|
|
437
|
+
|
|
438
|
+
const record = await (pipelinesDataProvider as any).getPipelineStageName(123, 'project1', 'Deploy');
|
|
439
|
+
expect(record).toBeUndefined();
|
|
440
|
+
expect(logger.error).toHaveBeenCalled();
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
describe('isStageSuccessful', () => {
|
|
445
|
+
it('should return false when stage is missing', async () => {
|
|
446
|
+
jest.spyOn(pipelinesDataProvider as any, 'getPipelineStageName').mockResolvedValueOnce(undefined);
|
|
447
|
+
await expect(
|
|
448
|
+
(pipelinesDataProvider as any).isStageSuccessful({ id: 1 }, 'project1', 'Deploy')
|
|
449
|
+
).resolves.toBeUndefined();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('should return false when stage is not completed', async () => {
|
|
453
|
+
jest
|
|
454
|
+
.spyOn(pipelinesDataProvider as any, 'getPipelineStageName')
|
|
455
|
+
.mockResolvedValueOnce({ state: 'inProgress', result: 'succeeded' });
|
|
456
|
+
await expect(
|
|
457
|
+
(pipelinesDataProvider as any).isStageSuccessful({ id: 1 }, 'project1', 'Deploy')
|
|
458
|
+
).resolves.toBe(false);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should return false when stage result is not succeeded', async () => {
|
|
462
|
+
jest
|
|
463
|
+
.spyOn(pipelinesDataProvider as any, 'getPipelineStageName')
|
|
464
|
+
.mockResolvedValueOnce({ state: 'completed', result: 'failed' });
|
|
465
|
+
await expect(
|
|
466
|
+
(pipelinesDataProvider as any).isStageSuccessful({ id: 1 }, 'project1', 'Deploy')
|
|
467
|
+
).resolves.toBe(false);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('should return true when stage is completed and succeeded', async () => {
|
|
471
|
+
jest
|
|
472
|
+
.spyOn(pipelinesDataProvider as any, 'getPipelineStageName')
|
|
473
|
+
.mockResolvedValueOnce({ state: 'completed', result: 'succeeded' });
|
|
474
|
+
await expect(
|
|
475
|
+
(pipelinesDataProvider as any).isStageSuccessful({ id: 1 }, 'project1', 'Deploy')
|
|
476
|
+
).resolves.toBe(true);
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
describe('GetReleaseByReleaseId', () => {
|
|
481
|
+
it('should fetch release by ID', async () => {
|
|
482
|
+
// Arrange
|
|
483
|
+
const projectName = 'project1';
|
|
484
|
+
const releaseId = 123;
|
|
485
|
+
const mockResponse = { id: releaseId, name: 'Release-1' };
|
|
486
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
487
|
+
|
|
488
|
+
// Act
|
|
489
|
+
const result = await pipelinesDataProvider.GetReleaseByReleaseId(projectName, releaseId);
|
|
490
|
+
|
|
491
|
+
// Assert
|
|
492
|
+
expect(result).toEqual(mockResponse);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('should replace dev.azure.com with vsrm.dev.azure.com for release URL', async () => {
|
|
496
|
+
// Arrange
|
|
497
|
+
const projectName = 'project1';
|
|
498
|
+
const releaseId = 123;
|
|
499
|
+
const mockResponse = { id: releaseId };
|
|
500
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
501
|
+
|
|
502
|
+
// Act
|
|
503
|
+
await pipelinesDataProvider.GetReleaseByReleaseId(projectName, releaseId);
|
|
504
|
+
|
|
505
|
+
// Assert
|
|
506
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
507
|
+
expect.stringContaining('vsrm.dev.azure.com'),
|
|
508
|
+
mockToken,
|
|
509
|
+
'get',
|
|
510
|
+
null,
|
|
511
|
+
null
|
|
512
|
+
);
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
describe('GetReleaseHistory', () => {
|
|
517
|
+
it('should fetch release history for a definition', async () => {
|
|
518
|
+
// Arrange
|
|
519
|
+
const projectName = 'project1';
|
|
520
|
+
const definitionId = '456';
|
|
521
|
+
const mockResponse = { value: [{ id: 1 }, { id: 2 }] };
|
|
522
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
523
|
+
|
|
524
|
+
// Act
|
|
525
|
+
const result = await pipelinesDataProvider.GetReleaseHistory(projectName, definitionId);
|
|
526
|
+
|
|
527
|
+
// Assert
|
|
528
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
529
|
+
expect.stringContaining(`definitionId=${definitionId}`),
|
|
530
|
+
mockToken,
|
|
531
|
+
'get',
|
|
532
|
+
null,
|
|
533
|
+
null
|
|
534
|
+
);
|
|
535
|
+
expect(result).toEqual(mockResponse);
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
describe('GetAllReleaseHistory', () => {
|
|
540
|
+
it('should fetch all releases with pagination', async () => {
|
|
541
|
+
// Arrange
|
|
542
|
+
const projectName = 'project1';
|
|
543
|
+
const definitionId = '456';
|
|
544
|
+
const mockResponse1 = {
|
|
545
|
+
data: { value: [{ id: 1 }, { id: 2 }] },
|
|
546
|
+
headers: { 'x-ms-continuationtoken': 'token123' },
|
|
547
|
+
};
|
|
548
|
+
const mockResponse2 = {
|
|
549
|
+
data: { value: [{ id: 3 }] },
|
|
550
|
+
headers: {},
|
|
551
|
+
};
|
|
552
|
+
(TFSServices.getItemContentWithHeaders as jest.Mock)
|
|
553
|
+
.mockResolvedValueOnce(mockResponse1)
|
|
554
|
+
.mockResolvedValueOnce(mockResponse2);
|
|
555
|
+
|
|
556
|
+
// Act
|
|
557
|
+
const result = await pipelinesDataProvider.GetAllReleaseHistory(projectName, definitionId);
|
|
558
|
+
|
|
559
|
+
// Assert
|
|
560
|
+
expect(result.count).toBe(3);
|
|
561
|
+
expect(result.value).toHaveLength(3);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('should support x-ms-continuation-token header and default value when data is missing', async () => {
|
|
565
|
+
const projectName = 'project1';
|
|
566
|
+
const definitionId = '456';
|
|
567
|
+
|
|
568
|
+
(TFSServices.getItemContentWithHeaders as jest.Mock)
|
|
569
|
+
.mockResolvedValueOnce({
|
|
570
|
+
data: undefined,
|
|
571
|
+
headers: { 'x-ms-continuation-token': 'token123' },
|
|
572
|
+
})
|
|
573
|
+
.mockResolvedValueOnce({
|
|
574
|
+
data: { value: [{ id: 1 }] },
|
|
575
|
+
headers: {},
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
const result = await pipelinesDataProvider.GetAllReleaseHistory(projectName, definitionId);
|
|
579
|
+
|
|
580
|
+
expect(result.count).toBe(1);
|
|
581
|
+
expect(result.value).toEqual([{ id: 1 }]);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('should handle errors during pagination', async () => {
|
|
585
|
+
// Arrange
|
|
586
|
+
const projectName = 'project1';
|
|
587
|
+
const definitionId = '456';
|
|
588
|
+
(TFSServices.getItemContentWithHeaders as jest.Mock).mockRejectedValueOnce(new Error('API Error'));
|
|
589
|
+
|
|
590
|
+
// Act
|
|
591
|
+
const result = await pipelinesDataProvider.GetAllReleaseHistory(projectName, definitionId);
|
|
592
|
+
|
|
593
|
+
// Assert
|
|
594
|
+
expect(result).toEqual({ count: 0, value: [] });
|
|
595
|
+
expect(logger.error).toHaveBeenCalled();
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
describe('GetAllPipelines', () => {
|
|
600
|
+
it('should fetch all pipelines for a project', async () => {
|
|
601
|
+
// Arrange
|
|
602
|
+
const projectName = 'project1';
|
|
603
|
+
const mockResponse = { value: [{ id: 1 }, { id: 2 }], count: 2 };
|
|
604
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
605
|
+
|
|
606
|
+
// Act
|
|
607
|
+
const result = await pipelinesDataProvider.GetAllPipelines(projectName);
|
|
608
|
+
|
|
609
|
+
// Assert
|
|
610
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
611
|
+
`${mockOrgUrl}${projectName}/_apis/pipelines?$top=2000`,
|
|
612
|
+
mockToken,
|
|
613
|
+
'get',
|
|
614
|
+
null,
|
|
615
|
+
null
|
|
616
|
+
);
|
|
617
|
+
expect(result).toEqual(mockResponse);
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
describe('GetAllReleaseDefenitions', () => {
|
|
622
|
+
it('should fetch all release definitions', async () => {
|
|
623
|
+
// Arrange
|
|
624
|
+
const projectName = 'project1';
|
|
625
|
+
const mockResponse = { value: [{ id: 1 }, { id: 2 }] };
|
|
626
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
627
|
+
|
|
628
|
+
// Act
|
|
629
|
+
const result = await pipelinesDataProvider.GetAllReleaseDefenitions(projectName);
|
|
630
|
+
|
|
631
|
+
// Assert
|
|
632
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
633
|
+
expect.stringContaining('vsrm.dev.azure.com'),
|
|
634
|
+
mockToken,
|
|
635
|
+
'get',
|
|
636
|
+
null,
|
|
637
|
+
null
|
|
638
|
+
);
|
|
639
|
+
expect(result).toEqual(mockResponse);
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
describe('GetRecentReleaseArtifactInfo', () => {
|
|
644
|
+
it('should return empty array when no releases exist', async () => {
|
|
645
|
+
// Arrange
|
|
646
|
+
const projectName = 'project1';
|
|
647
|
+
const mockResponse = { value: [] };
|
|
648
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
649
|
+
|
|
650
|
+
// Act
|
|
651
|
+
const result = await pipelinesDataProvider.GetRecentReleaseArtifactInfo(projectName);
|
|
652
|
+
|
|
653
|
+
// Assert
|
|
654
|
+
expect(result).toEqual([]);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it('should return artifact info from most recent release', async () => {
|
|
658
|
+
// Arrange
|
|
659
|
+
const projectName = 'project1';
|
|
660
|
+
const mockReleasesResponse = { value: [{ id: 123 }] };
|
|
661
|
+
const mockReleaseResponse = {
|
|
662
|
+
artifacts: [
|
|
663
|
+
{
|
|
664
|
+
definitionReference: {
|
|
665
|
+
definition: { name: 'artifact1' },
|
|
666
|
+
version: { name: '1.0.0' },
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
],
|
|
670
|
+
};
|
|
671
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
672
|
+
.mockResolvedValueOnce(mockReleasesResponse)
|
|
673
|
+
.mockResolvedValueOnce(mockReleaseResponse);
|
|
674
|
+
|
|
675
|
+
// Act
|
|
676
|
+
const result = await pipelinesDataProvider.GetRecentReleaseArtifactInfo(projectName);
|
|
677
|
+
|
|
678
|
+
// Assert
|
|
679
|
+
expect(result).toEqual([{ artifactName: 'artifact1', artifactVersion: '1.0.0' }]);
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
describe('getPipelineResourcePipelinesFromObject', () => {
|
|
684
|
+
it('should return empty set when no pipeline resources exist', async () => {
|
|
685
|
+
// Arrange
|
|
686
|
+
const inPipeline = {
|
|
687
|
+
resources: {},
|
|
688
|
+
} as unknown as PipelineRun;
|
|
689
|
+
|
|
690
|
+
// Act
|
|
691
|
+
const result = await pipelinesDataProvider.getPipelineResourcePipelinesFromObject(inPipeline);
|
|
692
|
+
|
|
693
|
+
// Assert
|
|
694
|
+
expect(result).toEqual(new Set());
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it('should extract pipeline resources from pipeline object', async () => {
|
|
698
|
+
// Arrange
|
|
699
|
+
const inPipeline = {
|
|
700
|
+
resources: {
|
|
701
|
+
pipelines: {
|
|
702
|
+
myPipeline: {
|
|
703
|
+
pipeline: {
|
|
704
|
+
id: 123,
|
|
705
|
+
url: 'https://dev.azure.com/org/project/_apis/pipelines/123?revision=1',
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
} as unknown as PipelineRun;
|
|
711
|
+
|
|
712
|
+
const mockBuildResponse = {
|
|
713
|
+
definition: { id: 456, type: 'build' },
|
|
714
|
+
buildNumber: '20231201.1',
|
|
715
|
+
project: { name: 'project1' },
|
|
716
|
+
repository: { type: 'TfsGit' },
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockBuildResponse);
|
|
720
|
+
|
|
721
|
+
// Act
|
|
722
|
+
const result = await pipelinesDataProvider.getPipelineResourcePipelinesFromObject(inPipeline);
|
|
723
|
+
|
|
724
|
+
// Assert
|
|
725
|
+
expect(result).toHaveLength(1);
|
|
726
|
+
expect((result as any[])[0]).toEqual({
|
|
727
|
+
name: 'myPipeline',
|
|
728
|
+
buildId: 123,
|
|
729
|
+
definitionId: 456,
|
|
730
|
+
buildNumber: '20231201.1',
|
|
731
|
+
teamProject: 'project1',
|
|
732
|
+
provider: 'TfsGit',
|
|
733
|
+
});
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
it('should handle errors when fetching pipeline resources', async () => {
|
|
737
|
+
// Arrange
|
|
738
|
+
const inPipeline = {
|
|
739
|
+
resources: {
|
|
740
|
+
pipelines: {
|
|
741
|
+
myPipeline: {
|
|
742
|
+
pipeline: {
|
|
743
|
+
id: 123,
|
|
744
|
+
url: 'https://dev.azure.com/org/project/_apis/pipelines/123?revision=1',
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
},
|
|
748
|
+
},
|
|
749
|
+
} as unknown as PipelineRun;
|
|
750
|
+
|
|
751
|
+
(TFSServices.getItemContent as jest.Mock).mockRejectedValueOnce(new Error('API Error'));
|
|
752
|
+
|
|
753
|
+
// Act
|
|
754
|
+
const result = await pipelinesDataProvider.getPipelineResourcePipelinesFromObject(inPipeline);
|
|
755
|
+
|
|
756
|
+
// Assert
|
|
757
|
+
expect(result).toEqual([]);
|
|
758
|
+
expect(logger.error).toHaveBeenCalled();
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
describe('getPipelineResourceRepositoriesFromObject', () => {
|
|
763
|
+
it('should return empty map when no repository resources exist', async () => {
|
|
764
|
+
// Arrange
|
|
765
|
+
const inPipeline = {
|
|
766
|
+
resources: {},
|
|
767
|
+
} as unknown as PipelineRun;
|
|
768
|
+
const mockGitDataProvider = {} as GitDataProvider;
|
|
769
|
+
|
|
770
|
+
// Act
|
|
771
|
+
const result = await pipelinesDataProvider.getPipelineResourceRepositoriesFromObject(
|
|
772
|
+
inPipeline,
|
|
773
|
+
mockGitDataProvider
|
|
774
|
+
);
|
|
775
|
+
|
|
776
|
+
// Assert
|
|
777
|
+
expect(result).toEqual(new Map());
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
it('should extract repository resources from pipeline object', async () => {
|
|
781
|
+
// Arrange
|
|
782
|
+
const inPipeline = {
|
|
783
|
+
resources: {
|
|
784
|
+
repositories: {
|
|
785
|
+
self: {
|
|
786
|
+
repository: { id: 'repo-123', type: 'azureReposGit' },
|
|
787
|
+
version: 'abc123',
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
},
|
|
791
|
+
} as unknown as PipelineRun;
|
|
792
|
+
|
|
793
|
+
const mockRepo = {
|
|
794
|
+
name: 'MyRepo',
|
|
795
|
+
url: 'https://dev.azure.com/org/project/_git/MyRepo',
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
const mockGitDataProvider = {
|
|
799
|
+
GetGitRepoFromRepoId: jest.fn().mockResolvedValue(mockRepo),
|
|
800
|
+
} as unknown as GitDataProvider;
|
|
801
|
+
|
|
802
|
+
// Act
|
|
803
|
+
const result = await pipelinesDataProvider.getPipelineResourceRepositoriesFromObject(
|
|
804
|
+
inPipeline,
|
|
805
|
+
mockGitDataProvider
|
|
806
|
+
);
|
|
807
|
+
|
|
808
|
+
// Assert
|
|
809
|
+
expect(result).toHaveLength(1);
|
|
810
|
+
expect((result as any[])[0]).toEqual({
|
|
811
|
+
repoName: 'MyRepo',
|
|
812
|
+
repoSha1: 'abc123',
|
|
813
|
+
url: 'https://dev.azure.com/org/project/_git/MyRepo',
|
|
814
|
+
});
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it('should skip non-azureReposGit repositories', async () => {
|
|
818
|
+
// Arrange
|
|
819
|
+
const inPipeline = {
|
|
820
|
+
resources: {
|
|
821
|
+
repositories: {
|
|
822
|
+
external: {
|
|
823
|
+
repository: { id: 'repo-123', type: 'GitHub' },
|
|
824
|
+
version: 'abc123',
|
|
825
|
+
},
|
|
826
|
+
},
|
|
827
|
+
},
|
|
828
|
+
} as unknown as PipelineRun;
|
|
829
|
+
|
|
830
|
+
const mockGitDataProvider = {
|
|
831
|
+
GetGitRepoFromRepoId: jest.fn(),
|
|
832
|
+
} as unknown as GitDataProvider;
|
|
833
|
+
|
|
834
|
+
// Act
|
|
835
|
+
const result = await pipelinesDataProvider.getPipelineResourceRepositoriesFromObject(
|
|
836
|
+
inPipeline,
|
|
837
|
+
mockGitDataProvider
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
// Assert
|
|
841
|
+
expect(result).toHaveLength(0);
|
|
842
|
+
expect(mockGitDataProvider.GetGitRepoFromRepoId).not.toHaveBeenCalled();
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
describe('findPreviousPipeline', () => {
|
|
847
|
+
it('should return undefined when no pipeline runs exist', async () => {
|
|
848
|
+
// Arrange
|
|
849
|
+
const teamProject = 'project1';
|
|
850
|
+
const pipelineId = '123';
|
|
851
|
+
const toPipelineRunId = 100;
|
|
852
|
+
const targetPipeline = {} as PipelineRun;
|
|
853
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({});
|
|
854
|
+
|
|
855
|
+
// Act
|
|
856
|
+
const result = await pipelinesDataProvider.findPreviousPipeline(
|
|
857
|
+
teamProject,
|
|
858
|
+
pipelineId,
|
|
859
|
+
toPipelineRunId,
|
|
860
|
+
targetPipeline,
|
|
861
|
+
false
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
// Assert
|
|
865
|
+
expect(result).toBeUndefined();
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
it('should skip invalid runs and return first matching previous pipeline', async () => {
|
|
869
|
+
const teamProject = 'project1';
|
|
870
|
+
const pipelineId = '123';
|
|
871
|
+
const toPipelineRunId = 100;
|
|
872
|
+
const targetPipeline = {
|
|
873
|
+
resources: {
|
|
874
|
+
repositories: { '0': { self: { repository: { id: 'r' }, version: 'v2', refName: 'main' } } },
|
|
875
|
+
},
|
|
876
|
+
} as any;
|
|
877
|
+
|
|
878
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({
|
|
879
|
+
value: [
|
|
880
|
+
{ id: 100, result: 'succeeded' },
|
|
881
|
+
{ id: 99, result: 'succeeded' },
|
|
882
|
+
],
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
jest.spyOn(pipelinesDataProvider as any, 'getPipelineRunDetails').mockResolvedValueOnce({
|
|
886
|
+
resources: {
|
|
887
|
+
repositories: { '0': { self: { repository: { id: 'r' }, version: 'v1', refName: 'main' } } },
|
|
888
|
+
},
|
|
889
|
+
});
|
|
890
|
+
jest.spyOn(pipelinesDataProvider as any, 'isMatchingPipeline').mockReturnValueOnce(true);
|
|
891
|
+
|
|
892
|
+
const res = await pipelinesDataProvider.findPreviousPipeline(
|
|
893
|
+
teamProject,
|
|
894
|
+
pipelineId,
|
|
895
|
+
toPipelineRunId,
|
|
896
|
+
targetPipeline,
|
|
897
|
+
true
|
|
898
|
+
);
|
|
899
|
+
expect(res).toBe(99);
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
it('should skip when fromStage provided but stage is not successful', async () => {
|
|
903
|
+
const teamProject = 'project1';
|
|
904
|
+
const pipelineId = '123';
|
|
905
|
+
const toPipelineRunId = 100;
|
|
906
|
+
const targetPipeline = {} as PipelineRun;
|
|
907
|
+
|
|
908
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({
|
|
909
|
+
value: [{ id: 99, result: 'succeeded' }],
|
|
910
|
+
});
|
|
911
|
+
jest.spyOn(pipelinesDataProvider as any, 'isStageSuccessful').mockResolvedValueOnce(false);
|
|
912
|
+
const detailsSpy = jest.spyOn(pipelinesDataProvider as any, 'getPipelineRunDetails');
|
|
913
|
+
|
|
914
|
+
const res = await pipelinesDataProvider.findPreviousPipeline(
|
|
915
|
+
teamProject,
|
|
916
|
+
pipelineId,
|
|
917
|
+
toPipelineRunId,
|
|
918
|
+
targetPipeline,
|
|
919
|
+
false,
|
|
920
|
+
'Deploy'
|
|
921
|
+
);
|
|
922
|
+
|
|
923
|
+
expect(res).toBeUndefined();
|
|
924
|
+
expect(detailsSpy).not.toHaveBeenCalled();
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
it('should skip when pipeline details do not include repositories', async () => {
|
|
928
|
+
const teamProject = 'project1';
|
|
929
|
+
const pipelineId = '123';
|
|
930
|
+
const toPipelineRunId = 100;
|
|
931
|
+
const targetPipeline = {} as PipelineRun;
|
|
932
|
+
|
|
933
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({
|
|
934
|
+
value: [{ id: 99, result: 'succeeded' }],
|
|
935
|
+
});
|
|
936
|
+
jest
|
|
937
|
+
.spyOn(pipelinesDataProvider as any, 'getPipelineRunDetails')
|
|
938
|
+
.mockResolvedValueOnce({ resources: {} });
|
|
939
|
+
|
|
940
|
+
const res = await pipelinesDataProvider.findPreviousPipeline(
|
|
941
|
+
teamProject,
|
|
942
|
+
pipelineId,
|
|
943
|
+
toPipelineRunId,
|
|
944
|
+
targetPipeline,
|
|
945
|
+
false
|
|
946
|
+
);
|
|
947
|
+
|
|
948
|
+
expect(res).toBeUndefined();
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
it('should find matching previous pipeline', async () => {
|
|
952
|
+
// Arrange
|
|
953
|
+
const teamProject = 'project1';
|
|
954
|
+
const pipelineId = '123';
|
|
955
|
+
const toPipelineRunId = 100;
|
|
956
|
+
const targetPipeline = {
|
|
957
|
+
resources: {
|
|
958
|
+
repositories: {
|
|
959
|
+
'0': {
|
|
960
|
+
self: {
|
|
961
|
+
repository: { id: 'repo1' },
|
|
962
|
+
version: 'v2',
|
|
963
|
+
refName: 'refs/heads/main',
|
|
964
|
+
},
|
|
965
|
+
},
|
|
966
|
+
},
|
|
967
|
+
},
|
|
968
|
+
} as unknown as PipelineRun;
|
|
969
|
+
|
|
970
|
+
const mockRunHistory = {
|
|
971
|
+
value: [{ id: 99, result: 'succeeded' }],
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
const mockPipelineDetails = {
|
|
975
|
+
resources: {
|
|
976
|
+
repositories: {
|
|
977
|
+
'0': {
|
|
978
|
+
self: {
|
|
979
|
+
repository: { id: 'repo1' },
|
|
980
|
+
version: 'v1',
|
|
981
|
+
refName: 'refs/heads/main',
|
|
982
|
+
},
|
|
983
|
+
},
|
|
984
|
+
},
|
|
985
|
+
},
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
989
|
+
.mockResolvedValueOnce(mockRunHistory)
|
|
990
|
+
.mockResolvedValueOnce(mockPipelineDetails);
|
|
991
|
+
|
|
992
|
+
// Act
|
|
993
|
+
const result = await pipelinesDataProvider.findPreviousPipeline(
|
|
994
|
+
teamProject,
|
|
995
|
+
pipelineId,
|
|
996
|
+
toPipelineRunId,
|
|
997
|
+
targetPipeline,
|
|
998
|
+
true
|
|
999
|
+
);
|
|
1000
|
+
|
|
1001
|
+
// Assert
|
|
1002
|
+
expect(result).toBe(99);
|
|
1003
|
+
});
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
describe('isInvalidPipelineRun', () => {
|
|
1007
|
+
const invokeIsInvalidPipelineRun = (
|
|
1008
|
+
pipelineRun: any,
|
|
1009
|
+
toPipelineRunId: number,
|
|
1010
|
+
fromStage: string
|
|
1011
|
+
): boolean => {
|
|
1012
|
+
return (pipelinesDataProvider as any).isInvalidPipelineRun(pipelineRun, toPipelineRunId, fromStage);
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
it('should return true when pipeline run id >= toPipelineRunId', () => {
|
|
1016
|
+
expect(invokeIsInvalidPipelineRun({ id: 100, result: 'succeeded' }, 100, '')).toBe(true);
|
|
1017
|
+
expect(invokeIsInvalidPipelineRun({ id: 101, result: 'succeeded' }, 100, '')).toBe(true);
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
it('should return true for canceled/failed/canceling results', () => {
|
|
1021
|
+
expect(invokeIsInvalidPipelineRun({ id: 99, result: 'canceled' }, 100, '')).toBe(true);
|
|
1022
|
+
expect(invokeIsInvalidPipelineRun({ id: 99, result: 'failed' }, 100, '')).toBe(true);
|
|
1023
|
+
expect(invokeIsInvalidPipelineRun({ id: 99, result: 'canceling' }, 100, '')).toBe(true);
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
it('should return true for unknown result without fromStage', () => {
|
|
1027
|
+
expect(invokeIsInvalidPipelineRun({ id: 99, result: 'unknown' }, 100, '')).toBe(true);
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
it('should return false for valid pipeline run with fromStage', () => {
|
|
1031
|
+
expect(invokeIsInvalidPipelineRun({ id: 99, result: 'unknown' }, 100, 'Deploy')).toBe(false);
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
it('should return false for succeeded result', () => {
|
|
1035
|
+
expect(invokeIsInvalidPipelineRun({ id: 99, result: 'succeeded' }, 100, '')).toBe(false);
|
|
1036
|
+
});
|
|
1037
|
+
});
|
|
1038
|
+
});
|