@elisra-devops/docgen-data-provider 1.63.13 → 1.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +26 -9
- package/.github/workflows/release.yml +9 -10
- package/bin/helpers/tfs.d.ts +3 -0
- package/bin/helpers/tfs.js +44 -7
- package/bin/helpers/tfs.js.map +1 -1
- package/bin/modules/GitDataProvider.d.ts +10 -0
- package/bin/modules/GitDataProvider.js +10 -0
- package/bin/modules/GitDataProvider.js.map +1 -1
- package/bin/modules/TestDataProvider.js +0 -1
- package/bin/modules/TestDataProvider.js.map +1 -1
- package/bin/modules/TicketsDataProvider.d.ts +63 -24
- package/bin/modules/TicketsDataProvider.js +216 -114
- package/bin/modules/TicketsDataProvider.js.map +1 -1
- package/bin/tests/helpers/helper.test.js +279 -0
- package/bin/tests/helpers/helper.test.js.map +1 -0
- package/bin/{helpers/test → tests/helpers}/tfs.test.js +312 -49
- package/bin/tests/helpers/tfs.test.js.map +1 -0
- package/bin/tests/index.test.js +25 -0
- package/bin/tests/index.test.js.map +1 -0
- package/bin/tests/models/tfs-data.test.js +160 -0
- package/bin/tests/models/tfs-data.test.js.map +1 -0
- package/bin/{modules/test → tests/modules}/JfrogDataProvider.test.js +9 -9
- package/bin/tests/modules/JfrogDataProvider.test.js.map +1 -0
- package/bin/tests/modules/ResultDataProvider.test.js +1942 -0
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -0
- package/bin/tests/modules/gitDataProvider.test.js +1888 -0
- package/bin/tests/modules/gitDataProvider.test.js.map +1 -0
- package/bin/{modules/test → tests/modules}/managmentDataProvider.test.js +13 -1
- package/bin/tests/modules/managmentDataProvider.test.js.map +1 -0
- package/bin/tests/modules/pipelineDataProvider.test.d.ts +1 -0
- package/bin/tests/modules/pipelineDataProvider.test.js +783 -0
- package/bin/tests/modules/pipelineDataProvider.test.js.map +1 -0
- package/bin/tests/modules/testDataProvider.test.d.ts +1 -0
- package/bin/tests/modules/testDataProvider.test.js +717 -0
- package/bin/tests/modules/testDataProvider.test.js.map +1 -0
- package/bin/tests/modules/ticketsDataProvider.test.d.ts +1 -0
- package/bin/tests/modules/ticketsDataProvider.test.js +1681 -0
- package/bin/tests/modules/ticketsDataProvider.test.js.map +1 -0
- package/bin/tests/utils/DataProviderUtils.test.d.ts +1 -0
- package/bin/tests/utils/DataProviderUtils.test.js +61 -0
- package/bin/tests/utils/DataProviderUtils.test.js.map +1 -0
- package/bin/tests/utils/testStepParserHelper.test.d.ts +1 -0
- package/bin/tests/utils/testStepParserHelper.test.js +359 -0
- package/bin/tests/utils/testStepParserHelper.test.js.map +1 -0
- package/package.json +9 -1
- package/src/helpers/tfs.ts +51 -7
- package/src/modules/GitDataProvider.ts +10 -0
- package/src/modules/TestDataProvider.ts +0 -1
- package/src/modules/TicketsDataProvider.ts +298 -141
- package/src/tests/helpers/helper.test.ts +337 -0
- package/src/tests/helpers/tfs.test.ts +1092 -0
- package/src/tests/index.test.ts +28 -0
- package/src/tests/models/tfs-data.test.ts +203 -0
- package/src/tests/modules/JfrogDataProvider.test.ts +167 -0
- package/src/tests/modules/ResultDataProvider.test.ts +2571 -0
- package/src/tests/modules/gitDataProvider.test.ts +2628 -0
- package/src/{modules/test → tests/modules}/managmentDataProvider.test.ts +33 -1
- package/src/tests/modules/pipelineDataProvider.test.ts +1038 -0
- package/src/tests/modules/testDataProvider.test.ts +1046 -0
- package/src/tests/modules/ticketsDataProvider.test.ts +2204 -0
- package/src/tests/utils/DataProviderUtils.test.ts +76 -0
- package/src/tests/utils/testStepParserHelper.test.ts +437 -0
- package/tsconfig.json +1 -0
- package/bin/helpers/test/tfs.test.js.map +0 -1
- package/bin/modules/test/JfrogDataProvider.test.js.map +0 -1
- package/bin/modules/test/ResultDataProvider.test.js +0 -444
- package/bin/modules/test/ResultDataProvider.test.js.map +0 -1
- package/bin/modules/test/gitDataProvider.test.js +0 -428
- package/bin/modules/test/gitDataProvider.test.js.map +0 -1
- package/bin/modules/test/managmentDataProvider.test.js.map +0 -1
- package/bin/modules/test/pipelineDataProvider.test.js +0 -237
- package/bin/modules/test/pipelineDataProvider.test.js.map +0 -1
- package/bin/modules/test/testDataProvider.test.js +0 -234
- package/bin/modules/test/testDataProvider.test.js.map +0 -1
- package/bin/modules/test/ticketsDataProvider.test.js +0 -348
- package/bin/modules/test/ticketsDataProvider.test.js.map +0 -1
- package/src/helpers/test/tfs.test.ts +0 -748
- package/src/modules/test/JfrogDataProvider.test.ts +0 -171
- package/src/modules/test/ResultDataProvider.test.ts +0 -542
- package/src/modules/test/gitDataProvider.test.ts +0 -645
- package/src/modules/test/pipelineDataProvider.test.ts +0 -292
- package/src/modules/test/testDataProvider.test.ts +0 -318
- package/src/modules/test/ticketsDataProvider.test.ts +0 -462
- /package/bin/{helpers/test/tfs.test.d.ts → tests/helpers/helper.test.d.ts} +0 -0
- /package/bin/{modules/test/JfrogDataProvider.test.d.ts → tests/helpers/tfs.test.d.ts} +0 -0
- /package/bin/{modules/test/ResultDataProvider.test.d.ts → tests/index.test.d.ts} +0 -0
- /package/bin/{modules/test/gitDataProvider.test.d.ts → tests/models/tfs-data.test.d.ts} +0 -0
- /package/bin/{modules/test/managmentDataProvider.test.d.ts → tests/modules/JfrogDataProvider.test.d.ts} +0 -0
- /package/bin/{modules/test/pipelineDataProvider.test.d.ts → tests/modules/ResultDataProvider.test.d.ts} +0 -0
- /package/bin/{modules/test/testDataProvider.test.d.ts → tests/modules/gitDataProvider.test.d.ts} +0 -0
- /package/bin/{modules/test/ticketsDataProvider.test.d.ts → tests/modules/managmentDataProvider.test.d.ts} +0 -0
|
@@ -0,0 +1,1942 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tfs_1 = require("../../helpers/tfs");
|
|
4
|
+
const ResultDataProvider_1 = require("../../modules/ResultDataProvider");
|
|
5
|
+
const logger_1 = require("../../utils/logger");
|
|
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) => fn());
|
|
19
|
+
describe('ResultDataProvider', () => {
|
|
20
|
+
let resultDataProvider;
|
|
21
|
+
const mockOrgUrl = 'https://dev.azure.com/organization/';
|
|
22
|
+
const mockToken = 'mock-token';
|
|
23
|
+
const mockProjectName = 'test-project';
|
|
24
|
+
const mockTestPlanId = '12345';
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
jest.clearAllMocks();
|
|
27
|
+
resultDataProvider = new ResultDataProvider_1.default(mockOrgUrl, mockToken);
|
|
28
|
+
});
|
|
29
|
+
describe('Utility methods', () => {
|
|
30
|
+
describe('flattenSuites', () => {
|
|
31
|
+
it('should flatten a hierarchical suite structure into a single-level array', () => {
|
|
32
|
+
// Arrange
|
|
33
|
+
const suites = [
|
|
34
|
+
{
|
|
35
|
+
id: 1,
|
|
36
|
+
name: 'Parent 1',
|
|
37
|
+
children: [
|
|
38
|
+
{ id: 2, name: 'Child 1' },
|
|
39
|
+
{
|
|
40
|
+
id: 3,
|
|
41
|
+
name: 'Child 2',
|
|
42
|
+
children: [{ id: 4, name: 'Grandchild 1' }],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
{ id: 5, name: 'Parent 2' },
|
|
47
|
+
];
|
|
48
|
+
// Act
|
|
49
|
+
const result = resultDataProvider.flattenSuites(suites);
|
|
50
|
+
// Assert
|
|
51
|
+
expect(result).toHaveLength(5);
|
|
52
|
+
expect(result.map((s) => s.id)).toEqual([1, 2, 3, 4, 5]);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('filterSuites', () => {
|
|
56
|
+
it('should filter suites based on selected suite IDs', () => {
|
|
57
|
+
// Arrange
|
|
58
|
+
const testSuites = [
|
|
59
|
+
{ id: 1, name: 'Suite 1', parentSuite: { id: 0 } },
|
|
60
|
+
{ id: 2, name: 'Suite 2', parentSuite: { id: 1 } },
|
|
61
|
+
{ id: 3, name: 'Suite 3', parentSuite: { id: 1 } },
|
|
62
|
+
];
|
|
63
|
+
const selectedSuiteIds = [1, 3];
|
|
64
|
+
// Act
|
|
65
|
+
const result = resultDataProvider.filterSuites(testSuites, selectedSuiteIds);
|
|
66
|
+
// Assert
|
|
67
|
+
expect(result).toHaveLength(2);
|
|
68
|
+
expect(result.map((s) => s.id)).toEqual([1, 3]);
|
|
69
|
+
});
|
|
70
|
+
it('should return all suites with parent when no suite IDs are selected', () => {
|
|
71
|
+
// Arrange
|
|
72
|
+
const testSuites = [
|
|
73
|
+
{ id: 1, name: 'Suite 1', parentSuite: { id: 0 } },
|
|
74
|
+
{ id: 2, name: 'Suite 2', parentSuite: { id: 1 } },
|
|
75
|
+
{ id: 3, name: 'Suite 3', parentSuite: null },
|
|
76
|
+
];
|
|
77
|
+
// Act
|
|
78
|
+
const result = resultDataProvider.filterSuites(testSuites);
|
|
79
|
+
// Assert
|
|
80
|
+
expect(result).toHaveLength(2);
|
|
81
|
+
expect(result.map((s) => s.id)).toEqual([1, 2]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe('buildTestGroupName', () => {
|
|
85
|
+
it('should return simple suite name when hierarchy is disabled', () => {
|
|
86
|
+
// Arrange
|
|
87
|
+
const suiteMap = new Map([[1, { id: 1, name: 'Suite 1', parentSuite: { id: 0 } }]]);
|
|
88
|
+
// Act
|
|
89
|
+
const result = resultDataProvider.buildTestGroupName(1, suiteMap, false);
|
|
90
|
+
// Assert
|
|
91
|
+
expect(result).toBe('Suite 1');
|
|
92
|
+
});
|
|
93
|
+
it('should build hierarchical name with parent info', () => {
|
|
94
|
+
// Arrange
|
|
95
|
+
const suiteMap = new Map([
|
|
96
|
+
[1, { id: 1, name: 'Parent', parentSuite: null }],
|
|
97
|
+
[2, { id: 2, name: 'Child', parentSuite: { id: 1 } }],
|
|
98
|
+
]);
|
|
99
|
+
// Act
|
|
100
|
+
const result = resultDataProvider.buildTestGroupName(2, suiteMap, true);
|
|
101
|
+
// Assert
|
|
102
|
+
expect(result).toBe('Child');
|
|
103
|
+
});
|
|
104
|
+
it('should abbreviate deep hierarchies', () => {
|
|
105
|
+
// Arrange
|
|
106
|
+
const suiteMap = new Map([
|
|
107
|
+
[1, { id: 1, name: 'Root', parentSuite: null }],
|
|
108
|
+
[2, { id: 2, name: 'Level1', parentSuite: { id: 1 } }],
|
|
109
|
+
[3, { id: 3, name: 'Level2', parentSuite: { id: 2 } }],
|
|
110
|
+
[4, { id: 4, name: 'Level3', parentSuite: { id: 3 } }],
|
|
111
|
+
]);
|
|
112
|
+
// Act
|
|
113
|
+
const result = resultDataProvider.buildTestGroupName(4, suiteMap, true);
|
|
114
|
+
// Assert
|
|
115
|
+
expect(result).toBe('Level1/.../Level3');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe('convertRunStatus', () => {
|
|
119
|
+
it('should convert API status to readable format', () => {
|
|
120
|
+
// Arrange & Act & Assert
|
|
121
|
+
expect(resultDataProvider.convertRunStatus('passed')).toBe('Passed');
|
|
122
|
+
expect(resultDataProvider.convertRunStatus('failed')).toBe('Failed');
|
|
123
|
+
expect(resultDataProvider.convertRunStatus('notApplicable')).toBe('Not Applicable');
|
|
124
|
+
expect(resultDataProvider.convertRunStatus('unknown')).toBe('Not Run');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
describe('compareActionResults', () => {
|
|
128
|
+
it('should compare version-like step positions correctly', () => {
|
|
129
|
+
// Act & Assert
|
|
130
|
+
const compare = resultDataProvider.compareActionResults;
|
|
131
|
+
expect(compare('1', '2')).toBe(-1);
|
|
132
|
+
expect(compare('2', '1')).toBe(1);
|
|
133
|
+
expect(compare('1.1', '1.2')).toBe(-1);
|
|
134
|
+
expect(compare('1.2', '1.1')).toBe(1);
|
|
135
|
+
expect(compare('1.1', '1.1')).toBe(0);
|
|
136
|
+
expect(compare('1.1.1', '1.1')).toBe(1);
|
|
137
|
+
expect(compare('1.1', '1.1.1')).toBe(-1);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
describe('Data fetching methods', () => {
|
|
142
|
+
describe('fetchTestSuites', () => {
|
|
143
|
+
it('should fetch and process test suites correctly', async () => {
|
|
144
|
+
// Arrange
|
|
145
|
+
const mockTestSuites = {
|
|
146
|
+
value: [
|
|
147
|
+
{
|
|
148
|
+
id: 1,
|
|
149
|
+
name: 'Root Suite',
|
|
150
|
+
children: [{ id: 2, name: 'Child Suite 1', parentSuite: { id: 1 } }],
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
count: 1,
|
|
154
|
+
};
|
|
155
|
+
tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockTestSuites);
|
|
156
|
+
// Act
|
|
157
|
+
const result = await resultDataProvider.fetchTestSuites(mockTestPlanId, mockProjectName);
|
|
158
|
+
// Assert
|
|
159
|
+
expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}${mockProjectName}/_apis/testplan/Plans/${mockTestPlanId}/Suites?asTreeView=true`, mockToken);
|
|
160
|
+
expect(result).toHaveLength(1);
|
|
161
|
+
expect(result[0]).toHaveProperty('testSuiteId', 2);
|
|
162
|
+
expect(result[0]).toHaveProperty('testGroupName');
|
|
163
|
+
});
|
|
164
|
+
it('should handle errors and return empty array', async () => {
|
|
165
|
+
// Arrange
|
|
166
|
+
const mockError = new Error('API error');
|
|
167
|
+
tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(mockError);
|
|
168
|
+
// Act
|
|
169
|
+
const result = await resultDataProvider.fetchTestSuites(mockTestPlanId, mockProjectName);
|
|
170
|
+
// Assert
|
|
171
|
+
expect(logger_1.default.error).toHaveBeenCalled();
|
|
172
|
+
expect(result).toEqual([]);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
describe('fetchTestPoints', () => {
|
|
176
|
+
it('should fetch and map test points correctly', async () => {
|
|
177
|
+
// Arrange
|
|
178
|
+
const mockSuiteId = '123';
|
|
179
|
+
const mockTestPoints = {
|
|
180
|
+
value: [
|
|
181
|
+
{
|
|
182
|
+
testCaseReference: { id: 1, name: 'Test Case 1' },
|
|
183
|
+
configuration: { name: 'Config 1' },
|
|
184
|
+
results: {
|
|
185
|
+
outcome: 'passed',
|
|
186
|
+
lastTestRunId: 100,
|
|
187
|
+
lastResultId: 200,
|
|
188
|
+
lastResultDetails: { dateCompleted: '2023-01-01', runBy: { displayName: 'Test User' } },
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
count: 1,
|
|
193
|
+
};
|
|
194
|
+
tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockTestPoints);
|
|
195
|
+
// Act
|
|
196
|
+
const result = await resultDataProvider.fetchTestPoints(mockProjectName, mockTestPlanId, mockSuiteId);
|
|
197
|
+
// Assert
|
|
198
|
+
expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}${mockProjectName}/_apis/testplan/Plans/${mockTestPlanId}/Suites/${mockSuiteId}/TestPoint?includePointDetails=true`, mockToken);
|
|
199
|
+
expect(result).toHaveLength(1);
|
|
200
|
+
expect(result[0]).toEqual({
|
|
201
|
+
testCaseId: 1,
|
|
202
|
+
testCaseName: 'Test Case 1',
|
|
203
|
+
configurationName: 'Config 1',
|
|
204
|
+
outcome: 'passed',
|
|
205
|
+
lastRunId: 100,
|
|
206
|
+
lastResultId: 200,
|
|
207
|
+
lastResultDetails: { dateCompleted: '2023-01-01', runBy: { displayName: 'Test User' } },
|
|
208
|
+
testCaseUrl: 'https://dev.azure.com/organization/test-project/_workitems/edit/1',
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
it('should handle errors and return empty array', async () => {
|
|
212
|
+
// Arrange
|
|
213
|
+
const mockSuiteId = '123';
|
|
214
|
+
const mockError = new Error('API error');
|
|
215
|
+
tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(mockError);
|
|
216
|
+
// Act
|
|
217
|
+
const result = await resultDataProvider.fetchTestPoints(mockProjectName, mockTestPlanId, mockSuiteId);
|
|
218
|
+
// Assert
|
|
219
|
+
expect(logger_1.default.error).toHaveBeenCalled();
|
|
220
|
+
expect(result).toEqual([]);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
describe('Data transformation methods', () => {
|
|
225
|
+
describe('mapTestPoint', () => {
|
|
226
|
+
it('should transform test point data correctly', () => {
|
|
227
|
+
// Arrange
|
|
228
|
+
const testPoint = {
|
|
229
|
+
testCaseReference: { id: 1, name: 'Test Case 1' },
|
|
230
|
+
configuration: { name: 'Config 1' },
|
|
231
|
+
results: {
|
|
232
|
+
outcome: 'passed',
|
|
233
|
+
lastTestRunId: 100,
|
|
234
|
+
lastResultId: 200,
|
|
235
|
+
lastResultDetails: { dateCompleted: '2023-01-01', runBy: { displayName: 'Test User' } },
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
// Act
|
|
239
|
+
const result = resultDataProvider.mapTestPoint(testPoint, mockProjectName);
|
|
240
|
+
// Assert
|
|
241
|
+
expect(result).toEqual({
|
|
242
|
+
testCaseId: 1,
|
|
243
|
+
testCaseName: 'Test Case 1',
|
|
244
|
+
configurationName: 'Config 1',
|
|
245
|
+
outcome: 'passed',
|
|
246
|
+
lastRunId: 100,
|
|
247
|
+
lastResultId: 200,
|
|
248
|
+
lastResultDetails: { dateCompleted: '2023-01-01', runBy: { displayName: 'Test User' } },
|
|
249
|
+
testCaseUrl: 'https://dev.azure.com/organization/test-project/_workitems/edit/1',
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
it('should handle missing fields', () => {
|
|
253
|
+
// Arrange
|
|
254
|
+
const testPoint = {
|
|
255
|
+
testCaseReference: { id: 1, name: 'Test Case 1' },
|
|
256
|
+
// No configuration or results
|
|
257
|
+
};
|
|
258
|
+
// Act
|
|
259
|
+
const result = resultDataProvider.mapTestPoint(testPoint, mockProjectName);
|
|
260
|
+
// Assert
|
|
261
|
+
expect(result).toEqual({
|
|
262
|
+
testCaseId: 1,
|
|
263
|
+
testCaseName: 'Test Case 1',
|
|
264
|
+
configurationName: undefined,
|
|
265
|
+
outcome: 'Not Run',
|
|
266
|
+
lastRunId: undefined,
|
|
267
|
+
lastResultId: undefined,
|
|
268
|
+
lastResultDetails: undefined,
|
|
269
|
+
testCaseUrl: 'https://dev.azure.com/organization/test-project/_workitems/edit/1',
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
describe('calculateGroupResultSummary', () => {
|
|
274
|
+
it('should return empty strings when includeHardCopyRun is true', () => {
|
|
275
|
+
// Arrange
|
|
276
|
+
const testPoints = [{ outcome: 'passed' }, { outcome: 'failed' }];
|
|
277
|
+
// Act
|
|
278
|
+
const result = resultDataProvider.calculateGroupResultSummary(testPoints, true);
|
|
279
|
+
// Assert
|
|
280
|
+
expect(result).toEqual({
|
|
281
|
+
passed: '',
|
|
282
|
+
failed: '',
|
|
283
|
+
notApplicable: '',
|
|
284
|
+
blocked: '',
|
|
285
|
+
notRun: '',
|
|
286
|
+
total: '',
|
|
287
|
+
successPercentage: '',
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
it('should calculate summary statistics correctly', () => {
|
|
291
|
+
// Arrange
|
|
292
|
+
const testPoints = [
|
|
293
|
+
{ outcome: 'passed' },
|
|
294
|
+
{ outcome: 'passed' },
|
|
295
|
+
{ outcome: 'failed' },
|
|
296
|
+
{ outcome: 'notApplicable' },
|
|
297
|
+
{ outcome: 'blocked' },
|
|
298
|
+
{ outcome: 'something else' },
|
|
299
|
+
];
|
|
300
|
+
// Act
|
|
301
|
+
const result = resultDataProvider.calculateGroupResultSummary(testPoints, false);
|
|
302
|
+
// Assert
|
|
303
|
+
expect(result).toEqual({
|
|
304
|
+
passed: 2,
|
|
305
|
+
failed: 1,
|
|
306
|
+
notApplicable: 1,
|
|
307
|
+
blocked: 1,
|
|
308
|
+
notRun: 1,
|
|
309
|
+
total: 6,
|
|
310
|
+
successPercentage: '33.33%',
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
it('should handle empty array', () => {
|
|
314
|
+
// Arrange
|
|
315
|
+
const testPoints = [];
|
|
316
|
+
// Act
|
|
317
|
+
const result = resultDataProvider.calculateGroupResultSummary(testPoints, false);
|
|
318
|
+
// Assert
|
|
319
|
+
expect(result).toEqual({
|
|
320
|
+
passed: 0,
|
|
321
|
+
failed: 0,
|
|
322
|
+
notApplicable: 0,
|
|
323
|
+
blocked: 0,
|
|
324
|
+
notRun: 0,
|
|
325
|
+
total: 0,
|
|
326
|
+
successPercentage: '0.00%',
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
describe('mapAttachmentsUrl', () => {
|
|
331
|
+
it('should map attachment URLs correctly', () => {
|
|
332
|
+
// Arrange
|
|
333
|
+
const mockRunResults = [
|
|
334
|
+
{
|
|
335
|
+
testCaseId: 1,
|
|
336
|
+
lastRunId: 100,
|
|
337
|
+
lastResultId: 200,
|
|
338
|
+
iteration: {
|
|
339
|
+
attachments: [{ id: 1, name: 'attachment1.png', actionPath: 'path1' }],
|
|
340
|
+
actionResults: [{ actionPath: 'path1', stepPosition: '1.1' }],
|
|
341
|
+
},
|
|
342
|
+
analysisAttachments: [{ id: 2, fileName: 'analysis1.txt' }],
|
|
343
|
+
},
|
|
344
|
+
];
|
|
345
|
+
// Act
|
|
346
|
+
const result = resultDataProvider.mapAttachmentsUrl(mockRunResults, mockProjectName);
|
|
347
|
+
// Assert
|
|
348
|
+
expect(result[0].iteration.attachments[0].downloadUrl).toBe(`${mockOrgUrl}${mockProjectName}/_apis/test/runs/100/results/200/attachments/1/attachment1.png`);
|
|
349
|
+
expect(result[0].iteration.attachments[0].stepNo).toBe('1.1');
|
|
350
|
+
expect(result[0].analysisAttachments[0].downloadUrl).toBe(`${mockOrgUrl}${mockProjectName}/_apis/test/runs/100/results/200/attachments/2/analysis1.txt`);
|
|
351
|
+
});
|
|
352
|
+
it('should handle missing iteration', () => {
|
|
353
|
+
// Arrange
|
|
354
|
+
const mockRunResults = [
|
|
355
|
+
{
|
|
356
|
+
testCaseId: 1,
|
|
357
|
+
lastRunId: 100,
|
|
358
|
+
lastResultId: 200,
|
|
359
|
+
// No iteration
|
|
360
|
+
analysisAttachments: [{ id: 2, fileName: 'analysis1.txt' }],
|
|
361
|
+
},
|
|
362
|
+
];
|
|
363
|
+
// Act
|
|
364
|
+
const result = resultDataProvider.mapAttachmentsUrl(mockRunResults, mockProjectName);
|
|
365
|
+
// Assert
|
|
366
|
+
expect(result[0]).toEqual(mockRunResults[0]);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
describe('formatTestResult', () => {
|
|
371
|
+
it('should format test result correctly', () => {
|
|
372
|
+
// Arrange
|
|
373
|
+
const testPoint = {
|
|
374
|
+
testCaseId: 1,
|
|
375
|
+
testCaseName: 'Test Case 1',
|
|
376
|
+
testGroupName: 'Suite 1',
|
|
377
|
+
testCaseUrl: 'https://example.com/workitems/1',
|
|
378
|
+
configurationName: 'Config 1',
|
|
379
|
+
outcome: 'passed',
|
|
380
|
+
};
|
|
381
|
+
// Act
|
|
382
|
+
const result = resultDataProvider.formatTestResult(testPoint, true, false);
|
|
383
|
+
// Assert
|
|
384
|
+
expect(result.testId).toBe(1);
|
|
385
|
+
expect(result.testName).toBe('Test Case 1');
|
|
386
|
+
expect(result.configuration).toBe('Config 1');
|
|
387
|
+
expect(result.runStatus).toBe('Passed');
|
|
388
|
+
});
|
|
389
|
+
it('should return empty strings when includeHardCopyRun is true', () => {
|
|
390
|
+
// Arrange
|
|
391
|
+
const testPoint = {
|
|
392
|
+
testCaseId: 1,
|
|
393
|
+
testCaseName: 'Test Case 1',
|
|
394
|
+
testGroupName: 'Suite 1',
|
|
395
|
+
outcome: 'passed',
|
|
396
|
+
};
|
|
397
|
+
// Act
|
|
398
|
+
const result = resultDataProvider.formatTestResult(testPoint, false, true);
|
|
399
|
+
// Assert
|
|
400
|
+
expect(result.runStatus).toBe('');
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
describe('calculateTotalSummary', () => {
|
|
404
|
+
it('should calculate total summary from summarized results', () => {
|
|
405
|
+
// Arrange
|
|
406
|
+
const summarizedResults = [
|
|
407
|
+
{ groupResultSummary: { passed: 2, failed: 1, notApplicable: 0, blocked: 0, notRun: 1, total: 4 } },
|
|
408
|
+
{ groupResultSummary: { passed: 3, failed: 0, notApplicable: 1, blocked: 1, notRun: 0, total: 5 } },
|
|
409
|
+
];
|
|
410
|
+
// Act
|
|
411
|
+
const result = resultDataProvider.calculateTotalSummary(summarizedResults, false);
|
|
412
|
+
// Assert
|
|
413
|
+
expect(result.passed).toBe(5);
|
|
414
|
+
expect(result.failed).toBe(1);
|
|
415
|
+
expect(result.notApplicable).toBe(1);
|
|
416
|
+
expect(result.blocked).toBe(1);
|
|
417
|
+
expect(result.notRun).toBe(1);
|
|
418
|
+
expect(result.total).toBe(9);
|
|
419
|
+
});
|
|
420
|
+
it('should return empty strings when includeHardCopyRun is true', () => {
|
|
421
|
+
// Arrange
|
|
422
|
+
const summarizedResults = [{ groupResultSummary: { passed: 2, failed: 1, total: 3 } }];
|
|
423
|
+
// Act
|
|
424
|
+
const result = resultDataProvider.calculateTotalSummary(summarizedResults, true);
|
|
425
|
+
// Assert
|
|
426
|
+
expect(result.passed).toBe('');
|
|
427
|
+
expect(result.failed).toBe('');
|
|
428
|
+
expect(result.total).toBe('');
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
describe('flattenTestPoints', () => {
|
|
432
|
+
it('should flatten test points from suites', () => {
|
|
433
|
+
// Arrange
|
|
434
|
+
const testPoints = [
|
|
435
|
+
{ testSuiteId: 1, testGroupName: 'Suite 1', testPointsItems: [{ id: 1 }, { id: 2 }] },
|
|
436
|
+
{ testSuiteId: 2, testGroupName: 'Suite 2', testPointsItems: [{ id: 3 }] },
|
|
437
|
+
];
|
|
438
|
+
// Act
|
|
439
|
+
const result = resultDataProvider.flattenTestPoints(testPoints);
|
|
440
|
+
// Assert
|
|
441
|
+
expect(result).toHaveLength(3);
|
|
442
|
+
expect(result[0].testGroupName).toBe('Suite 1');
|
|
443
|
+
expect(result[2].testGroupName).toBe('Suite 2');
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
describe('createSuiteMap', () => {
|
|
447
|
+
it('should create a map of suites by ID', () => {
|
|
448
|
+
// Arrange
|
|
449
|
+
const suites = [
|
|
450
|
+
{ id: 1, name: 'Suite 1', children: [{ id: 2, name: 'Suite 2' }] },
|
|
451
|
+
{ id: 3, name: 'Suite 3' },
|
|
452
|
+
];
|
|
453
|
+
// Act
|
|
454
|
+
const result = resultDataProvider.createSuiteMap(suites);
|
|
455
|
+
// Assert
|
|
456
|
+
expect(result).toBeInstanceOf(Map);
|
|
457
|
+
expect(result.size).toBe(3);
|
|
458
|
+
expect(result.get(1).name).toBe('Suite 1');
|
|
459
|
+
expect(result.get(2).name).toBe('Suite 2');
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
describe('isNotRunStep', () => {
|
|
463
|
+
it('should return true for not run step', () => {
|
|
464
|
+
// Arrange
|
|
465
|
+
const step = { stepStatus: 'Not Run' };
|
|
466
|
+
// Act
|
|
467
|
+
const result = resultDataProvider.isNotRunStep(step);
|
|
468
|
+
// Assert
|
|
469
|
+
expect(result).toBe(true);
|
|
470
|
+
});
|
|
471
|
+
it('should return false for run step', () => {
|
|
472
|
+
// Arrange
|
|
473
|
+
const step = { stepStatus: 'Passed' };
|
|
474
|
+
// Act
|
|
475
|
+
const result = resultDataProvider.isNotRunStep(step);
|
|
476
|
+
// Assert
|
|
477
|
+
expect(result).toBe(false);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
describe('CreateAttachmentPathIndexMap', () => {
|
|
481
|
+
it('should create map of action paths to step positions', () => {
|
|
482
|
+
// Arrange
|
|
483
|
+
const actionResults = [
|
|
484
|
+
{ actionPath: 'path1', stepPosition: '1.1' },
|
|
485
|
+
{ actionPath: 'path2', stepPosition: '1.2' },
|
|
486
|
+
];
|
|
487
|
+
// Act
|
|
488
|
+
const result = resultDataProvider.CreateAttachmentPathIndexMap(actionResults);
|
|
489
|
+
// Assert
|
|
490
|
+
expect(result).toBeInstanceOf(Map);
|
|
491
|
+
expect(result.get('path1')).toBe('1.1');
|
|
492
|
+
expect(result.get('path2')).toBe('1.2');
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
describe('getTestPointsForTestCases', () => {
|
|
496
|
+
it('should fetch test points for test cases', async () => {
|
|
497
|
+
// Arrange
|
|
498
|
+
const mockTestCaseIds = ['1', '2'];
|
|
499
|
+
const mockResponse = { data: { points: [{ id: 1 }] } };
|
|
500
|
+
tfs_1.TFSServices.postRequest.mockResolvedValueOnce(mockResponse);
|
|
501
|
+
// Act
|
|
502
|
+
const result = await resultDataProvider.getTestPointsForTestCases(mockProjectName, mockTestCaseIds);
|
|
503
|
+
// Assert
|
|
504
|
+
expect(tfs_1.TFSServices.postRequest).toHaveBeenCalled();
|
|
505
|
+
expect(result).toEqual(mockResponse);
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
describe('mapActionPathToPosition', () => {
|
|
509
|
+
it('should map action paths to positions', () => {
|
|
510
|
+
// Arrange
|
|
511
|
+
const actionResults = [
|
|
512
|
+
{ testId: 1, actionPath: 'path1', stepNo: '1.1' },
|
|
513
|
+
{ testId: 1, actionPath: 'path2', stepNo: '1.2' },
|
|
514
|
+
];
|
|
515
|
+
// Act
|
|
516
|
+
const result = resultDataProvider.mapActionPathToPosition(actionResults);
|
|
517
|
+
// Assert
|
|
518
|
+
expect(result).toBeInstanceOf(Map);
|
|
519
|
+
expect(result.get('1-path1')).toBe('1.1');
|
|
520
|
+
expect(result.get('1-path2')).toBe('1.2');
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
describe('fetchTestPlanName', () => {
|
|
524
|
+
it('should fetch test plan name', async () => {
|
|
525
|
+
// Arrange
|
|
526
|
+
const mockPlan = { name: 'Test Plan 1' };
|
|
527
|
+
tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockPlan);
|
|
528
|
+
// Act
|
|
529
|
+
const result = await resultDataProvider.fetchTestPlanName(mockTestPlanId, mockProjectName);
|
|
530
|
+
// Assert
|
|
531
|
+
expect(result).toBe('Test Plan 1');
|
|
532
|
+
});
|
|
533
|
+
it('should return empty string on error', async () => {
|
|
534
|
+
// Arrange
|
|
535
|
+
tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(new Error('API Error'));
|
|
536
|
+
// Act
|
|
537
|
+
const result = await resultDataProvider.fetchTestPlanName(mockTestPlanId, mockProjectName);
|
|
538
|
+
// Assert
|
|
539
|
+
expect(result).toBe('');
|
|
540
|
+
expect(logger_1.default.error).toHaveBeenCalled();
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
describe('fetchTestCasesBySuiteId', () => {
|
|
544
|
+
it('should fetch test cases by suite ID', async () => {
|
|
545
|
+
// Arrange
|
|
546
|
+
const mockSuiteId = '123';
|
|
547
|
+
const mockTestCases = { value: [{ workItem: { id: 1 } }] };
|
|
548
|
+
tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockTestCases);
|
|
549
|
+
// Act
|
|
550
|
+
const result = await resultDataProvider.fetchTestCasesBySuiteId(mockProjectName, mockTestPlanId, mockSuiteId);
|
|
551
|
+
// Assert
|
|
552
|
+
expect(result).toEqual(mockTestCases.value);
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
describe('mapTestPointForCrossPlans', () => {
|
|
556
|
+
it('should map test point for cross plans', () => {
|
|
557
|
+
// Arrange
|
|
558
|
+
const testPoint = {
|
|
559
|
+
testCase: { id: 1, name: 'Test Case 1' },
|
|
560
|
+
testSuite: { id: 2, name: 'Suite 1' },
|
|
561
|
+
configuration: { name: 'Config 1' },
|
|
562
|
+
outcome: 'passed',
|
|
563
|
+
lastTestRun: { id: '100' },
|
|
564
|
+
lastResult: { id: '200' },
|
|
565
|
+
};
|
|
566
|
+
// Act
|
|
567
|
+
const result = resultDataProvider.mapTestPointForCrossPlans(testPoint, mockProjectName);
|
|
568
|
+
// Assert
|
|
569
|
+
expect(result.testCaseId).toBe(1);
|
|
570
|
+
expect(result.testCaseName).toBe('Test Case 1');
|
|
571
|
+
expect(result.outcome).toBe('passed');
|
|
572
|
+
expect(result.lastRunId).toBe('100');
|
|
573
|
+
});
|
|
574
|
+
it('should provide default values for missing fields', () => {
|
|
575
|
+
// Arrange
|
|
576
|
+
const testPoint = {
|
|
577
|
+
testCase: { id: 1, name: 'Test Case 1' },
|
|
578
|
+
testSuite: { id: 2, name: 'Suite 1' },
|
|
579
|
+
};
|
|
580
|
+
// Act
|
|
581
|
+
const result = resultDataProvider.mapTestPointForCrossPlans(testPoint, mockProjectName);
|
|
582
|
+
// Assert
|
|
583
|
+
expect(result.outcome).toBe('Not Run');
|
|
584
|
+
expect(result.lastResultDetails).toBeDefined();
|
|
585
|
+
expect(result.lastResultDetails.duration).toBe(0);
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
describe('mapStepResultsForExecutionAppendix', () => {
|
|
589
|
+
it('should map step results for execution appendix', () => {
|
|
590
|
+
// Arrange
|
|
591
|
+
const detailedResults = [
|
|
592
|
+
{
|
|
593
|
+
testId: 1,
|
|
594
|
+
testCaseRevision: { rev: 1 },
|
|
595
|
+
stepNo: '1.1',
|
|
596
|
+
stepIdentifier: 'step1',
|
|
597
|
+
stepAction: 'Do something',
|
|
598
|
+
stepExpected: 'Something happens',
|
|
599
|
+
stepStatus: 'Passed',
|
|
600
|
+
stepComments: '',
|
|
601
|
+
isSharedStepTitle: false,
|
|
602
|
+
actionPath: 'path1',
|
|
603
|
+
},
|
|
604
|
+
];
|
|
605
|
+
const runResultData = [
|
|
606
|
+
{
|
|
607
|
+
testCaseId: 1,
|
|
608
|
+
iteration: {
|
|
609
|
+
attachments: [{ name: 'attachment.png', actionPath: 'path1', downloadUrl: 'http://example.com' }],
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
];
|
|
613
|
+
// Act
|
|
614
|
+
const result = resultDataProvider.mapStepResultsForExecutionAppendix(detailedResults, runResultData);
|
|
615
|
+
// Assert
|
|
616
|
+
expect(result).toBeInstanceOf(Map);
|
|
617
|
+
expect(result.has('1')).toBe(true);
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
describe('fetchCrossTestPoints', () => {
|
|
621
|
+
it('should return empty array when no test case IDs provided', async () => {
|
|
622
|
+
// Act
|
|
623
|
+
const result = await resultDataProvider.fetchCrossTestPoints(mockProjectName, []);
|
|
624
|
+
// Assert
|
|
625
|
+
expect(result).toEqual([]);
|
|
626
|
+
});
|
|
627
|
+
it('should handle error and return empty array', async () => {
|
|
628
|
+
// Arrange
|
|
629
|
+
tfs_1.TFSServices.postRequest.mockRejectedValueOnce(new Error('API Error'));
|
|
630
|
+
// Act
|
|
631
|
+
const result = await resultDataProvider.fetchCrossTestPoints(mockProjectName, [1, 2]);
|
|
632
|
+
// Assert
|
|
633
|
+
expect(result).toEqual([]);
|
|
634
|
+
expect(logger_1.default.error).toHaveBeenCalled();
|
|
635
|
+
});
|
|
636
|
+
it('should return empty array when API returns invalid response format', async () => {
|
|
637
|
+
tfs_1.TFSServices.postRequest.mockResolvedValueOnce({ data: {} });
|
|
638
|
+
const result = await resultDataProvider.fetchCrossTestPoints(mockProjectName, [1, 2]);
|
|
639
|
+
expect(result).toEqual([]);
|
|
640
|
+
expect(logger_1.default.warn).toHaveBeenCalledWith('No test points found or invalid response format');
|
|
641
|
+
});
|
|
642
|
+
it('should pick the latest point per test case and map details with defaults', async () => {
|
|
643
|
+
tfs_1.TFSServices.postRequest.mockResolvedValueOnce({
|
|
644
|
+
data: {
|
|
645
|
+
points: [
|
|
646
|
+
{
|
|
647
|
+
testCase: { id: 1 },
|
|
648
|
+
lastTestRun: { id: '1' },
|
|
649
|
+
lastResult: { id: '5' },
|
|
650
|
+
url: 'https://example.com/points/1',
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
testCase: { id: 1 },
|
|
654
|
+
lastTestRun: { id: '2' },
|
|
655
|
+
lastResult: { id: '1' },
|
|
656
|
+
url: 'https://example.com/points/2',
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
testCase: { id: 2 },
|
|
660
|
+
lastTestRun: { id: '1' },
|
|
661
|
+
lastResult: { id: '1' },
|
|
662
|
+
url: 'https://example.com/points/3',
|
|
663
|
+
},
|
|
664
|
+
],
|
|
665
|
+
},
|
|
666
|
+
});
|
|
667
|
+
tfs_1.TFSServices.getItemContent
|
|
668
|
+
.mockResolvedValueOnce({
|
|
669
|
+
testCase: { id: 1, name: 'TC 1' },
|
|
670
|
+
testSuite: { id: 10, name: 'Suite' },
|
|
671
|
+
configuration: { name: 'Config' },
|
|
672
|
+
outcome: 'passed',
|
|
673
|
+
lastTestRun: { id: '2' },
|
|
674
|
+
lastResult: { id: '1' },
|
|
675
|
+
})
|
|
676
|
+
.mockResolvedValueOnce({
|
|
677
|
+
testCase: { id: 2, name: 'TC 2' },
|
|
678
|
+
testSuite: { id: 11, name: 'Suite' },
|
|
679
|
+
configuration: { name: 'Config' },
|
|
680
|
+
outcome: 'failed',
|
|
681
|
+
lastTestRun: { id: '1' },
|
|
682
|
+
lastResult: { id: '1' },
|
|
683
|
+
lastResultDetails: { duration: 5, dateCompleted: '2023-01-01', runBy: { displayName: 'User' } },
|
|
684
|
+
});
|
|
685
|
+
const result = await resultDataProvider.fetchCrossTestPoints(mockProjectName, [1, 2]);
|
|
686
|
+
expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith('https://example.com/points/2?witFields=Microsoft.VSTS.TCM.Steps&includePointDetails=true', mockToken);
|
|
687
|
+
expect(result).toHaveLength(2);
|
|
688
|
+
const tc1 = result.find((r) => r.testCaseId === 1);
|
|
689
|
+
expect(tc1.lastResultDetails).toEqual(expect.objectContaining({
|
|
690
|
+
duration: 0,
|
|
691
|
+
runBy: expect.objectContaining({ displayName: 'No tester' }),
|
|
692
|
+
}));
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
describe('fetchLinkedWi', () => {
|
|
696
|
+
it('should fetch linked work items and filter only open Bugs/Change Requests', async () => {
|
|
697
|
+
const testItems = [
|
|
698
|
+
{
|
|
699
|
+
testId: 1,
|
|
700
|
+
testName: 'TC 1',
|
|
701
|
+
testCaseUrl: 'http://example.com/tc/1',
|
|
702
|
+
runStatus: 'Passed',
|
|
703
|
+
},
|
|
704
|
+
];
|
|
705
|
+
tfs_1.TFSServices.getItemContent
|
|
706
|
+
.mockResolvedValueOnce({
|
|
707
|
+
value: [
|
|
708
|
+
{
|
|
709
|
+
id: 1,
|
|
710
|
+
relations: [
|
|
711
|
+
{ url: `${mockOrgUrl}_apis/wit/workItems/100` },
|
|
712
|
+
{ url: `${mockOrgUrl}_apis/wit/workItems/101` },
|
|
713
|
+
],
|
|
714
|
+
},
|
|
715
|
+
],
|
|
716
|
+
})
|
|
717
|
+
.mockResolvedValueOnce({
|
|
718
|
+
value: [
|
|
719
|
+
{
|
|
720
|
+
id: 100,
|
|
721
|
+
fields: {
|
|
722
|
+
'System.WorkItemType': 'Bug',
|
|
723
|
+
'System.State': 'Active',
|
|
724
|
+
'System.Title': 'Open bug',
|
|
725
|
+
'Microsoft.VSTS.Common.Severity': '1 - Critical',
|
|
726
|
+
},
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
id: 101,
|
|
730
|
+
fields: {
|
|
731
|
+
'System.WorkItemType': 'Bug',
|
|
732
|
+
'System.State': 'Closed',
|
|
733
|
+
'System.Title': 'Closed bug',
|
|
734
|
+
},
|
|
735
|
+
},
|
|
736
|
+
],
|
|
737
|
+
});
|
|
738
|
+
const result = await resultDataProvider.fetchLinkedWi(mockProjectName, testItems);
|
|
739
|
+
expect(result).toHaveLength(1);
|
|
740
|
+
expect(result[0].linkItems).toHaveLength(1);
|
|
741
|
+
expect(result[0].linkItems[0]).toEqual(expect.objectContaining({
|
|
742
|
+
pcrId: 100,
|
|
743
|
+
workItemType: 'Bug',
|
|
744
|
+
title: 'Open bug',
|
|
745
|
+
pcrUrl: `${mockOrgUrl}${mockProjectName}/_workitems/edit/100`,
|
|
746
|
+
}));
|
|
747
|
+
});
|
|
748
|
+
});
|
|
749
|
+
describe('getTestReporterResults filtering', () => {
|
|
750
|
+
it('should apply errorFilterMode=both and run-step filter and return the filtered rows', async () => {
|
|
751
|
+
const testReporterRows = [
|
|
752
|
+
{
|
|
753
|
+
testCase: {
|
|
754
|
+
comment: 'has comment',
|
|
755
|
+
result: { resultMessage: 'Failed in Run 1' },
|
|
756
|
+
},
|
|
757
|
+
stepComments: 'step comment',
|
|
758
|
+
stepStatus: 'Failed',
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
testCase: {
|
|
762
|
+
comment: '',
|
|
763
|
+
result: { resultMessage: 'Passed' },
|
|
764
|
+
},
|
|
765
|
+
stepComments: '',
|
|
766
|
+
stepStatus: 'Not Run',
|
|
767
|
+
},
|
|
768
|
+
];
|
|
769
|
+
jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
770
|
+
jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([]);
|
|
771
|
+
jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([]);
|
|
772
|
+
jest.spyOn(resultDataProvider, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([]);
|
|
773
|
+
jest
|
|
774
|
+
.spyOn(resultDataProvider, 'alignStepsWithIterationsTestReporter')
|
|
775
|
+
.mockReturnValueOnce(testReporterRows);
|
|
776
|
+
const linkedQueryRequest = {
|
|
777
|
+
linkedQueryMode: 'none',
|
|
778
|
+
testAssociatedQuery: { wiql: { href: 'https://example.com/wiql' }, columns: [] },
|
|
779
|
+
};
|
|
780
|
+
const result = await resultDataProvider.getTestReporterResults('planId', mockProjectName, [], [], false, true, true, linkedQueryRequest, 'both');
|
|
781
|
+
expect(result).toBeDefined();
|
|
782
|
+
const first = result[0];
|
|
783
|
+
expect(first).toBeDefined();
|
|
784
|
+
expect(first.data).toHaveLength(1);
|
|
785
|
+
expect(first.data[0].stepStatus).toBe('Failed');
|
|
786
|
+
});
|
|
787
|
+
it('should filter only test case results when errorFilterMode=onlyTestCaseResult', async () => {
|
|
788
|
+
const rows = [
|
|
789
|
+
{
|
|
790
|
+
testCase: { comment: 'c', result: { resultMessage: 'Passed' } },
|
|
791
|
+
stepComments: '',
|
|
792
|
+
stepStatus: 'Not Run',
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
testCase: { comment: '', result: { resultMessage: 'Failed in Run 1' } },
|
|
796
|
+
stepComments: '',
|
|
797
|
+
stepStatus: 'Not Run',
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
testCase: { comment: '', result: { resultMessage: 'Passed' } },
|
|
801
|
+
stepComments: '',
|
|
802
|
+
stepStatus: 'Not Run',
|
|
803
|
+
},
|
|
804
|
+
];
|
|
805
|
+
jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
806
|
+
jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([]);
|
|
807
|
+
jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([]);
|
|
808
|
+
jest.spyOn(resultDataProvider, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([]);
|
|
809
|
+
jest.spyOn(resultDataProvider, 'alignStepsWithIterationsTestReporter').mockReturnValueOnce(rows);
|
|
810
|
+
const res = await resultDataProvider.getTestReporterResults('planId', mockProjectName, [], [], false, true, false, { linkedQueryMode: 'none', testAssociatedQuery: { wiql: { href: 'x' }, columns: [] } }, 'onlyTestCaseResult');
|
|
811
|
+
expect(res[0].data).toHaveLength(2);
|
|
812
|
+
});
|
|
813
|
+
it('should filter only step results when errorFilterMode=onlyTestStepsResult', async () => {
|
|
814
|
+
const rows = [
|
|
815
|
+
{
|
|
816
|
+
testCase: { comment: '', result: { resultMessage: 'Passed' } },
|
|
817
|
+
stepComments: 'x',
|
|
818
|
+
stepStatus: 'Not Run',
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
testCase: { comment: '', result: { resultMessage: 'Passed' } },
|
|
822
|
+
stepComments: '',
|
|
823
|
+
stepStatus: 'Failed',
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
testCase: { comment: '', result: { resultMessage: 'Passed' } },
|
|
827
|
+
stepComments: '',
|
|
828
|
+
stepStatus: 'Not Run',
|
|
829
|
+
},
|
|
830
|
+
];
|
|
831
|
+
jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
832
|
+
jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([]);
|
|
833
|
+
jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([]);
|
|
834
|
+
jest.spyOn(resultDataProvider, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([]);
|
|
835
|
+
jest.spyOn(resultDataProvider, 'alignStepsWithIterationsTestReporter').mockReturnValueOnce(rows);
|
|
836
|
+
const res = await resultDataProvider.getTestReporterResults('planId', mockProjectName, [], [], false, true, false, { linkedQueryMode: 'none', testAssociatedQuery: { wiql: { href: 'x' }, columns: [] } }, 'onlyTestStepsResult');
|
|
837
|
+
expect(res[0].data).toHaveLength(2);
|
|
838
|
+
});
|
|
839
|
+
it('should execute query mode and call TicketsDataProvider when linkedQueryMode=query', async () => {
|
|
840
|
+
jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
841
|
+
jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([]);
|
|
842
|
+
jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([]);
|
|
843
|
+
jest.spyOn(resultDataProvider, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([]);
|
|
844
|
+
jest.spyOn(resultDataProvider, 'alignStepsWithIterationsTestReporter').mockReturnValueOnce([]);
|
|
845
|
+
const linkedQueryRequest = {
|
|
846
|
+
linkedQueryMode: 'query',
|
|
847
|
+
testAssociatedQuery: {
|
|
848
|
+
wiql: { href: 'https://example.com/wiql' },
|
|
849
|
+
columns: [{ referenceName: 'X', name: 'X' }],
|
|
850
|
+
},
|
|
851
|
+
};
|
|
852
|
+
await resultDataProvider.getTestReporterResults('planId', mockProjectName, [], [], false, true, false, linkedQueryRequest, 'none');
|
|
853
|
+
const TicketsProviderMock = require('../../modules/TicketsDataProvider').default;
|
|
854
|
+
expect(TicketsProviderMock).toHaveBeenCalled();
|
|
855
|
+
const instance = TicketsProviderMock.mock.results[0].value;
|
|
856
|
+
expect(instance.GetQueryResultsFromWiql).toHaveBeenCalledWith('https://example.com/wiql', true, expect.any(Map));
|
|
857
|
+
});
|
|
858
|
+
});
|
|
859
|
+
describe('fetchResultDataForTestReporter (runResultField switch)', () => {
|
|
860
|
+
it('should populate requested runResultField values including testCaseResult URL branches', async () => {
|
|
861
|
+
jest
|
|
862
|
+
.spyOn(resultDataProvider, 'fetchResultDataBase')
|
|
863
|
+
.mockImplementation(async (...args) => {
|
|
864
|
+
const testSuiteId = args[1];
|
|
865
|
+
const formatter = args[4];
|
|
866
|
+
const extra = args[5];
|
|
867
|
+
const selectedFields = extra[0];
|
|
868
|
+
const isQueryMode = extra[1];
|
|
869
|
+
const pt = extra[2];
|
|
870
|
+
return formatter({
|
|
871
|
+
testCase: { id: 1, name: 'TC 1' },
|
|
872
|
+
testSuite: { name: 'Suite' },
|
|
873
|
+
testCaseRevision: 7,
|
|
874
|
+
resolutionState: 'x',
|
|
875
|
+
failureType: 'FT',
|
|
876
|
+
priority: 2,
|
|
877
|
+
outcome: 'passed',
|
|
878
|
+
iterationDetails: [],
|
|
879
|
+
filteredFields: { 'Custom.Field1': 'v1' },
|
|
880
|
+
relatedRequirements: [],
|
|
881
|
+
relatedBugs: [],
|
|
882
|
+
relatedCRs: [],
|
|
883
|
+
}, testSuiteId, pt, selectedFields, isQueryMode);
|
|
884
|
+
});
|
|
885
|
+
const selectedFields = [
|
|
886
|
+
'priority@runResultField',
|
|
887
|
+
'testCaseResult@runResultField',
|
|
888
|
+
'testCaseComment@runResultField',
|
|
889
|
+
'failureType@runResultField',
|
|
890
|
+
'runBy@runResultField',
|
|
891
|
+
'executionDate@runResultField',
|
|
892
|
+
'configurationName@runResultField',
|
|
893
|
+
'unknownField@runResultField',
|
|
894
|
+
];
|
|
895
|
+
const point = {
|
|
896
|
+
lastRunId: 10,
|
|
897
|
+
lastResultId: 20,
|
|
898
|
+
configurationName: 'Cfg',
|
|
899
|
+
lastResultDetails: { runBy: { displayName: 'User' }, dateCompleted: '2023-01-01' },
|
|
900
|
+
};
|
|
901
|
+
const res = await resultDataProvider.fetchResultDataForTestReporter(mockProjectName, 'suite1', point, selectedFields, false);
|
|
902
|
+
expect(res.priority).toBe(2);
|
|
903
|
+
expect(res.testCaseResult).toEqual(expect.objectContaining({
|
|
904
|
+
resultMessage: expect.stringContaining('Run 10'),
|
|
905
|
+
url: expect.stringContaining('runId=10'),
|
|
906
|
+
}));
|
|
907
|
+
expect(res.runBy).toBe('User');
|
|
908
|
+
expect(res.executionDate).toBe('2023-01-01');
|
|
909
|
+
expect(res.configurationName).toBe('Cfg');
|
|
910
|
+
expect(res.customFields).toEqual(expect.objectContaining({ field1: 'v1' }));
|
|
911
|
+
});
|
|
912
|
+
it('should set testCaseResult url empty when lastRunId/lastResultId are undefined', async () => {
|
|
913
|
+
jest
|
|
914
|
+
.spyOn(resultDataProvider, 'fetchResultDataBase')
|
|
915
|
+
.mockImplementation(async (...args) => {
|
|
916
|
+
const testSuiteId = args[1];
|
|
917
|
+
const point = args[2];
|
|
918
|
+
const formatter = args[4];
|
|
919
|
+
const extra = args[5];
|
|
920
|
+
return formatter({
|
|
921
|
+
testCase: { id: 1, name: 'TC 1' },
|
|
922
|
+
testSuite: { name: 'Suite' },
|
|
923
|
+
testCaseRevision: 1,
|
|
924
|
+
resolutionState: 'x',
|
|
925
|
+
failureType: 'FT',
|
|
926
|
+
priority: 1,
|
|
927
|
+
outcome: 'passed',
|
|
928
|
+
iterationDetails: [],
|
|
929
|
+
}, testSuiteId, point, extra[0]);
|
|
930
|
+
});
|
|
931
|
+
const res = await resultDataProvider.fetchResultDataForTestReporter(mockProjectName, 'suite1', {
|
|
932
|
+
lastRunId: undefined,
|
|
933
|
+
lastResultId: undefined,
|
|
934
|
+
configurationName: 'Cfg',
|
|
935
|
+
lastResultDetails: { runBy: { displayName: 'U' }, dateCompleted: 'd' },
|
|
936
|
+
}, ['testCaseResult@runResultField'], false);
|
|
937
|
+
expect(res.testCaseResult).toEqual(expect.objectContaining({ url: '' }));
|
|
938
|
+
});
|
|
939
|
+
});
|
|
940
|
+
describe('fetchResultDataBasedOnWiBase', () => {
|
|
941
|
+
it('should return null and warn when runId/resultId are 0 and no point is provided', async () => {
|
|
942
|
+
const res = await resultDataProvider.fetchResultDataBasedOnWiBase(mockProjectName, '0', '0');
|
|
943
|
+
expect(res).toBeNull();
|
|
944
|
+
expect(logger_1.default.warn).toHaveBeenCalled();
|
|
945
|
+
});
|
|
946
|
+
it('should build synthetic result for Active state when runId/resultId are 0 and point is provided', async () => {
|
|
947
|
+
const point = {
|
|
948
|
+
testCaseId: '123',
|
|
949
|
+
testCaseName: 'TC 123',
|
|
950
|
+
outcome: 'passed',
|
|
951
|
+
testSuite: { id: '1', name: 'Suite' },
|
|
952
|
+
};
|
|
953
|
+
tfs_1.TFSServices.getItemContent.mockResolvedValueOnce({
|
|
954
|
+
id: 123,
|
|
955
|
+
rev: 7,
|
|
956
|
+
fields: {
|
|
957
|
+
'System.State': 'Active',
|
|
958
|
+
'System.CreatedDate': '2023-01-01T00:00:00',
|
|
959
|
+
'Microsoft.VSTS.TCM.Priority': 2,
|
|
960
|
+
'System.Title': 'Title 123',
|
|
961
|
+
'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
|
|
962
|
+
},
|
|
963
|
+
relations: null,
|
|
964
|
+
});
|
|
965
|
+
const selectedFields = ['System.Title@testCaseWorkItemField'];
|
|
966
|
+
const res = await resultDataProvider.fetchResultDataBasedOnWiBase(mockProjectName, '0', '0', true, selectedFields, false, point);
|
|
967
|
+
expect(res).toEqual(expect.objectContaining({
|
|
968
|
+
id: 0,
|
|
969
|
+
failureType: 'None',
|
|
970
|
+
testCaseRevision: 7,
|
|
971
|
+
stepsResultXml: '<steps></steps>',
|
|
972
|
+
filteredFields: { 'System.Title': 'Title 123' },
|
|
973
|
+
}));
|
|
974
|
+
});
|
|
975
|
+
it('should append linked relations and filter testCaseWorkItemField when isTestReporter=true and isQueryMode=false', async () => {
|
|
976
|
+
tfs_1.TFSServices.getItemContent.mockReset();
|
|
977
|
+
const selectedFields = ['associatedBug@linked', 'System.Title@testCaseWorkItemField'];
|
|
978
|
+
// 1) run result
|
|
979
|
+
tfs_1.TFSServices.getItemContent
|
|
980
|
+
.mockResolvedValueOnce({
|
|
981
|
+
testCase: { id: 123 },
|
|
982
|
+
testCaseRevision: 7,
|
|
983
|
+
testSuite: { name: 'S' },
|
|
984
|
+
})
|
|
985
|
+
// 2) attachments
|
|
986
|
+
.mockResolvedValueOnce({ value: [] })
|
|
987
|
+
// 3) wiByRevision (with relations)
|
|
988
|
+
.mockResolvedValueOnce({
|
|
989
|
+
id: 123,
|
|
990
|
+
fields: {
|
|
991
|
+
'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
|
|
992
|
+
'System.Title': { displayName: 'My Title' },
|
|
993
|
+
},
|
|
994
|
+
relations: [{ rel: 'System.LinkTypes.Related', url: 'https://example.com/wi/200' }],
|
|
995
|
+
})
|
|
996
|
+
// 4) linked bug
|
|
997
|
+
.mockResolvedValueOnce({
|
|
998
|
+
id: 200,
|
|
999
|
+
fields: { 'System.WorkItemType': 'Bug', 'System.State': 'Active', 'System.Title': 'B200' },
|
|
1000
|
+
_links: { html: { href: 'http://example.com/200' } },
|
|
1001
|
+
});
|
|
1002
|
+
const res = await resultDataProvider.fetchResultDataBasedOnWiBase(mockProjectName, '10', '20', true, selectedFields, false, undefined, false);
|
|
1003
|
+
expect(res).toEqual(expect.objectContaining({ testCaseRevision: 7 }));
|
|
1004
|
+
expect(res.filteredFields).toEqual({ 'System.Title': 'My Title' });
|
|
1005
|
+
expect(res.relatedBugs).toEqual(expect.arrayContaining([expect.objectContaining({ id: 200, title: 'B200', workItemType: 'Bug' })]));
|
|
1006
|
+
});
|
|
1007
|
+
});
|
|
1008
|
+
describe('alignStepsWithIterationsBase - additional branches', () => {
|
|
1009
|
+
it('should include not-run test cases when enabled and includeItemsWithNoIterations=true (creates test-level result)', () => {
|
|
1010
|
+
const testData = [
|
|
1011
|
+
{
|
|
1012
|
+
testPointsItems: [{ testCaseId: 123, lastRunId: undefined, lastResultId: undefined }],
|
|
1013
|
+
testCasesItems: [
|
|
1014
|
+
{ workItem: { id: 123, workItemFields: [{ key: 'Steps', value: '<steps></steps>' }] } },
|
|
1015
|
+
],
|
|
1016
|
+
},
|
|
1017
|
+
];
|
|
1018
|
+
const iterations = [
|
|
1019
|
+
{ testCaseId: 123, lastRunId: undefined, lastResultId: undefined, iteration: null },
|
|
1020
|
+
];
|
|
1021
|
+
const createResultObject = jest.fn().mockReturnValue({ ok: true });
|
|
1022
|
+
const shouldProcessStepLevel = jest.fn().mockReturnValue(false);
|
|
1023
|
+
const res = resultDataProvider.alignStepsWithIterationsBase(testData, iterations, true, true, true, {
|
|
1024
|
+
selectedFields: [],
|
|
1025
|
+
createResultObject,
|
|
1026
|
+
shouldProcessStepLevel,
|
|
1027
|
+
});
|
|
1028
|
+
expect(res).toEqual([{ ok: true }]);
|
|
1029
|
+
expect(createResultObject).toHaveBeenCalled();
|
|
1030
|
+
});
|
|
1031
|
+
it('should skip items without iterations when includeItemsWithNoIterations=false', () => {
|
|
1032
|
+
const testData = [
|
|
1033
|
+
{
|
|
1034
|
+
testPointsItems: [{ testCaseId: 123, lastRunId: undefined, lastResultId: undefined }],
|
|
1035
|
+
testCasesItems: [
|
|
1036
|
+
{ workItem: { id: 123, workItemFields: [{ key: 'Steps', value: '<steps></steps>' }] } },
|
|
1037
|
+
],
|
|
1038
|
+
},
|
|
1039
|
+
];
|
|
1040
|
+
const iterations = [
|
|
1041
|
+
{ testCaseId: 123, lastRunId: undefined, lastResultId: undefined, iteration: null },
|
|
1042
|
+
];
|
|
1043
|
+
const res = resultDataProvider.alignStepsWithIterationsBase(testData, iterations, true, false, true, {
|
|
1044
|
+
selectedFields: [],
|
|
1045
|
+
createResultObject: jest.fn().mockReturnValue({ ok: true }),
|
|
1046
|
+
shouldProcessStepLevel: jest.fn().mockReturnValue(false),
|
|
1047
|
+
});
|
|
1048
|
+
expect(res).toEqual([]);
|
|
1049
|
+
});
|
|
1050
|
+
it('should fallback to test-level result when step-level processing enabled but actionResults is empty', () => {
|
|
1051
|
+
const testData = [
|
|
1052
|
+
{
|
|
1053
|
+
testPointsItems: [{ testCaseId: 123, lastRunId: '10', lastResultId: '20' }],
|
|
1054
|
+
testCasesItems: [
|
|
1055
|
+
{ workItem: { id: 123, workItemFields: [{ key: 'Steps', value: '<steps></steps>' }] } },
|
|
1056
|
+
],
|
|
1057
|
+
},
|
|
1058
|
+
];
|
|
1059
|
+
const iterations = [
|
|
1060
|
+
{
|
|
1061
|
+
testCaseId: 123,
|
|
1062
|
+
lastRunId: '10',
|
|
1063
|
+
lastResultId: '20',
|
|
1064
|
+
iteration: { actionResults: [] },
|
|
1065
|
+
},
|
|
1066
|
+
];
|
|
1067
|
+
const createResultObject = jest.fn().mockReturnValue({ mode: 'test-level' });
|
|
1068
|
+
const shouldProcessStepLevel = jest.fn().mockReturnValue(true);
|
|
1069
|
+
const res = resultDataProvider.alignStepsWithIterationsBase(testData, iterations, false, true, false, {
|
|
1070
|
+
selectedFields: ['includeSteps@stepsRunProperties'],
|
|
1071
|
+
createResultObject,
|
|
1072
|
+
shouldProcessStepLevel,
|
|
1073
|
+
});
|
|
1074
|
+
expect(res).toEqual([{ mode: 'test-level' }]);
|
|
1075
|
+
expect(createResultObject).toHaveBeenCalledTimes(1);
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
describe('getCombinedResultsSummary - optional outputs', () => {
|
|
1079
|
+
it('should include open PCRs, test log, appendix-a and appendix-b when enabled', async () => {
|
|
1080
|
+
jest
|
|
1081
|
+
.spyOn(resultDataProvider, 'fetchTestSuites')
|
|
1082
|
+
.mockResolvedValueOnce([{ testSuiteId: '1', testGroupName: 'Group 1' }]);
|
|
1083
|
+
jest.spyOn(resultDataProvider, 'fetchTestPoints').mockResolvedValueOnce([
|
|
1084
|
+
{
|
|
1085
|
+
testCaseId: 1,
|
|
1086
|
+
testCaseName: 'TC 1',
|
|
1087
|
+
testCaseUrl: 'http://example.com/1',
|
|
1088
|
+
configurationName: 'Cfg',
|
|
1089
|
+
outcome: 'passed',
|
|
1090
|
+
lastRunId: 10,
|
|
1091
|
+
lastResultId: 20,
|
|
1092
|
+
lastResultDetails: { dateCompleted: '2023-01-01T00:00:00.000Z', runBy: { displayName: 'User 1' } },
|
|
1093
|
+
},
|
|
1094
|
+
]);
|
|
1095
|
+
jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([]);
|
|
1096
|
+
// runResults for appendix-a filtering
|
|
1097
|
+
jest.spyOn(resultDataProvider, 'fetchAllResultData').mockResolvedValueOnce([
|
|
1098
|
+
{
|
|
1099
|
+
comment: 'has comment',
|
|
1100
|
+
iteration: { attachments: [] },
|
|
1101
|
+
analysisAttachments: [],
|
|
1102
|
+
lastRunId: 1,
|
|
1103
|
+
lastResultId: 2,
|
|
1104
|
+
},
|
|
1105
|
+
]);
|
|
1106
|
+
jest.spyOn(resultDataProvider, 'alignStepsWithIterations').mockReturnValueOnce([]);
|
|
1107
|
+
const openSpy = jest
|
|
1108
|
+
.spyOn(resultDataProvider, 'fetchOpenPcrData')
|
|
1109
|
+
.mockResolvedValueOnce(undefined);
|
|
1110
|
+
// Ensure appendix-b mapping runs but doesn't require attachments
|
|
1111
|
+
jest
|
|
1112
|
+
.spyOn(resultDataProvider, 'mapStepResultsForExecutionAppendix')
|
|
1113
|
+
.mockReturnValueOnce(new Map());
|
|
1114
|
+
const res = await resultDataProvider.getCombinedResultsSummary(mockTestPlanId, mockProjectName, undefined, false, false, { openPcrMode: 'linked', openPcrLinkedQuery: { wiql: { href: 'x' }, columns: [] } }, true, { isEnabled: true, generateAttachments: { isEnabled: true, runAttachmentMode: 'planOnly' } }, { isEnabled: true, generateRunAttachments: { isEnabled: false } }, false);
|
|
1115
|
+
expect(openSpy).toHaveBeenCalled();
|
|
1116
|
+
const hasTestLog = res.combinedResults.some((x) => x.contentControl === 'test-execution-content-control');
|
|
1117
|
+
expect(hasTestLog).toBe(true);
|
|
1118
|
+
const hasAppendixA = res.combinedResults.some((x) => x.contentControl === 'appendix-a-content-control');
|
|
1119
|
+
expect(hasAppendixA).toBe(true);
|
|
1120
|
+
const hasAppendixB = res.combinedResults.some((x) => x.contentControl === 'appendix-b-content-control');
|
|
1121
|
+
expect(hasAppendixB).toBe(true);
|
|
1122
|
+
});
|
|
1123
|
+
});
|
|
1124
|
+
describe('appendLinkedRelations', () => {
|
|
1125
|
+
it('should append requirement/bug/cr when enabled and skip closed items', async () => {
|
|
1126
|
+
const relations = [
|
|
1127
|
+
{ rel: 'System.LinkTypes.Related', url: 'https://example.com/wi/1' },
|
|
1128
|
+
{ rel: 'System.LinkTypes.Related', url: 'https://example.com/wi/2' },
|
|
1129
|
+
{ rel: 'System.LinkTypes.Related', url: 'https://example.com/wi/3' },
|
|
1130
|
+
{ rel: 'System.LinkTypes.Related', url: 'https://example.com/wi/4' },
|
|
1131
|
+
];
|
|
1132
|
+
const relatedRequirements = [];
|
|
1133
|
+
const relatedBugs = [];
|
|
1134
|
+
const relatedCRs = [];
|
|
1135
|
+
const selected = new Set(['associatedRequirement', 'associatedBug', 'associatedCR']);
|
|
1136
|
+
tfs_1.TFSServices.getItemContent
|
|
1137
|
+
.mockResolvedValueOnce({
|
|
1138
|
+
id: 1,
|
|
1139
|
+
fields: {
|
|
1140
|
+
'System.WorkItemType': 'Requirement',
|
|
1141
|
+
'System.State': 'Active',
|
|
1142
|
+
'System.Title': 'Req 1',
|
|
1143
|
+
'Custom.CustomerRequirementId': 'CUST-1',
|
|
1144
|
+
},
|
|
1145
|
+
_links: { html: { href: 'http://example.com/1' } },
|
|
1146
|
+
})
|
|
1147
|
+
.mockResolvedValueOnce({
|
|
1148
|
+
id: 2,
|
|
1149
|
+
fields: {
|
|
1150
|
+
'System.WorkItemType': 'Bug',
|
|
1151
|
+
'System.State': 'Active',
|
|
1152
|
+
'System.Title': 'Bug 2',
|
|
1153
|
+
},
|
|
1154
|
+
_links: { html: { href: 'http://example.com/2' } },
|
|
1155
|
+
})
|
|
1156
|
+
.mockResolvedValueOnce({
|
|
1157
|
+
id: 3,
|
|
1158
|
+
fields: {
|
|
1159
|
+
'System.WorkItemType': 'Change Request',
|
|
1160
|
+
'System.State': 'Active',
|
|
1161
|
+
'System.Title': 'CR 3',
|
|
1162
|
+
},
|
|
1163
|
+
_links: { html: { href: 'http://example.com/3' } },
|
|
1164
|
+
})
|
|
1165
|
+
.mockResolvedValueOnce({
|
|
1166
|
+
id: 4,
|
|
1167
|
+
fields: {
|
|
1168
|
+
'System.WorkItemType': 'Bug',
|
|
1169
|
+
'System.State': 'Closed',
|
|
1170
|
+
'System.Title': 'Closed bug',
|
|
1171
|
+
},
|
|
1172
|
+
_links: { html: { href: 'http://example.com/4' } },
|
|
1173
|
+
});
|
|
1174
|
+
await resultDataProvider.appendLinkedRelations(relations, relatedRequirements, relatedBugs, relatedCRs, { id: 123 }, selected);
|
|
1175
|
+
expect(relatedRequirements).toHaveLength(1);
|
|
1176
|
+
expect(relatedRequirements[0]).toEqual(expect.objectContaining({ id: 1, customerId: 'CUST-1', workItemType: 'Requirement' }));
|
|
1177
|
+
expect(relatedBugs).toHaveLength(1);
|
|
1178
|
+
expect(relatedCRs).toHaveLength(1);
|
|
1179
|
+
});
|
|
1180
|
+
it('should log an error when fetching a related item fails', async () => {
|
|
1181
|
+
const relations = [{ rel: 'System.LinkTypes.Related', url: 'https://example.com/wi/1' }];
|
|
1182
|
+
tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(new Error('network'));
|
|
1183
|
+
await resultDataProvider.appendLinkedRelations(relations, [], [], [], { id: 999 }, new Set(['associatedBug']));
|
|
1184
|
+
expect(logger_1.default.error).toHaveBeenCalledWith(expect.stringContaining('Could not append related work item to test case 999'));
|
|
1185
|
+
});
|
|
1186
|
+
});
|
|
1187
|
+
describe('appendQueryRelations', () => {
|
|
1188
|
+
it('should append query relations when map has items', () => {
|
|
1189
|
+
// Arrange
|
|
1190
|
+
const testCaseId = 1;
|
|
1191
|
+
const relatedRequirements = [];
|
|
1192
|
+
const relatedBugs = [];
|
|
1193
|
+
const relatedCRs = [];
|
|
1194
|
+
// Set up the map
|
|
1195
|
+
resultDataProvider.testToAssociatedItemMap = new Map([
|
|
1196
|
+
[
|
|
1197
|
+
1,
|
|
1198
|
+
[
|
|
1199
|
+
{
|
|
1200
|
+
id: 100,
|
|
1201
|
+
fields: { 'System.Title': 'Req 1', 'System.WorkItemType': 'Requirement' },
|
|
1202
|
+
_links: { html: { href: 'http://example.com/100' } },
|
|
1203
|
+
},
|
|
1204
|
+
],
|
|
1205
|
+
],
|
|
1206
|
+
]);
|
|
1207
|
+
resultDataProvider.querySelectedColumns = [];
|
|
1208
|
+
// Act
|
|
1209
|
+
resultDataProvider.appendQueryRelations(testCaseId, relatedRequirements, relatedBugs, relatedCRs);
|
|
1210
|
+
// Assert
|
|
1211
|
+
expect(relatedRequirements).toHaveLength(1);
|
|
1212
|
+
expect(relatedRequirements[0].id).toBe(100);
|
|
1213
|
+
});
|
|
1214
|
+
it('should map Requirement/Bug/Change Request into correct buckets', () => {
|
|
1215
|
+
const testCaseId = 1;
|
|
1216
|
+
const relatedRequirements = [];
|
|
1217
|
+
const relatedBugs = [];
|
|
1218
|
+
const relatedCRs = [];
|
|
1219
|
+
jest.spyOn(resultDataProvider, 'standardCustomField').mockReturnValue({});
|
|
1220
|
+
resultDataProvider.testToAssociatedItemMap = new Map([
|
|
1221
|
+
[
|
|
1222
|
+
1,
|
|
1223
|
+
[
|
|
1224
|
+
{
|
|
1225
|
+
id: 10,
|
|
1226
|
+
fields: { 'System.Title': 'R', 'System.WorkItemType': 'Requirement', X: '1' },
|
|
1227
|
+
_links: { html: { href: 'http://example.com/10' } },
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
id: 11,
|
|
1231
|
+
fields: { 'System.Title': 'B', 'System.WorkItemType': 'Bug', Y: '2' },
|
|
1232
|
+
_links: { html: { href: 'http://example.com/11' } },
|
|
1233
|
+
},
|
|
1234
|
+
{
|
|
1235
|
+
id: 12,
|
|
1236
|
+
fields: { 'System.Title': 'C', 'System.WorkItemType': 'Change Request', Z: '3' },
|
|
1237
|
+
_links: { html: { href: 'http://example.com/12' } },
|
|
1238
|
+
},
|
|
1239
|
+
],
|
|
1240
|
+
],
|
|
1241
|
+
]);
|
|
1242
|
+
resultDataProvider.querySelectedColumns = [];
|
|
1243
|
+
resultDataProvider.appendQueryRelations(testCaseId, relatedRequirements, relatedBugs, relatedCRs);
|
|
1244
|
+
expect(relatedRequirements).toHaveLength(1);
|
|
1245
|
+
expect(relatedBugs).toHaveLength(1);
|
|
1246
|
+
expect(relatedCRs).toHaveLength(1);
|
|
1247
|
+
});
|
|
1248
|
+
it('should handle empty map', () => {
|
|
1249
|
+
// Arrange
|
|
1250
|
+
const testCaseId = 1;
|
|
1251
|
+
const relatedRequirements = [];
|
|
1252
|
+
const relatedBugs = [];
|
|
1253
|
+
const relatedCRs = [];
|
|
1254
|
+
resultDataProvider.testToAssociatedItemMap = new Map();
|
|
1255
|
+
// Act
|
|
1256
|
+
resultDataProvider.appendQueryRelations(testCaseId, relatedRequirements, relatedBugs, relatedCRs);
|
|
1257
|
+
// Assert
|
|
1258
|
+
expect(relatedRequirements).toHaveLength(0);
|
|
1259
|
+
});
|
|
1260
|
+
});
|
|
1261
|
+
describe('convertUnspecifiedRunStatus', () => {
|
|
1262
|
+
it('should return empty string for null actionResult', () => {
|
|
1263
|
+
// Act
|
|
1264
|
+
const result = resultDataProvider.convertUnspecifiedRunStatus(null);
|
|
1265
|
+
// Assert
|
|
1266
|
+
expect(result).toBe('');
|
|
1267
|
+
});
|
|
1268
|
+
it('should return empty string for Unspecified shared step title', () => {
|
|
1269
|
+
// Arrange
|
|
1270
|
+
const actionResult = { outcome: 'Unspecified', isSharedStepTitle: true };
|
|
1271
|
+
// Act
|
|
1272
|
+
const result = resultDataProvider.convertUnspecifiedRunStatus(actionResult);
|
|
1273
|
+
// Assert
|
|
1274
|
+
expect(result).toBe('');
|
|
1275
|
+
});
|
|
1276
|
+
it('should return Not Run for Unspecified non-shared step', () => {
|
|
1277
|
+
// Arrange
|
|
1278
|
+
const actionResult = { outcome: 'Unspecified', isSharedStepTitle: false };
|
|
1279
|
+
// Act
|
|
1280
|
+
const result = resultDataProvider.convertUnspecifiedRunStatus(actionResult);
|
|
1281
|
+
// Assert
|
|
1282
|
+
expect(result).toBe('Not Run');
|
|
1283
|
+
});
|
|
1284
|
+
it('should return original outcome for non-Unspecified status', () => {
|
|
1285
|
+
// Arrange
|
|
1286
|
+
const actionResult = { outcome: 'Passed', isSharedStepTitle: false };
|
|
1287
|
+
// Act
|
|
1288
|
+
const result = resultDataProvider.convertUnspecifiedRunStatus(actionResult);
|
|
1289
|
+
// Assert
|
|
1290
|
+
expect(result).toBe('Passed');
|
|
1291
|
+
});
|
|
1292
|
+
});
|
|
1293
|
+
describe('fetchResultDataBasedOnWi', () => {
|
|
1294
|
+
it('should call fetchResultDataBasedOnWiBase', async () => {
|
|
1295
|
+
// Arrange
|
|
1296
|
+
const mockResult = { id: 1, outcome: 'passed' };
|
|
1297
|
+
tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResult);
|
|
1298
|
+
// Act - just verify it doesn't throw
|
|
1299
|
+
const spy = jest.spyOn(resultDataProvider, 'fetchResultDataBasedOnWiBase');
|
|
1300
|
+
try {
|
|
1301
|
+
await resultDataProvider.fetchResultDataBasedOnWi(mockProjectName, '100', '200');
|
|
1302
|
+
}
|
|
1303
|
+
catch (_a) {
|
|
1304
|
+
// Expected to fail due to missing mocks
|
|
1305
|
+
}
|
|
1306
|
+
// Assert
|
|
1307
|
+
expect(spy).toHaveBeenCalledWith(mockProjectName, '100', '200');
|
|
1308
|
+
});
|
|
1309
|
+
});
|
|
1310
|
+
describe('alignStepsWithIterationsBase', () => {
|
|
1311
|
+
it('should return empty array when no iterations', () => {
|
|
1312
|
+
// Arrange
|
|
1313
|
+
const testData = [];
|
|
1314
|
+
const iterations = [];
|
|
1315
|
+
const options = {
|
|
1316
|
+
createResultObject: jest.fn(),
|
|
1317
|
+
shouldProcessStepLevel: jest.fn(),
|
|
1318
|
+
};
|
|
1319
|
+
// Act
|
|
1320
|
+
const result = resultDataProvider.alignStepsWithIterationsBase(testData, iterations, false, false, false, options);
|
|
1321
|
+
// Assert
|
|
1322
|
+
expect(result).toEqual([]);
|
|
1323
|
+
});
|
|
1324
|
+
it('should return [null] when fetchedTestCase.iteration.actionResults is null (shouldProcessStepLevel=false)', () => {
|
|
1325
|
+
const testData = [
|
|
1326
|
+
{
|
|
1327
|
+
testGroupName: 'G',
|
|
1328
|
+
testPointsItems: [{ testCaseId: 1, testCaseName: 'TC', lastRunId: 10, lastResultId: 20 }],
|
|
1329
|
+
testCasesItems: [{ workItem: { id: 1, workItemFields: [{ key: 'Steps', value: '<steps />' }] } }],
|
|
1330
|
+
},
|
|
1331
|
+
];
|
|
1332
|
+
const iterations = [
|
|
1333
|
+
{
|
|
1334
|
+
testCaseId: 1,
|
|
1335
|
+
lastRunId: 10,
|
|
1336
|
+
lastResultId: 20,
|
|
1337
|
+
iteration: { actionResults: null },
|
|
1338
|
+
testCaseRevision: 1,
|
|
1339
|
+
},
|
|
1340
|
+
];
|
|
1341
|
+
const res = resultDataProvider.alignStepsWithIterations(testData, iterations);
|
|
1342
|
+
expect(res).toEqual([null]);
|
|
1343
|
+
});
|
|
1344
|
+
it('should include a null row when actionResults contains an undefined element', () => {
|
|
1345
|
+
const testData = [
|
|
1346
|
+
{
|
|
1347
|
+
testGroupName: 'G',
|
|
1348
|
+
testPointsItems: [{ testCaseId: 1, testCaseName: 'TC', lastRunId: 10, lastResultId: 20 }],
|
|
1349
|
+
testCasesItems: [{ workItem: { id: 1, workItemFields: [{ key: 'Steps', value: '<steps />' }] } }],
|
|
1350
|
+
},
|
|
1351
|
+
];
|
|
1352
|
+
const iterations = [
|
|
1353
|
+
{
|
|
1354
|
+
testCaseId: 1,
|
|
1355
|
+
lastRunId: 10,
|
|
1356
|
+
lastResultId: 20,
|
|
1357
|
+
iteration: { actionResults: [undefined] },
|
|
1358
|
+
testCaseRevision: 1,
|
|
1359
|
+
},
|
|
1360
|
+
];
|
|
1361
|
+
const res = resultDataProvider.alignStepsWithIterations(testData, iterations);
|
|
1362
|
+
expect(res).toEqual([null]);
|
|
1363
|
+
});
|
|
1364
|
+
});
|
|
1365
|
+
describe('standardCustomField', () => {
|
|
1366
|
+
it('should standardize custom fields with columns', () => {
|
|
1367
|
+
// Arrange
|
|
1368
|
+
const fields = { 'Custom.Field1': 'value1', 'Custom.Field2': 'value2' };
|
|
1369
|
+
const columns = [
|
|
1370
|
+
{ referenceName: 'Custom.Field1', name: 'Field 1' },
|
|
1371
|
+
{ referenceName: 'Custom.Field2', name: 'Field 2' },
|
|
1372
|
+
];
|
|
1373
|
+
// Act
|
|
1374
|
+
const result = resultDataProvider.standardCustomField(fields, columns);
|
|
1375
|
+
// Assert
|
|
1376
|
+
expect(result).toBeDefined();
|
|
1377
|
+
expect(result.field1).toBe('value1');
|
|
1378
|
+
});
|
|
1379
|
+
it('should handle uppercase field names', () => {
|
|
1380
|
+
// Arrange
|
|
1381
|
+
const fields = { 'Custom.ABC': 'value1' };
|
|
1382
|
+
const columns = [{ referenceName: 'Custom.ABC', name: 'ABC' }];
|
|
1383
|
+
// Act
|
|
1384
|
+
const result = resultDataProvider.standardCustomField(fields, columns);
|
|
1385
|
+
// Assert
|
|
1386
|
+
expect(result.abc).toBe('value1');
|
|
1387
|
+
});
|
|
1388
|
+
it('should skip standard fields', () => {
|
|
1389
|
+
// Arrange
|
|
1390
|
+
const fields = { 'System.Id': 1, 'Custom.Field1': 'value1' };
|
|
1391
|
+
const columns = [
|
|
1392
|
+
{ referenceName: 'System.Id', name: 'id' },
|
|
1393
|
+
{ referenceName: 'Custom.Field1', name: 'Field 1' },
|
|
1394
|
+
];
|
|
1395
|
+
// Act
|
|
1396
|
+
const result = resultDataProvider.standardCustomField(fields, columns);
|
|
1397
|
+
// Assert
|
|
1398
|
+
expect(result.id).toBeUndefined();
|
|
1399
|
+
expect(result.field1).toBe('value1');
|
|
1400
|
+
});
|
|
1401
|
+
it('should handle null/undefined field values', () => {
|
|
1402
|
+
// Arrange
|
|
1403
|
+
const fields = { 'Custom.Field1': null };
|
|
1404
|
+
const columns = [{ referenceName: 'Custom.Field1', name: 'Field 1' }];
|
|
1405
|
+
// Act
|
|
1406
|
+
const result = resultDataProvider.standardCustomField(fields, columns);
|
|
1407
|
+
// Assert
|
|
1408
|
+
expect(result.field1).toBeNull();
|
|
1409
|
+
});
|
|
1410
|
+
it('should handle fields without columns', () => {
|
|
1411
|
+
// Arrange
|
|
1412
|
+
const fields = { 'Custom.Field1': 'value1', 'System.Title': 'Title' };
|
|
1413
|
+
// Act
|
|
1414
|
+
const result = resultDataProvider.standardCustomField(fields);
|
|
1415
|
+
// Assert
|
|
1416
|
+
expect(result).toBeDefined();
|
|
1417
|
+
});
|
|
1418
|
+
it('should handle displayName property', () => {
|
|
1419
|
+
// Arrange
|
|
1420
|
+
const fields = { 'Custom.Field1': { displayName: 'Display Value' } };
|
|
1421
|
+
const columns = [{ referenceName: 'Custom.Field1', name: 'Field 1' }];
|
|
1422
|
+
// Act
|
|
1423
|
+
const result = resultDataProvider.standardCustomField(fields, columns);
|
|
1424
|
+
// Assert
|
|
1425
|
+
expect(result.field1).toBe('Display Value');
|
|
1426
|
+
});
|
|
1427
|
+
});
|
|
1428
|
+
describe('getTestOutcome', () => {
|
|
1429
|
+
it('should return outcome from last iteration', () => {
|
|
1430
|
+
// Arrange
|
|
1431
|
+
const resultData = {
|
|
1432
|
+
iterationDetails: [{ outcome: 'Failed' }, { outcome: 'Passed' }],
|
|
1433
|
+
outcome: 'Failed',
|
|
1434
|
+
};
|
|
1435
|
+
// Act
|
|
1436
|
+
const result = resultDataProvider.getTestOutcome(resultData);
|
|
1437
|
+
// Assert
|
|
1438
|
+
expect(result).toBe('Passed');
|
|
1439
|
+
});
|
|
1440
|
+
it('should return result outcome when no iteration details', () => {
|
|
1441
|
+
// Arrange
|
|
1442
|
+
const resultData = { outcome: 'Passed' };
|
|
1443
|
+
// Act
|
|
1444
|
+
const result = resultDataProvider.getTestOutcome(resultData);
|
|
1445
|
+
// Assert
|
|
1446
|
+
expect(result).toBe('Passed');
|
|
1447
|
+
});
|
|
1448
|
+
it('should return default outcome when no data', () => {
|
|
1449
|
+
// Arrange
|
|
1450
|
+
const resultData = {};
|
|
1451
|
+
// Act
|
|
1452
|
+
const result = resultDataProvider.getTestOutcome(resultData);
|
|
1453
|
+
// Assert
|
|
1454
|
+
expect(result).toBe('NotApplicable');
|
|
1455
|
+
});
|
|
1456
|
+
});
|
|
1457
|
+
describe('createIterationsMap', () => {
|
|
1458
|
+
it('should create iterations map from results', () => {
|
|
1459
|
+
// Arrange
|
|
1460
|
+
const iterations = [{ lastRunId: 100, lastResultId: 200, testCase: { id: 1 } }];
|
|
1461
|
+
// Act
|
|
1462
|
+
const result = resultDataProvider.createIterationsMap(iterations, false, false);
|
|
1463
|
+
// Assert
|
|
1464
|
+
expect(result).toBeDefined();
|
|
1465
|
+
});
|
|
1466
|
+
it('should create iterations map from results with iteration', () => {
|
|
1467
|
+
// Arrange
|
|
1468
|
+
const iterations = [{ lastRunId: 100, lastResultId: 200, testCaseId: 1, iteration: { id: 1 } }];
|
|
1469
|
+
// Act
|
|
1470
|
+
const result = resultDataProvider.createIterationsMap(iterations, false, false);
|
|
1471
|
+
// Assert
|
|
1472
|
+
expect(result).toBeDefined();
|
|
1473
|
+
expect(result['100-200-1']).toBeDefined();
|
|
1474
|
+
});
|
|
1475
|
+
it('should create iterations map for test reporter mode', () => {
|
|
1476
|
+
// Arrange
|
|
1477
|
+
const iterations = [{ lastRunId: 100, lastResultId: 200, testCaseId: 1 }];
|
|
1478
|
+
// Act
|
|
1479
|
+
const result = resultDataProvider.createIterationsMap(iterations, true, false);
|
|
1480
|
+
// Assert
|
|
1481
|
+
expect(result['100-200-1']).toBeDefined();
|
|
1482
|
+
});
|
|
1483
|
+
it('should include not run test cases when flag is set', () => {
|
|
1484
|
+
// Arrange
|
|
1485
|
+
const iterations = [{ testCaseId: 1 }];
|
|
1486
|
+
// Act
|
|
1487
|
+
const result = resultDataProvider.createIterationsMap(iterations, false, true);
|
|
1488
|
+
// Assert
|
|
1489
|
+
expect(result['1']).toBeDefined();
|
|
1490
|
+
});
|
|
1491
|
+
});
|
|
1492
|
+
describe('alignStepsWithIterationsBase', () => {
|
|
1493
|
+
it('should return empty array when no iterations', () => {
|
|
1494
|
+
// Arrange
|
|
1495
|
+
const testData = [];
|
|
1496
|
+
const iterations = [];
|
|
1497
|
+
const options = {
|
|
1498
|
+
createResultObject: jest.fn(),
|
|
1499
|
+
shouldProcessStepLevel: jest.fn(),
|
|
1500
|
+
};
|
|
1501
|
+
// Act
|
|
1502
|
+
const result = resultDataProvider.alignStepsWithIterationsBase(testData, iterations, false, false, false, options);
|
|
1503
|
+
// Assert
|
|
1504
|
+
expect(result).toEqual([]);
|
|
1505
|
+
});
|
|
1506
|
+
});
|
|
1507
|
+
describe('alignStepsWithIterations', () => {
|
|
1508
|
+
it('should return empty array when no iterations', () => {
|
|
1509
|
+
// Arrange
|
|
1510
|
+
const testData = [];
|
|
1511
|
+
const iterations = [];
|
|
1512
|
+
// Act
|
|
1513
|
+
const result = resultDataProvider.alignStepsWithIterations(testData, iterations);
|
|
1514
|
+
// Assert
|
|
1515
|
+
expect(result).toEqual([]);
|
|
1516
|
+
});
|
|
1517
|
+
});
|
|
1518
|
+
describe('fetchTestData', () => {
|
|
1519
|
+
it('should fetch test data for suites', async () => {
|
|
1520
|
+
// Arrange
|
|
1521
|
+
const suites = [{ testSuiteId: 1, testGroupName: 'Suite 1' }];
|
|
1522
|
+
const mockTestCases = { value: [{ workItem: { id: 1 } }] };
|
|
1523
|
+
const mockTestPoints = { value: [{ testCaseReference: { id: 1 } }], count: 1 };
|
|
1524
|
+
tfs_1.TFSServices.getItemContent
|
|
1525
|
+
.mockResolvedValueOnce(mockTestCases)
|
|
1526
|
+
.mockResolvedValueOnce(mockTestPoints);
|
|
1527
|
+
// Act
|
|
1528
|
+
const result = await resultDataProvider.fetchTestData(suites, mockProjectName, mockTestPlanId, false);
|
|
1529
|
+
// Assert
|
|
1530
|
+
expect(result).toHaveLength(1);
|
|
1531
|
+
expect(result[0].testCasesItems).toBeDefined();
|
|
1532
|
+
});
|
|
1533
|
+
it('should handle errors gracefully', async () => {
|
|
1534
|
+
// Arrange
|
|
1535
|
+
const suites = [{ testSuiteId: 1, testGroupName: 'Suite 1' }];
|
|
1536
|
+
tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(new Error('API Error'));
|
|
1537
|
+
// Act
|
|
1538
|
+
const result = await resultDataProvider.fetchTestData(suites, mockProjectName, mockTestPlanId, false);
|
|
1539
|
+
// Assert
|
|
1540
|
+
expect(result).toHaveLength(1);
|
|
1541
|
+
expect(logger_1.default.error).toHaveBeenCalled();
|
|
1542
|
+
});
|
|
1543
|
+
});
|
|
1544
|
+
describe('fetchAllResultData', () => {
|
|
1545
|
+
it('should return empty array when no test data', async () => {
|
|
1546
|
+
// Arrange
|
|
1547
|
+
const testData = [];
|
|
1548
|
+
// Act
|
|
1549
|
+
const result = await resultDataProvider.fetchAllResultData(testData, mockProjectName);
|
|
1550
|
+
// Assert
|
|
1551
|
+
expect(result).toEqual([]);
|
|
1552
|
+
});
|
|
1553
|
+
it('should fetch result data for test points', async () => {
|
|
1554
|
+
// Arrange
|
|
1555
|
+
const testData = [
|
|
1556
|
+
{
|
|
1557
|
+
testPointsItems: [{ testCaseId: 1, lastRunId: 100, lastResultId: 200 }],
|
|
1558
|
+
},
|
|
1559
|
+
];
|
|
1560
|
+
const mockResult = {
|
|
1561
|
+
testCase: { id: 1 },
|
|
1562
|
+
iteration: { actionResults: [] },
|
|
1563
|
+
};
|
|
1564
|
+
const mockAttachments = { value: [] };
|
|
1565
|
+
const mockWi = { fields: {} };
|
|
1566
|
+
tfs_1.TFSServices.getItemContent
|
|
1567
|
+
.mockResolvedValueOnce(mockResult)
|
|
1568
|
+
.mockResolvedValueOnce(mockAttachments)
|
|
1569
|
+
.mockResolvedValueOnce(mockWi);
|
|
1570
|
+
// Act
|
|
1571
|
+
const result = await resultDataProvider.fetchAllResultData(testData, mockProjectName);
|
|
1572
|
+
// Assert
|
|
1573
|
+
expect(result).toBeDefined();
|
|
1574
|
+
});
|
|
1575
|
+
it('should log response data and rethrow when an error with response is thrown', async () => {
|
|
1576
|
+
const err = new Error('boom');
|
|
1577
|
+
err.response = { data: { detail: 'bad' } };
|
|
1578
|
+
jest.spyOn(resultDataProvider, 'fetchTestSuites').mockRejectedValueOnce(err);
|
|
1579
|
+
await expect(resultDataProvider.getCombinedResultsSummary(mockTestPlanId, mockProjectName)).rejects.toThrow('boom');
|
|
1580
|
+
expect(logger_1.default.error).toHaveBeenCalledWith('Error during getCombinedResultsSummary: boom');
|
|
1581
|
+
expect(logger_1.default.error).toHaveBeenCalledWith('Response Data: {"detail":"bad"}');
|
|
1582
|
+
});
|
|
1583
|
+
});
|
|
1584
|
+
describe('fetchAllResultDataTestReporter', () => {
|
|
1585
|
+
it('should return empty array when no test data', async () => {
|
|
1586
|
+
// Arrange
|
|
1587
|
+
const testData = [];
|
|
1588
|
+
// Act
|
|
1589
|
+
const result = await resultDataProvider.fetchAllResultDataTestReporter(testData, mockProjectName, [], false);
|
|
1590
|
+
// Assert
|
|
1591
|
+
expect(result).toEqual([]);
|
|
1592
|
+
});
|
|
1593
|
+
});
|
|
1594
|
+
describe('alignStepsWithIterationsTestReporter', () => {
|
|
1595
|
+
it('should return empty array when no iterations', () => {
|
|
1596
|
+
// Arrange
|
|
1597
|
+
const testData = [];
|
|
1598
|
+
const iterations = [];
|
|
1599
|
+
// Act
|
|
1600
|
+
const result = resultDataProvider.alignStepsWithIterationsTestReporter(testData, iterations, [], false);
|
|
1601
|
+
// Assert
|
|
1602
|
+
expect(result).toEqual([]);
|
|
1603
|
+
});
|
|
1604
|
+
});
|
|
1605
|
+
describe('fetchAllResultDataBase', () => {
|
|
1606
|
+
it('should return empty array when no test data', async () => {
|
|
1607
|
+
// Arrange
|
|
1608
|
+
const testData = [];
|
|
1609
|
+
const fetchStrategy = jest.fn();
|
|
1610
|
+
// Act
|
|
1611
|
+
const result = await resultDataProvider.fetchAllResultDataBase(testData, mockProjectName, false, fetchStrategy);
|
|
1612
|
+
// Assert
|
|
1613
|
+
expect(result).toEqual([]);
|
|
1614
|
+
});
|
|
1615
|
+
it('should filter out points without run/result IDs when not test reporter', async () => {
|
|
1616
|
+
// Arrange
|
|
1617
|
+
const testData = [
|
|
1618
|
+
{
|
|
1619
|
+
testSuiteId: 1,
|
|
1620
|
+
testPointsItems: [
|
|
1621
|
+
{ testCaseId: 1 }, // No lastRunId/lastResultId
|
|
1622
|
+
],
|
|
1623
|
+
},
|
|
1624
|
+
];
|
|
1625
|
+
const fetchStrategy = jest.fn();
|
|
1626
|
+
// Act
|
|
1627
|
+
const result = await resultDataProvider.fetchAllResultDataBase(testData, mockProjectName, false, fetchStrategy);
|
|
1628
|
+
// Assert
|
|
1629
|
+
expect(fetchStrategy).not.toHaveBeenCalled();
|
|
1630
|
+
expect(result).toEqual([]);
|
|
1631
|
+
});
|
|
1632
|
+
});
|
|
1633
|
+
describe('fetchResultDataBase', () => {
|
|
1634
|
+
it('should fetch result data for a point', async () => {
|
|
1635
|
+
// Arrange
|
|
1636
|
+
const point = { lastRunId: 100, lastResultId: 200 };
|
|
1637
|
+
const mockResultData = {
|
|
1638
|
+
testCase: { id: 1 },
|
|
1639
|
+
iterationDetails: [],
|
|
1640
|
+
};
|
|
1641
|
+
const fetchResultMethod = jest.fn().mockResolvedValue(mockResultData);
|
|
1642
|
+
const createResponseObject = jest.fn().mockReturnValue({ id: 1 });
|
|
1643
|
+
// Act
|
|
1644
|
+
const result = await resultDataProvider.fetchResultDataBase(mockProjectName, '1', point, fetchResultMethod, createResponseObject);
|
|
1645
|
+
// Assert
|
|
1646
|
+
expect(fetchResultMethod).toHaveBeenCalled();
|
|
1647
|
+
expect(result).toBeDefined();
|
|
1648
|
+
});
|
|
1649
|
+
});
|
|
1650
|
+
describe('getCombinedResultsSummary', () => {
|
|
1651
|
+
it('should return combined results summary with expected content controls', async () => {
|
|
1652
|
+
const mockTestSuites = {
|
|
1653
|
+
value: [
|
|
1654
|
+
{
|
|
1655
|
+
id: 1,
|
|
1656
|
+
name: 'Root Suite',
|
|
1657
|
+
children: [{ id: 2, name: 'Child Suite 1', parentSuite: { id: 1 } }],
|
|
1658
|
+
},
|
|
1659
|
+
],
|
|
1660
|
+
count: 1,
|
|
1661
|
+
};
|
|
1662
|
+
const mockTestPoints = {
|
|
1663
|
+
value: [
|
|
1664
|
+
{
|
|
1665
|
+
testCaseReference: { id: 1, name: 'Test Case 1' },
|
|
1666
|
+
configuration: { name: 'Config 1' },
|
|
1667
|
+
results: {
|
|
1668
|
+
outcome: 'passed',
|
|
1669
|
+
lastTestRunId: 100,
|
|
1670
|
+
lastResultId: 200,
|
|
1671
|
+
lastResultDetails: { dateCompleted: '2023-01-01', runBy: { displayName: 'Test User' } },
|
|
1672
|
+
},
|
|
1673
|
+
},
|
|
1674
|
+
],
|
|
1675
|
+
count: 1,
|
|
1676
|
+
};
|
|
1677
|
+
const mockTestCases = {
|
|
1678
|
+
value: [
|
|
1679
|
+
{
|
|
1680
|
+
workItem: {
|
|
1681
|
+
id: 1,
|
|
1682
|
+
workItemFields: [{ key: 'Steps', value: '<steps>...</steps>' }],
|
|
1683
|
+
},
|
|
1684
|
+
},
|
|
1685
|
+
],
|
|
1686
|
+
};
|
|
1687
|
+
const mockResult = {
|
|
1688
|
+
testCase: { id: 1, name: 'Test Case 1' },
|
|
1689
|
+
testSuite: { id: 2, name: 'Child Suite 1' },
|
|
1690
|
+
iterationDetails: [
|
|
1691
|
+
{
|
|
1692
|
+
actionResults: [
|
|
1693
|
+
{ stepIdentifier: '1', outcome: 'Passed', errorMessage: '', actionPath: 'path1' },
|
|
1694
|
+
],
|
|
1695
|
+
attachments: [],
|
|
1696
|
+
},
|
|
1697
|
+
],
|
|
1698
|
+
testCaseRevision: 1,
|
|
1699
|
+
failureType: null,
|
|
1700
|
+
resolutionState: null,
|
|
1701
|
+
comment: null,
|
|
1702
|
+
};
|
|
1703
|
+
// Setup mocks for API calls
|
|
1704
|
+
tfs_1.TFSServices.getItemContent
|
|
1705
|
+
.mockResolvedValueOnce(mockTestSuites) // fetchTestSuites
|
|
1706
|
+
.mockResolvedValueOnce(mockTestPoints) // fetchTestPoints
|
|
1707
|
+
.mockResolvedValueOnce(mockTestCases) // fetchTestCasesBySuiteId
|
|
1708
|
+
.mockResolvedValueOnce(mockResult) // fetchResult
|
|
1709
|
+
.mockResolvedValueOnce({ value: [] }) // fetchResult - attachments
|
|
1710
|
+
.mockResolvedValueOnce({ fields: {} }); // fetchResult - wiByRevision
|
|
1711
|
+
const mockTestStepParserHelper = resultDataProvider.testStepParserHelper;
|
|
1712
|
+
mockTestStepParserHelper.parseTestSteps.mockResolvedValueOnce([
|
|
1713
|
+
{
|
|
1714
|
+
stepId: 1,
|
|
1715
|
+
stepPosition: '1',
|
|
1716
|
+
action: 'Do something',
|
|
1717
|
+
expected: 'Something happens',
|
|
1718
|
+
isSharedStepTitle: false,
|
|
1719
|
+
},
|
|
1720
|
+
]);
|
|
1721
|
+
// Act
|
|
1722
|
+
const result = await resultDataProvider.getCombinedResultsSummary(mockTestPlanId, mockProjectName, undefined, true);
|
|
1723
|
+
// Assert
|
|
1724
|
+
expect(result.combinedResults.length).toBeGreaterThan(0);
|
|
1725
|
+
expect(result.combinedResults[0]).toHaveProperty('contentControl', 'test-group-summary-content-control');
|
|
1726
|
+
expect(result.combinedResults[1]).toHaveProperty('contentControl', 'test-result-summary-content-control');
|
|
1727
|
+
expect(result.combinedResults[2]).toHaveProperty('contentControl', 'detailed-test-result-content-control');
|
|
1728
|
+
});
|
|
1729
|
+
});
|
|
1730
|
+
describe('fetchResultDataBase - shared step mapping', () => {
|
|
1731
|
+
it('should map parsed steps into actionResults, filter missing stepPosition, and sort by stepPosition', async () => {
|
|
1732
|
+
const point = { testCaseId: 1, lastRunId: 10, lastResultId: 20 };
|
|
1733
|
+
const fetchResultMethod = jest.fn().mockResolvedValue({
|
|
1734
|
+
testCase: { id: 1, name: 'TC' },
|
|
1735
|
+
stepsResultXml: '<steps></steps>',
|
|
1736
|
+
iterationDetails: [
|
|
1737
|
+
{
|
|
1738
|
+
actionResults: [
|
|
1739
|
+
{ stepIdentifier: '2', actionPath: 'p2', sharedStepModel: { id: 5, revision: 7 } },
|
|
1740
|
+
{ stepIdentifier: '999', actionPath: 'px' },
|
|
1741
|
+
{ stepIdentifier: '1', actionPath: 'p1' },
|
|
1742
|
+
],
|
|
1743
|
+
},
|
|
1744
|
+
],
|
|
1745
|
+
});
|
|
1746
|
+
const createResponseObject = (resultData) => ({ iteration: resultData.iterationDetails[0] });
|
|
1747
|
+
const helper = resultDataProvider.testStepParserHelper;
|
|
1748
|
+
helper.parseTestSteps.mockImplementationOnce(async (_xml, map) => {
|
|
1749
|
+
// cover sharedStepIdToRevisionLookupMap population
|
|
1750
|
+
expect(map.get(5)).toBe(7);
|
|
1751
|
+
return [
|
|
1752
|
+
{ stepId: 1, stepPosition: '1', action: 'A1', expected: 'E1', isSharedStepTitle: false },
|
|
1753
|
+
{ stepId: 2, stepPosition: '2', action: 'A2', expected: 'E2', isSharedStepTitle: true },
|
|
1754
|
+
];
|
|
1755
|
+
});
|
|
1756
|
+
const res = await resultDataProvider.fetchResultDataBase(mockProjectName, 'suite1', point, fetchResultMethod, createResponseObject, []);
|
|
1757
|
+
const actionResults = res.iteration.actionResults;
|
|
1758
|
+
// 999 should be filtered out (no stepPosition)
|
|
1759
|
+
expect(actionResults).toHaveLength(2);
|
|
1760
|
+
// sorted by stepPosition numeric
|
|
1761
|
+
expect(actionResults[0]).toEqual(expect.objectContaining({ stepIdentifier: '1', action: 'A1' }));
|
|
1762
|
+
expect(actionResults[1]).toEqual(expect.objectContaining({ stepIdentifier: '2', action: 'A2', isSharedStepTitle: true }));
|
|
1763
|
+
});
|
|
1764
|
+
});
|
|
1765
|
+
describe('getCombinedResultsSummary - appendix branches', () => {
|
|
1766
|
+
it('should use mapAttachmentsUrl when stepAnalysis.generateRunAttachments is enabled and stepExecution.runAttachmentMode != planOnly', async () => {
|
|
1767
|
+
jest
|
|
1768
|
+
.spyOn(resultDataProvider, 'fetchTestSuites')
|
|
1769
|
+
.mockResolvedValueOnce([{ testSuiteId: '1', testGroupName: 'Group 1' }]);
|
|
1770
|
+
jest.spyOn(resultDataProvider, 'fetchTestPoints').mockResolvedValueOnce([
|
|
1771
|
+
{
|
|
1772
|
+
testCaseId: 1,
|
|
1773
|
+
testCaseName: 'TC 1',
|
|
1774
|
+
testCaseUrl: 'http://example.com/1',
|
|
1775
|
+
configurationName: 'Cfg',
|
|
1776
|
+
outcome: 'passed',
|
|
1777
|
+
lastRunId: 10,
|
|
1778
|
+
lastResultId: 20,
|
|
1779
|
+
lastResultDetails: { dateCompleted: '2023-01-01T00:00:00.000Z', runBy: { displayName: 'User 1' } },
|
|
1780
|
+
},
|
|
1781
|
+
]);
|
|
1782
|
+
jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([]);
|
|
1783
|
+
// ensure all predicates in stepAnalysis/filter can be true (comment false, attachments true, analysisAttachments true)
|
|
1784
|
+
const runResults = [
|
|
1785
|
+
{
|
|
1786
|
+
comment: '',
|
|
1787
|
+
iteration: { attachments: [{ actionPath: 'p', name: 'n', downloadUrl: 'd' }] },
|
|
1788
|
+
analysisAttachments: [{ id: 1 }],
|
|
1789
|
+
},
|
|
1790
|
+
];
|
|
1791
|
+
jest.spyOn(resultDataProvider, 'fetchAllResultData').mockResolvedValueOnce(runResults);
|
|
1792
|
+
jest.spyOn(resultDataProvider, 'alignStepsWithIterations').mockReturnValueOnce([]);
|
|
1793
|
+
const mapSpy = jest
|
|
1794
|
+
.spyOn(resultDataProvider, 'mapAttachmentsUrl')
|
|
1795
|
+
.mockReturnValueOnce(runResults)
|
|
1796
|
+
.mockReturnValueOnce(runResults);
|
|
1797
|
+
jest
|
|
1798
|
+
.spyOn(resultDataProvider, 'mapStepResultsForExecutionAppendix')
|
|
1799
|
+
.mockReturnValueOnce(new Map());
|
|
1800
|
+
const res = await resultDataProvider.getCombinedResultsSummary(mockTestPlanId, mockProjectName, undefined, false, false, null, false, { isEnabled: true, generateAttachments: { isEnabled: true, runAttachmentMode: 'runOnly' } }, { isEnabled: true, generateRunAttachments: { isEnabled: true } }, false);
|
|
1801
|
+
expect(mapSpy).toHaveBeenCalled();
|
|
1802
|
+
expect(res.combinedResults.some((x) => x.contentControl === 'appendix-a-content-control')).toBe(true);
|
|
1803
|
+
expect(res.combinedResults.some((x) => x.contentControl === 'appendix-b-content-control')).toBe(true);
|
|
1804
|
+
});
|
|
1805
|
+
});
|
|
1806
|
+
describe('alignStepsWithIterationsTestReporter - step-level rows', () => {
|
|
1807
|
+
it('should emit step-level fields when includeSteps/stepRunStatus/testStepComment are selected', () => {
|
|
1808
|
+
const testData = [
|
|
1809
|
+
{
|
|
1810
|
+
testGroupName: 'G',
|
|
1811
|
+
testPointsItems: [
|
|
1812
|
+
{
|
|
1813
|
+
testCaseId: 123,
|
|
1814
|
+
testCaseName: 'TC',
|
|
1815
|
+
testCaseUrl: 'u',
|
|
1816
|
+
lastRunId: 10,
|
|
1817
|
+
lastResultId: 20,
|
|
1818
|
+
},
|
|
1819
|
+
],
|
|
1820
|
+
testCasesItems: [
|
|
1821
|
+
{ workItem: { id: 123, workItemFields: [{ key: 'Steps', value: '<steps></steps>' }] } },
|
|
1822
|
+
],
|
|
1823
|
+
},
|
|
1824
|
+
];
|
|
1825
|
+
const iterations = [
|
|
1826
|
+
{
|
|
1827
|
+
testCaseId: 123,
|
|
1828
|
+
lastRunId: 10,
|
|
1829
|
+
lastResultId: 20,
|
|
1830
|
+
iteration: {
|
|
1831
|
+
actionResults: [
|
|
1832
|
+
{
|
|
1833
|
+
stepIdentifier: '1',
|
|
1834
|
+
stepPosition: '1',
|
|
1835
|
+
action: 'A',
|
|
1836
|
+
expected: 'E',
|
|
1837
|
+
outcome: 'Unspecified',
|
|
1838
|
+
isSharedStepTitle: false,
|
|
1839
|
+
errorMessage: 'err',
|
|
1840
|
+
},
|
|
1841
|
+
],
|
|
1842
|
+
},
|
|
1843
|
+
testCaseResult: 'Failed',
|
|
1844
|
+
comment: 'c',
|
|
1845
|
+
runBy: { displayName: 'u' },
|
|
1846
|
+
failureType: 'ft',
|
|
1847
|
+
executionDate: 'd',
|
|
1848
|
+
configurationName: 'cfg',
|
|
1849
|
+
relatedRequirements: [],
|
|
1850
|
+
relatedBugs: [],
|
|
1851
|
+
relatedCRs: [],
|
|
1852
|
+
customFields: {},
|
|
1853
|
+
},
|
|
1854
|
+
];
|
|
1855
|
+
const res = resultDataProvider.alignStepsWithIterationsTestReporter(testData, iterations, [
|
|
1856
|
+
'includeSteps@stepsRunProperties',
|
|
1857
|
+
'stepRunStatus@stepsRunProperties',
|
|
1858
|
+
'testStepComment@stepsRunProperties',
|
|
1859
|
+
], true);
|
|
1860
|
+
expect(res).toHaveLength(1);
|
|
1861
|
+
expect(res[0]).toEqual(expect.objectContaining({
|
|
1862
|
+
stepNo: '1',
|
|
1863
|
+
stepAction: 'A',
|
|
1864
|
+
stepExpected: 'E',
|
|
1865
|
+
stepStatus: 'Not Run',
|
|
1866
|
+
stepComments: 'err',
|
|
1867
|
+
}));
|
|
1868
|
+
});
|
|
1869
|
+
it('should omit step fields when no @stepsRunProperties are selected', () => {
|
|
1870
|
+
const testData = [
|
|
1871
|
+
{
|
|
1872
|
+
testGroupName: 'G',
|
|
1873
|
+
testPointsItems: [
|
|
1874
|
+
{ testCaseId: 123, testCaseName: 'TC', testCaseUrl: 'u', lastRunId: 10, lastResultId: 20 },
|
|
1875
|
+
],
|
|
1876
|
+
testCasesItems: [
|
|
1877
|
+
{ workItem: { id: 123, workItemFields: [{ key: 'Steps', value: '<steps></steps>' }] } },
|
|
1878
|
+
],
|
|
1879
|
+
},
|
|
1880
|
+
];
|
|
1881
|
+
const iterations = [
|
|
1882
|
+
{
|
|
1883
|
+
testCaseId: 123,
|
|
1884
|
+
lastRunId: 10,
|
|
1885
|
+
lastResultId: 20,
|
|
1886
|
+
iteration: {
|
|
1887
|
+
actionResults: [{ stepIdentifier: '1', stepPosition: '1', action: 'A', expected: 'E' }],
|
|
1888
|
+
},
|
|
1889
|
+
testCaseResult: 'Passed',
|
|
1890
|
+
comment: '',
|
|
1891
|
+
runBy: { displayName: 'u' },
|
|
1892
|
+
failureType: '',
|
|
1893
|
+
executionDate: 'd',
|
|
1894
|
+
configurationName: 'cfg',
|
|
1895
|
+
relatedRequirements: [],
|
|
1896
|
+
relatedBugs: [],
|
|
1897
|
+
relatedCRs: [],
|
|
1898
|
+
customFields: {},
|
|
1899
|
+
},
|
|
1900
|
+
];
|
|
1901
|
+
const res = resultDataProvider.alignStepsWithIterationsTestReporter(testData, iterations, [], true);
|
|
1902
|
+
expect(res).toHaveLength(1);
|
|
1903
|
+
expect(res[0].stepNo).toBeUndefined();
|
|
1904
|
+
});
|
|
1905
|
+
});
|
|
1906
|
+
describe('fetchOpenPcrData', () => {
|
|
1907
|
+
it('should populate both trace maps using linked work items', async () => {
|
|
1908
|
+
const testItems = [
|
|
1909
|
+
{
|
|
1910
|
+
testId: 1,
|
|
1911
|
+
testName: 'T1',
|
|
1912
|
+
testCaseUrl: 'u1',
|
|
1913
|
+
runStatus: 'Passed',
|
|
1914
|
+
},
|
|
1915
|
+
];
|
|
1916
|
+
const linked = [
|
|
1917
|
+
{
|
|
1918
|
+
testId: 1,
|
|
1919
|
+
testName: 'T1',
|
|
1920
|
+
testCaseUrl: 'u1',
|
|
1921
|
+
runStatus: 'Passed',
|
|
1922
|
+
linkItems: [
|
|
1923
|
+
{
|
|
1924
|
+
pcrId: 10,
|
|
1925
|
+
workItemType: 'Bug',
|
|
1926
|
+
title: 'B10',
|
|
1927
|
+
severity: '2',
|
|
1928
|
+
pcrUrl: 'p10',
|
|
1929
|
+
},
|
|
1930
|
+
],
|
|
1931
|
+
},
|
|
1932
|
+
];
|
|
1933
|
+
jest.spyOn(resultDataProvider, 'fetchLinkedWi').mockResolvedValueOnce(linked);
|
|
1934
|
+
const openPcrToTestCaseTraceMap = new Map();
|
|
1935
|
+
const testCaseToOpenPcrTraceMap = new Map();
|
|
1936
|
+
await resultDataProvider.fetchOpenPcrData(testItems, mockProjectName, openPcrToTestCaseTraceMap, testCaseToOpenPcrTraceMap);
|
|
1937
|
+
expect(openPcrToTestCaseTraceMap.size).toBe(1);
|
|
1938
|
+
expect(testCaseToOpenPcrTraceMap.size).toBe(1);
|
|
1939
|
+
});
|
|
1940
|
+
});
|
|
1941
|
+
});
|
|
1942
|
+
//# sourceMappingURL=ResultDataProvider.test.js.map
|