@elisra-devops/docgen-data-provider 1.63.12 → 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/MangementDataProvider.js +7 -1
- package/bin/modules/MangementDataProvider.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 -27
- package/bin/modules/TicketsDataProvider.js +226 -122
- 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 +39 -31
- 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 +10 -1
- package/src/helpers/tfs.ts +51 -7
- package/src/modules/GitDataProvider.ts +10 -0
- package/src/modules/MangementDataProvider.ts +6 -1
- package/src/modules/TestDataProvider.ts +0 -1
- package/src/modules/TicketsDataProvider.ts +311 -151
- 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 +63 -32
- 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 -433
- 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 -322
- 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 -691
- 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 -434
- /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,2571 @@
|
|
|
1
|
+
import { TFSServices } from '../../helpers/tfs';
|
|
2
|
+
import ResultDataProvider from '../../modules/ResultDataProvider';
|
|
3
|
+
import logger from '../../utils/logger';
|
|
4
|
+
import Utils from '../../utils/testStepParserHelper';
|
|
5
|
+
|
|
6
|
+
// Mock dependencies
|
|
7
|
+
jest.mock('../../helpers/tfs');
|
|
8
|
+
jest.mock('../../utils/logger');
|
|
9
|
+
jest.mock('../../utils/testStepParserHelper');
|
|
10
|
+
jest.mock('../../modules/TicketsDataProvider', () => {
|
|
11
|
+
return {
|
|
12
|
+
__esModule: true,
|
|
13
|
+
default: jest.fn().mockImplementation(() => ({
|
|
14
|
+
GetQueryResultsFromWiql: jest.fn(),
|
|
15
|
+
})),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
jest.mock('p-limit', () => () => (fn: Function) => fn());
|
|
19
|
+
|
|
20
|
+
describe('ResultDataProvider', () => {
|
|
21
|
+
let resultDataProvider: ResultDataProvider;
|
|
22
|
+
const mockOrgUrl = 'https://dev.azure.com/organization/';
|
|
23
|
+
const mockToken = 'mock-token';
|
|
24
|
+
const mockProjectName = 'test-project';
|
|
25
|
+
const mockTestPlanId = '12345';
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
jest.clearAllMocks();
|
|
29
|
+
resultDataProvider = new ResultDataProvider(mockOrgUrl, mockToken);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('Utility methods', () => {
|
|
33
|
+
describe('flattenSuites', () => {
|
|
34
|
+
it('should flatten a hierarchical suite structure into a single-level array', () => {
|
|
35
|
+
// Arrange
|
|
36
|
+
const suites = [
|
|
37
|
+
{
|
|
38
|
+
id: 1,
|
|
39
|
+
name: 'Parent 1',
|
|
40
|
+
children: [
|
|
41
|
+
{ id: 2, name: 'Child 1' },
|
|
42
|
+
{
|
|
43
|
+
id: 3,
|
|
44
|
+
name: 'Child 2',
|
|
45
|
+
children: [{ id: 4, name: 'Grandchild 1' }],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{ id: 5, name: 'Parent 2' },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
// Act
|
|
53
|
+
const result: any[] = (resultDataProvider as any).flattenSuites(suites);
|
|
54
|
+
|
|
55
|
+
// Assert
|
|
56
|
+
expect(result).toHaveLength(5);
|
|
57
|
+
expect(result.map((s) => s.id)).toEqual([1, 2, 3, 4, 5]);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('filterSuites', () => {
|
|
62
|
+
it('should filter suites based on selected suite IDs', () => {
|
|
63
|
+
// Arrange
|
|
64
|
+
const testSuites = [
|
|
65
|
+
{ id: 1, name: 'Suite 1', parentSuite: { id: 0 } },
|
|
66
|
+
{ id: 2, name: 'Suite 2', parentSuite: { id: 1 } },
|
|
67
|
+
{ id: 3, name: 'Suite 3', parentSuite: { id: 1 } },
|
|
68
|
+
];
|
|
69
|
+
const selectedSuiteIds = [1, 3];
|
|
70
|
+
|
|
71
|
+
// Act
|
|
72
|
+
const result: any[] = (resultDataProvider as any).filterSuites(testSuites, selectedSuiteIds);
|
|
73
|
+
|
|
74
|
+
// Assert
|
|
75
|
+
expect(result).toHaveLength(2);
|
|
76
|
+
expect(result.map((s) => s.id)).toEqual([1, 3]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return all suites with parent when no suite IDs are selected', () => {
|
|
80
|
+
// Arrange
|
|
81
|
+
const testSuites = [
|
|
82
|
+
{ id: 1, name: 'Suite 1', parentSuite: { id: 0 } },
|
|
83
|
+
{ id: 2, name: 'Suite 2', parentSuite: { id: 1 } },
|
|
84
|
+
{ id: 3, name: 'Suite 3', parentSuite: null },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
// Act
|
|
88
|
+
const result: any[] = (resultDataProvider as any).filterSuites(testSuites);
|
|
89
|
+
|
|
90
|
+
// Assert
|
|
91
|
+
expect(result).toHaveLength(2);
|
|
92
|
+
expect(result.map((s) => s.id)).toEqual([1, 2]);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('buildTestGroupName', () => {
|
|
97
|
+
it('should return simple suite name when hierarchy is disabled', () => {
|
|
98
|
+
// Arrange
|
|
99
|
+
const suiteMap = new Map([[1, { id: 1, name: 'Suite 1', parentSuite: { id: 0 } }]]);
|
|
100
|
+
|
|
101
|
+
// Act
|
|
102
|
+
const result = (resultDataProvider as any).buildTestGroupName(1, suiteMap, false);
|
|
103
|
+
|
|
104
|
+
// Assert
|
|
105
|
+
expect(result).toBe('Suite 1');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should build hierarchical name with parent info', () => {
|
|
109
|
+
// Arrange
|
|
110
|
+
const suiteMap = new Map([
|
|
111
|
+
[1, { id: 1, name: 'Parent', parentSuite: null }],
|
|
112
|
+
[2, { id: 2, name: 'Child', parentSuite: { id: 1 } }],
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
// Act
|
|
116
|
+
const result = (resultDataProvider as any).buildTestGroupName(2, suiteMap, true);
|
|
117
|
+
|
|
118
|
+
// Assert
|
|
119
|
+
expect(result).toBe('Child');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should abbreviate deep hierarchies', () => {
|
|
123
|
+
// Arrange
|
|
124
|
+
const suiteMap = new Map([
|
|
125
|
+
[1, { id: 1, name: 'Root', parentSuite: null }],
|
|
126
|
+
[2, { id: 2, name: 'Level1', parentSuite: { id: 1 } }],
|
|
127
|
+
[3, { id: 3, name: 'Level2', parentSuite: { id: 2 } }],
|
|
128
|
+
[4, { id: 4, name: 'Level3', parentSuite: { id: 3 } }],
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
// Act
|
|
132
|
+
const result = (resultDataProvider as any).buildTestGroupName(4, suiteMap, true);
|
|
133
|
+
|
|
134
|
+
// Assert
|
|
135
|
+
expect(result).toBe('Level1/.../Level3');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('convertRunStatus', () => {
|
|
140
|
+
it('should convert API status to readable format', () => {
|
|
141
|
+
// Arrange & Act & Assert
|
|
142
|
+
expect((resultDataProvider as any).convertRunStatus('passed')).toBe('Passed');
|
|
143
|
+
expect((resultDataProvider as any).convertRunStatus('failed')).toBe('Failed');
|
|
144
|
+
expect((resultDataProvider as any).convertRunStatus('notApplicable')).toBe('Not Applicable');
|
|
145
|
+
expect((resultDataProvider as any).convertRunStatus('unknown')).toBe('Not Run');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('compareActionResults', () => {
|
|
150
|
+
it('should compare version-like step positions correctly', () => {
|
|
151
|
+
// Act & Assert
|
|
152
|
+
const compare = (resultDataProvider as any).compareActionResults;
|
|
153
|
+
expect(compare('1', '2')).toBe(-1);
|
|
154
|
+
expect(compare('2', '1')).toBe(1);
|
|
155
|
+
expect(compare('1.1', '1.2')).toBe(-1);
|
|
156
|
+
expect(compare('1.2', '1.1')).toBe(1);
|
|
157
|
+
expect(compare('1.1', '1.1')).toBe(0);
|
|
158
|
+
expect(compare('1.1.1', '1.1')).toBe(1);
|
|
159
|
+
expect(compare('1.1', '1.1.1')).toBe(-1);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('Data fetching methods', () => {
|
|
165
|
+
describe('fetchTestSuites', () => {
|
|
166
|
+
it('should fetch and process test suites correctly', async () => {
|
|
167
|
+
// Arrange
|
|
168
|
+
const mockTestSuites = {
|
|
169
|
+
value: [
|
|
170
|
+
{
|
|
171
|
+
id: 1,
|
|
172
|
+
name: 'Root Suite',
|
|
173
|
+
children: [{ id: 2, name: 'Child Suite 1', parentSuite: { id: 1 } }],
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
count: 1,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockTestSuites);
|
|
180
|
+
|
|
181
|
+
// Act
|
|
182
|
+
const result = await (resultDataProvider as any).fetchTestSuites(mockTestPlanId, mockProjectName);
|
|
183
|
+
|
|
184
|
+
// Assert
|
|
185
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
186
|
+
`${mockOrgUrl}${mockProjectName}/_apis/testplan/Plans/${mockTestPlanId}/Suites?asTreeView=true`,
|
|
187
|
+
mockToken
|
|
188
|
+
);
|
|
189
|
+
expect(result).toHaveLength(1);
|
|
190
|
+
expect(result[0]).toHaveProperty('testSuiteId', 2);
|
|
191
|
+
expect(result[0]).toHaveProperty('testGroupName');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should handle errors and return empty array', async () => {
|
|
195
|
+
// Arrange
|
|
196
|
+
const mockError = new Error('API error');
|
|
197
|
+
(TFSServices.getItemContent as jest.Mock).mockRejectedValueOnce(mockError);
|
|
198
|
+
|
|
199
|
+
// Act
|
|
200
|
+
const result = await (resultDataProvider as any).fetchTestSuites(mockTestPlanId, mockProjectName);
|
|
201
|
+
|
|
202
|
+
// Assert
|
|
203
|
+
expect(logger.error).toHaveBeenCalled();
|
|
204
|
+
expect(result).toEqual([]);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('fetchTestPoints', () => {
|
|
209
|
+
it('should fetch and map test points correctly', async () => {
|
|
210
|
+
// Arrange
|
|
211
|
+
const mockSuiteId = '123';
|
|
212
|
+
const mockTestPoints = {
|
|
213
|
+
value: [
|
|
214
|
+
{
|
|
215
|
+
testCaseReference: { id: 1, name: 'Test Case 1' },
|
|
216
|
+
configuration: { name: 'Config 1' },
|
|
217
|
+
results: {
|
|
218
|
+
outcome: 'passed',
|
|
219
|
+
lastTestRunId: 100,
|
|
220
|
+
lastResultId: 200,
|
|
221
|
+
lastResultDetails: { dateCompleted: '2023-01-01', runBy: { displayName: 'Test User' } },
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
count: 1,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockTestPoints);
|
|
229
|
+
|
|
230
|
+
// Act
|
|
231
|
+
const result = await (resultDataProvider as any).fetchTestPoints(
|
|
232
|
+
mockProjectName,
|
|
233
|
+
mockTestPlanId,
|
|
234
|
+
mockSuiteId
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// Assert
|
|
238
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
239
|
+
`${mockOrgUrl}${mockProjectName}/_apis/testplan/Plans/${mockTestPlanId}/Suites/${mockSuiteId}/TestPoint?includePointDetails=true`,
|
|
240
|
+
mockToken
|
|
241
|
+
);
|
|
242
|
+
expect(result).toHaveLength(1);
|
|
243
|
+
expect(result[0]).toEqual({
|
|
244
|
+
testCaseId: 1,
|
|
245
|
+
testCaseName: 'Test Case 1',
|
|
246
|
+
configurationName: 'Config 1',
|
|
247
|
+
outcome: 'passed',
|
|
248
|
+
lastRunId: 100,
|
|
249
|
+
lastResultId: 200,
|
|
250
|
+
lastResultDetails: { dateCompleted: '2023-01-01', runBy: { displayName: 'Test User' } },
|
|
251
|
+
testCaseUrl: 'https://dev.azure.com/organization/test-project/_workitems/edit/1',
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should handle errors and return empty array', async () => {
|
|
256
|
+
// Arrange
|
|
257
|
+
const mockSuiteId = '123';
|
|
258
|
+
const mockError = new Error('API error');
|
|
259
|
+
(TFSServices.getItemContent as jest.Mock).mockRejectedValueOnce(mockError);
|
|
260
|
+
|
|
261
|
+
// Act
|
|
262
|
+
const result = await (resultDataProvider as any).fetchTestPoints(
|
|
263
|
+
mockProjectName,
|
|
264
|
+
mockTestPlanId,
|
|
265
|
+
mockSuiteId
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Assert
|
|
269
|
+
expect(logger.error).toHaveBeenCalled();
|
|
270
|
+
expect(result).toEqual([]);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe('Data transformation methods', () => {
|
|
276
|
+
describe('mapTestPoint', () => {
|
|
277
|
+
it('should transform test point data correctly', () => {
|
|
278
|
+
// Arrange
|
|
279
|
+
const testPoint = {
|
|
280
|
+
testCaseReference: { id: 1, name: 'Test Case 1' },
|
|
281
|
+
configuration: { name: 'Config 1' },
|
|
282
|
+
results: {
|
|
283
|
+
outcome: 'passed',
|
|
284
|
+
lastTestRunId: 100,
|
|
285
|
+
lastResultId: 200,
|
|
286
|
+
lastResultDetails: { dateCompleted: '2023-01-01', runBy: { displayName: 'Test User' } },
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// Act
|
|
291
|
+
const result = (resultDataProvider as any).mapTestPoint(testPoint, mockProjectName);
|
|
292
|
+
|
|
293
|
+
// Assert
|
|
294
|
+
expect(result).toEqual({
|
|
295
|
+
testCaseId: 1,
|
|
296
|
+
testCaseName: 'Test Case 1',
|
|
297
|
+
configurationName: 'Config 1',
|
|
298
|
+
outcome: 'passed',
|
|
299
|
+
lastRunId: 100,
|
|
300
|
+
lastResultId: 200,
|
|
301
|
+
lastResultDetails: { dateCompleted: '2023-01-01', runBy: { displayName: 'Test User' } },
|
|
302
|
+
testCaseUrl: 'https://dev.azure.com/organization/test-project/_workitems/edit/1',
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should handle missing fields', () => {
|
|
307
|
+
// Arrange
|
|
308
|
+
const testPoint = {
|
|
309
|
+
testCaseReference: { id: 1, name: 'Test Case 1' },
|
|
310
|
+
// No configuration or results
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Act
|
|
314
|
+
const result = (resultDataProvider as any).mapTestPoint(testPoint, mockProjectName);
|
|
315
|
+
|
|
316
|
+
// Assert
|
|
317
|
+
expect(result).toEqual({
|
|
318
|
+
testCaseId: 1,
|
|
319
|
+
testCaseName: 'Test Case 1',
|
|
320
|
+
configurationName: undefined,
|
|
321
|
+
outcome: 'Not Run',
|
|
322
|
+
lastRunId: undefined,
|
|
323
|
+
lastResultId: undefined,
|
|
324
|
+
lastResultDetails: undefined,
|
|
325
|
+
testCaseUrl: 'https://dev.azure.com/organization/test-project/_workitems/edit/1',
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe('calculateGroupResultSummary', () => {
|
|
331
|
+
it('should return empty strings when includeHardCopyRun is true', () => {
|
|
332
|
+
// Arrange
|
|
333
|
+
const testPoints = [{ outcome: 'passed' }, { outcome: 'failed' }];
|
|
334
|
+
|
|
335
|
+
// Act
|
|
336
|
+
const result = (resultDataProvider as any).calculateGroupResultSummary(testPoints, true);
|
|
337
|
+
|
|
338
|
+
// Assert
|
|
339
|
+
expect(result).toEqual({
|
|
340
|
+
passed: '',
|
|
341
|
+
failed: '',
|
|
342
|
+
notApplicable: '',
|
|
343
|
+
blocked: '',
|
|
344
|
+
notRun: '',
|
|
345
|
+
total: '',
|
|
346
|
+
successPercentage: '',
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should calculate summary statistics correctly', () => {
|
|
351
|
+
// Arrange
|
|
352
|
+
const testPoints = [
|
|
353
|
+
{ outcome: 'passed' },
|
|
354
|
+
{ outcome: 'passed' },
|
|
355
|
+
{ outcome: 'failed' },
|
|
356
|
+
{ outcome: 'notApplicable' },
|
|
357
|
+
{ outcome: 'blocked' },
|
|
358
|
+
{ outcome: 'something else' },
|
|
359
|
+
];
|
|
360
|
+
|
|
361
|
+
// Act
|
|
362
|
+
const result = (resultDataProvider as any).calculateGroupResultSummary(testPoints, false);
|
|
363
|
+
|
|
364
|
+
// Assert
|
|
365
|
+
expect(result).toEqual({
|
|
366
|
+
passed: 2,
|
|
367
|
+
failed: 1,
|
|
368
|
+
notApplicable: 1,
|
|
369
|
+
blocked: 1,
|
|
370
|
+
notRun: 1,
|
|
371
|
+
total: 6,
|
|
372
|
+
successPercentage: '33.33%',
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should handle empty array', () => {
|
|
377
|
+
// Arrange
|
|
378
|
+
const testPoints: any[] = [];
|
|
379
|
+
|
|
380
|
+
// Act
|
|
381
|
+
const result = (resultDataProvider as any).calculateGroupResultSummary(testPoints, false);
|
|
382
|
+
|
|
383
|
+
// Assert
|
|
384
|
+
expect(result).toEqual({
|
|
385
|
+
passed: 0,
|
|
386
|
+
failed: 0,
|
|
387
|
+
notApplicable: 0,
|
|
388
|
+
blocked: 0,
|
|
389
|
+
notRun: 0,
|
|
390
|
+
total: 0,
|
|
391
|
+
successPercentage: '0.00%',
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe('mapAttachmentsUrl', () => {
|
|
397
|
+
it('should map attachment URLs correctly', () => {
|
|
398
|
+
// Arrange
|
|
399
|
+
const mockRunResults = [
|
|
400
|
+
{
|
|
401
|
+
testCaseId: 1,
|
|
402
|
+
lastRunId: 100,
|
|
403
|
+
lastResultId: 200,
|
|
404
|
+
iteration: {
|
|
405
|
+
attachments: [{ id: 1, name: 'attachment1.png', actionPath: 'path1' }],
|
|
406
|
+
actionResults: [{ actionPath: 'path1', stepPosition: '1.1' }],
|
|
407
|
+
},
|
|
408
|
+
analysisAttachments: [{ id: 2, fileName: 'analysis1.txt' }],
|
|
409
|
+
},
|
|
410
|
+
];
|
|
411
|
+
|
|
412
|
+
// Act
|
|
413
|
+
const result = resultDataProvider.mapAttachmentsUrl(mockRunResults, mockProjectName);
|
|
414
|
+
|
|
415
|
+
// Assert
|
|
416
|
+
expect(result[0].iteration.attachments[0].downloadUrl).toBe(
|
|
417
|
+
`${mockOrgUrl}${mockProjectName}/_apis/test/runs/100/results/200/attachments/1/attachment1.png`
|
|
418
|
+
);
|
|
419
|
+
expect(result[0].iteration.attachments[0].stepNo).toBe('1.1');
|
|
420
|
+
expect(result[0].analysisAttachments[0].downloadUrl).toBe(
|
|
421
|
+
`${mockOrgUrl}${mockProjectName}/_apis/test/runs/100/results/200/attachments/2/analysis1.txt`
|
|
422
|
+
);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('should handle missing iteration', () => {
|
|
426
|
+
// Arrange
|
|
427
|
+
const mockRunResults = [
|
|
428
|
+
{
|
|
429
|
+
testCaseId: 1,
|
|
430
|
+
lastRunId: 100,
|
|
431
|
+
lastResultId: 200,
|
|
432
|
+
// No iteration
|
|
433
|
+
analysisAttachments: [{ id: 2, fileName: 'analysis1.txt' }],
|
|
434
|
+
},
|
|
435
|
+
];
|
|
436
|
+
|
|
437
|
+
// Act
|
|
438
|
+
const result = resultDataProvider.mapAttachmentsUrl(mockRunResults, mockProjectName);
|
|
439
|
+
|
|
440
|
+
// Assert
|
|
441
|
+
expect(result[0]).toEqual(mockRunResults[0]);
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
describe('formatTestResult', () => {
|
|
447
|
+
it('should format test result correctly', () => {
|
|
448
|
+
// Arrange
|
|
449
|
+
const testPoint = {
|
|
450
|
+
testCaseId: 1,
|
|
451
|
+
testCaseName: 'Test Case 1',
|
|
452
|
+
testGroupName: 'Suite 1',
|
|
453
|
+
testCaseUrl: 'https://example.com/workitems/1',
|
|
454
|
+
configurationName: 'Config 1',
|
|
455
|
+
outcome: 'passed',
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// Act
|
|
459
|
+
const result = (resultDataProvider as any).formatTestResult(testPoint, true, false);
|
|
460
|
+
|
|
461
|
+
// Assert
|
|
462
|
+
expect(result.testId).toBe(1);
|
|
463
|
+
expect(result.testName).toBe('Test Case 1');
|
|
464
|
+
expect(result.configuration).toBe('Config 1');
|
|
465
|
+
expect(result.runStatus).toBe('Passed');
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('should return empty strings when includeHardCopyRun is true', () => {
|
|
469
|
+
// Arrange
|
|
470
|
+
const testPoint = {
|
|
471
|
+
testCaseId: 1,
|
|
472
|
+
testCaseName: 'Test Case 1',
|
|
473
|
+
testGroupName: 'Suite 1',
|
|
474
|
+
outcome: 'passed',
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// Act
|
|
478
|
+
const result = (resultDataProvider as any).formatTestResult(testPoint, false, true);
|
|
479
|
+
|
|
480
|
+
// Assert
|
|
481
|
+
expect(result.runStatus).toBe('');
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
describe('calculateTotalSummary', () => {
|
|
486
|
+
it('should calculate total summary from summarized results', () => {
|
|
487
|
+
// Arrange
|
|
488
|
+
const summarizedResults = [
|
|
489
|
+
{ groupResultSummary: { passed: 2, failed: 1, notApplicable: 0, blocked: 0, notRun: 1, total: 4 } },
|
|
490
|
+
{ groupResultSummary: { passed: 3, failed: 0, notApplicable: 1, blocked: 1, notRun: 0, total: 5 } },
|
|
491
|
+
];
|
|
492
|
+
|
|
493
|
+
// Act
|
|
494
|
+
const result = (resultDataProvider as any).calculateTotalSummary(summarizedResults, false);
|
|
495
|
+
|
|
496
|
+
// Assert
|
|
497
|
+
expect(result.passed).toBe(5);
|
|
498
|
+
expect(result.failed).toBe(1);
|
|
499
|
+
expect(result.notApplicable).toBe(1);
|
|
500
|
+
expect(result.blocked).toBe(1);
|
|
501
|
+
expect(result.notRun).toBe(1);
|
|
502
|
+
expect(result.total).toBe(9);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('should return empty strings when includeHardCopyRun is true', () => {
|
|
506
|
+
// Arrange
|
|
507
|
+
const summarizedResults = [{ groupResultSummary: { passed: 2, failed: 1, total: 3 } }];
|
|
508
|
+
|
|
509
|
+
// Act
|
|
510
|
+
const result = (resultDataProvider as any).calculateTotalSummary(summarizedResults, true);
|
|
511
|
+
|
|
512
|
+
// Assert
|
|
513
|
+
expect(result.passed).toBe('');
|
|
514
|
+
expect(result.failed).toBe('');
|
|
515
|
+
expect(result.total).toBe('');
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
describe('flattenTestPoints', () => {
|
|
520
|
+
it('should flatten test points from suites', () => {
|
|
521
|
+
// Arrange
|
|
522
|
+
const testPoints = [
|
|
523
|
+
{ testSuiteId: 1, testGroupName: 'Suite 1', testPointsItems: [{ id: 1 }, { id: 2 }] },
|
|
524
|
+
{ testSuiteId: 2, testGroupName: 'Suite 2', testPointsItems: [{ id: 3 }] },
|
|
525
|
+
];
|
|
526
|
+
|
|
527
|
+
// Act
|
|
528
|
+
const result = (resultDataProvider as any).flattenTestPoints(testPoints);
|
|
529
|
+
|
|
530
|
+
// Assert
|
|
531
|
+
expect(result).toHaveLength(3);
|
|
532
|
+
expect(result[0].testGroupName).toBe('Suite 1');
|
|
533
|
+
expect(result[2].testGroupName).toBe('Suite 2');
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
describe('createSuiteMap', () => {
|
|
538
|
+
it('should create a map of suites by ID', () => {
|
|
539
|
+
// Arrange
|
|
540
|
+
const suites = [
|
|
541
|
+
{ id: 1, name: 'Suite 1', children: [{ id: 2, name: 'Suite 2' }] },
|
|
542
|
+
{ id: 3, name: 'Suite 3' },
|
|
543
|
+
];
|
|
544
|
+
|
|
545
|
+
// Act
|
|
546
|
+
const result = (resultDataProvider as any).createSuiteMap(suites);
|
|
547
|
+
|
|
548
|
+
// Assert
|
|
549
|
+
expect(result).toBeInstanceOf(Map);
|
|
550
|
+
expect(result.size).toBe(3);
|
|
551
|
+
expect(result.get(1).name).toBe('Suite 1');
|
|
552
|
+
expect(result.get(2).name).toBe('Suite 2');
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
describe('isNotRunStep', () => {
|
|
557
|
+
it('should return true for not run step', () => {
|
|
558
|
+
// Arrange
|
|
559
|
+
const step = { stepStatus: 'Not Run' };
|
|
560
|
+
|
|
561
|
+
// Act
|
|
562
|
+
const result = (resultDataProvider as any).isNotRunStep(step);
|
|
563
|
+
|
|
564
|
+
// Assert
|
|
565
|
+
expect(result).toBe(true);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('should return false for run step', () => {
|
|
569
|
+
// Arrange
|
|
570
|
+
const step = { stepStatus: 'Passed' };
|
|
571
|
+
|
|
572
|
+
// Act
|
|
573
|
+
const result = (resultDataProvider as any).isNotRunStep(step);
|
|
574
|
+
|
|
575
|
+
// Assert
|
|
576
|
+
expect(result).toBe(false);
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
describe('CreateAttachmentPathIndexMap', () => {
|
|
581
|
+
it('should create map of action paths to step positions', () => {
|
|
582
|
+
// Arrange
|
|
583
|
+
const actionResults = [
|
|
584
|
+
{ actionPath: 'path1', stepPosition: '1.1' },
|
|
585
|
+
{ actionPath: 'path2', stepPosition: '1.2' },
|
|
586
|
+
];
|
|
587
|
+
|
|
588
|
+
// Act
|
|
589
|
+
const result = (resultDataProvider as any).CreateAttachmentPathIndexMap(actionResults);
|
|
590
|
+
|
|
591
|
+
// Assert
|
|
592
|
+
expect(result).toBeInstanceOf(Map);
|
|
593
|
+
expect(result.get('path1')).toBe('1.1');
|
|
594
|
+
expect(result.get('path2')).toBe('1.2');
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
describe('getTestPointsForTestCases', () => {
|
|
599
|
+
it('should fetch test points for test cases', async () => {
|
|
600
|
+
// Arrange
|
|
601
|
+
const mockTestCaseIds = ['1', '2'];
|
|
602
|
+
const mockResponse = { data: { points: [{ id: 1 }] } };
|
|
603
|
+
(TFSServices.postRequest as jest.Mock).mockResolvedValueOnce(mockResponse);
|
|
604
|
+
|
|
605
|
+
// Act
|
|
606
|
+
const result = await resultDataProvider.getTestPointsForTestCases(mockProjectName, mockTestCaseIds);
|
|
607
|
+
|
|
608
|
+
// Assert
|
|
609
|
+
expect(TFSServices.postRequest).toHaveBeenCalled();
|
|
610
|
+
expect(result).toEqual(mockResponse);
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
describe('mapActionPathToPosition', () => {
|
|
615
|
+
it('should map action paths to positions', () => {
|
|
616
|
+
// Arrange
|
|
617
|
+
const actionResults = [
|
|
618
|
+
{ testId: 1, actionPath: 'path1', stepNo: '1.1' },
|
|
619
|
+
{ testId: 1, actionPath: 'path2', stepNo: '1.2' },
|
|
620
|
+
];
|
|
621
|
+
|
|
622
|
+
// Act
|
|
623
|
+
const result = (resultDataProvider as any).mapActionPathToPosition(actionResults);
|
|
624
|
+
|
|
625
|
+
// Assert
|
|
626
|
+
expect(result).toBeInstanceOf(Map);
|
|
627
|
+
expect(result.get('1-path1')).toBe('1.1');
|
|
628
|
+
expect(result.get('1-path2')).toBe('1.2');
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
describe('fetchTestPlanName', () => {
|
|
633
|
+
it('should fetch test plan name', async () => {
|
|
634
|
+
// Arrange
|
|
635
|
+
const mockPlan = { name: 'Test Plan 1' };
|
|
636
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockPlan);
|
|
637
|
+
|
|
638
|
+
// Act
|
|
639
|
+
const result = await (resultDataProvider as any).fetchTestPlanName(mockTestPlanId, mockProjectName);
|
|
640
|
+
|
|
641
|
+
// Assert
|
|
642
|
+
expect(result).toBe('Test Plan 1');
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it('should return empty string on error', async () => {
|
|
646
|
+
// Arrange
|
|
647
|
+
(TFSServices.getItemContent as jest.Mock).mockRejectedValueOnce(new Error('API Error'));
|
|
648
|
+
|
|
649
|
+
// Act
|
|
650
|
+
const result = await (resultDataProvider as any).fetchTestPlanName(mockTestPlanId, mockProjectName);
|
|
651
|
+
|
|
652
|
+
// Assert
|
|
653
|
+
expect(result).toBe('');
|
|
654
|
+
expect(logger.error).toHaveBeenCalled();
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
describe('fetchTestCasesBySuiteId', () => {
|
|
659
|
+
it('should fetch test cases by suite ID', async () => {
|
|
660
|
+
// Arrange
|
|
661
|
+
const mockSuiteId = '123';
|
|
662
|
+
const mockTestCases = { value: [{ workItem: { id: 1 } }] };
|
|
663
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockTestCases);
|
|
664
|
+
|
|
665
|
+
// Act
|
|
666
|
+
const result = await (resultDataProvider as any).fetchTestCasesBySuiteId(
|
|
667
|
+
mockProjectName,
|
|
668
|
+
mockTestPlanId,
|
|
669
|
+
mockSuiteId
|
|
670
|
+
);
|
|
671
|
+
|
|
672
|
+
// Assert
|
|
673
|
+
expect(result).toEqual(mockTestCases.value);
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
describe('mapTestPointForCrossPlans', () => {
|
|
678
|
+
it('should map test point for cross plans', () => {
|
|
679
|
+
// Arrange
|
|
680
|
+
const testPoint = {
|
|
681
|
+
testCase: { id: 1, name: 'Test Case 1' },
|
|
682
|
+
testSuite: { id: 2, name: 'Suite 1' },
|
|
683
|
+
configuration: { name: 'Config 1' },
|
|
684
|
+
outcome: 'passed',
|
|
685
|
+
lastTestRun: { id: '100' },
|
|
686
|
+
lastResult: { id: '200' },
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
// Act
|
|
690
|
+
const result = (resultDataProvider as any).mapTestPointForCrossPlans(testPoint, mockProjectName);
|
|
691
|
+
|
|
692
|
+
// Assert
|
|
693
|
+
expect(result.testCaseId).toBe(1);
|
|
694
|
+
expect(result.testCaseName).toBe('Test Case 1');
|
|
695
|
+
expect(result.outcome).toBe('passed');
|
|
696
|
+
expect(result.lastRunId).toBe('100');
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
it('should provide default values for missing fields', () => {
|
|
700
|
+
// Arrange
|
|
701
|
+
const testPoint = {
|
|
702
|
+
testCase: { id: 1, name: 'Test Case 1' },
|
|
703
|
+
testSuite: { id: 2, name: 'Suite 1' },
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
// Act
|
|
707
|
+
const result = (resultDataProvider as any).mapTestPointForCrossPlans(testPoint, mockProjectName);
|
|
708
|
+
|
|
709
|
+
// Assert
|
|
710
|
+
expect(result.outcome).toBe('Not Run');
|
|
711
|
+
expect(result.lastResultDetails).toBeDefined();
|
|
712
|
+
expect(result.lastResultDetails.duration).toBe(0);
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
describe('mapStepResultsForExecutionAppendix', () => {
|
|
717
|
+
it('should map step results for execution appendix', () => {
|
|
718
|
+
// Arrange
|
|
719
|
+
const detailedResults = [
|
|
720
|
+
{
|
|
721
|
+
testId: 1,
|
|
722
|
+
testCaseRevision: { rev: 1 },
|
|
723
|
+
stepNo: '1.1',
|
|
724
|
+
stepIdentifier: 'step1',
|
|
725
|
+
stepAction: 'Do something',
|
|
726
|
+
stepExpected: 'Something happens',
|
|
727
|
+
stepStatus: 'Passed',
|
|
728
|
+
stepComments: '',
|
|
729
|
+
isSharedStepTitle: false,
|
|
730
|
+
actionPath: 'path1',
|
|
731
|
+
},
|
|
732
|
+
];
|
|
733
|
+
const runResultData = [
|
|
734
|
+
{
|
|
735
|
+
testCaseId: 1,
|
|
736
|
+
iteration: {
|
|
737
|
+
attachments: [{ name: 'attachment.png', actionPath: 'path1', downloadUrl: 'http://example.com' }],
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
];
|
|
741
|
+
|
|
742
|
+
// Act
|
|
743
|
+
const result = (resultDataProvider as any).mapStepResultsForExecutionAppendix(
|
|
744
|
+
detailedResults,
|
|
745
|
+
runResultData
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
// Assert
|
|
749
|
+
expect(result).toBeInstanceOf(Map);
|
|
750
|
+
expect(result.has('1')).toBe(true);
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
describe('fetchCrossTestPoints', () => {
|
|
755
|
+
it('should return empty array when no test case IDs provided', async () => {
|
|
756
|
+
// Act
|
|
757
|
+
const result = await (resultDataProvider as any).fetchCrossTestPoints(mockProjectName, []);
|
|
758
|
+
|
|
759
|
+
// Assert
|
|
760
|
+
expect(result).toEqual([]);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
it('should handle error and return empty array', async () => {
|
|
764
|
+
// Arrange
|
|
765
|
+
(TFSServices.postRequest as jest.Mock).mockRejectedValueOnce(new Error('API Error'));
|
|
766
|
+
|
|
767
|
+
// Act
|
|
768
|
+
const result = await (resultDataProvider as any).fetchCrossTestPoints(mockProjectName, [1, 2]);
|
|
769
|
+
|
|
770
|
+
// Assert
|
|
771
|
+
expect(result).toEqual([]);
|
|
772
|
+
expect(logger.error).toHaveBeenCalled();
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
it('should return empty array when API returns invalid response format', async () => {
|
|
776
|
+
(TFSServices.postRequest as jest.Mock).mockResolvedValueOnce({ data: {} });
|
|
777
|
+
|
|
778
|
+
const result = await (resultDataProvider as any).fetchCrossTestPoints(mockProjectName, [1, 2]);
|
|
779
|
+
|
|
780
|
+
expect(result).toEqual([]);
|
|
781
|
+
expect(logger.warn).toHaveBeenCalledWith('No test points found or invalid response format');
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
it('should pick the latest point per test case and map details with defaults', async () => {
|
|
785
|
+
(TFSServices.postRequest as jest.Mock).mockResolvedValueOnce({
|
|
786
|
+
data: {
|
|
787
|
+
points: [
|
|
788
|
+
{
|
|
789
|
+
testCase: { id: 1 },
|
|
790
|
+
lastTestRun: { id: '1' },
|
|
791
|
+
lastResult: { id: '5' },
|
|
792
|
+
url: 'https://example.com/points/1',
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
testCase: { id: 1 },
|
|
796
|
+
lastTestRun: { id: '2' },
|
|
797
|
+
lastResult: { id: '1' },
|
|
798
|
+
url: 'https://example.com/points/2',
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
testCase: { id: 2 },
|
|
802
|
+
lastTestRun: { id: '1' },
|
|
803
|
+
lastResult: { id: '1' },
|
|
804
|
+
url: 'https://example.com/points/3',
|
|
805
|
+
},
|
|
806
|
+
],
|
|
807
|
+
},
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
811
|
+
.mockResolvedValueOnce({
|
|
812
|
+
testCase: { id: 1, name: 'TC 1' },
|
|
813
|
+
testSuite: { id: 10, name: 'Suite' },
|
|
814
|
+
configuration: { name: 'Config' },
|
|
815
|
+
outcome: 'passed',
|
|
816
|
+
lastTestRun: { id: '2' },
|
|
817
|
+
lastResult: { id: '1' },
|
|
818
|
+
})
|
|
819
|
+
.mockResolvedValueOnce({
|
|
820
|
+
testCase: { id: 2, name: 'TC 2' },
|
|
821
|
+
testSuite: { id: 11, name: 'Suite' },
|
|
822
|
+
configuration: { name: 'Config' },
|
|
823
|
+
outcome: 'failed',
|
|
824
|
+
lastTestRun: { id: '1' },
|
|
825
|
+
lastResult: { id: '1' },
|
|
826
|
+
lastResultDetails: { duration: 5, dateCompleted: '2023-01-01', runBy: { displayName: 'User' } },
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
const result = await (resultDataProvider as any).fetchCrossTestPoints(mockProjectName, [1, 2]);
|
|
830
|
+
|
|
831
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
832
|
+
'https://example.com/points/2?witFields=Microsoft.VSTS.TCM.Steps&includePointDetails=true',
|
|
833
|
+
mockToken
|
|
834
|
+
);
|
|
835
|
+
expect(result).toHaveLength(2);
|
|
836
|
+
const tc1 = result.find((r: any) => r.testCaseId === 1);
|
|
837
|
+
expect(tc1.lastResultDetails).toEqual(
|
|
838
|
+
expect.objectContaining({
|
|
839
|
+
duration: 0,
|
|
840
|
+
runBy: expect.objectContaining({ displayName: 'No tester' }),
|
|
841
|
+
})
|
|
842
|
+
);
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
describe('fetchLinkedWi', () => {
|
|
847
|
+
it('should fetch linked work items and filter only open Bugs/Change Requests', async () => {
|
|
848
|
+
const testItems = [
|
|
849
|
+
{
|
|
850
|
+
testId: 1,
|
|
851
|
+
testName: 'TC 1',
|
|
852
|
+
testCaseUrl: 'http://example.com/tc/1',
|
|
853
|
+
runStatus: 'Passed',
|
|
854
|
+
},
|
|
855
|
+
];
|
|
856
|
+
|
|
857
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
858
|
+
.mockResolvedValueOnce({
|
|
859
|
+
value: [
|
|
860
|
+
{
|
|
861
|
+
id: 1,
|
|
862
|
+
relations: [
|
|
863
|
+
{ url: `${mockOrgUrl}_apis/wit/workItems/100` },
|
|
864
|
+
{ url: `${mockOrgUrl}_apis/wit/workItems/101` },
|
|
865
|
+
],
|
|
866
|
+
},
|
|
867
|
+
],
|
|
868
|
+
})
|
|
869
|
+
.mockResolvedValueOnce({
|
|
870
|
+
value: [
|
|
871
|
+
{
|
|
872
|
+
id: 100,
|
|
873
|
+
fields: {
|
|
874
|
+
'System.WorkItemType': 'Bug',
|
|
875
|
+
'System.State': 'Active',
|
|
876
|
+
'System.Title': 'Open bug',
|
|
877
|
+
'Microsoft.VSTS.Common.Severity': '1 - Critical',
|
|
878
|
+
},
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
id: 101,
|
|
882
|
+
fields: {
|
|
883
|
+
'System.WorkItemType': 'Bug',
|
|
884
|
+
'System.State': 'Closed',
|
|
885
|
+
'System.Title': 'Closed bug',
|
|
886
|
+
},
|
|
887
|
+
},
|
|
888
|
+
],
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
const result = await (resultDataProvider as any).fetchLinkedWi(mockProjectName, testItems);
|
|
892
|
+
|
|
893
|
+
expect(result).toHaveLength(1);
|
|
894
|
+
expect(result[0].linkItems).toHaveLength(1);
|
|
895
|
+
expect(result[0].linkItems[0]).toEqual(
|
|
896
|
+
expect.objectContaining({
|
|
897
|
+
pcrId: 100,
|
|
898
|
+
workItemType: 'Bug',
|
|
899
|
+
title: 'Open bug',
|
|
900
|
+
pcrUrl: `${mockOrgUrl}${mockProjectName}/_workitems/edit/100`,
|
|
901
|
+
})
|
|
902
|
+
);
|
|
903
|
+
});
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
describe('getTestReporterResults filtering', () => {
|
|
907
|
+
it('should apply errorFilterMode=both and run-step filter and return the filtered rows', async () => {
|
|
908
|
+
const testReporterRows = [
|
|
909
|
+
{
|
|
910
|
+
testCase: {
|
|
911
|
+
comment: 'has comment',
|
|
912
|
+
result: { resultMessage: 'Failed in Run 1' },
|
|
913
|
+
},
|
|
914
|
+
stepComments: 'step comment',
|
|
915
|
+
stepStatus: 'Failed',
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
testCase: {
|
|
919
|
+
comment: '',
|
|
920
|
+
result: { resultMessage: 'Passed' },
|
|
921
|
+
},
|
|
922
|
+
stepComments: '',
|
|
923
|
+
stepStatus: 'Not Run',
|
|
924
|
+
},
|
|
925
|
+
];
|
|
926
|
+
|
|
927
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
928
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([]);
|
|
929
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([]);
|
|
930
|
+
jest.spyOn(resultDataProvider as any, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([]);
|
|
931
|
+
jest
|
|
932
|
+
.spyOn(resultDataProvider as any, 'alignStepsWithIterationsTestReporter')
|
|
933
|
+
.mockReturnValueOnce(testReporterRows);
|
|
934
|
+
|
|
935
|
+
const linkedQueryRequest = {
|
|
936
|
+
linkedQueryMode: 'none',
|
|
937
|
+
testAssociatedQuery: { wiql: { href: 'https://example.com/wiql' }, columns: [] },
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
const result = await resultDataProvider.getTestReporterResults(
|
|
941
|
+
'planId',
|
|
942
|
+
mockProjectName,
|
|
943
|
+
[],
|
|
944
|
+
[],
|
|
945
|
+
false,
|
|
946
|
+
true,
|
|
947
|
+
true,
|
|
948
|
+
linkedQueryRequest,
|
|
949
|
+
'both'
|
|
950
|
+
);
|
|
951
|
+
|
|
952
|
+
expect(result).toBeDefined();
|
|
953
|
+
const first = (result as any[])[0];
|
|
954
|
+
expect(first).toBeDefined();
|
|
955
|
+
expect(first.data).toHaveLength(1);
|
|
956
|
+
expect(first.data[0].stepStatus).toBe('Failed');
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
it('should filter only test case results when errorFilterMode=onlyTestCaseResult', async () => {
|
|
960
|
+
const rows = [
|
|
961
|
+
{
|
|
962
|
+
testCase: { comment: 'c', result: { resultMessage: 'Passed' } },
|
|
963
|
+
stepComments: '',
|
|
964
|
+
stepStatus: 'Not Run',
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
testCase: { comment: '', result: { resultMessage: 'Failed in Run 1' } },
|
|
968
|
+
stepComments: '',
|
|
969
|
+
stepStatus: 'Not Run',
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
testCase: { comment: '', result: { resultMessage: 'Passed' } },
|
|
973
|
+
stepComments: '',
|
|
974
|
+
stepStatus: 'Not Run',
|
|
975
|
+
},
|
|
976
|
+
];
|
|
977
|
+
|
|
978
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
979
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([]);
|
|
980
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([]);
|
|
981
|
+
jest.spyOn(resultDataProvider as any, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([]);
|
|
982
|
+
jest.spyOn(resultDataProvider as any, 'alignStepsWithIterationsTestReporter').mockReturnValueOnce(rows);
|
|
983
|
+
|
|
984
|
+
const res = await resultDataProvider.getTestReporterResults(
|
|
985
|
+
'planId',
|
|
986
|
+
mockProjectName,
|
|
987
|
+
[],
|
|
988
|
+
[],
|
|
989
|
+
false,
|
|
990
|
+
true,
|
|
991
|
+
false,
|
|
992
|
+
{ linkedQueryMode: 'none', testAssociatedQuery: { wiql: { href: 'x' }, columns: [] } },
|
|
993
|
+
'onlyTestCaseResult'
|
|
994
|
+
);
|
|
995
|
+
|
|
996
|
+
expect((res as any[])[0].data).toHaveLength(2);
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
it('should filter only step results when errorFilterMode=onlyTestStepsResult', async () => {
|
|
1000
|
+
const rows = [
|
|
1001
|
+
{
|
|
1002
|
+
testCase: { comment: '', result: { resultMessage: 'Passed' } },
|
|
1003
|
+
stepComments: 'x',
|
|
1004
|
+
stepStatus: 'Not Run',
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
testCase: { comment: '', result: { resultMessage: 'Passed' } },
|
|
1008
|
+
stepComments: '',
|
|
1009
|
+
stepStatus: 'Failed',
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
testCase: { comment: '', result: { resultMessage: 'Passed' } },
|
|
1013
|
+
stepComments: '',
|
|
1014
|
+
stepStatus: 'Not Run',
|
|
1015
|
+
},
|
|
1016
|
+
];
|
|
1017
|
+
|
|
1018
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
1019
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([]);
|
|
1020
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([]);
|
|
1021
|
+
jest.spyOn(resultDataProvider as any, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([]);
|
|
1022
|
+
jest.spyOn(resultDataProvider as any, 'alignStepsWithIterationsTestReporter').mockReturnValueOnce(rows);
|
|
1023
|
+
|
|
1024
|
+
const res = await resultDataProvider.getTestReporterResults(
|
|
1025
|
+
'planId',
|
|
1026
|
+
mockProjectName,
|
|
1027
|
+
[],
|
|
1028
|
+
[],
|
|
1029
|
+
false,
|
|
1030
|
+
true,
|
|
1031
|
+
false,
|
|
1032
|
+
{ linkedQueryMode: 'none', testAssociatedQuery: { wiql: { href: 'x' }, columns: [] } },
|
|
1033
|
+
'onlyTestStepsResult'
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
expect((res as any[])[0].data).toHaveLength(2);
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
it('should execute query mode and call TicketsDataProvider when linkedQueryMode=query', async () => {
|
|
1040
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
1041
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([]);
|
|
1042
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([]);
|
|
1043
|
+
jest.spyOn(resultDataProvider as any, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([]);
|
|
1044
|
+
jest.spyOn(resultDataProvider as any, 'alignStepsWithIterationsTestReporter').mockReturnValueOnce([]);
|
|
1045
|
+
|
|
1046
|
+
const linkedQueryRequest = {
|
|
1047
|
+
linkedQueryMode: 'query',
|
|
1048
|
+
testAssociatedQuery: {
|
|
1049
|
+
wiql: { href: 'https://example.com/wiql' },
|
|
1050
|
+
columns: [{ referenceName: 'X', name: 'X' }],
|
|
1051
|
+
},
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
await resultDataProvider.getTestReporterResults(
|
|
1055
|
+
'planId',
|
|
1056
|
+
mockProjectName,
|
|
1057
|
+
[],
|
|
1058
|
+
[],
|
|
1059
|
+
false,
|
|
1060
|
+
true,
|
|
1061
|
+
false,
|
|
1062
|
+
linkedQueryRequest,
|
|
1063
|
+
'none'
|
|
1064
|
+
);
|
|
1065
|
+
|
|
1066
|
+
const TicketsProviderMock: any = require('../../modules/TicketsDataProvider').default;
|
|
1067
|
+
expect(TicketsProviderMock).toHaveBeenCalled();
|
|
1068
|
+
const instance = TicketsProviderMock.mock.results[0].value;
|
|
1069
|
+
expect(instance.GetQueryResultsFromWiql).toHaveBeenCalledWith(
|
|
1070
|
+
'https://example.com/wiql',
|
|
1071
|
+
true,
|
|
1072
|
+
expect.any(Map)
|
|
1073
|
+
);
|
|
1074
|
+
});
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
describe('fetchResultDataForTestReporter (runResultField switch)', () => {
|
|
1078
|
+
it('should populate requested runResultField values including testCaseResult URL branches', async () => {
|
|
1079
|
+
jest
|
|
1080
|
+
.spyOn(resultDataProvider as any, 'fetchResultDataBase')
|
|
1081
|
+
.mockImplementation(async (...args: any[]) => {
|
|
1082
|
+
const testSuiteId = args[1];
|
|
1083
|
+
const formatter = args[4];
|
|
1084
|
+
const extra = args[5] as any[];
|
|
1085
|
+
const selectedFields = extra[0];
|
|
1086
|
+
const isQueryMode = extra[1];
|
|
1087
|
+
const pt = extra[2];
|
|
1088
|
+
return formatter(
|
|
1089
|
+
{
|
|
1090
|
+
testCase: { id: 1, name: 'TC 1' },
|
|
1091
|
+
testSuite: { name: 'Suite' },
|
|
1092
|
+
testCaseRevision: 7,
|
|
1093
|
+
resolutionState: 'x',
|
|
1094
|
+
failureType: 'FT',
|
|
1095
|
+
priority: 2,
|
|
1096
|
+
outcome: 'passed',
|
|
1097
|
+
iterationDetails: [],
|
|
1098
|
+
filteredFields: { 'Custom.Field1': 'v1' },
|
|
1099
|
+
relatedRequirements: [],
|
|
1100
|
+
relatedBugs: [],
|
|
1101
|
+
relatedCRs: [],
|
|
1102
|
+
},
|
|
1103
|
+
testSuiteId,
|
|
1104
|
+
pt,
|
|
1105
|
+
selectedFields,
|
|
1106
|
+
isQueryMode
|
|
1107
|
+
);
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
const selectedFields = [
|
|
1111
|
+
'priority@runResultField',
|
|
1112
|
+
'testCaseResult@runResultField',
|
|
1113
|
+
'testCaseComment@runResultField',
|
|
1114
|
+
'failureType@runResultField',
|
|
1115
|
+
'runBy@runResultField',
|
|
1116
|
+
'executionDate@runResultField',
|
|
1117
|
+
'configurationName@runResultField',
|
|
1118
|
+
'unknownField@runResultField',
|
|
1119
|
+
];
|
|
1120
|
+
|
|
1121
|
+
const point = {
|
|
1122
|
+
lastRunId: 10,
|
|
1123
|
+
lastResultId: 20,
|
|
1124
|
+
configurationName: 'Cfg',
|
|
1125
|
+
lastResultDetails: { runBy: { displayName: 'User' }, dateCompleted: '2023-01-01' },
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
const res = await (resultDataProvider as any).fetchResultDataForTestReporter(
|
|
1129
|
+
mockProjectName,
|
|
1130
|
+
'suite1',
|
|
1131
|
+
point,
|
|
1132
|
+
selectedFields,
|
|
1133
|
+
false
|
|
1134
|
+
);
|
|
1135
|
+
|
|
1136
|
+
expect(res.priority).toBe(2);
|
|
1137
|
+
expect(res.testCaseResult).toEqual(
|
|
1138
|
+
expect.objectContaining({
|
|
1139
|
+
resultMessage: expect.stringContaining('Run 10'),
|
|
1140
|
+
url: expect.stringContaining('runId=10'),
|
|
1141
|
+
})
|
|
1142
|
+
);
|
|
1143
|
+
expect(res.runBy).toBe('User');
|
|
1144
|
+
expect(res.executionDate).toBe('2023-01-01');
|
|
1145
|
+
expect(res.configurationName).toBe('Cfg');
|
|
1146
|
+
expect(res.customFields).toEqual(expect.objectContaining({ field1: 'v1' }));
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
it('should set testCaseResult url empty when lastRunId/lastResultId are undefined', async () => {
|
|
1150
|
+
jest
|
|
1151
|
+
.spyOn(resultDataProvider as any, 'fetchResultDataBase')
|
|
1152
|
+
.mockImplementation(async (...args: any[]) => {
|
|
1153
|
+
const testSuiteId = args[1];
|
|
1154
|
+
const point = args[2];
|
|
1155
|
+
const formatter = args[4];
|
|
1156
|
+
const extra = args[5] as any[];
|
|
1157
|
+
return formatter(
|
|
1158
|
+
{
|
|
1159
|
+
testCase: { id: 1, name: 'TC 1' },
|
|
1160
|
+
testSuite: { name: 'Suite' },
|
|
1161
|
+
testCaseRevision: 1,
|
|
1162
|
+
resolutionState: 'x',
|
|
1163
|
+
failureType: 'FT',
|
|
1164
|
+
priority: 1,
|
|
1165
|
+
outcome: 'passed',
|
|
1166
|
+
iterationDetails: [],
|
|
1167
|
+
},
|
|
1168
|
+
testSuiteId,
|
|
1169
|
+
point,
|
|
1170
|
+
extra[0]
|
|
1171
|
+
);
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
const res = await (resultDataProvider as any).fetchResultDataForTestReporter(
|
|
1175
|
+
mockProjectName,
|
|
1176
|
+
'suite1',
|
|
1177
|
+
{
|
|
1178
|
+
lastRunId: undefined,
|
|
1179
|
+
lastResultId: undefined,
|
|
1180
|
+
configurationName: 'Cfg',
|
|
1181
|
+
lastResultDetails: { runBy: { displayName: 'U' }, dateCompleted: 'd' },
|
|
1182
|
+
},
|
|
1183
|
+
['testCaseResult@runResultField'],
|
|
1184
|
+
false
|
|
1185
|
+
);
|
|
1186
|
+
|
|
1187
|
+
expect(res.testCaseResult).toEqual(expect.objectContaining({ url: '' }));
|
|
1188
|
+
});
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
describe('fetchResultDataBasedOnWiBase', () => {
|
|
1192
|
+
it('should return null and warn when runId/resultId are 0 and no point is provided', async () => {
|
|
1193
|
+
const res = await (resultDataProvider as any).fetchResultDataBasedOnWiBase(mockProjectName, '0', '0');
|
|
1194
|
+
expect(res).toBeNull();
|
|
1195
|
+
expect(logger.warn).toHaveBeenCalled();
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
it('should build synthetic result for Active state when runId/resultId are 0 and point is provided', async () => {
|
|
1199
|
+
const point = {
|
|
1200
|
+
testCaseId: '123',
|
|
1201
|
+
testCaseName: 'TC 123',
|
|
1202
|
+
outcome: 'passed',
|
|
1203
|
+
testSuite: { id: '1', name: 'Suite' },
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({
|
|
1207
|
+
id: 123,
|
|
1208
|
+
rev: 7,
|
|
1209
|
+
fields: {
|
|
1210
|
+
'System.State': 'Active',
|
|
1211
|
+
'System.CreatedDate': '2023-01-01T00:00:00',
|
|
1212
|
+
'Microsoft.VSTS.TCM.Priority': 2,
|
|
1213
|
+
'System.Title': 'Title 123',
|
|
1214
|
+
'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
|
|
1215
|
+
},
|
|
1216
|
+
relations: null,
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
const selectedFields = ['System.Title@testCaseWorkItemField'];
|
|
1220
|
+
const res = await (resultDataProvider as any).fetchResultDataBasedOnWiBase(
|
|
1221
|
+
mockProjectName,
|
|
1222
|
+
'0',
|
|
1223
|
+
'0',
|
|
1224
|
+
true,
|
|
1225
|
+
selectedFields,
|
|
1226
|
+
false,
|
|
1227
|
+
point
|
|
1228
|
+
);
|
|
1229
|
+
|
|
1230
|
+
expect(res).toEqual(
|
|
1231
|
+
expect.objectContaining({
|
|
1232
|
+
id: 0,
|
|
1233
|
+
failureType: 'None',
|
|
1234
|
+
testCaseRevision: 7,
|
|
1235
|
+
stepsResultXml: '<steps></steps>',
|
|
1236
|
+
filteredFields: { 'System.Title': 'Title 123' },
|
|
1237
|
+
})
|
|
1238
|
+
);
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
it('should append linked relations and filter testCaseWorkItemField when isTestReporter=true and isQueryMode=false', async () => {
|
|
1242
|
+
(TFSServices.getItemContent as jest.Mock).mockReset();
|
|
1243
|
+
|
|
1244
|
+
const selectedFields = ['associatedBug@linked', 'System.Title@testCaseWorkItemField'];
|
|
1245
|
+
|
|
1246
|
+
// 1) run result
|
|
1247
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
1248
|
+
.mockResolvedValueOnce({
|
|
1249
|
+
testCase: { id: 123 },
|
|
1250
|
+
testCaseRevision: 7,
|
|
1251
|
+
testSuite: { name: 'S' },
|
|
1252
|
+
})
|
|
1253
|
+
// 2) attachments
|
|
1254
|
+
.mockResolvedValueOnce({ value: [] })
|
|
1255
|
+
// 3) wiByRevision (with relations)
|
|
1256
|
+
.mockResolvedValueOnce({
|
|
1257
|
+
id: 123,
|
|
1258
|
+
fields: {
|
|
1259
|
+
'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
|
|
1260
|
+
'System.Title': { displayName: 'My Title' },
|
|
1261
|
+
},
|
|
1262
|
+
relations: [{ rel: 'System.LinkTypes.Related', url: 'https://example.com/wi/200' }],
|
|
1263
|
+
})
|
|
1264
|
+
// 4) linked bug
|
|
1265
|
+
.mockResolvedValueOnce({
|
|
1266
|
+
id: 200,
|
|
1267
|
+
fields: { 'System.WorkItemType': 'Bug', 'System.State': 'Active', 'System.Title': 'B200' },
|
|
1268
|
+
_links: { html: { href: 'http://example.com/200' } },
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
const res = await (resultDataProvider as any).fetchResultDataBasedOnWiBase(
|
|
1272
|
+
mockProjectName,
|
|
1273
|
+
'10',
|
|
1274
|
+
'20',
|
|
1275
|
+
true,
|
|
1276
|
+
selectedFields,
|
|
1277
|
+
false,
|
|
1278
|
+
undefined,
|
|
1279
|
+
false
|
|
1280
|
+
);
|
|
1281
|
+
|
|
1282
|
+
expect(res).toEqual(expect.objectContaining({ testCaseRevision: 7 }));
|
|
1283
|
+
expect(res.filteredFields).toEqual({ 'System.Title': 'My Title' });
|
|
1284
|
+
expect(res.relatedBugs).toEqual(
|
|
1285
|
+
expect.arrayContaining([expect.objectContaining({ id: 200, title: 'B200', workItemType: 'Bug' })])
|
|
1286
|
+
);
|
|
1287
|
+
});
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
describe('alignStepsWithIterationsBase - additional branches', () => {
|
|
1291
|
+
it('should include not-run test cases when enabled and includeItemsWithNoIterations=true (creates test-level result)', () => {
|
|
1292
|
+
const testData = [
|
|
1293
|
+
{
|
|
1294
|
+
testPointsItems: [{ testCaseId: 123, lastRunId: undefined, lastResultId: undefined }],
|
|
1295
|
+
testCasesItems: [
|
|
1296
|
+
{ workItem: { id: 123, workItemFields: [{ key: 'Steps', value: '<steps></steps>' }] } },
|
|
1297
|
+
],
|
|
1298
|
+
},
|
|
1299
|
+
];
|
|
1300
|
+
const iterations = [
|
|
1301
|
+
{ testCaseId: 123, lastRunId: undefined, lastResultId: undefined, iteration: null },
|
|
1302
|
+
];
|
|
1303
|
+
|
|
1304
|
+
const createResultObject = jest.fn().mockReturnValue({ ok: true });
|
|
1305
|
+
const shouldProcessStepLevel = jest.fn().mockReturnValue(false);
|
|
1306
|
+
|
|
1307
|
+
const res = (resultDataProvider as any).alignStepsWithIterationsBase(
|
|
1308
|
+
testData,
|
|
1309
|
+
iterations,
|
|
1310
|
+
true,
|
|
1311
|
+
true,
|
|
1312
|
+
true,
|
|
1313
|
+
{
|
|
1314
|
+
selectedFields: [],
|
|
1315
|
+
createResultObject,
|
|
1316
|
+
shouldProcessStepLevel,
|
|
1317
|
+
}
|
|
1318
|
+
);
|
|
1319
|
+
|
|
1320
|
+
expect(res).toEqual([{ ok: true }]);
|
|
1321
|
+
expect(createResultObject).toHaveBeenCalled();
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
it('should skip items without iterations when includeItemsWithNoIterations=false', () => {
|
|
1325
|
+
const testData = [
|
|
1326
|
+
{
|
|
1327
|
+
testPointsItems: [{ testCaseId: 123, lastRunId: undefined, lastResultId: undefined }],
|
|
1328
|
+
testCasesItems: [
|
|
1329
|
+
{ workItem: { id: 123, workItemFields: [{ key: 'Steps', value: '<steps></steps>' }] } },
|
|
1330
|
+
],
|
|
1331
|
+
},
|
|
1332
|
+
];
|
|
1333
|
+
const iterations = [
|
|
1334
|
+
{ testCaseId: 123, lastRunId: undefined, lastResultId: undefined, iteration: null },
|
|
1335
|
+
];
|
|
1336
|
+
|
|
1337
|
+
const res = (resultDataProvider as any).alignStepsWithIterationsBase(
|
|
1338
|
+
testData,
|
|
1339
|
+
iterations,
|
|
1340
|
+
true,
|
|
1341
|
+
false,
|
|
1342
|
+
true,
|
|
1343
|
+
{
|
|
1344
|
+
selectedFields: [],
|
|
1345
|
+
createResultObject: jest.fn().mockReturnValue({ ok: true }),
|
|
1346
|
+
shouldProcessStepLevel: jest.fn().mockReturnValue(false),
|
|
1347
|
+
}
|
|
1348
|
+
);
|
|
1349
|
+
|
|
1350
|
+
expect(res).toEqual([]);
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
it('should fallback to test-level result when step-level processing enabled but actionResults is empty', () => {
|
|
1354
|
+
const testData = [
|
|
1355
|
+
{
|
|
1356
|
+
testPointsItems: [{ testCaseId: 123, lastRunId: '10', lastResultId: '20' }],
|
|
1357
|
+
testCasesItems: [
|
|
1358
|
+
{ workItem: { id: 123, workItemFields: [{ key: 'Steps', value: '<steps></steps>' }] } },
|
|
1359
|
+
],
|
|
1360
|
+
},
|
|
1361
|
+
];
|
|
1362
|
+
const iterations = [
|
|
1363
|
+
{
|
|
1364
|
+
testCaseId: 123,
|
|
1365
|
+
lastRunId: '10',
|
|
1366
|
+
lastResultId: '20',
|
|
1367
|
+
iteration: { actionResults: [] },
|
|
1368
|
+
},
|
|
1369
|
+
];
|
|
1370
|
+
|
|
1371
|
+
const createResultObject = jest.fn().mockReturnValue({ mode: 'test-level' });
|
|
1372
|
+
const shouldProcessStepLevel = jest.fn().mockReturnValue(true);
|
|
1373
|
+
|
|
1374
|
+
const res = (resultDataProvider as any).alignStepsWithIterationsBase(
|
|
1375
|
+
testData,
|
|
1376
|
+
iterations,
|
|
1377
|
+
false,
|
|
1378
|
+
true,
|
|
1379
|
+
false,
|
|
1380
|
+
{
|
|
1381
|
+
selectedFields: ['includeSteps@stepsRunProperties'],
|
|
1382
|
+
createResultObject,
|
|
1383
|
+
shouldProcessStepLevel,
|
|
1384
|
+
}
|
|
1385
|
+
);
|
|
1386
|
+
|
|
1387
|
+
expect(res).toEqual([{ mode: 'test-level' }]);
|
|
1388
|
+
expect(createResultObject).toHaveBeenCalledTimes(1);
|
|
1389
|
+
});
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
describe('getCombinedResultsSummary - optional outputs', () => {
|
|
1393
|
+
it('should include open PCRs, test log, appendix-a and appendix-b when enabled', async () => {
|
|
1394
|
+
jest
|
|
1395
|
+
.spyOn(resultDataProvider as any, 'fetchTestSuites')
|
|
1396
|
+
.mockResolvedValueOnce([{ testSuiteId: '1', testGroupName: 'Group 1' }]);
|
|
1397
|
+
|
|
1398
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestPoints').mockResolvedValueOnce([
|
|
1399
|
+
{
|
|
1400
|
+
testCaseId: 1,
|
|
1401
|
+
testCaseName: 'TC 1',
|
|
1402
|
+
testCaseUrl: 'http://example.com/1',
|
|
1403
|
+
configurationName: 'Cfg',
|
|
1404
|
+
outcome: 'passed',
|
|
1405
|
+
lastRunId: 10,
|
|
1406
|
+
lastResultId: 20,
|
|
1407
|
+
lastResultDetails: { dateCompleted: '2023-01-01T00:00:00.000Z', runBy: { displayName: 'User 1' } },
|
|
1408
|
+
},
|
|
1409
|
+
]);
|
|
1410
|
+
|
|
1411
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([]);
|
|
1412
|
+
|
|
1413
|
+
// runResults for appendix-a filtering
|
|
1414
|
+
jest.spyOn(resultDataProvider as any, 'fetchAllResultData').mockResolvedValueOnce([
|
|
1415
|
+
{
|
|
1416
|
+
comment: 'has comment',
|
|
1417
|
+
iteration: { attachments: [] },
|
|
1418
|
+
analysisAttachments: [],
|
|
1419
|
+
lastRunId: 1,
|
|
1420
|
+
lastResultId: 2,
|
|
1421
|
+
},
|
|
1422
|
+
]);
|
|
1423
|
+
|
|
1424
|
+
jest.spyOn(resultDataProvider as any, 'alignStepsWithIterations').mockReturnValueOnce([]);
|
|
1425
|
+
|
|
1426
|
+
const openSpy = jest
|
|
1427
|
+
.spyOn(resultDataProvider as any, 'fetchOpenPcrData')
|
|
1428
|
+
.mockResolvedValueOnce(undefined);
|
|
1429
|
+
|
|
1430
|
+
// Ensure appendix-b mapping runs but doesn't require attachments
|
|
1431
|
+
jest
|
|
1432
|
+
.spyOn(resultDataProvider as any, 'mapStepResultsForExecutionAppendix')
|
|
1433
|
+
.mockReturnValueOnce(new Map());
|
|
1434
|
+
|
|
1435
|
+
const res = await resultDataProvider.getCombinedResultsSummary(
|
|
1436
|
+
mockTestPlanId,
|
|
1437
|
+
mockProjectName,
|
|
1438
|
+
undefined,
|
|
1439
|
+
false,
|
|
1440
|
+
false,
|
|
1441
|
+
{ openPcrMode: 'linked', openPcrLinkedQuery: { wiql: { href: 'x' }, columns: [] } } as any,
|
|
1442
|
+
true,
|
|
1443
|
+
{ isEnabled: true, generateAttachments: { isEnabled: true, runAttachmentMode: 'planOnly' } },
|
|
1444
|
+
{ isEnabled: true, generateRunAttachments: { isEnabled: false } },
|
|
1445
|
+
false
|
|
1446
|
+
);
|
|
1447
|
+
|
|
1448
|
+
expect(openSpy).toHaveBeenCalled();
|
|
1449
|
+
|
|
1450
|
+
const hasTestLog = res.combinedResults.some(
|
|
1451
|
+
(x: any) => x.contentControl === 'test-execution-content-control'
|
|
1452
|
+
);
|
|
1453
|
+
expect(hasTestLog).toBe(true);
|
|
1454
|
+
|
|
1455
|
+
const hasAppendixA = res.combinedResults.some(
|
|
1456
|
+
(x: any) => x.contentControl === 'appendix-a-content-control'
|
|
1457
|
+
);
|
|
1458
|
+
expect(hasAppendixA).toBe(true);
|
|
1459
|
+
|
|
1460
|
+
const hasAppendixB = res.combinedResults.some(
|
|
1461
|
+
(x: any) => x.contentControl === 'appendix-b-content-control'
|
|
1462
|
+
);
|
|
1463
|
+
expect(hasAppendixB).toBe(true);
|
|
1464
|
+
});
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
describe('appendLinkedRelations', () => {
|
|
1468
|
+
it('should append requirement/bug/cr when enabled and skip closed items', async () => {
|
|
1469
|
+
const relations = [
|
|
1470
|
+
{ rel: 'System.LinkTypes.Related', url: 'https://example.com/wi/1' },
|
|
1471
|
+
{ rel: 'System.LinkTypes.Related', url: 'https://example.com/wi/2' },
|
|
1472
|
+
{ rel: 'System.LinkTypes.Related', url: 'https://example.com/wi/3' },
|
|
1473
|
+
{ rel: 'System.LinkTypes.Related', url: 'https://example.com/wi/4' },
|
|
1474
|
+
];
|
|
1475
|
+
const relatedRequirements: any[] = [];
|
|
1476
|
+
const relatedBugs: any[] = [];
|
|
1477
|
+
const relatedCRs: any[] = [];
|
|
1478
|
+
const selected = new Set(['associatedRequirement', 'associatedBug', 'associatedCR']);
|
|
1479
|
+
|
|
1480
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
1481
|
+
.mockResolvedValueOnce({
|
|
1482
|
+
id: 1,
|
|
1483
|
+
fields: {
|
|
1484
|
+
'System.WorkItemType': 'Requirement',
|
|
1485
|
+
'System.State': 'Active',
|
|
1486
|
+
'System.Title': 'Req 1',
|
|
1487
|
+
'Custom.CustomerRequirementId': 'CUST-1',
|
|
1488
|
+
},
|
|
1489
|
+
_links: { html: { href: 'http://example.com/1' } },
|
|
1490
|
+
})
|
|
1491
|
+
.mockResolvedValueOnce({
|
|
1492
|
+
id: 2,
|
|
1493
|
+
fields: {
|
|
1494
|
+
'System.WorkItemType': 'Bug',
|
|
1495
|
+
'System.State': 'Active',
|
|
1496
|
+
'System.Title': 'Bug 2',
|
|
1497
|
+
},
|
|
1498
|
+
_links: { html: { href: 'http://example.com/2' } },
|
|
1499
|
+
})
|
|
1500
|
+
.mockResolvedValueOnce({
|
|
1501
|
+
id: 3,
|
|
1502
|
+
fields: {
|
|
1503
|
+
'System.WorkItemType': 'Change Request',
|
|
1504
|
+
'System.State': 'Active',
|
|
1505
|
+
'System.Title': 'CR 3',
|
|
1506
|
+
},
|
|
1507
|
+
_links: { html: { href: 'http://example.com/3' } },
|
|
1508
|
+
})
|
|
1509
|
+
.mockResolvedValueOnce({
|
|
1510
|
+
id: 4,
|
|
1511
|
+
fields: {
|
|
1512
|
+
'System.WorkItemType': 'Bug',
|
|
1513
|
+
'System.State': 'Closed',
|
|
1514
|
+
'System.Title': 'Closed bug',
|
|
1515
|
+
},
|
|
1516
|
+
_links: { html: { href: 'http://example.com/4' } },
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
await (resultDataProvider as any).appendLinkedRelations(
|
|
1520
|
+
relations,
|
|
1521
|
+
relatedRequirements,
|
|
1522
|
+
relatedBugs,
|
|
1523
|
+
relatedCRs,
|
|
1524
|
+
{ id: 123 },
|
|
1525
|
+
selected
|
|
1526
|
+
);
|
|
1527
|
+
|
|
1528
|
+
expect(relatedRequirements).toHaveLength(1);
|
|
1529
|
+
expect(relatedRequirements[0]).toEqual(
|
|
1530
|
+
expect.objectContaining({ id: 1, customerId: 'CUST-1', workItemType: 'Requirement' })
|
|
1531
|
+
);
|
|
1532
|
+
expect(relatedBugs).toHaveLength(1);
|
|
1533
|
+
expect(relatedCRs).toHaveLength(1);
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
it('should log an error when fetching a related item fails', async () => {
|
|
1537
|
+
const relations = [{ rel: 'System.LinkTypes.Related', url: 'https://example.com/wi/1' }];
|
|
1538
|
+
(TFSServices.getItemContent as jest.Mock).mockRejectedValueOnce(new Error('network'));
|
|
1539
|
+
|
|
1540
|
+
await (resultDataProvider as any).appendLinkedRelations(
|
|
1541
|
+
relations,
|
|
1542
|
+
[],
|
|
1543
|
+
[],
|
|
1544
|
+
[],
|
|
1545
|
+
{ id: 999 },
|
|
1546
|
+
new Set(['associatedBug'])
|
|
1547
|
+
);
|
|
1548
|
+
|
|
1549
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
1550
|
+
expect.stringContaining('Could not append related work item to test case 999')
|
|
1551
|
+
);
|
|
1552
|
+
});
|
|
1553
|
+
});
|
|
1554
|
+
|
|
1555
|
+
describe('appendQueryRelations', () => {
|
|
1556
|
+
it('should append query relations when map has items', () => {
|
|
1557
|
+
// Arrange
|
|
1558
|
+
const testCaseId = 1;
|
|
1559
|
+
const relatedRequirements: any[] = [];
|
|
1560
|
+
const relatedBugs: any[] = [];
|
|
1561
|
+
const relatedCRs: any[] = [];
|
|
1562
|
+
|
|
1563
|
+
// Set up the map
|
|
1564
|
+
(resultDataProvider as any).testToAssociatedItemMap = new Map([
|
|
1565
|
+
[
|
|
1566
|
+
1,
|
|
1567
|
+
[
|
|
1568
|
+
{
|
|
1569
|
+
id: 100,
|
|
1570
|
+
fields: { 'System.Title': 'Req 1', 'System.WorkItemType': 'Requirement' },
|
|
1571
|
+
_links: { html: { href: 'http://example.com/100' } },
|
|
1572
|
+
},
|
|
1573
|
+
],
|
|
1574
|
+
],
|
|
1575
|
+
]);
|
|
1576
|
+
(resultDataProvider as any).querySelectedColumns = [];
|
|
1577
|
+
|
|
1578
|
+
// Act
|
|
1579
|
+
(resultDataProvider as any).appendQueryRelations(
|
|
1580
|
+
testCaseId,
|
|
1581
|
+
relatedRequirements,
|
|
1582
|
+
relatedBugs,
|
|
1583
|
+
relatedCRs
|
|
1584
|
+
);
|
|
1585
|
+
|
|
1586
|
+
// Assert
|
|
1587
|
+
expect(relatedRequirements).toHaveLength(1);
|
|
1588
|
+
expect(relatedRequirements[0].id).toBe(100);
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
it('should map Requirement/Bug/Change Request into correct buckets', () => {
|
|
1592
|
+
const testCaseId = 1;
|
|
1593
|
+
const relatedRequirements: any[] = [];
|
|
1594
|
+
const relatedBugs: any[] = [];
|
|
1595
|
+
const relatedCRs: any[] = [];
|
|
1596
|
+
|
|
1597
|
+
jest.spyOn(resultDataProvider as any, 'standardCustomField').mockReturnValue({});
|
|
1598
|
+
|
|
1599
|
+
(resultDataProvider as any).testToAssociatedItemMap = new Map([
|
|
1600
|
+
[
|
|
1601
|
+
1,
|
|
1602
|
+
[
|
|
1603
|
+
{
|
|
1604
|
+
id: 10,
|
|
1605
|
+
fields: { 'System.Title': 'R', 'System.WorkItemType': 'Requirement', X: '1' },
|
|
1606
|
+
_links: { html: { href: 'http://example.com/10' } },
|
|
1607
|
+
},
|
|
1608
|
+
{
|
|
1609
|
+
id: 11,
|
|
1610
|
+
fields: { 'System.Title': 'B', 'System.WorkItemType': 'Bug', Y: '2' },
|
|
1611
|
+
_links: { html: { href: 'http://example.com/11' } },
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
id: 12,
|
|
1615
|
+
fields: { 'System.Title': 'C', 'System.WorkItemType': 'Change Request', Z: '3' },
|
|
1616
|
+
_links: { html: { href: 'http://example.com/12' } },
|
|
1617
|
+
},
|
|
1618
|
+
],
|
|
1619
|
+
],
|
|
1620
|
+
]);
|
|
1621
|
+
(resultDataProvider as any).querySelectedColumns = [];
|
|
1622
|
+
|
|
1623
|
+
(resultDataProvider as any).appendQueryRelations(
|
|
1624
|
+
testCaseId,
|
|
1625
|
+
relatedRequirements,
|
|
1626
|
+
relatedBugs,
|
|
1627
|
+
relatedCRs
|
|
1628
|
+
);
|
|
1629
|
+
|
|
1630
|
+
expect(relatedRequirements).toHaveLength(1);
|
|
1631
|
+
expect(relatedBugs).toHaveLength(1);
|
|
1632
|
+
expect(relatedCRs).toHaveLength(1);
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
it('should handle empty map', () => {
|
|
1636
|
+
// Arrange
|
|
1637
|
+
const testCaseId = 1;
|
|
1638
|
+
const relatedRequirements: any[] = [];
|
|
1639
|
+
const relatedBugs: any[] = [];
|
|
1640
|
+
const relatedCRs: any[] = [];
|
|
1641
|
+
(resultDataProvider as any).testToAssociatedItemMap = new Map();
|
|
1642
|
+
|
|
1643
|
+
// Act
|
|
1644
|
+
(resultDataProvider as any).appendQueryRelations(
|
|
1645
|
+
testCaseId,
|
|
1646
|
+
relatedRequirements,
|
|
1647
|
+
relatedBugs,
|
|
1648
|
+
relatedCRs
|
|
1649
|
+
);
|
|
1650
|
+
|
|
1651
|
+
// Assert
|
|
1652
|
+
expect(relatedRequirements).toHaveLength(0);
|
|
1653
|
+
});
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
describe('convertUnspecifiedRunStatus', () => {
|
|
1657
|
+
it('should return empty string for null actionResult', () => {
|
|
1658
|
+
// Act
|
|
1659
|
+
const result = (resultDataProvider as any).convertUnspecifiedRunStatus(null);
|
|
1660
|
+
|
|
1661
|
+
// Assert
|
|
1662
|
+
expect(result).toBe('');
|
|
1663
|
+
});
|
|
1664
|
+
|
|
1665
|
+
it('should return empty string for Unspecified shared step title', () => {
|
|
1666
|
+
// Arrange
|
|
1667
|
+
const actionResult = { outcome: 'Unspecified', isSharedStepTitle: true };
|
|
1668
|
+
|
|
1669
|
+
// Act
|
|
1670
|
+
const result = (resultDataProvider as any).convertUnspecifiedRunStatus(actionResult);
|
|
1671
|
+
|
|
1672
|
+
// Assert
|
|
1673
|
+
expect(result).toBe('');
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
it('should return Not Run for Unspecified non-shared step', () => {
|
|
1677
|
+
// Arrange
|
|
1678
|
+
const actionResult = { outcome: 'Unspecified', isSharedStepTitle: false };
|
|
1679
|
+
|
|
1680
|
+
// Act
|
|
1681
|
+
const result = (resultDataProvider as any).convertUnspecifiedRunStatus(actionResult);
|
|
1682
|
+
|
|
1683
|
+
// Assert
|
|
1684
|
+
expect(result).toBe('Not Run');
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
it('should return original outcome for non-Unspecified status', () => {
|
|
1688
|
+
// Arrange
|
|
1689
|
+
const actionResult = { outcome: 'Passed', isSharedStepTitle: false };
|
|
1690
|
+
|
|
1691
|
+
// Act
|
|
1692
|
+
const result = (resultDataProvider as any).convertUnspecifiedRunStatus(actionResult);
|
|
1693
|
+
|
|
1694
|
+
// Assert
|
|
1695
|
+
expect(result).toBe('Passed');
|
|
1696
|
+
});
|
|
1697
|
+
});
|
|
1698
|
+
|
|
1699
|
+
describe('fetchResultDataBasedOnWi', () => {
|
|
1700
|
+
it('should call fetchResultDataBasedOnWiBase', async () => {
|
|
1701
|
+
// Arrange
|
|
1702
|
+
const mockResult = { id: 1, outcome: 'passed' };
|
|
1703
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce(mockResult);
|
|
1704
|
+
|
|
1705
|
+
// Act - just verify it doesn't throw
|
|
1706
|
+
const spy = jest.spyOn(resultDataProvider as any, 'fetchResultDataBasedOnWiBase');
|
|
1707
|
+
try {
|
|
1708
|
+
await (resultDataProvider as any).fetchResultDataBasedOnWi(mockProjectName, '100', '200');
|
|
1709
|
+
} catch {
|
|
1710
|
+
// Expected to fail due to missing mocks
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
// Assert
|
|
1714
|
+
expect(spy).toHaveBeenCalledWith(mockProjectName, '100', '200');
|
|
1715
|
+
});
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1718
|
+
describe('alignStepsWithIterationsBase', () => {
|
|
1719
|
+
it('should return empty array when no iterations', () => {
|
|
1720
|
+
// Arrange
|
|
1721
|
+
const testData: any[] = [];
|
|
1722
|
+
const iterations: any[] = [];
|
|
1723
|
+
const options = {
|
|
1724
|
+
createResultObject: jest.fn(),
|
|
1725
|
+
shouldProcessStepLevel: jest.fn(),
|
|
1726
|
+
};
|
|
1727
|
+
|
|
1728
|
+
// Act
|
|
1729
|
+
const result = (resultDataProvider as any).alignStepsWithIterationsBase(
|
|
1730
|
+
testData,
|
|
1731
|
+
iterations,
|
|
1732
|
+
false,
|
|
1733
|
+
false,
|
|
1734
|
+
false,
|
|
1735
|
+
options
|
|
1736
|
+
);
|
|
1737
|
+
|
|
1738
|
+
// Assert
|
|
1739
|
+
expect(result).toEqual([]);
|
|
1740
|
+
});
|
|
1741
|
+
|
|
1742
|
+
it('should return [null] when fetchedTestCase.iteration.actionResults is null (shouldProcessStepLevel=false)', () => {
|
|
1743
|
+
const testData = [
|
|
1744
|
+
{
|
|
1745
|
+
testGroupName: 'G',
|
|
1746
|
+
testPointsItems: [{ testCaseId: 1, testCaseName: 'TC', lastRunId: 10, lastResultId: 20 }],
|
|
1747
|
+
testCasesItems: [{ workItem: { id: 1, workItemFields: [{ key: 'Steps', value: '<steps />' }] } }],
|
|
1748
|
+
},
|
|
1749
|
+
];
|
|
1750
|
+
const iterations = [
|
|
1751
|
+
{
|
|
1752
|
+
testCaseId: 1,
|
|
1753
|
+
lastRunId: 10,
|
|
1754
|
+
lastResultId: 20,
|
|
1755
|
+
iteration: { actionResults: null },
|
|
1756
|
+
testCaseRevision: 1,
|
|
1757
|
+
},
|
|
1758
|
+
];
|
|
1759
|
+
|
|
1760
|
+
const res = (resultDataProvider as any).alignStepsWithIterations(testData, iterations);
|
|
1761
|
+
expect(res).toEqual([null]);
|
|
1762
|
+
});
|
|
1763
|
+
|
|
1764
|
+
it('should include a null row when actionResults contains an undefined element', () => {
|
|
1765
|
+
const testData = [
|
|
1766
|
+
{
|
|
1767
|
+
testGroupName: 'G',
|
|
1768
|
+
testPointsItems: [{ testCaseId: 1, testCaseName: 'TC', lastRunId: 10, lastResultId: 20 }],
|
|
1769
|
+
testCasesItems: [{ workItem: { id: 1, workItemFields: [{ key: 'Steps', value: '<steps />' }] } }],
|
|
1770
|
+
},
|
|
1771
|
+
];
|
|
1772
|
+
const iterations = [
|
|
1773
|
+
{
|
|
1774
|
+
testCaseId: 1,
|
|
1775
|
+
lastRunId: 10,
|
|
1776
|
+
lastResultId: 20,
|
|
1777
|
+
iteration: { actionResults: [undefined] },
|
|
1778
|
+
testCaseRevision: 1,
|
|
1779
|
+
},
|
|
1780
|
+
];
|
|
1781
|
+
|
|
1782
|
+
const res = (resultDataProvider as any).alignStepsWithIterations(testData, iterations);
|
|
1783
|
+
expect(res).toEqual([null]);
|
|
1784
|
+
});
|
|
1785
|
+
});
|
|
1786
|
+
|
|
1787
|
+
describe('standardCustomField', () => {
|
|
1788
|
+
it('should standardize custom fields with columns', () => {
|
|
1789
|
+
// Arrange
|
|
1790
|
+
const fields = { 'Custom.Field1': 'value1', 'Custom.Field2': 'value2' };
|
|
1791
|
+
const columns = [
|
|
1792
|
+
{ referenceName: 'Custom.Field1', name: 'Field 1' },
|
|
1793
|
+
{ referenceName: 'Custom.Field2', name: 'Field 2' },
|
|
1794
|
+
];
|
|
1795
|
+
|
|
1796
|
+
// Act
|
|
1797
|
+
const result = (resultDataProvider as any).standardCustomField(fields, columns);
|
|
1798
|
+
|
|
1799
|
+
// Assert
|
|
1800
|
+
expect(result).toBeDefined();
|
|
1801
|
+
expect(result.field1).toBe('value1');
|
|
1802
|
+
});
|
|
1803
|
+
|
|
1804
|
+
it('should handle uppercase field names', () => {
|
|
1805
|
+
// Arrange
|
|
1806
|
+
const fields = { 'Custom.ABC': 'value1' };
|
|
1807
|
+
const columns = [{ referenceName: 'Custom.ABC', name: 'ABC' }];
|
|
1808
|
+
|
|
1809
|
+
// Act
|
|
1810
|
+
const result = (resultDataProvider as any).standardCustomField(fields, columns);
|
|
1811
|
+
|
|
1812
|
+
// Assert
|
|
1813
|
+
expect(result.abc).toBe('value1');
|
|
1814
|
+
});
|
|
1815
|
+
|
|
1816
|
+
it('should skip standard fields', () => {
|
|
1817
|
+
// Arrange
|
|
1818
|
+
const fields = { 'System.Id': 1, 'Custom.Field1': 'value1' };
|
|
1819
|
+
const columns = [
|
|
1820
|
+
{ referenceName: 'System.Id', name: 'id' },
|
|
1821
|
+
{ referenceName: 'Custom.Field1', name: 'Field 1' },
|
|
1822
|
+
];
|
|
1823
|
+
|
|
1824
|
+
// Act
|
|
1825
|
+
const result = (resultDataProvider as any).standardCustomField(fields, columns);
|
|
1826
|
+
|
|
1827
|
+
// Assert
|
|
1828
|
+
expect(result.id).toBeUndefined();
|
|
1829
|
+
expect(result.field1).toBe('value1');
|
|
1830
|
+
});
|
|
1831
|
+
|
|
1832
|
+
it('should handle null/undefined field values', () => {
|
|
1833
|
+
// Arrange
|
|
1834
|
+
const fields = { 'Custom.Field1': null };
|
|
1835
|
+
const columns = [{ referenceName: 'Custom.Field1', name: 'Field 1' }];
|
|
1836
|
+
|
|
1837
|
+
// Act
|
|
1838
|
+
const result = (resultDataProvider as any).standardCustomField(fields, columns);
|
|
1839
|
+
|
|
1840
|
+
// Assert
|
|
1841
|
+
expect(result.field1).toBeNull();
|
|
1842
|
+
});
|
|
1843
|
+
|
|
1844
|
+
it('should handle fields without columns', () => {
|
|
1845
|
+
// Arrange
|
|
1846
|
+
const fields = { 'Custom.Field1': 'value1', 'System.Title': 'Title' };
|
|
1847
|
+
|
|
1848
|
+
// Act
|
|
1849
|
+
const result = (resultDataProvider as any).standardCustomField(fields);
|
|
1850
|
+
|
|
1851
|
+
// Assert
|
|
1852
|
+
expect(result).toBeDefined();
|
|
1853
|
+
});
|
|
1854
|
+
|
|
1855
|
+
it('should handle displayName property', () => {
|
|
1856
|
+
// Arrange
|
|
1857
|
+
const fields = { 'Custom.Field1': { displayName: 'Display Value' } };
|
|
1858
|
+
const columns = [{ referenceName: 'Custom.Field1', name: 'Field 1' }];
|
|
1859
|
+
|
|
1860
|
+
// Act
|
|
1861
|
+
const result = (resultDataProvider as any).standardCustomField(fields, columns);
|
|
1862
|
+
|
|
1863
|
+
// Assert
|
|
1864
|
+
expect(result.field1).toBe('Display Value');
|
|
1865
|
+
});
|
|
1866
|
+
});
|
|
1867
|
+
|
|
1868
|
+
describe('getTestOutcome', () => {
|
|
1869
|
+
it('should return outcome from last iteration', () => {
|
|
1870
|
+
// Arrange
|
|
1871
|
+
const resultData = {
|
|
1872
|
+
iterationDetails: [{ outcome: 'Failed' }, { outcome: 'Passed' }],
|
|
1873
|
+
outcome: 'Failed',
|
|
1874
|
+
};
|
|
1875
|
+
|
|
1876
|
+
// Act
|
|
1877
|
+
const result = (resultDataProvider as any).getTestOutcome(resultData);
|
|
1878
|
+
|
|
1879
|
+
// Assert
|
|
1880
|
+
expect(result).toBe('Passed');
|
|
1881
|
+
});
|
|
1882
|
+
|
|
1883
|
+
it('should return result outcome when no iteration details', () => {
|
|
1884
|
+
// Arrange
|
|
1885
|
+
const resultData = { outcome: 'Passed' };
|
|
1886
|
+
|
|
1887
|
+
// Act
|
|
1888
|
+
const result = (resultDataProvider as any).getTestOutcome(resultData);
|
|
1889
|
+
|
|
1890
|
+
// Assert
|
|
1891
|
+
expect(result).toBe('Passed');
|
|
1892
|
+
});
|
|
1893
|
+
|
|
1894
|
+
it('should return default outcome when no data', () => {
|
|
1895
|
+
// Arrange
|
|
1896
|
+
const resultData = {};
|
|
1897
|
+
|
|
1898
|
+
// Act
|
|
1899
|
+
const result = (resultDataProvider as any).getTestOutcome(resultData);
|
|
1900
|
+
|
|
1901
|
+
// Assert
|
|
1902
|
+
expect(result).toBe('NotApplicable');
|
|
1903
|
+
});
|
|
1904
|
+
});
|
|
1905
|
+
|
|
1906
|
+
describe('createIterationsMap', () => {
|
|
1907
|
+
it('should create iterations map from results', () => {
|
|
1908
|
+
// Arrange
|
|
1909
|
+
const iterations = [{ lastRunId: 100, lastResultId: 200, testCase: { id: 1 } }];
|
|
1910
|
+
|
|
1911
|
+
// Act
|
|
1912
|
+
const result = (resultDataProvider as any).createIterationsMap(iterations, false, false);
|
|
1913
|
+
|
|
1914
|
+
// Assert
|
|
1915
|
+
expect(result).toBeDefined();
|
|
1916
|
+
});
|
|
1917
|
+
|
|
1918
|
+
it('should create iterations map from results with iteration', () => {
|
|
1919
|
+
// Arrange
|
|
1920
|
+
const iterations = [{ lastRunId: 100, lastResultId: 200, testCaseId: 1, iteration: { id: 1 } }];
|
|
1921
|
+
|
|
1922
|
+
// Act
|
|
1923
|
+
const result = (resultDataProvider as any).createIterationsMap(iterations, false, false);
|
|
1924
|
+
|
|
1925
|
+
// Assert
|
|
1926
|
+
expect(result).toBeDefined();
|
|
1927
|
+
expect(result['100-200-1']).toBeDefined();
|
|
1928
|
+
});
|
|
1929
|
+
|
|
1930
|
+
it('should create iterations map for test reporter mode', () => {
|
|
1931
|
+
// Arrange
|
|
1932
|
+
const iterations = [{ lastRunId: 100, lastResultId: 200, testCaseId: 1 }];
|
|
1933
|
+
|
|
1934
|
+
// Act
|
|
1935
|
+
const result = (resultDataProvider as any).createIterationsMap(iterations, true, false);
|
|
1936
|
+
|
|
1937
|
+
// Assert
|
|
1938
|
+
expect(result['100-200-1']).toBeDefined();
|
|
1939
|
+
});
|
|
1940
|
+
|
|
1941
|
+
it('should include not run test cases when flag is set', () => {
|
|
1942
|
+
// Arrange
|
|
1943
|
+
const iterations = [{ testCaseId: 1 }];
|
|
1944
|
+
|
|
1945
|
+
// Act
|
|
1946
|
+
const result = (resultDataProvider as any).createIterationsMap(iterations, false, true);
|
|
1947
|
+
|
|
1948
|
+
// Assert
|
|
1949
|
+
expect(result['1']).toBeDefined();
|
|
1950
|
+
});
|
|
1951
|
+
});
|
|
1952
|
+
|
|
1953
|
+
describe('alignStepsWithIterationsBase', () => {
|
|
1954
|
+
it('should return empty array when no iterations', () => {
|
|
1955
|
+
// Arrange
|
|
1956
|
+
const testData: any[] = [];
|
|
1957
|
+
const iterations: any[] = [];
|
|
1958
|
+
const options = {
|
|
1959
|
+
createResultObject: jest.fn(),
|
|
1960
|
+
shouldProcessStepLevel: jest.fn(),
|
|
1961
|
+
};
|
|
1962
|
+
|
|
1963
|
+
// Act
|
|
1964
|
+
const result = (resultDataProvider as any).alignStepsWithIterationsBase(
|
|
1965
|
+
testData,
|
|
1966
|
+
iterations,
|
|
1967
|
+
false,
|
|
1968
|
+
false,
|
|
1969
|
+
false,
|
|
1970
|
+
options
|
|
1971
|
+
);
|
|
1972
|
+
|
|
1973
|
+
// Assert
|
|
1974
|
+
expect(result).toEqual([]);
|
|
1975
|
+
});
|
|
1976
|
+
});
|
|
1977
|
+
|
|
1978
|
+
describe('alignStepsWithIterations', () => {
|
|
1979
|
+
it('should return empty array when no iterations', () => {
|
|
1980
|
+
// Arrange
|
|
1981
|
+
const testData: any[] = [];
|
|
1982
|
+
const iterations: any[] = [];
|
|
1983
|
+
|
|
1984
|
+
// Act
|
|
1985
|
+
const result = (resultDataProvider as any).alignStepsWithIterations(testData, iterations);
|
|
1986
|
+
|
|
1987
|
+
// Assert
|
|
1988
|
+
expect(result).toEqual([]);
|
|
1989
|
+
});
|
|
1990
|
+
});
|
|
1991
|
+
|
|
1992
|
+
describe('fetchTestData', () => {
|
|
1993
|
+
it('should fetch test data for suites', async () => {
|
|
1994
|
+
// Arrange
|
|
1995
|
+
const suites = [{ testSuiteId: 1, testGroupName: 'Suite 1' }];
|
|
1996
|
+
const mockTestCases = { value: [{ workItem: { id: 1 } }] };
|
|
1997
|
+
const mockTestPoints = { value: [{ testCaseReference: { id: 1 } }], count: 1 };
|
|
1998
|
+
|
|
1999
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
2000
|
+
.mockResolvedValueOnce(mockTestCases)
|
|
2001
|
+
.mockResolvedValueOnce(mockTestPoints);
|
|
2002
|
+
|
|
2003
|
+
// Act
|
|
2004
|
+
const result = await (resultDataProvider as any).fetchTestData(
|
|
2005
|
+
suites,
|
|
2006
|
+
mockProjectName,
|
|
2007
|
+
mockTestPlanId,
|
|
2008
|
+
false
|
|
2009
|
+
);
|
|
2010
|
+
|
|
2011
|
+
// Assert
|
|
2012
|
+
expect(result).toHaveLength(1);
|
|
2013
|
+
expect(result[0].testCasesItems).toBeDefined();
|
|
2014
|
+
});
|
|
2015
|
+
|
|
2016
|
+
it('should handle errors gracefully', async () => {
|
|
2017
|
+
// Arrange
|
|
2018
|
+
const suites = [{ testSuiteId: 1, testGroupName: 'Suite 1' }];
|
|
2019
|
+
(TFSServices.getItemContent as jest.Mock).mockRejectedValueOnce(new Error('API Error'));
|
|
2020
|
+
|
|
2021
|
+
// Act
|
|
2022
|
+
const result = await (resultDataProvider as any).fetchTestData(
|
|
2023
|
+
suites,
|
|
2024
|
+
mockProjectName,
|
|
2025
|
+
mockTestPlanId,
|
|
2026
|
+
false
|
|
2027
|
+
);
|
|
2028
|
+
|
|
2029
|
+
// Assert
|
|
2030
|
+
expect(result).toHaveLength(1);
|
|
2031
|
+
expect(logger.error).toHaveBeenCalled();
|
|
2032
|
+
});
|
|
2033
|
+
});
|
|
2034
|
+
|
|
2035
|
+
describe('fetchAllResultData', () => {
|
|
2036
|
+
it('should return empty array when no test data', async () => {
|
|
2037
|
+
// Arrange
|
|
2038
|
+
const testData: any[] = [];
|
|
2039
|
+
|
|
2040
|
+
// Act
|
|
2041
|
+
const result = await (resultDataProvider as any).fetchAllResultData(testData, mockProjectName);
|
|
2042
|
+
|
|
2043
|
+
// Assert
|
|
2044
|
+
expect(result).toEqual([]);
|
|
2045
|
+
});
|
|
2046
|
+
|
|
2047
|
+
it('should fetch result data for test points', async () => {
|
|
2048
|
+
// Arrange
|
|
2049
|
+
const testData = [
|
|
2050
|
+
{
|
|
2051
|
+
testPointsItems: [{ testCaseId: 1, lastRunId: 100, lastResultId: 200 }],
|
|
2052
|
+
},
|
|
2053
|
+
];
|
|
2054
|
+
const mockResult = {
|
|
2055
|
+
testCase: { id: 1 },
|
|
2056
|
+
iteration: { actionResults: [] },
|
|
2057
|
+
};
|
|
2058
|
+
const mockAttachments = { value: [] };
|
|
2059
|
+
const mockWi = { fields: {} };
|
|
2060
|
+
|
|
2061
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
2062
|
+
.mockResolvedValueOnce(mockResult)
|
|
2063
|
+
.mockResolvedValueOnce(mockAttachments)
|
|
2064
|
+
.mockResolvedValueOnce(mockWi);
|
|
2065
|
+
|
|
2066
|
+
// Act
|
|
2067
|
+
const result = await (resultDataProvider as any).fetchAllResultData(testData, mockProjectName);
|
|
2068
|
+
|
|
2069
|
+
// Assert
|
|
2070
|
+
expect(result).toBeDefined();
|
|
2071
|
+
});
|
|
2072
|
+
|
|
2073
|
+
it('should log response data and rethrow when an error with response is thrown', async () => {
|
|
2074
|
+
const err: any = new Error('boom');
|
|
2075
|
+
err.response = { data: { detail: 'bad' } };
|
|
2076
|
+
|
|
2077
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockRejectedValueOnce(err);
|
|
2078
|
+
|
|
2079
|
+
await expect(
|
|
2080
|
+
resultDataProvider.getCombinedResultsSummary(mockTestPlanId, mockProjectName)
|
|
2081
|
+
).rejects.toThrow('boom');
|
|
2082
|
+
|
|
2083
|
+
expect(logger.error).toHaveBeenCalledWith('Error during getCombinedResultsSummary: boom');
|
|
2084
|
+
expect(logger.error).toHaveBeenCalledWith('Response Data: {"detail":"bad"}');
|
|
2085
|
+
});
|
|
2086
|
+
});
|
|
2087
|
+
|
|
2088
|
+
describe('fetchAllResultDataTestReporter', () => {
|
|
2089
|
+
it('should return empty array when no test data', async () => {
|
|
2090
|
+
// Arrange
|
|
2091
|
+
const testData: any[] = [];
|
|
2092
|
+
|
|
2093
|
+
// Act
|
|
2094
|
+
const result = await (resultDataProvider as any).fetchAllResultDataTestReporter(
|
|
2095
|
+
testData,
|
|
2096
|
+
mockProjectName,
|
|
2097
|
+
[],
|
|
2098
|
+
false
|
|
2099
|
+
);
|
|
2100
|
+
|
|
2101
|
+
// Assert
|
|
2102
|
+
expect(result).toEqual([]);
|
|
2103
|
+
});
|
|
2104
|
+
});
|
|
2105
|
+
|
|
2106
|
+
describe('alignStepsWithIterationsTestReporter', () => {
|
|
2107
|
+
it('should return empty array when no iterations', () => {
|
|
2108
|
+
// Arrange
|
|
2109
|
+
const testData: any[] = [];
|
|
2110
|
+
const iterations: any[] = [];
|
|
2111
|
+
|
|
2112
|
+
// Act
|
|
2113
|
+
const result = (resultDataProvider as any).alignStepsWithIterationsTestReporter(
|
|
2114
|
+
testData,
|
|
2115
|
+
iterations,
|
|
2116
|
+
[],
|
|
2117
|
+
false
|
|
2118
|
+
);
|
|
2119
|
+
|
|
2120
|
+
// Assert
|
|
2121
|
+
expect(result).toEqual([]);
|
|
2122
|
+
});
|
|
2123
|
+
});
|
|
2124
|
+
|
|
2125
|
+
describe('fetchAllResultDataBase', () => {
|
|
2126
|
+
it('should return empty array when no test data', async () => {
|
|
2127
|
+
// Arrange
|
|
2128
|
+
const testData: any[] = [];
|
|
2129
|
+
const fetchStrategy = jest.fn();
|
|
2130
|
+
|
|
2131
|
+
// Act
|
|
2132
|
+
const result = await (resultDataProvider as any).fetchAllResultDataBase(
|
|
2133
|
+
testData,
|
|
2134
|
+
mockProjectName,
|
|
2135
|
+
false,
|
|
2136
|
+
fetchStrategy
|
|
2137
|
+
);
|
|
2138
|
+
|
|
2139
|
+
// Assert
|
|
2140
|
+
expect(result).toEqual([]);
|
|
2141
|
+
});
|
|
2142
|
+
|
|
2143
|
+
it('should filter out points without run/result IDs when not test reporter', async () => {
|
|
2144
|
+
// Arrange
|
|
2145
|
+
const testData = [
|
|
2146
|
+
{
|
|
2147
|
+
testSuiteId: 1,
|
|
2148
|
+
testPointsItems: [
|
|
2149
|
+
{ testCaseId: 1 }, // No lastRunId/lastResultId
|
|
2150
|
+
],
|
|
2151
|
+
},
|
|
2152
|
+
];
|
|
2153
|
+
const fetchStrategy = jest.fn();
|
|
2154
|
+
|
|
2155
|
+
// Act
|
|
2156
|
+
const result = await (resultDataProvider as any).fetchAllResultDataBase(
|
|
2157
|
+
testData,
|
|
2158
|
+
mockProjectName,
|
|
2159
|
+
false,
|
|
2160
|
+
fetchStrategy
|
|
2161
|
+
);
|
|
2162
|
+
|
|
2163
|
+
// Assert
|
|
2164
|
+
expect(fetchStrategy).not.toHaveBeenCalled();
|
|
2165
|
+
expect(result).toEqual([]);
|
|
2166
|
+
});
|
|
2167
|
+
});
|
|
2168
|
+
|
|
2169
|
+
describe('fetchResultDataBase', () => {
|
|
2170
|
+
it('should fetch result data for a point', async () => {
|
|
2171
|
+
// Arrange
|
|
2172
|
+
const point = { lastRunId: 100, lastResultId: 200 };
|
|
2173
|
+
const mockResultData = {
|
|
2174
|
+
testCase: { id: 1 },
|
|
2175
|
+
iterationDetails: [],
|
|
2176
|
+
};
|
|
2177
|
+
const fetchResultMethod = jest.fn().mockResolvedValue(mockResultData);
|
|
2178
|
+
const createResponseObject = jest.fn().mockReturnValue({ id: 1 });
|
|
2179
|
+
|
|
2180
|
+
// Act
|
|
2181
|
+
const result = await (resultDataProvider as any).fetchResultDataBase(
|
|
2182
|
+
mockProjectName,
|
|
2183
|
+
'1',
|
|
2184
|
+
point,
|
|
2185
|
+
fetchResultMethod,
|
|
2186
|
+
createResponseObject
|
|
2187
|
+
);
|
|
2188
|
+
|
|
2189
|
+
// Assert
|
|
2190
|
+
expect(fetchResultMethod).toHaveBeenCalled();
|
|
2191
|
+
expect(result).toBeDefined();
|
|
2192
|
+
});
|
|
2193
|
+
});
|
|
2194
|
+
|
|
2195
|
+
describe('getCombinedResultsSummary', () => {
|
|
2196
|
+
it('should return combined results summary with expected content controls', async () => {
|
|
2197
|
+
const mockTestSuites = {
|
|
2198
|
+
value: [
|
|
2199
|
+
{
|
|
2200
|
+
id: 1,
|
|
2201
|
+
name: 'Root Suite',
|
|
2202
|
+
children: [{ id: 2, name: 'Child Suite 1', parentSuite: { id: 1 } }],
|
|
2203
|
+
},
|
|
2204
|
+
],
|
|
2205
|
+
count: 1,
|
|
2206
|
+
};
|
|
2207
|
+
|
|
2208
|
+
const mockTestPoints = {
|
|
2209
|
+
value: [
|
|
2210
|
+
{
|
|
2211
|
+
testCaseReference: { id: 1, name: 'Test Case 1' },
|
|
2212
|
+
configuration: { name: 'Config 1' },
|
|
2213
|
+
results: {
|
|
2214
|
+
outcome: 'passed',
|
|
2215
|
+
lastTestRunId: 100,
|
|
2216
|
+
lastResultId: 200,
|
|
2217
|
+
lastResultDetails: { dateCompleted: '2023-01-01', runBy: { displayName: 'Test User' } },
|
|
2218
|
+
},
|
|
2219
|
+
},
|
|
2220
|
+
],
|
|
2221
|
+
count: 1,
|
|
2222
|
+
};
|
|
2223
|
+
|
|
2224
|
+
const mockTestCases = {
|
|
2225
|
+
value: [
|
|
2226
|
+
{
|
|
2227
|
+
workItem: {
|
|
2228
|
+
id: 1,
|
|
2229
|
+
workItemFields: [{ key: 'Steps', value: '<steps>...</steps>' }],
|
|
2230
|
+
},
|
|
2231
|
+
},
|
|
2232
|
+
],
|
|
2233
|
+
};
|
|
2234
|
+
|
|
2235
|
+
const mockResult = {
|
|
2236
|
+
testCase: { id: 1, name: 'Test Case 1' },
|
|
2237
|
+
testSuite: { id: 2, name: 'Child Suite 1' },
|
|
2238
|
+
iterationDetails: [
|
|
2239
|
+
{
|
|
2240
|
+
actionResults: [
|
|
2241
|
+
{ stepIdentifier: '1', outcome: 'Passed', errorMessage: '', actionPath: 'path1' },
|
|
2242
|
+
],
|
|
2243
|
+
attachments: [],
|
|
2244
|
+
},
|
|
2245
|
+
],
|
|
2246
|
+
testCaseRevision: 1,
|
|
2247
|
+
failureType: null,
|
|
2248
|
+
resolutionState: null,
|
|
2249
|
+
comment: null,
|
|
2250
|
+
};
|
|
2251
|
+
|
|
2252
|
+
// Setup mocks for API calls
|
|
2253
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
2254
|
+
.mockResolvedValueOnce(mockTestSuites) // fetchTestSuites
|
|
2255
|
+
.mockResolvedValueOnce(mockTestPoints) // fetchTestPoints
|
|
2256
|
+
.mockResolvedValueOnce(mockTestCases) // fetchTestCasesBySuiteId
|
|
2257
|
+
.mockResolvedValueOnce(mockResult) // fetchResult
|
|
2258
|
+
.mockResolvedValueOnce({ value: [] }) // fetchResult - attachments
|
|
2259
|
+
.mockResolvedValueOnce({ fields: {} }); // fetchResult - wiByRevision
|
|
2260
|
+
|
|
2261
|
+
const mockTestStepParserHelper = (resultDataProvider as any).testStepParserHelper;
|
|
2262
|
+
mockTestStepParserHelper.parseTestSteps.mockResolvedValueOnce([
|
|
2263
|
+
{
|
|
2264
|
+
stepId: 1,
|
|
2265
|
+
stepPosition: '1',
|
|
2266
|
+
action: 'Do something',
|
|
2267
|
+
expected: 'Something happens',
|
|
2268
|
+
isSharedStepTitle: false,
|
|
2269
|
+
},
|
|
2270
|
+
]);
|
|
2271
|
+
|
|
2272
|
+
// Act
|
|
2273
|
+
const result = await resultDataProvider.getCombinedResultsSummary(
|
|
2274
|
+
mockTestPlanId,
|
|
2275
|
+
mockProjectName,
|
|
2276
|
+
undefined,
|
|
2277
|
+
true
|
|
2278
|
+
);
|
|
2279
|
+
|
|
2280
|
+
// Assert
|
|
2281
|
+
expect(result.combinedResults.length).toBeGreaterThan(0);
|
|
2282
|
+
expect(result.combinedResults[0]).toHaveProperty(
|
|
2283
|
+
'contentControl',
|
|
2284
|
+
'test-group-summary-content-control'
|
|
2285
|
+
);
|
|
2286
|
+
expect(result.combinedResults[1]).toHaveProperty(
|
|
2287
|
+
'contentControl',
|
|
2288
|
+
'test-result-summary-content-control'
|
|
2289
|
+
);
|
|
2290
|
+
expect(result.combinedResults[2]).toHaveProperty(
|
|
2291
|
+
'contentControl',
|
|
2292
|
+
'detailed-test-result-content-control'
|
|
2293
|
+
);
|
|
2294
|
+
});
|
|
2295
|
+
});
|
|
2296
|
+
|
|
2297
|
+
describe('fetchResultDataBase - shared step mapping', () => {
|
|
2298
|
+
it('should map parsed steps into actionResults, filter missing stepPosition, and sort by stepPosition', async () => {
|
|
2299
|
+
const point = { testCaseId: 1, lastRunId: 10, lastResultId: 20 };
|
|
2300
|
+
const fetchResultMethod = jest.fn().mockResolvedValue({
|
|
2301
|
+
testCase: { id: 1, name: 'TC' },
|
|
2302
|
+
stepsResultXml: '<steps></steps>',
|
|
2303
|
+
iterationDetails: [
|
|
2304
|
+
{
|
|
2305
|
+
actionResults: [
|
|
2306
|
+
{ stepIdentifier: '2', actionPath: 'p2', sharedStepModel: { id: 5, revision: 7 } },
|
|
2307
|
+
{ stepIdentifier: '999', actionPath: 'px' },
|
|
2308
|
+
{ stepIdentifier: '1', actionPath: 'p1' },
|
|
2309
|
+
],
|
|
2310
|
+
},
|
|
2311
|
+
],
|
|
2312
|
+
});
|
|
2313
|
+
|
|
2314
|
+
const createResponseObject = (resultData: any) => ({ iteration: resultData.iterationDetails[0] });
|
|
2315
|
+
|
|
2316
|
+
const helper = (resultDataProvider as any).testStepParserHelper;
|
|
2317
|
+
helper.parseTestSteps.mockImplementationOnce(async (_xml: any, map: Map<number, number>) => {
|
|
2318
|
+
// cover sharedStepIdToRevisionLookupMap population
|
|
2319
|
+
expect(map.get(5)).toBe(7);
|
|
2320
|
+
return [
|
|
2321
|
+
{ stepId: 1, stepPosition: '1', action: 'A1', expected: 'E1', isSharedStepTitle: false },
|
|
2322
|
+
{ stepId: 2, stepPosition: '2', action: 'A2', expected: 'E2', isSharedStepTitle: true },
|
|
2323
|
+
];
|
|
2324
|
+
});
|
|
2325
|
+
|
|
2326
|
+
const res = await (resultDataProvider as any).fetchResultDataBase(
|
|
2327
|
+
mockProjectName,
|
|
2328
|
+
'suite1',
|
|
2329
|
+
point,
|
|
2330
|
+
fetchResultMethod,
|
|
2331
|
+
createResponseObject,
|
|
2332
|
+
[]
|
|
2333
|
+
);
|
|
2334
|
+
|
|
2335
|
+
const actionResults = res.iteration.actionResults;
|
|
2336
|
+
// 999 should be filtered out (no stepPosition)
|
|
2337
|
+
expect(actionResults).toHaveLength(2);
|
|
2338
|
+
// sorted by stepPosition numeric
|
|
2339
|
+
expect(actionResults[0]).toEqual(expect.objectContaining({ stepIdentifier: '1', action: 'A1' }));
|
|
2340
|
+
expect(actionResults[1]).toEqual(
|
|
2341
|
+
expect.objectContaining({ stepIdentifier: '2', action: 'A2', isSharedStepTitle: true })
|
|
2342
|
+
);
|
|
2343
|
+
});
|
|
2344
|
+
});
|
|
2345
|
+
|
|
2346
|
+
describe('getCombinedResultsSummary - appendix branches', () => {
|
|
2347
|
+
it('should use mapAttachmentsUrl when stepAnalysis.generateRunAttachments is enabled and stepExecution.runAttachmentMode != planOnly', async () => {
|
|
2348
|
+
jest
|
|
2349
|
+
.spyOn(resultDataProvider as any, 'fetchTestSuites')
|
|
2350
|
+
.mockResolvedValueOnce([{ testSuiteId: '1', testGroupName: 'Group 1' }]);
|
|
2351
|
+
|
|
2352
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestPoints').mockResolvedValueOnce([
|
|
2353
|
+
{
|
|
2354
|
+
testCaseId: 1,
|
|
2355
|
+
testCaseName: 'TC 1',
|
|
2356
|
+
testCaseUrl: 'http://example.com/1',
|
|
2357
|
+
configurationName: 'Cfg',
|
|
2358
|
+
outcome: 'passed',
|
|
2359
|
+
lastRunId: 10,
|
|
2360
|
+
lastResultId: 20,
|
|
2361
|
+
lastResultDetails: { dateCompleted: '2023-01-01T00:00:00.000Z', runBy: { displayName: 'User 1' } },
|
|
2362
|
+
},
|
|
2363
|
+
]);
|
|
2364
|
+
|
|
2365
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([]);
|
|
2366
|
+
|
|
2367
|
+
// ensure all predicates in stepAnalysis/filter can be true (comment false, attachments true, analysisAttachments true)
|
|
2368
|
+
const runResults = [
|
|
2369
|
+
{
|
|
2370
|
+
comment: '',
|
|
2371
|
+
iteration: { attachments: [{ actionPath: 'p', name: 'n', downloadUrl: 'd' }] },
|
|
2372
|
+
analysisAttachments: [{ id: 1 }],
|
|
2373
|
+
},
|
|
2374
|
+
];
|
|
2375
|
+
jest.spyOn(resultDataProvider as any, 'fetchAllResultData').mockResolvedValueOnce(runResults);
|
|
2376
|
+
jest.spyOn(resultDataProvider as any, 'alignStepsWithIterations').mockReturnValueOnce([]);
|
|
2377
|
+
|
|
2378
|
+
const mapSpy = jest
|
|
2379
|
+
.spyOn(resultDataProvider as any, 'mapAttachmentsUrl')
|
|
2380
|
+
.mockReturnValueOnce(runResults as any)
|
|
2381
|
+
.mockReturnValueOnce(runResults as any);
|
|
2382
|
+
|
|
2383
|
+
jest
|
|
2384
|
+
.spyOn(resultDataProvider as any, 'mapStepResultsForExecutionAppendix')
|
|
2385
|
+
.mockReturnValueOnce(new Map());
|
|
2386
|
+
|
|
2387
|
+
const res = await resultDataProvider.getCombinedResultsSummary(
|
|
2388
|
+
mockTestPlanId,
|
|
2389
|
+
mockProjectName,
|
|
2390
|
+
undefined,
|
|
2391
|
+
false,
|
|
2392
|
+
false,
|
|
2393
|
+
null,
|
|
2394
|
+
false,
|
|
2395
|
+
{ isEnabled: true, generateAttachments: { isEnabled: true, runAttachmentMode: 'runOnly' } },
|
|
2396
|
+
{ isEnabled: true, generateRunAttachments: { isEnabled: true } },
|
|
2397
|
+
false
|
|
2398
|
+
);
|
|
2399
|
+
|
|
2400
|
+
expect(mapSpy).toHaveBeenCalled();
|
|
2401
|
+
expect(res.combinedResults.some((x: any) => x.contentControl === 'appendix-a-content-control')).toBe(
|
|
2402
|
+
true
|
|
2403
|
+
);
|
|
2404
|
+
expect(res.combinedResults.some((x: any) => x.contentControl === 'appendix-b-content-control')).toBe(
|
|
2405
|
+
true
|
|
2406
|
+
);
|
|
2407
|
+
});
|
|
2408
|
+
});
|
|
2409
|
+
|
|
2410
|
+
describe('alignStepsWithIterationsTestReporter - step-level rows', () => {
|
|
2411
|
+
it('should emit step-level fields when includeSteps/stepRunStatus/testStepComment are selected', () => {
|
|
2412
|
+
const testData = [
|
|
2413
|
+
{
|
|
2414
|
+
testGroupName: 'G',
|
|
2415
|
+
testPointsItems: [
|
|
2416
|
+
{
|
|
2417
|
+
testCaseId: 123,
|
|
2418
|
+
testCaseName: 'TC',
|
|
2419
|
+
testCaseUrl: 'u',
|
|
2420
|
+
lastRunId: 10,
|
|
2421
|
+
lastResultId: 20,
|
|
2422
|
+
},
|
|
2423
|
+
],
|
|
2424
|
+
testCasesItems: [
|
|
2425
|
+
{ workItem: { id: 123, workItemFields: [{ key: 'Steps', value: '<steps></steps>' }] } },
|
|
2426
|
+
],
|
|
2427
|
+
},
|
|
2428
|
+
];
|
|
2429
|
+
const iterations = [
|
|
2430
|
+
{
|
|
2431
|
+
testCaseId: 123,
|
|
2432
|
+
lastRunId: 10,
|
|
2433
|
+
lastResultId: 20,
|
|
2434
|
+
iteration: {
|
|
2435
|
+
actionResults: [
|
|
2436
|
+
{
|
|
2437
|
+
stepIdentifier: '1',
|
|
2438
|
+
stepPosition: '1',
|
|
2439
|
+
action: 'A',
|
|
2440
|
+
expected: 'E',
|
|
2441
|
+
outcome: 'Unspecified',
|
|
2442
|
+
isSharedStepTitle: false,
|
|
2443
|
+
errorMessage: 'err',
|
|
2444
|
+
},
|
|
2445
|
+
],
|
|
2446
|
+
},
|
|
2447
|
+
testCaseResult: 'Failed',
|
|
2448
|
+
comment: 'c',
|
|
2449
|
+
runBy: { displayName: 'u' },
|
|
2450
|
+
failureType: 'ft',
|
|
2451
|
+
executionDate: 'd',
|
|
2452
|
+
configurationName: 'cfg',
|
|
2453
|
+
relatedRequirements: [],
|
|
2454
|
+
relatedBugs: [],
|
|
2455
|
+
relatedCRs: [],
|
|
2456
|
+
customFields: {},
|
|
2457
|
+
},
|
|
2458
|
+
];
|
|
2459
|
+
|
|
2460
|
+
const res = (resultDataProvider as any).alignStepsWithIterationsTestReporter(
|
|
2461
|
+
testData,
|
|
2462
|
+
iterations,
|
|
2463
|
+
[
|
|
2464
|
+
'includeSteps@stepsRunProperties',
|
|
2465
|
+
'stepRunStatus@stepsRunProperties',
|
|
2466
|
+
'testStepComment@stepsRunProperties',
|
|
2467
|
+
],
|
|
2468
|
+
true
|
|
2469
|
+
);
|
|
2470
|
+
|
|
2471
|
+
expect(res).toHaveLength(1);
|
|
2472
|
+
expect(res[0]).toEqual(
|
|
2473
|
+
expect.objectContaining({
|
|
2474
|
+
stepNo: '1',
|
|
2475
|
+
stepAction: 'A',
|
|
2476
|
+
stepExpected: 'E',
|
|
2477
|
+
stepStatus: 'Not Run',
|
|
2478
|
+
stepComments: 'err',
|
|
2479
|
+
})
|
|
2480
|
+
);
|
|
2481
|
+
});
|
|
2482
|
+
|
|
2483
|
+
it('should omit step fields when no @stepsRunProperties are selected', () => {
|
|
2484
|
+
const testData = [
|
|
2485
|
+
{
|
|
2486
|
+
testGroupName: 'G',
|
|
2487
|
+
testPointsItems: [
|
|
2488
|
+
{ testCaseId: 123, testCaseName: 'TC', testCaseUrl: 'u', lastRunId: 10, lastResultId: 20 },
|
|
2489
|
+
],
|
|
2490
|
+
testCasesItems: [
|
|
2491
|
+
{ workItem: { id: 123, workItemFields: [{ key: 'Steps', value: '<steps></steps>' }] } },
|
|
2492
|
+
],
|
|
2493
|
+
},
|
|
2494
|
+
];
|
|
2495
|
+
const iterations = [
|
|
2496
|
+
{
|
|
2497
|
+
testCaseId: 123,
|
|
2498
|
+
lastRunId: 10,
|
|
2499
|
+
lastResultId: 20,
|
|
2500
|
+
iteration: {
|
|
2501
|
+
actionResults: [{ stepIdentifier: '1', stepPosition: '1', action: 'A', expected: 'E' }],
|
|
2502
|
+
},
|
|
2503
|
+
testCaseResult: 'Passed',
|
|
2504
|
+
comment: '',
|
|
2505
|
+
runBy: { displayName: 'u' },
|
|
2506
|
+
failureType: '',
|
|
2507
|
+
executionDate: 'd',
|
|
2508
|
+
configurationName: 'cfg',
|
|
2509
|
+
relatedRequirements: [],
|
|
2510
|
+
relatedBugs: [],
|
|
2511
|
+
relatedCRs: [],
|
|
2512
|
+
customFields: {},
|
|
2513
|
+
},
|
|
2514
|
+
];
|
|
2515
|
+
|
|
2516
|
+
const res = (resultDataProvider as any).alignStepsWithIterationsTestReporter(
|
|
2517
|
+
testData,
|
|
2518
|
+
iterations,
|
|
2519
|
+
[],
|
|
2520
|
+
true
|
|
2521
|
+
);
|
|
2522
|
+
expect(res).toHaveLength(1);
|
|
2523
|
+
expect(res[0].stepNo).toBeUndefined();
|
|
2524
|
+
});
|
|
2525
|
+
});
|
|
2526
|
+
|
|
2527
|
+
describe('fetchOpenPcrData', () => {
|
|
2528
|
+
it('should populate both trace maps using linked work items', async () => {
|
|
2529
|
+
const testItems = [
|
|
2530
|
+
{
|
|
2531
|
+
testId: 1,
|
|
2532
|
+
testName: 'T1',
|
|
2533
|
+
testCaseUrl: 'u1',
|
|
2534
|
+
runStatus: 'Passed',
|
|
2535
|
+
},
|
|
2536
|
+
];
|
|
2537
|
+
const linked = [
|
|
2538
|
+
{
|
|
2539
|
+
testId: 1,
|
|
2540
|
+
testName: 'T1',
|
|
2541
|
+
testCaseUrl: 'u1',
|
|
2542
|
+
runStatus: 'Passed',
|
|
2543
|
+
linkItems: [
|
|
2544
|
+
{
|
|
2545
|
+
pcrId: 10,
|
|
2546
|
+
workItemType: 'Bug',
|
|
2547
|
+
title: 'B10',
|
|
2548
|
+
severity: '2',
|
|
2549
|
+
pcrUrl: 'p10',
|
|
2550
|
+
},
|
|
2551
|
+
],
|
|
2552
|
+
},
|
|
2553
|
+
];
|
|
2554
|
+
|
|
2555
|
+
jest.spyOn(resultDataProvider as any, 'fetchLinkedWi').mockResolvedValueOnce(linked);
|
|
2556
|
+
|
|
2557
|
+
const openPcrToTestCaseTraceMap = new Map<string, string[]>();
|
|
2558
|
+
const testCaseToOpenPcrTraceMap = new Map<string, string[]>();
|
|
2559
|
+
|
|
2560
|
+
await (resultDataProvider as any).fetchOpenPcrData(
|
|
2561
|
+
testItems,
|
|
2562
|
+
mockProjectName,
|
|
2563
|
+
openPcrToTestCaseTraceMap,
|
|
2564
|
+
testCaseToOpenPcrTraceMap
|
|
2565
|
+
);
|
|
2566
|
+
|
|
2567
|
+
expect(openPcrToTestCaseTraceMap.size).toBe(1);
|
|
2568
|
+
expect(testCaseToOpenPcrTraceMap.size).toBe(1);
|
|
2569
|
+
});
|
|
2570
|
+
});
|
|
2571
|
+
});
|