@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,1046 @@
|
|
|
1
|
+
import { TFSServices } from '../../helpers/tfs';
|
|
2
|
+
import { Helper, suiteData } from '../../helpers/helper';
|
|
3
|
+
import TestDataProvider from '../../modules/TestDataProvider';
|
|
4
|
+
import Utils from '../../utils/testStepParserHelper';
|
|
5
|
+
import logger from '../../utils/logger';
|
|
6
|
+
import { TestCase } from '../../models/tfs-data';
|
|
7
|
+
|
|
8
|
+
jest.mock('../../helpers/tfs');
|
|
9
|
+
jest.mock('../../utils/logger');
|
|
10
|
+
jest.mock('../../helpers/helper');
|
|
11
|
+
jest.mock('../../utils/testStepParserHelper', () => {
|
|
12
|
+
return {
|
|
13
|
+
__esModule: true,
|
|
14
|
+
default: jest.fn().mockImplementation(() => ({
|
|
15
|
+
parseTestSteps: jest.fn(),
|
|
16
|
+
})),
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
jest.mock('p-limit', () => jest.fn(() => (fn: Function) => fn()));
|
|
20
|
+
|
|
21
|
+
describe('TestDataProvider', () => {
|
|
22
|
+
let testDataProvider: TestDataProvider;
|
|
23
|
+
const mockOrgUrl = 'https://dev.azure.com/orgname/';
|
|
24
|
+
const mockToken = 'mock-token';
|
|
25
|
+
const mockProject = 'project-123';
|
|
26
|
+
const mockPlanId = '456';
|
|
27
|
+
const mockSuiteId = '789';
|
|
28
|
+
const mockTestCaseId = '101112';
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
jest.clearAllMocks();
|
|
32
|
+
|
|
33
|
+
testDataProvider = new TestDataProvider(mockOrgUrl, mockToken);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Helper to access private method for testing
|
|
37
|
+
const invokeFetchWithCache = async (instance: any, url: string, ttl = 60000) => {
|
|
38
|
+
return instance.fetchWithCache.call(instance, url, ttl);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
describe('fetchWithCache', () => {
|
|
42
|
+
it('should return cached data when available and not expired', async () => {
|
|
43
|
+
// Arrange
|
|
44
|
+
const mockUrl = `${mockOrgUrl}_apis/test/endpoint`;
|
|
45
|
+
const mockData = { value: 'test data' };
|
|
46
|
+
const cache = new Map();
|
|
47
|
+
cache.set(mockUrl, {
|
|
48
|
+
data: mockData,
|
|
49
|
+
timestamp: Date.now(),
|
|
50
|
+
});
|
|
51
|
+
(testDataProvider as any).cache = cache;
|
|
52
|
+
|
|
53
|
+
// Act
|
|
54
|
+
const result = await invokeFetchWithCache(testDataProvider, mockUrl);
|
|
55
|
+
|
|
56
|
+
// Assert
|
|
57
|
+
expect(result).toEqual(mockData);
|
|
58
|
+
expect(TFSServices.getItemContent).not.toHaveBeenCalled();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should fetch new data when cache is expired', async () => {
|
|
62
|
+
// Arrange
|
|
63
|
+
const mockUrl = `${mockOrgUrl}_apis/test/endpoint`;
|
|
64
|
+
const mockData = { value: 'old data' };
|
|
65
|
+
const newData = { value: 'new data' };
|
|
66
|
+
const cache = new Map();
|
|
67
|
+
cache.set(mockUrl, {
|
|
68
|
+
data: mockData,
|
|
69
|
+
timestamp: Date.now() - 70000, // Expired (default TTL is 60000ms)
|
|
70
|
+
});
|
|
71
|
+
(testDataProvider as any).cache = cache;
|
|
72
|
+
|
|
73
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(newData);
|
|
74
|
+
|
|
75
|
+
// Act
|
|
76
|
+
const result = await invokeFetchWithCache(testDataProvider, mockUrl);
|
|
77
|
+
|
|
78
|
+
// Assert
|
|
79
|
+
expect(result).toEqual(newData);
|
|
80
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(mockUrl, mockToken);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should fetch and cache new data when not in cache', async () => {
|
|
84
|
+
// Arrange
|
|
85
|
+
const mockUrl = `${mockOrgUrl}_apis/test/endpoint`;
|
|
86
|
+
const mockData = { value: 'new data' };
|
|
87
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockData);
|
|
88
|
+
|
|
89
|
+
// Act
|
|
90
|
+
const result = await invokeFetchWithCache(testDataProvider, mockUrl);
|
|
91
|
+
|
|
92
|
+
// Assert
|
|
93
|
+
expect(result).toEqual(mockData);
|
|
94
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(mockUrl, mockToken);
|
|
95
|
+
expect((testDataProvider as any).cache.has(mockUrl)).toBeTruthy();
|
|
96
|
+
expect((testDataProvider as any).cache.get(mockUrl).data).toEqual(mockData);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should throw and log error when fetch fails', async () => {
|
|
100
|
+
// Arrange
|
|
101
|
+
const mockUrl = `${mockOrgUrl}_apis/test/endpoint`;
|
|
102
|
+
const mockError = new Error('API call failed');
|
|
103
|
+
(TFSServices.getItemContent as jest.Mock).mockRejectedValueOnce(mockError);
|
|
104
|
+
|
|
105
|
+
// Act & Assert
|
|
106
|
+
await expect(invokeFetchWithCache(testDataProvider, mockUrl)).rejects.toThrow('API call failed');
|
|
107
|
+
expect(logger.error).toHaveBeenCalledWith(`Error fetching ${mockUrl}: API call failed`);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('GetTestSuiteByTestCase', () => {
|
|
112
|
+
it('should return test suites for a given test case ID', async () => {
|
|
113
|
+
// Arrange
|
|
114
|
+
const mockData = { value: [{ id: '123', name: 'Test Suite 1' }] };
|
|
115
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockData);
|
|
116
|
+
|
|
117
|
+
// Act
|
|
118
|
+
const result = await testDataProvider.GetTestSuiteByTestCase(mockTestCaseId);
|
|
119
|
+
|
|
120
|
+
// Assert
|
|
121
|
+
expect(result).toEqual(mockData);
|
|
122
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
123
|
+
`${mockOrgUrl}_apis/testplan/suites?testCaseId=${mockTestCaseId}`,
|
|
124
|
+
mockToken
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('GetTestPlans', () => {
|
|
130
|
+
it('should return test plans for a given project', async () => {
|
|
131
|
+
// Arrange
|
|
132
|
+
const mockData = {
|
|
133
|
+
value: [
|
|
134
|
+
{ id: '456', name: 'Test Plan 1' },
|
|
135
|
+
{ id: '789', name: 'Test Plan 2' },
|
|
136
|
+
],
|
|
137
|
+
};
|
|
138
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockData);
|
|
139
|
+
|
|
140
|
+
// Act
|
|
141
|
+
const result = await testDataProvider.GetTestPlans(mockProject);
|
|
142
|
+
|
|
143
|
+
// Assert
|
|
144
|
+
expect(result).toEqual(mockData);
|
|
145
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
146
|
+
`${mockOrgUrl}${mockProject}/_apis/test/plans`,
|
|
147
|
+
mockToken
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('GetTestSuites', () => {
|
|
153
|
+
it('should return test suites for a given project and plan ID', async () => {
|
|
154
|
+
// Arrange
|
|
155
|
+
const mockData = {
|
|
156
|
+
value: [
|
|
157
|
+
{ id: '123', name: 'Test Suite 1' },
|
|
158
|
+
{ id: '456', name: 'Test Suite 2' },
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockData);
|
|
162
|
+
|
|
163
|
+
// Act
|
|
164
|
+
const result = await testDataProvider.GetTestSuites(mockProject, mockPlanId);
|
|
165
|
+
|
|
166
|
+
// Assert
|
|
167
|
+
expect(result).toEqual(mockData);
|
|
168
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
169
|
+
`${mockOrgUrl}${mockProject}/_apis/test/Plans/${mockPlanId}/suites`,
|
|
170
|
+
mockToken
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should return null and log error if fetching test suites fails', async () => {
|
|
175
|
+
// Arrange
|
|
176
|
+
const mockError = new Error('Failed to get test suites');
|
|
177
|
+
(TFSServices.getItemContent as jest.Mock).mockRejectedValueOnce(mockError);
|
|
178
|
+
|
|
179
|
+
// Act
|
|
180
|
+
const result = await testDataProvider.GetTestSuites(mockProject, mockPlanId);
|
|
181
|
+
|
|
182
|
+
// Assert
|
|
183
|
+
expect(result).toBeNull();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('GetTestSuitesForPlan', () => {
|
|
188
|
+
it('should throw error when project is not provided', async () => {
|
|
189
|
+
// Act & Assert
|
|
190
|
+
await expect(testDataProvider.GetTestSuitesForPlan('', mockPlanId)).rejects.toThrow(
|
|
191
|
+
'Project not selected'
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should throw error when plan ID is not provided', async () => {
|
|
196
|
+
// Act & Assert
|
|
197
|
+
await expect(testDataProvider.GetTestSuitesForPlan(mockProject, '')).rejects.toThrow(
|
|
198
|
+
'Plan not selected'
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should return test suites for a plan', async () => {
|
|
203
|
+
// Arrange
|
|
204
|
+
const mockData = {
|
|
205
|
+
testSuites: [
|
|
206
|
+
{ id: '123', name: 'Test Suite 1' },
|
|
207
|
+
{ id: '456', name: 'Test Suite 2' },
|
|
208
|
+
],
|
|
209
|
+
};
|
|
210
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockData);
|
|
211
|
+
|
|
212
|
+
// Act
|
|
213
|
+
const result = await testDataProvider.GetTestSuitesForPlan(mockProject, mockPlanId);
|
|
214
|
+
|
|
215
|
+
// Assert
|
|
216
|
+
expect(result).toEqual(mockData);
|
|
217
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
218
|
+
`${mockOrgUrl}/${mockProject}/_api/_testManagement/GetTestSuitesForPlan?__v=5&planId=${mockPlanId}`,
|
|
219
|
+
mockToken
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('GetTestSuiteById', () => {
|
|
225
|
+
it('should call GetTestSuitesForPlan and Helper.findSuitesRecursive with correct params', async () => {
|
|
226
|
+
// Arrange
|
|
227
|
+
const mockTestSuites = { testSuites: [{ id: '123', name: 'Test Suite 1' }] };
|
|
228
|
+
const mockSuiteData = [new suiteData('Test Suite 1', '123', '456', 1)];
|
|
229
|
+
|
|
230
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockTestSuites);
|
|
231
|
+
(Helper.findSuitesRecursive as jest.Mock).mockReturnValueOnce(mockSuiteData);
|
|
232
|
+
|
|
233
|
+
// Act
|
|
234
|
+
const result = await testDataProvider.GetTestSuiteById(mockProject, mockPlanId, mockSuiteId, true);
|
|
235
|
+
|
|
236
|
+
// Assert
|
|
237
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
238
|
+
`${mockOrgUrl}/${mockProject}/_api/_testManagement/GetTestSuitesForPlan?__v=5&planId=${mockPlanId}`,
|
|
239
|
+
mockToken
|
|
240
|
+
);
|
|
241
|
+
expect(Helper.findSuitesRecursive).toHaveBeenCalledWith(
|
|
242
|
+
mockPlanId,
|
|
243
|
+
mockOrgUrl,
|
|
244
|
+
mockProject,
|
|
245
|
+
mockTestSuites.testSuites,
|
|
246
|
+
mockSuiteId,
|
|
247
|
+
true
|
|
248
|
+
);
|
|
249
|
+
expect(result).toEqual(mockSuiteData);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('GetTestCases', () => {
|
|
254
|
+
it('should return test cases for a given project, plan ID, and suite ID', async () => {
|
|
255
|
+
// Arrange
|
|
256
|
+
const mockData = {
|
|
257
|
+
count: 2,
|
|
258
|
+
value: [
|
|
259
|
+
{ testCase: { id: '101', name: 'Test Case 1', url: 'url1' } },
|
|
260
|
+
{ testCase: { id: '102', name: 'Test Case 2', url: 'url2' } },
|
|
261
|
+
],
|
|
262
|
+
};
|
|
263
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockData);
|
|
264
|
+
|
|
265
|
+
// Act
|
|
266
|
+
const result = await testDataProvider.GetTestCases(mockProject, mockPlanId, mockSuiteId);
|
|
267
|
+
|
|
268
|
+
// Assert
|
|
269
|
+
expect(result).toEqual(mockData);
|
|
270
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
271
|
+
`${mockOrgUrl}${mockProject}/_apis/test/Plans/${mockPlanId}/suites/${mockSuiteId}/testcases/`,
|
|
272
|
+
mockToken
|
|
273
|
+
);
|
|
274
|
+
expect(logger.debug).toHaveBeenCalledWith(
|
|
275
|
+
`test cases for plan ${mockPlanId} and ${mockSuiteId} were found`
|
|
276
|
+
);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('clearCache', () => {
|
|
281
|
+
it('should clear the cache', () => {
|
|
282
|
+
// Arrange
|
|
283
|
+
const mockUrl = `${mockOrgUrl}_apis/test/endpoint`;
|
|
284
|
+
const mockData = { value: 'test data' };
|
|
285
|
+
const cache = new Map();
|
|
286
|
+
cache.set(mockUrl, {
|
|
287
|
+
data: mockData,
|
|
288
|
+
timestamp: Date.now(),
|
|
289
|
+
});
|
|
290
|
+
(testDataProvider as any).cache = cache;
|
|
291
|
+
|
|
292
|
+
// Act
|
|
293
|
+
testDataProvider.clearCache();
|
|
294
|
+
|
|
295
|
+
// Assert
|
|
296
|
+
expect((testDataProvider as any).cache.size).toBe(0);
|
|
297
|
+
expect(logger.debug).toHaveBeenCalledWith('Cache cleared');
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('UpdateTestRun', () => {
|
|
302
|
+
it('should update a test run with the correct state', async () => {
|
|
303
|
+
// Arrange
|
|
304
|
+
const mockRunId = '12345';
|
|
305
|
+
const mockState = 'Completed';
|
|
306
|
+
const mockResponse = { id: mockRunId, state: mockState };
|
|
307
|
+
|
|
308
|
+
(TFSServices.postRequest as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
309
|
+
|
|
310
|
+
// Act
|
|
311
|
+
const result = await testDataProvider.UpdateTestRun(mockProject, mockRunId, mockState);
|
|
312
|
+
|
|
313
|
+
// Assert
|
|
314
|
+
expect(result).toEqual(mockResponse);
|
|
315
|
+
expect(TFSServices.postRequest).toHaveBeenCalledWith(
|
|
316
|
+
`${mockOrgUrl}${mockProject}/_apis/test/Runs/${mockRunId}?api-version=5.0`,
|
|
317
|
+
mockToken,
|
|
318
|
+
'PATCH',
|
|
319
|
+
{ state: mockState },
|
|
320
|
+
null
|
|
321
|
+
);
|
|
322
|
+
expect(logger.info).toHaveBeenCalledWith(`Update runId : ${mockRunId} to state : ${mockState}`);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe('GetTestSuitesByPlan', () => {
|
|
327
|
+
it('should return suites without filter using default suiteId', async () => {
|
|
328
|
+
// Arrange
|
|
329
|
+
const mockTestSuites = { testSuites: [{ id: 457, name: 'Suite 1', parentSuiteId: 0 }] };
|
|
330
|
+
const mockSuiteData = [new suiteData('Suite 1', '457', '456', 1)];
|
|
331
|
+
|
|
332
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValue(mockTestSuites);
|
|
333
|
+
(Helper.findSuitesRecursive as jest.Mock).mockReturnValue(mockSuiteData);
|
|
334
|
+
|
|
335
|
+
// Act
|
|
336
|
+
const result = await testDataProvider.GetTestSuitesByPlan(mockProject, mockPlanId, true);
|
|
337
|
+
|
|
338
|
+
// Assert
|
|
339
|
+
expect(Helper.findSuitesRecursive).toHaveBeenCalled();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should process multiple top-level suite hierarchies and combine results', async () => {
|
|
343
|
+
jest.spyOn(testDataProvider, 'GetTestSuitesForPlan').mockResolvedValueOnce({
|
|
344
|
+
testSuites: [
|
|
345
|
+
{ id: 1, parentSuiteId: 0 },
|
|
346
|
+
{ id: 2, parentSuiteId: 0 },
|
|
347
|
+
{ id: 10, parentSuiteId: 1 },
|
|
348
|
+
{ id: 11, parentSuiteId: 2 },
|
|
349
|
+
],
|
|
350
|
+
} as any);
|
|
351
|
+
|
|
352
|
+
const suiteIdsFilter = [10, 11];
|
|
353
|
+
const getByIdSpy = jest
|
|
354
|
+
.spyOn(testDataProvider, 'GetTestSuiteById')
|
|
355
|
+
.mockResolvedValueOnce([{ id: '10' }])
|
|
356
|
+
.mockResolvedValueOnce([{ id: '11' }]);
|
|
357
|
+
|
|
358
|
+
const res = await testDataProvider.GetTestSuitesByPlan(mockProject, mockPlanId, true, suiteIdsFilter);
|
|
359
|
+
|
|
360
|
+
expect(getByIdSpy).toHaveBeenCalledTimes(2);
|
|
361
|
+
expect(res).toEqual([{ id: '10' }, { id: '11' }]);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should fallback to first suite when no top-level suites can be determined', async () => {
|
|
365
|
+
jest.spyOn(testDataProvider, 'GetTestSuitesForPlan').mockResolvedValueOnce({
|
|
366
|
+
testSuites: [{ id: 1, parentSuiteId: 0 }],
|
|
367
|
+
} as any);
|
|
368
|
+
|
|
369
|
+
const getByIdSpy = jest
|
|
370
|
+
.spyOn(testDataProvider, 'GetTestSuiteById')
|
|
371
|
+
.mockResolvedValueOnce([{ id: '1' }]);
|
|
372
|
+
|
|
373
|
+
const res = await testDataProvider.GetTestSuitesByPlan(mockProject, mockPlanId, true, [1]);
|
|
374
|
+
|
|
375
|
+
expect(getByIdSpy).toHaveBeenCalledWith(mockProject, mockPlanId, '1', true, [1]);
|
|
376
|
+
expect(res).toEqual([{ id: '1' }]);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe('createNewRequirement', () => {
|
|
381
|
+
it('should pick customer id from any supported customer fields when enabled', () => {
|
|
382
|
+
const rel = (testDataProvider as any).createNewRequirement(true, {
|
|
383
|
+
id: '123',
|
|
384
|
+
fields: {
|
|
385
|
+
'System.Title': 'Req title',
|
|
386
|
+
'Custom.CustomerID': 'CID-1',
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
expect(rel).toEqual({ type: 'requirement', id: '123', title: 'Req title', customerId: 'CID-1' });
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('should default customerId to a single space when enabled and no field exists', () => {
|
|
394
|
+
const rel = (testDataProvider as any).createNewRequirement(true, {
|
|
395
|
+
id: '123',
|
|
396
|
+
fields: {
|
|
397
|
+
'System.Title': 'Req title',
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
expect(rel).toEqual({ type: 'requirement', id: '123', title: 'Req title', customerId: ' ' });
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
describe('addToMap', () => {
|
|
406
|
+
it('should create array for missing key and append values', () => {
|
|
407
|
+
const map = new Map<string, string[]>();
|
|
408
|
+
(testDataProvider as any).addToMap(map, 'k', 'v1');
|
|
409
|
+
(testDataProvider as any).addToMap(map, 'k', 'v2');
|
|
410
|
+
expect(map.get('k')).toEqual(['v1', 'v2']);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe('GetTestSuiteById with filtering', () => {
|
|
415
|
+
it('should filter suites when suiteIdsFilter is provided', async () => {
|
|
416
|
+
// Arrange
|
|
417
|
+
const suiteIdsFilter = [123, 456];
|
|
418
|
+
const mockTestSuites = {
|
|
419
|
+
testSuites: [
|
|
420
|
+
{ id: 123, name: 'Suite 1', parentSuiteId: 100 },
|
|
421
|
+
{ id: 456, name: 'Suite 2', parentSuiteId: 100 },
|
|
422
|
+
],
|
|
423
|
+
};
|
|
424
|
+
const mockSuiteData = [new suiteData('Suite 1', '123', '100', 1)];
|
|
425
|
+
|
|
426
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValue(mockTestSuites);
|
|
427
|
+
(Helper.findSuitesRecursive as jest.Mock).mockReturnValue(mockSuiteData);
|
|
428
|
+
|
|
429
|
+
// Act
|
|
430
|
+
const result = await testDataProvider.GetTestSuiteById(
|
|
431
|
+
mockProject,
|
|
432
|
+
mockPlanId,
|
|
433
|
+
'123',
|
|
434
|
+
true,
|
|
435
|
+
suiteIdsFilter
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
// Assert
|
|
439
|
+
expect(Helper.findSuitesRecursive).toHaveBeenCalled();
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
describe('GetTestPoint', () => {
|
|
444
|
+
it('should return test points for a test case', async () => {
|
|
445
|
+
// Arrange
|
|
446
|
+
const mockResponse = { value: [{ id: 1, testCaseId: mockTestCaseId }] };
|
|
447
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValue(mockResponse);
|
|
448
|
+
|
|
449
|
+
// Act
|
|
450
|
+
const result = await testDataProvider.GetTestPoint(
|
|
451
|
+
mockProject,
|
|
452
|
+
mockPlanId,
|
|
453
|
+
mockSuiteId,
|
|
454
|
+
mockTestCaseId
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
// Assert
|
|
458
|
+
expect(result).toEqual(mockResponse);
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
describe('CreateTestRun', () => {
|
|
463
|
+
it('should create a test run successfully', async () => {
|
|
464
|
+
// Arrange
|
|
465
|
+
const testRunName = 'Test Run 1';
|
|
466
|
+
const testPointId = '12345';
|
|
467
|
+
const mockResponse = { data: { id: 1, name: testRunName } };
|
|
468
|
+
|
|
469
|
+
(TFSServices.postRequest as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
470
|
+
|
|
471
|
+
// Act
|
|
472
|
+
const result = await testDataProvider.CreateTestRun(mockProject, testRunName, mockPlanId, testPointId);
|
|
473
|
+
|
|
474
|
+
// Assert
|
|
475
|
+
expect(result).toEqual(mockResponse);
|
|
476
|
+
expect(TFSServices.postRequest).toHaveBeenCalledWith(
|
|
477
|
+
`${mockOrgUrl}${mockProject}/_apis/test/runs`,
|
|
478
|
+
mockToken,
|
|
479
|
+
'Post',
|
|
480
|
+
{
|
|
481
|
+
name: testRunName,
|
|
482
|
+
plan: { id: mockPlanId },
|
|
483
|
+
pointIds: [testPointId],
|
|
484
|
+
},
|
|
485
|
+
null
|
|
486
|
+
);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it('should throw error when creation fails', async () => {
|
|
490
|
+
// Arrange
|
|
491
|
+
const testRunName = 'Test Run 1';
|
|
492
|
+
const testPointId = '12345';
|
|
493
|
+
const mockError = new Error('Creation failed');
|
|
494
|
+
|
|
495
|
+
(TFSServices.postRequest as jest.Mock).mockRejectedValueOnce(mockError);
|
|
496
|
+
|
|
497
|
+
// Act & Assert
|
|
498
|
+
await expect(
|
|
499
|
+
testDataProvider.CreateTestRun(mockProject, testRunName, mockPlanId, testPointId)
|
|
500
|
+
).rejects.toThrow('Error: Creation failed');
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
describe('UpdateTestCase', () => {
|
|
505
|
+
it('should update test case to Active state (0)', async () => {
|
|
506
|
+
// Arrange
|
|
507
|
+
const mockRunId = '12345';
|
|
508
|
+
const mockResponse = { data: { outcome: '0' } };
|
|
509
|
+
(TFSServices.postRequest as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
510
|
+
|
|
511
|
+
// Act
|
|
512
|
+
const result = await testDataProvider.UpdateTestCase(mockProject, mockRunId, 0);
|
|
513
|
+
|
|
514
|
+
// Assert
|
|
515
|
+
expect(result).toEqual(mockResponse);
|
|
516
|
+
expect(logger.info).toHaveBeenCalledWith('Reset test case to Active state ');
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('should update test case to Completed state (1)', async () => {
|
|
520
|
+
// Arrange
|
|
521
|
+
const mockRunId = '12345';
|
|
522
|
+
const mockResponse = { data: { state: 'Completed', outcome: '1' } };
|
|
523
|
+
(TFSServices.postRequest as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
524
|
+
|
|
525
|
+
// Act
|
|
526
|
+
const result = await testDataProvider.UpdateTestCase(mockProject, mockRunId, 1);
|
|
527
|
+
|
|
528
|
+
// Assert
|
|
529
|
+
expect(result).toEqual(mockResponse);
|
|
530
|
+
expect(logger.info).toHaveBeenCalledWith('Update test case to complite state ');
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('should update test case to Passed state (2)', async () => {
|
|
534
|
+
// Arrange
|
|
535
|
+
const mockRunId = '12345';
|
|
536
|
+
const mockResponse = { data: { state: 'Completed', outcome: '2' } };
|
|
537
|
+
(TFSServices.postRequest as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
538
|
+
|
|
539
|
+
// Act
|
|
540
|
+
const result = await testDataProvider.UpdateTestCase(mockProject, mockRunId, 2);
|
|
541
|
+
|
|
542
|
+
// Assert
|
|
543
|
+
expect(result).toEqual(mockResponse);
|
|
544
|
+
expect(logger.info).toHaveBeenCalledWith('Update test case to passed state ');
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('should update test case to Failed state (3)', async () => {
|
|
548
|
+
// Arrange
|
|
549
|
+
const mockRunId = '12345';
|
|
550
|
+
const mockResponse = { data: { state: 'Completed', outcome: '3' } };
|
|
551
|
+
(TFSServices.postRequest as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
552
|
+
|
|
553
|
+
// Act
|
|
554
|
+
const result = await testDataProvider.UpdateTestCase(mockProject, mockRunId, 3);
|
|
555
|
+
|
|
556
|
+
// Assert
|
|
557
|
+
expect(result).toEqual(mockResponse);
|
|
558
|
+
expect(logger.info).toHaveBeenCalledWith('Update test case to failed state ');
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
describe('UploadTestAttachment', () => {
|
|
563
|
+
it('should upload attachment to test run', async () => {
|
|
564
|
+
// Arrange
|
|
565
|
+
const runId = '12345';
|
|
566
|
+
const stream = 'base64encodeddata';
|
|
567
|
+
const fileName = 'test.png';
|
|
568
|
+
const comment = 'Test attachment';
|
|
569
|
+
const attachmentType = 'GeneralAttachment';
|
|
570
|
+
const mockResponse = { data: { id: 1 } };
|
|
571
|
+
|
|
572
|
+
(TFSServices.postRequest as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
573
|
+
|
|
574
|
+
// Act
|
|
575
|
+
const result = await testDataProvider.UploadTestAttachment(
|
|
576
|
+
runId,
|
|
577
|
+
mockProject,
|
|
578
|
+
stream,
|
|
579
|
+
fileName,
|
|
580
|
+
comment,
|
|
581
|
+
attachmentType
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
// Assert
|
|
585
|
+
expect(result).toEqual(mockResponse);
|
|
586
|
+
expect(TFSServices.postRequest).toHaveBeenCalledWith(
|
|
587
|
+
`${mockOrgUrl}${mockProject}/_apis/test/Runs/${runId}/attachments?api-version=5.0-preview.1`,
|
|
588
|
+
mockToken,
|
|
589
|
+
'Post',
|
|
590
|
+
{ stream, fileName, comment, attachmentType },
|
|
591
|
+
null
|
|
592
|
+
);
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
describe('GetTestRunById', () => {
|
|
597
|
+
it('should return test run by ID', async () => {
|
|
598
|
+
// Arrange
|
|
599
|
+
const runId = '12345';
|
|
600
|
+
const mockResponse = { id: runId, name: 'Test Run 1' };
|
|
601
|
+
|
|
602
|
+
// Act
|
|
603
|
+
const result = await testDataProvider.GetTestRunById(mockProject, runId);
|
|
604
|
+
|
|
605
|
+
// Assert
|
|
606
|
+
expect(TFSServices.getItemContent).toHaveBeenCalled();
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
describe('GetTestPointByTestCaseId', () => {
|
|
611
|
+
it('should return test points for a test case ID', async () => {
|
|
612
|
+
// Arrange
|
|
613
|
+
const mockResponse = { data: { value: [{ id: 1 }] } };
|
|
614
|
+
(TFSServices.postRequest as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
615
|
+
|
|
616
|
+
// Act
|
|
617
|
+
const result = await testDataProvider.GetTestPointByTestCaseId(mockProject, mockTestCaseId);
|
|
618
|
+
|
|
619
|
+
// Assert
|
|
620
|
+
expect(result).toEqual(mockResponse);
|
|
621
|
+
expect(TFSServices.postRequest).toHaveBeenCalledWith(
|
|
622
|
+
`${mockOrgUrl}${mockProject}/_apis/test/points`,
|
|
623
|
+
mockToken,
|
|
624
|
+
'Post',
|
|
625
|
+
{ PointsFilter: { TestcaseIds: [mockTestCaseId] } },
|
|
626
|
+
null
|
|
627
|
+
);
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
describe('GetTestCasesBySuites', () => {
|
|
632
|
+
it('should return test cases for suites', async () => {
|
|
633
|
+
// Arrange
|
|
634
|
+
const mockSuiteData = [new suiteData('Suite 1', '123', '456', 1)];
|
|
635
|
+
const mockTestCases = {
|
|
636
|
+
count: 1,
|
|
637
|
+
value: [{ testCase: { id: '101', url: 'http://test.com/101' } }],
|
|
638
|
+
};
|
|
639
|
+
const mockTestCaseDetails = {
|
|
640
|
+
id: 101,
|
|
641
|
+
fields: {
|
|
642
|
+
'System.Title': 'Test Case 1',
|
|
643
|
+
'System.AreaPath': 'Area/Path',
|
|
644
|
+
'System.Description': 'Description',
|
|
645
|
+
'Microsoft.VSTS.TCM.Steps': null,
|
|
646
|
+
},
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
650
|
+
.mockResolvedValueOnce({ testSuites: mockSuiteData })
|
|
651
|
+
.mockResolvedValueOnce(mockTestCases)
|
|
652
|
+
.mockResolvedValueOnce(mockTestCaseDetails);
|
|
653
|
+
(Helper.findSuitesRecursive as jest.Mock).mockReturnValueOnce(mockSuiteData);
|
|
654
|
+
|
|
655
|
+
// Act
|
|
656
|
+
const result = await testDataProvider.GetTestCasesBySuites(
|
|
657
|
+
mockProject,
|
|
658
|
+
mockPlanId,
|
|
659
|
+
mockSuiteId,
|
|
660
|
+
false,
|
|
661
|
+
false,
|
|
662
|
+
false,
|
|
663
|
+
false
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
// Assert
|
|
667
|
+
expect(result.testCasesList).toBeDefined();
|
|
668
|
+
expect(result.requirementToTestCaseTraceMap).toBeDefined();
|
|
669
|
+
expect(result.testCaseToRequirementsTraceMap).toBeDefined();
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it('should use pre-filtered suites when provided', async () => {
|
|
673
|
+
// Arrange
|
|
674
|
+
const preFilteredSuites = [new suiteData('Suite 1', '123', '456', 1)];
|
|
675
|
+
const mockTestCases = {
|
|
676
|
+
count: 1,
|
|
677
|
+
value: [{ testCase: { id: '101', url: 'http://test.com/101' } }],
|
|
678
|
+
};
|
|
679
|
+
const mockTestCaseDetails = {
|
|
680
|
+
id: 101,
|
|
681
|
+
fields: {
|
|
682
|
+
'System.Title': 'Test Case 1',
|
|
683
|
+
'System.AreaPath': 'Area/Path',
|
|
684
|
+
'System.Description': 'Description',
|
|
685
|
+
'Microsoft.VSTS.TCM.Steps': null,
|
|
686
|
+
},
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
690
|
+
.mockResolvedValueOnce(mockTestCases)
|
|
691
|
+
.mockResolvedValueOnce(mockTestCaseDetails);
|
|
692
|
+
|
|
693
|
+
// Act
|
|
694
|
+
const result = await testDataProvider.GetTestCasesBySuites(
|
|
695
|
+
mockProject,
|
|
696
|
+
mockPlanId,
|
|
697
|
+
mockSuiteId,
|
|
698
|
+
false,
|
|
699
|
+
false,
|
|
700
|
+
false,
|
|
701
|
+
false,
|
|
702
|
+
undefined,
|
|
703
|
+
undefined,
|
|
704
|
+
preFilteredSuites
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
// Assert
|
|
708
|
+
expect(result.testCasesList).toBeDefined();
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
it('should handle errors in suite processing', async () => {
|
|
712
|
+
// Arrange
|
|
713
|
+
const preFilteredSuites = [new suiteData('Suite 1', '123', '456', 1)];
|
|
714
|
+
|
|
715
|
+
(TFSServices.getItemContent as jest.Mock).mockRejectedValueOnce(new Error('API Error'));
|
|
716
|
+
|
|
717
|
+
// Act
|
|
718
|
+
const result = await testDataProvider.GetTestCasesBySuites(
|
|
719
|
+
mockProject,
|
|
720
|
+
mockPlanId,
|
|
721
|
+
mockSuiteId,
|
|
722
|
+
false,
|
|
723
|
+
false,
|
|
724
|
+
false,
|
|
725
|
+
false,
|
|
726
|
+
undefined,
|
|
727
|
+
undefined,
|
|
728
|
+
preFilteredSuites
|
|
729
|
+
);
|
|
730
|
+
|
|
731
|
+
// Assert
|
|
732
|
+
expect(result.testCasesList).toEqual([]);
|
|
733
|
+
});
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
describe('StructureTestCase', () => {
|
|
737
|
+
it('should return empty array when no test cases', async () => {
|
|
738
|
+
// Arrange
|
|
739
|
+
const suite = new suiteData('Suite 1', '123', '456', 1);
|
|
740
|
+
const testCases = { count: 0, value: [] };
|
|
741
|
+
|
|
742
|
+
// Act
|
|
743
|
+
const result = await testDataProvider.StructureTestCase(
|
|
744
|
+
mockProject,
|
|
745
|
+
testCases,
|
|
746
|
+
suite,
|
|
747
|
+
false,
|
|
748
|
+
false,
|
|
749
|
+
false,
|
|
750
|
+
new Map(),
|
|
751
|
+
new Map()
|
|
752
|
+
);
|
|
753
|
+
|
|
754
|
+
// Assert
|
|
755
|
+
expect(result).toEqual([]);
|
|
756
|
+
expect(logger.warn).toHaveBeenCalled();
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
it('should warn and return [] when no testCases are provided', async () => {
|
|
760
|
+
const res = await testDataProvider.StructureTestCase(
|
|
761
|
+
mockProject,
|
|
762
|
+
{ value: [], count: 0 } as any,
|
|
763
|
+
{ id: '1', name: 'Suite 1' } as any,
|
|
764
|
+
true,
|
|
765
|
+
true,
|
|
766
|
+
false,
|
|
767
|
+
new Map<string, string[]>(),
|
|
768
|
+
new Map<string, string[]>()
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
expect(res).toEqual([]);
|
|
772
|
+
expect(logger.warn).toHaveBeenCalledWith('No test cases found for suite: 1');
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
it('should parse steps, add requirement relation when enabled, and add mom relation when enabled', async () => {
|
|
776
|
+
const UtilsMock: any = require('../../utils/testStepParserHelper').default;
|
|
777
|
+
const parserInstance = UtilsMock.mock.results[0].value;
|
|
778
|
+
parserInstance.parseTestSteps.mockResolvedValueOnce([{ stepId: '1' }]);
|
|
779
|
+
|
|
780
|
+
const suite = { id: '1', name: 'Suite 1' } as any;
|
|
781
|
+
const testCases = {
|
|
782
|
+
count: 1,
|
|
783
|
+
value: [{ testCase: { id: 123, url: 'https://example.com/testcase/123' } }],
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
jest.spyOn(testDataProvider as any, 'fetchWithCache').mockImplementation(async (...args: any[]) => {
|
|
787
|
+
const url = String(args[0] || '');
|
|
788
|
+
if (url.includes('testcase/123') && url.includes('?$expand=All')) {
|
|
789
|
+
return {
|
|
790
|
+
id: 123,
|
|
791
|
+
fields: {
|
|
792
|
+
'System.Title': 'TC 123',
|
|
793
|
+
'System.AreaPath': 'A',
|
|
794
|
+
'System.Description': 'D',
|
|
795
|
+
'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
|
|
796
|
+
},
|
|
797
|
+
relations: [
|
|
798
|
+
{ url: 'https://example.com/_apis/wit/workItems/200' },
|
|
799
|
+
{ url: 'https://example.com/not-work-items/ignore' },
|
|
800
|
+
{ url: 'https://example.com/_apis/wit/workItems/201' },
|
|
801
|
+
],
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
if (url.includes('/workItems/200')) {
|
|
805
|
+
return {
|
|
806
|
+
id: 200,
|
|
807
|
+
fields: {
|
|
808
|
+
'System.WorkItemType': 'Requirement',
|
|
809
|
+
'System.Title': 'REQ 200',
|
|
810
|
+
'Custom.CustomerRequirementId': 'C-200',
|
|
811
|
+
},
|
|
812
|
+
_links: { html: { href: 'http://example.com/200' } },
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
if (url.includes('/workItems/201')) {
|
|
816
|
+
return {
|
|
817
|
+
id: 201,
|
|
818
|
+
fields: {
|
|
819
|
+
'System.WorkItemType': 'Bug',
|
|
820
|
+
'System.Title': 'BUG 201',
|
|
821
|
+
'System.State': 'Active',
|
|
822
|
+
},
|
|
823
|
+
_links: { html: { href: 'http://example.com/201' } },
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
throw new Error(`unexpected url ${url}`);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
const requirementToTestCaseTraceMap = new Map<string, string[]>();
|
|
830
|
+
const testCaseToRequirementsTraceMap = new Map<string, string[]>();
|
|
831
|
+
|
|
832
|
+
const res = await testDataProvider.StructureTestCase(
|
|
833
|
+
mockProject,
|
|
834
|
+
testCases as any,
|
|
835
|
+
suite,
|
|
836
|
+
true,
|
|
837
|
+
true,
|
|
838
|
+
true,
|
|
839
|
+
requirementToTestCaseTraceMap,
|
|
840
|
+
testCaseToRequirementsTraceMap
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
expect(res).toHaveLength(1);
|
|
844
|
+
expect(res[0].steps).toEqual([{ stepId: '1' }]);
|
|
845
|
+
expect(res[0].relations).toEqual(
|
|
846
|
+
expect.arrayContaining([
|
|
847
|
+
expect.objectContaining({ type: 'requirement', id: 200, customerId: 'C-200' }),
|
|
848
|
+
])
|
|
849
|
+
);
|
|
850
|
+
expect(res[0].relations).toEqual(
|
|
851
|
+
expect.arrayContaining([expect.objectContaining({ type: 'Bug', id: 201 })])
|
|
852
|
+
);
|
|
853
|
+
expect(requirementToTestCaseTraceMap.size).toBe(1);
|
|
854
|
+
expect(testCaseToRequirementsTraceMap.size).toBe(1);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
it('should use stepResultDetailsMap when provided and skip parseTestSteps', async () => {
|
|
858
|
+
const UtilsMock: any = require('../../utils/testStepParserHelper').default;
|
|
859
|
+
const parserInstance = UtilsMock.mock.results[0].value;
|
|
860
|
+
parserInstance.parseTestSteps.mockClear();
|
|
861
|
+
|
|
862
|
+
const suite = { id: '1', name: 'Suite 1' } as any;
|
|
863
|
+
const testCases = {
|
|
864
|
+
count: 1,
|
|
865
|
+
value: [{ testCase: { id: 123, url: 'https://example.com/testcase/123' } }],
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
const stepResultDetailsMap = new Map<string, any>();
|
|
869
|
+
stepResultDetailsMap.set('123', {
|
|
870
|
+
testCaseRevision: 7,
|
|
871
|
+
stepList: [{ stepId: 'from-cache' }],
|
|
872
|
+
caseEvidenceAttachments: [{ id: 1 }],
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
jest.spyOn(testDataProvider as any, 'fetchWithCache').mockResolvedValueOnce({
|
|
876
|
+
id: 123,
|
|
877
|
+
fields: {
|
|
878
|
+
'System.Title': 'TC 123',
|
|
879
|
+
'System.AreaPath': 'A',
|
|
880
|
+
'System.Description': 'D',
|
|
881
|
+
'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
|
|
882
|
+
},
|
|
883
|
+
relations: [],
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
const res = await testDataProvider.StructureTestCase(
|
|
887
|
+
mockProject,
|
|
888
|
+
testCases as any,
|
|
889
|
+
suite,
|
|
890
|
+
true,
|
|
891
|
+
false,
|
|
892
|
+
false,
|
|
893
|
+
new Map<string, string[]>(),
|
|
894
|
+
new Map<string, string[]>(),
|
|
895
|
+
stepResultDetailsMap
|
|
896
|
+
);
|
|
897
|
+
|
|
898
|
+
expect(res).toHaveLength(1);
|
|
899
|
+
expect(res[0].steps).toEqual([{ stepId: 'from-cache' }]);
|
|
900
|
+
expect(res[0].caseEvidenceAttachments).toEqual([{ id: 1 }]);
|
|
901
|
+
expect(parserInstance.parseTestSteps).not.toHaveBeenCalled();
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it('should log error and continue when fetching relation content fails', async () => {
|
|
905
|
+
const suite = { id: '1', name: 'Suite 1' } as any;
|
|
906
|
+
const testCases = {
|
|
907
|
+
count: 1,
|
|
908
|
+
value: [{ testCase: { id: 123, url: 'https://example.com/testcase/123' } }],
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
jest.spyOn(testDataProvider as any, 'fetchWithCache').mockImplementation(async (...args: any[]) => {
|
|
912
|
+
const url = String(args[0] || '');
|
|
913
|
+
if (url.includes('testcase/123') && url.includes('?$expand=All')) {
|
|
914
|
+
return {
|
|
915
|
+
id: 123,
|
|
916
|
+
fields: {
|
|
917
|
+
'System.Title': 'TC 123',
|
|
918
|
+
'System.AreaPath': 'A',
|
|
919
|
+
'System.Description': 'D',
|
|
920
|
+
'Microsoft.VSTS.TCM.Steps': null,
|
|
921
|
+
},
|
|
922
|
+
relations: [{ url: 'https://example.com/_apis/wit/workItems/200' }],
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
if (url.includes('/workItems/200')) {
|
|
926
|
+
throw new Error('boom');
|
|
927
|
+
}
|
|
928
|
+
return null;
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
const res = await testDataProvider.StructureTestCase(
|
|
932
|
+
mockProject,
|
|
933
|
+
testCases as any,
|
|
934
|
+
suite,
|
|
935
|
+
true,
|
|
936
|
+
true,
|
|
937
|
+
false,
|
|
938
|
+
new Map<string, string[]>(),
|
|
939
|
+
new Map<string, string[]>()
|
|
940
|
+
);
|
|
941
|
+
|
|
942
|
+
expect(res).toHaveLength(1);
|
|
943
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
944
|
+
expect.stringContaining(
|
|
945
|
+
'Failed to fetch relation content for URL https://example.com/_apis/wit/workItems/200'
|
|
946
|
+
)
|
|
947
|
+
);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
it('should append linked MOMs from testCaseToLinkedMomLookup when provided', async () => {
|
|
951
|
+
const suite = { id: '1', name: 'Suite 1' } as any;
|
|
952
|
+
const testCases = {
|
|
953
|
+
count: 1,
|
|
954
|
+
value: [{ testCase: { id: 123, url: 'https://example.com/testcase/123' } }],
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
jest.spyOn(testDataProvider as any, 'fetchWithCache').mockResolvedValueOnce({
|
|
958
|
+
id: 123,
|
|
959
|
+
fields: {
|
|
960
|
+
'System.Title': 'TC 123',
|
|
961
|
+
'System.AreaPath': 'A',
|
|
962
|
+
'System.Description': 'D',
|
|
963
|
+
'Microsoft.VSTS.TCM.Steps': null,
|
|
964
|
+
},
|
|
965
|
+
relations: [],
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
const testCaseToLinkedMomLookup = new Map<number, Set<any>>();
|
|
969
|
+
testCaseToLinkedMomLookup.set(
|
|
970
|
+
123,
|
|
971
|
+
new Set([
|
|
972
|
+
{
|
|
973
|
+
id: 900,
|
|
974
|
+
fields: {
|
|
975
|
+
'System.WorkItemType': 'Task',
|
|
976
|
+
'System.Title': 'Task 900',
|
|
977
|
+
'System.State': 'Active',
|
|
978
|
+
},
|
|
979
|
+
_links: { html: { href: 'http://example.com/900' } },
|
|
980
|
+
},
|
|
981
|
+
])
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
const res = await testDataProvider.StructureTestCase(
|
|
985
|
+
mockProject,
|
|
986
|
+
testCases as any,
|
|
987
|
+
suite,
|
|
988
|
+
false,
|
|
989
|
+
false,
|
|
990
|
+
false,
|
|
991
|
+
new Map<string, string[]>(),
|
|
992
|
+
new Map<string, string[]>(),
|
|
993
|
+
undefined,
|
|
994
|
+
testCaseToLinkedMomLookup
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
expect(res).toHaveLength(1);
|
|
998
|
+
expect(res[0].relations).toEqual(
|
|
999
|
+
expect.arrayContaining([expect.objectContaining({ type: 'Task', id: 900 })])
|
|
1000
|
+
);
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
it('should return empty list when test case fetch fails', async () => {
|
|
1004
|
+
const suite = { id: '1', name: 'Suite 1' } as any;
|
|
1005
|
+
const testCases = {
|
|
1006
|
+
count: 1,
|
|
1007
|
+
value: [{ testCase: { id: 123, url: 'https://example.com/testcase/123' } }],
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
jest.spyOn(testDataProvider as any, 'fetchWithCache').mockRejectedValueOnce(new Error('boom'));
|
|
1011
|
+
|
|
1012
|
+
const res = await testDataProvider.StructureTestCase(
|
|
1013
|
+
mockProject,
|
|
1014
|
+
testCases as any,
|
|
1015
|
+
suite,
|
|
1016
|
+
false,
|
|
1017
|
+
false,
|
|
1018
|
+
false,
|
|
1019
|
+
new Map<string, string[]>(),
|
|
1020
|
+
new Map<string, string[]>()
|
|
1021
|
+
);
|
|
1022
|
+
|
|
1023
|
+
expect(res).toEqual([]);
|
|
1024
|
+
expect(logger.error).toHaveBeenCalledWith('Error: ran into an issue while retrieving testCase 123');
|
|
1025
|
+
});
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
describe('ParseSteps', () => {
|
|
1029
|
+
it('should parse XML steps correctly', () => {
|
|
1030
|
+
// Arrange
|
|
1031
|
+
const stepsXml = `<steps>
|
|
1032
|
+
<step>
|
|
1033
|
+
<parameterizedString>Action 1</parameterizedString>
|
|
1034
|
+
<parameterizedString>Expected 1</parameterizedString>
|
|
1035
|
+
</step>
|
|
1036
|
+
</steps>`;
|
|
1037
|
+
|
|
1038
|
+
// Act
|
|
1039
|
+
const result = testDataProvider.ParseSteps(stepsXml);
|
|
1040
|
+
|
|
1041
|
+
// Assert - ParseSteps uses xml2js.parseString which is synchronous callback-based
|
|
1042
|
+
// The result may be empty if parsing fails or steps format doesn't match
|
|
1043
|
+
expect(Array.isArray(result)).toBe(true);
|
|
1044
|
+
});
|
|
1045
|
+
});
|
|
1046
|
+
});
|