@elisra-devops/docgen-data-provider 1.63.13 → 1.68.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.
Files changed (92) hide show
  1. package/.github/workflows/ci.yml +26 -9
  2. package/.github/workflows/release.yml +9 -10
  3. package/README.md +50 -24
  4. package/bin/helpers/tfs.d.ts +3 -0
  5. package/bin/helpers/tfs.js +44 -7
  6. package/bin/helpers/tfs.js.map +1 -1
  7. package/bin/modules/GitDataProvider.d.ts +10 -0
  8. package/bin/modules/GitDataProvider.js +10 -0
  9. package/bin/modules/GitDataProvider.js.map +1 -1
  10. package/bin/modules/TestDataProvider.js +0 -1
  11. package/bin/modules/TestDataProvider.js.map +1 -1
  12. package/bin/modules/TicketsDataProvider.d.ts +63 -24
  13. package/bin/modules/TicketsDataProvider.js +216 -114
  14. package/bin/modules/TicketsDataProvider.js.map +1 -1
  15. package/bin/tests/helpers/helper.test.js +279 -0
  16. package/bin/tests/helpers/helper.test.js.map +1 -0
  17. package/bin/{helpers/test → tests/helpers}/tfs.test.js +312 -49
  18. package/bin/tests/helpers/tfs.test.js.map +1 -0
  19. package/bin/tests/index.test.js +25 -0
  20. package/bin/tests/index.test.js.map +1 -0
  21. package/bin/tests/models/tfs-data.test.js +160 -0
  22. package/bin/tests/models/tfs-data.test.js.map +1 -0
  23. package/bin/{modules/test → tests/modules}/JfrogDataProvider.test.js +9 -9
  24. package/bin/tests/modules/JfrogDataProvider.test.js.map +1 -0
  25. package/bin/tests/modules/ResultDataProvider.test.js +1942 -0
  26. package/bin/tests/modules/ResultDataProvider.test.js.map +1 -0
  27. package/bin/tests/modules/gitDataProvider.test.js +1888 -0
  28. package/bin/tests/modules/gitDataProvider.test.js.map +1 -0
  29. package/bin/{modules/test → tests/modules}/managmentDataProvider.test.js +13 -1
  30. package/bin/tests/modules/managmentDataProvider.test.js.map +1 -0
  31. package/bin/tests/modules/pipelineDataProvider.test.d.ts +1 -0
  32. package/bin/tests/modules/pipelineDataProvider.test.js +783 -0
  33. package/bin/tests/modules/pipelineDataProvider.test.js.map +1 -0
  34. package/bin/tests/modules/testDataProvider.test.d.ts +1 -0
  35. package/bin/tests/modules/testDataProvider.test.js +717 -0
  36. package/bin/tests/modules/testDataProvider.test.js.map +1 -0
  37. package/bin/tests/modules/ticketsDataProvider.test.d.ts +1 -0
  38. package/bin/tests/modules/ticketsDataProvider.test.js +1681 -0
  39. package/bin/tests/modules/ticketsDataProvider.test.js.map +1 -0
  40. package/bin/tests/utils/DataProviderUtils.test.d.ts +1 -0
  41. package/bin/tests/utils/DataProviderUtils.test.js +61 -0
  42. package/bin/tests/utils/DataProviderUtils.test.js.map +1 -0
  43. package/bin/tests/utils/testStepParserHelper.test.d.ts +1 -0
  44. package/bin/tests/utils/testStepParserHelper.test.js +359 -0
  45. package/bin/tests/utils/testStepParserHelper.test.js.map +1 -0
  46. package/package.json +9 -1
  47. package/src/helpers/tfs.ts +51 -7
  48. package/src/modules/GitDataProvider.ts +10 -0
  49. package/src/modules/TestDataProvider.ts +0 -1
  50. package/src/modules/TicketsDataProvider.ts +298 -141
  51. package/src/tests/helpers/helper.test.ts +337 -0
  52. package/src/tests/helpers/tfs.test.ts +1092 -0
  53. package/src/tests/index.test.ts +28 -0
  54. package/src/tests/models/tfs-data.test.ts +203 -0
  55. package/src/tests/modules/JfrogDataProvider.test.ts +167 -0
  56. package/src/tests/modules/ResultDataProvider.test.ts +2571 -0
  57. package/src/tests/modules/gitDataProvider.test.ts +2628 -0
  58. package/src/{modules/test → tests/modules}/managmentDataProvider.test.ts +33 -1
  59. package/src/tests/modules/pipelineDataProvider.test.ts +1038 -0
  60. package/src/tests/modules/testDataProvider.test.ts +1046 -0
  61. package/src/tests/modules/ticketsDataProvider.test.ts +2204 -0
  62. package/src/tests/utils/DataProviderUtils.test.ts +76 -0
  63. package/src/tests/utils/testStepParserHelper.test.ts +437 -0
  64. package/tsconfig.json +1 -0
  65. package/bin/helpers/test/tfs.test.js.map +0 -1
  66. package/bin/modules/test/JfrogDataProvider.test.js.map +0 -1
  67. package/bin/modules/test/ResultDataProvider.test.js +0 -444
  68. package/bin/modules/test/ResultDataProvider.test.js.map +0 -1
  69. package/bin/modules/test/gitDataProvider.test.js +0 -428
  70. package/bin/modules/test/gitDataProvider.test.js.map +0 -1
  71. package/bin/modules/test/managmentDataProvider.test.js.map +0 -1
  72. package/bin/modules/test/pipelineDataProvider.test.js +0 -237
  73. package/bin/modules/test/pipelineDataProvider.test.js.map +0 -1
  74. package/bin/modules/test/testDataProvider.test.js +0 -234
  75. package/bin/modules/test/testDataProvider.test.js.map +0 -1
  76. package/bin/modules/test/ticketsDataProvider.test.js +0 -348
  77. package/bin/modules/test/ticketsDataProvider.test.js.map +0 -1
  78. package/src/helpers/test/tfs.test.ts +0 -748
  79. package/src/modules/test/JfrogDataProvider.test.ts +0 -171
  80. package/src/modules/test/ResultDataProvider.test.ts +0 -542
  81. package/src/modules/test/gitDataProvider.test.ts +0 -645
  82. package/src/modules/test/pipelineDataProvider.test.ts +0 -292
  83. package/src/modules/test/testDataProvider.test.ts +0 -318
  84. package/src/modules/test/ticketsDataProvider.test.ts +0 -462
  85. /package/bin/{helpers/test/tfs.test.d.ts → tests/helpers/helper.test.d.ts} +0 -0
  86. /package/bin/{modules/test/JfrogDataProvider.test.d.ts → tests/helpers/tfs.test.d.ts} +0 -0
  87. /package/bin/{modules/test/ResultDataProvider.test.d.ts → tests/index.test.d.ts} +0 -0
  88. /package/bin/{modules/test/gitDataProvider.test.d.ts → tests/models/tfs-data.test.d.ts} +0 -0
  89. /package/bin/{modules/test/managmentDataProvider.test.d.ts → tests/modules/JfrogDataProvider.test.d.ts} +0 -0
  90. /package/bin/{modules/test/pipelineDataProvider.test.d.ts → tests/modules/ResultDataProvider.test.d.ts} +0 -0
  91. /package/bin/{modules/test/testDataProvider.test.d.ts → tests/modules/gitDataProvider.test.d.ts} +0 -0
  92. /package/bin/{modules/test/ticketsDataProvider.test.d.ts → tests/modules/managmentDataProvider.test.d.ts} +0 -0
@@ -0,0 +1,717 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tfs_1 = require("../../helpers/tfs");
4
+ const helper_1 = require("../../helpers/helper");
5
+ const TestDataProvider_1 = require("../../modules/TestDataProvider");
6
+ const logger_1 = require("../../utils/logger");
7
+ jest.mock('../../helpers/tfs');
8
+ jest.mock('../../utils/logger');
9
+ jest.mock('../../helpers/helper');
10
+ jest.mock('../../utils/testStepParserHelper', () => {
11
+ return {
12
+ __esModule: true,
13
+ default: jest.fn().mockImplementation(() => ({
14
+ parseTestSteps: jest.fn(),
15
+ })),
16
+ };
17
+ });
18
+ jest.mock('p-limit', () => jest.fn(() => (fn) => fn()));
19
+ describe('TestDataProvider', () => {
20
+ let testDataProvider;
21
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
22
+ const mockToken = 'mock-token';
23
+ const mockProject = 'project-123';
24
+ const mockPlanId = '456';
25
+ const mockSuiteId = '789';
26
+ const mockTestCaseId = '101112';
27
+ beforeEach(() => {
28
+ jest.clearAllMocks();
29
+ testDataProvider = new TestDataProvider_1.default(mockOrgUrl, mockToken);
30
+ });
31
+ // Helper to access private method for testing
32
+ const invokeFetchWithCache = async (instance, url, ttl = 60000) => {
33
+ return instance.fetchWithCache.call(instance, url, ttl);
34
+ };
35
+ describe('fetchWithCache', () => {
36
+ it('should return cached data when available and not expired', async () => {
37
+ // Arrange
38
+ const mockUrl = `${mockOrgUrl}_apis/test/endpoint`;
39
+ const mockData = { value: 'test data' };
40
+ const cache = new Map();
41
+ cache.set(mockUrl, {
42
+ data: mockData,
43
+ timestamp: Date.now(),
44
+ });
45
+ testDataProvider.cache = cache;
46
+ // Act
47
+ const result = await invokeFetchWithCache(testDataProvider, mockUrl);
48
+ // Assert
49
+ expect(result).toEqual(mockData);
50
+ expect(tfs_1.TFSServices.getItemContent).not.toHaveBeenCalled();
51
+ });
52
+ it('should fetch new data when cache is expired', async () => {
53
+ // Arrange
54
+ const mockUrl = `${mockOrgUrl}_apis/test/endpoint`;
55
+ const mockData = { value: 'old data' };
56
+ const newData = { value: 'new data' };
57
+ const cache = new Map();
58
+ cache.set(mockUrl, {
59
+ data: mockData,
60
+ timestamp: Date.now() - 70000, // Expired (default TTL is 60000ms)
61
+ });
62
+ testDataProvider.cache = cache;
63
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(newData);
64
+ // Act
65
+ const result = await invokeFetchWithCache(testDataProvider, mockUrl);
66
+ // Assert
67
+ expect(result).toEqual(newData);
68
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(mockUrl, mockToken);
69
+ });
70
+ it('should fetch and cache new data when not in cache', async () => {
71
+ // Arrange
72
+ const mockUrl = `${mockOrgUrl}_apis/test/endpoint`;
73
+ const mockData = { value: 'new data' };
74
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockData);
75
+ // Act
76
+ const result = await invokeFetchWithCache(testDataProvider, mockUrl);
77
+ // Assert
78
+ expect(result).toEqual(mockData);
79
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(mockUrl, mockToken);
80
+ expect(testDataProvider.cache.has(mockUrl)).toBeTruthy();
81
+ expect(testDataProvider.cache.get(mockUrl).data).toEqual(mockData);
82
+ });
83
+ it('should throw and log error when fetch fails', async () => {
84
+ // Arrange
85
+ const mockUrl = `${mockOrgUrl}_apis/test/endpoint`;
86
+ const mockError = new Error('API call failed');
87
+ tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(mockError);
88
+ // Act & Assert
89
+ await expect(invokeFetchWithCache(testDataProvider, mockUrl)).rejects.toThrow('API call failed');
90
+ expect(logger_1.default.error).toHaveBeenCalledWith(`Error fetching ${mockUrl}: API call failed`);
91
+ });
92
+ });
93
+ describe('GetTestSuiteByTestCase', () => {
94
+ it('should return test suites for a given test case ID', async () => {
95
+ // Arrange
96
+ const mockData = { value: [{ id: '123', name: 'Test Suite 1' }] };
97
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockData);
98
+ // Act
99
+ const result = await testDataProvider.GetTestSuiteByTestCase(mockTestCaseId);
100
+ // Assert
101
+ expect(result).toEqual(mockData);
102
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}_apis/testplan/suites?testCaseId=${mockTestCaseId}`, mockToken);
103
+ });
104
+ });
105
+ describe('GetTestPlans', () => {
106
+ it('should return test plans for a given project', async () => {
107
+ // Arrange
108
+ const mockData = {
109
+ value: [
110
+ { id: '456', name: 'Test Plan 1' },
111
+ { id: '789', name: 'Test Plan 2' },
112
+ ],
113
+ };
114
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockData);
115
+ // Act
116
+ const result = await testDataProvider.GetTestPlans(mockProject);
117
+ // Assert
118
+ expect(result).toEqual(mockData);
119
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}${mockProject}/_apis/test/plans`, mockToken);
120
+ });
121
+ });
122
+ describe('GetTestSuites', () => {
123
+ it('should return test suites for a given project and plan ID', async () => {
124
+ // Arrange
125
+ const mockData = {
126
+ value: [
127
+ { id: '123', name: 'Test Suite 1' },
128
+ { id: '456', name: 'Test Suite 2' },
129
+ ],
130
+ };
131
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockData);
132
+ // Act
133
+ const result = await testDataProvider.GetTestSuites(mockProject, mockPlanId);
134
+ // Assert
135
+ expect(result).toEqual(mockData);
136
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}${mockProject}/_apis/test/Plans/${mockPlanId}/suites`, mockToken);
137
+ });
138
+ it('should return null and log error if fetching test suites fails', async () => {
139
+ // Arrange
140
+ const mockError = new Error('Failed to get test suites');
141
+ tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(mockError);
142
+ // Act
143
+ const result = await testDataProvider.GetTestSuites(mockProject, mockPlanId);
144
+ // Assert
145
+ expect(result).toBeNull();
146
+ });
147
+ });
148
+ describe('GetTestSuitesForPlan', () => {
149
+ it('should throw error when project is not provided', async () => {
150
+ // Act & Assert
151
+ await expect(testDataProvider.GetTestSuitesForPlan('', mockPlanId)).rejects.toThrow('Project not selected');
152
+ });
153
+ it('should throw error when plan ID is not provided', async () => {
154
+ // Act & Assert
155
+ await expect(testDataProvider.GetTestSuitesForPlan(mockProject, '')).rejects.toThrow('Plan not selected');
156
+ });
157
+ it('should return test suites for a plan', async () => {
158
+ // Arrange
159
+ const mockData = {
160
+ testSuites: [
161
+ { id: '123', name: 'Test Suite 1' },
162
+ { id: '456', name: 'Test Suite 2' },
163
+ ],
164
+ };
165
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockData);
166
+ // Act
167
+ const result = await testDataProvider.GetTestSuitesForPlan(mockProject, mockPlanId);
168
+ // Assert
169
+ expect(result).toEqual(mockData);
170
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}/${mockProject}/_api/_testManagement/GetTestSuitesForPlan?__v=5&planId=${mockPlanId}`, mockToken);
171
+ });
172
+ });
173
+ describe('GetTestSuiteById', () => {
174
+ it('should call GetTestSuitesForPlan and Helper.findSuitesRecursive with correct params', async () => {
175
+ // Arrange
176
+ const mockTestSuites = { testSuites: [{ id: '123', name: 'Test Suite 1' }] };
177
+ const mockSuiteData = [new helper_1.suiteData('Test Suite 1', '123', '456', 1)];
178
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockTestSuites);
179
+ helper_1.Helper.findSuitesRecursive.mockReturnValueOnce(mockSuiteData);
180
+ // Act
181
+ const result = await testDataProvider.GetTestSuiteById(mockProject, mockPlanId, mockSuiteId, true);
182
+ // Assert
183
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}/${mockProject}/_api/_testManagement/GetTestSuitesForPlan?__v=5&planId=${mockPlanId}`, mockToken);
184
+ expect(helper_1.Helper.findSuitesRecursive).toHaveBeenCalledWith(mockPlanId, mockOrgUrl, mockProject, mockTestSuites.testSuites, mockSuiteId, true);
185
+ expect(result).toEqual(mockSuiteData);
186
+ });
187
+ });
188
+ describe('GetTestCases', () => {
189
+ it('should return test cases for a given project, plan ID, and suite ID', async () => {
190
+ // Arrange
191
+ const mockData = {
192
+ count: 2,
193
+ value: [
194
+ { testCase: { id: '101', name: 'Test Case 1', url: 'url1' } },
195
+ { testCase: { id: '102', name: 'Test Case 2', url: 'url2' } },
196
+ ],
197
+ };
198
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockData);
199
+ // Act
200
+ const result = await testDataProvider.GetTestCases(mockProject, mockPlanId, mockSuiteId);
201
+ // Assert
202
+ expect(result).toEqual(mockData);
203
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}${mockProject}/_apis/test/Plans/${mockPlanId}/suites/${mockSuiteId}/testcases/`, mockToken);
204
+ expect(logger_1.default.debug).toHaveBeenCalledWith(`test cases for plan ${mockPlanId} and ${mockSuiteId} were found`);
205
+ });
206
+ });
207
+ describe('clearCache', () => {
208
+ it('should clear the cache', () => {
209
+ // Arrange
210
+ const mockUrl = `${mockOrgUrl}_apis/test/endpoint`;
211
+ const mockData = { value: 'test data' };
212
+ const cache = new Map();
213
+ cache.set(mockUrl, {
214
+ data: mockData,
215
+ timestamp: Date.now(),
216
+ });
217
+ testDataProvider.cache = cache;
218
+ // Act
219
+ testDataProvider.clearCache();
220
+ // Assert
221
+ expect(testDataProvider.cache.size).toBe(0);
222
+ expect(logger_1.default.debug).toHaveBeenCalledWith('Cache cleared');
223
+ });
224
+ });
225
+ describe('UpdateTestRun', () => {
226
+ it('should update a test run with the correct state', async () => {
227
+ // Arrange
228
+ const mockRunId = '12345';
229
+ const mockState = 'Completed';
230
+ const mockResponse = { id: mockRunId, state: mockState };
231
+ tfs_1.TFSServices.postRequest.mockResolvedValueOnce(mockResponse);
232
+ // Act
233
+ const result = await testDataProvider.UpdateTestRun(mockProject, mockRunId, mockState);
234
+ // Assert
235
+ expect(result).toEqual(mockResponse);
236
+ expect(tfs_1.TFSServices.postRequest).toHaveBeenCalledWith(`${mockOrgUrl}${mockProject}/_apis/test/Runs/${mockRunId}?api-version=5.0`, mockToken, 'PATCH', { state: mockState }, null);
237
+ expect(logger_1.default.info).toHaveBeenCalledWith(`Update runId : ${mockRunId} to state : ${mockState}`);
238
+ });
239
+ });
240
+ describe('GetTestSuitesByPlan', () => {
241
+ it('should return suites without filter using default suiteId', async () => {
242
+ // Arrange
243
+ const mockTestSuites = { testSuites: [{ id: 457, name: 'Suite 1', parentSuiteId: 0 }] };
244
+ const mockSuiteData = [new helper_1.suiteData('Suite 1', '457', '456', 1)];
245
+ tfs_1.TFSServices.getItemContent.mockResolvedValue(mockTestSuites);
246
+ helper_1.Helper.findSuitesRecursive.mockReturnValue(mockSuiteData);
247
+ // Act
248
+ const result = await testDataProvider.GetTestSuitesByPlan(mockProject, mockPlanId, true);
249
+ // Assert
250
+ expect(helper_1.Helper.findSuitesRecursive).toHaveBeenCalled();
251
+ });
252
+ it('should process multiple top-level suite hierarchies and combine results', async () => {
253
+ jest.spyOn(testDataProvider, 'GetTestSuitesForPlan').mockResolvedValueOnce({
254
+ testSuites: [
255
+ { id: 1, parentSuiteId: 0 },
256
+ { id: 2, parentSuiteId: 0 },
257
+ { id: 10, parentSuiteId: 1 },
258
+ { id: 11, parentSuiteId: 2 },
259
+ ],
260
+ });
261
+ const suiteIdsFilter = [10, 11];
262
+ const getByIdSpy = jest
263
+ .spyOn(testDataProvider, 'GetTestSuiteById')
264
+ .mockResolvedValueOnce([{ id: '10' }])
265
+ .mockResolvedValueOnce([{ id: '11' }]);
266
+ const res = await testDataProvider.GetTestSuitesByPlan(mockProject, mockPlanId, true, suiteIdsFilter);
267
+ expect(getByIdSpy).toHaveBeenCalledTimes(2);
268
+ expect(res).toEqual([{ id: '10' }, { id: '11' }]);
269
+ });
270
+ it('should fallback to first suite when no top-level suites can be determined', async () => {
271
+ jest.spyOn(testDataProvider, 'GetTestSuitesForPlan').mockResolvedValueOnce({
272
+ testSuites: [{ id: 1, parentSuiteId: 0 }],
273
+ });
274
+ const getByIdSpy = jest
275
+ .spyOn(testDataProvider, 'GetTestSuiteById')
276
+ .mockResolvedValueOnce([{ id: '1' }]);
277
+ const res = await testDataProvider.GetTestSuitesByPlan(mockProject, mockPlanId, true, [1]);
278
+ expect(getByIdSpy).toHaveBeenCalledWith(mockProject, mockPlanId, '1', true, [1]);
279
+ expect(res).toEqual([{ id: '1' }]);
280
+ });
281
+ });
282
+ describe('createNewRequirement', () => {
283
+ it('should pick customer id from any supported customer fields when enabled', () => {
284
+ const rel = testDataProvider.createNewRequirement(true, {
285
+ id: '123',
286
+ fields: {
287
+ 'System.Title': 'Req title',
288
+ 'Custom.CustomerID': 'CID-1',
289
+ },
290
+ });
291
+ expect(rel).toEqual({ type: 'requirement', id: '123', title: 'Req title', customerId: 'CID-1' });
292
+ });
293
+ it('should default customerId to a single space when enabled and no field exists', () => {
294
+ const rel = testDataProvider.createNewRequirement(true, {
295
+ id: '123',
296
+ fields: {
297
+ 'System.Title': 'Req title',
298
+ },
299
+ });
300
+ expect(rel).toEqual({ type: 'requirement', id: '123', title: 'Req title', customerId: ' ' });
301
+ });
302
+ });
303
+ describe('addToMap', () => {
304
+ it('should create array for missing key and append values', () => {
305
+ const map = new Map();
306
+ testDataProvider.addToMap(map, 'k', 'v1');
307
+ testDataProvider.addToMap(map, 'k', 'v2');
308
+ expect(map.get('k')).toEqual(['v1', 'v2']);
309
+ });
310
+ });
311
+ describe('GetTestSuiteById with filtering', () => {
312
+ it('should filter suites when suiteIdsFilter is provided', async () => {
313
+ // Arrange
314
+ const suiteIdsFilter = [123, 456];
315
+ const mockTestSuites = {
316
+ testSuites: [
317
+ { id: 123, name: 'Suite 1', parentSuiteId: 100 },
318
+ { id: 456, name: 'Suite 2', parentSuiteId: 100 },
319
+ ],
320
+ };
321
+ const mockSuiteData = [new helper_1.suiteData('Suite 1', '123', '100', 1)];
322
+ tfs_1.TFSServices.getItemContent.mockResolvedValue(mockTestSuites);
323
+ helper_1.Helper.findSuitesRecursive.mockReturnValue(mockSuiteData);
324
+ // Act
325
+ const result = await testDataProvider.GetTestSuiteById(mockProject, mockPlanId, '123', true, suiteIdsFilter);
326
+ // Assert
327
+ expect(helper_1.Helper.findSuitesRecursive).toHaveBeenCalled();
328
+ });
329
+ });
330
+ describe('GetTestPoint', () => {
331
+ it('should return test points for a test case', async () => {
332
+ // Arrange
333
+ const mockResponse = { value: [{ id: 1, testCaseId: mockTestCaseId }] };
334
+ tfs_1.TFSServices.getItemContent.mockResolvedValue(mockResponse);
335
+ // Act
336
+ const result = await testDataProvider.GetTestPoint(mockProject, mockPlanId, mockSuiteId, mockTestCaseId);
337
+ // Assert
338
+ expect(result).toEqual(mockResponse);
339
+ });
340
+ });
341
+ describe('CreateTestRun', () => {
342
+ it('should create a test run successfully', async () => {
343
+ // Arrange
344
+ const testRunName = 'Test Run 1';
345
+ const testPointId = '12345';
346
+ const mockResponse = { data: { id: 1, name: testRunName } };
347
+ tfs_1.TFSServices.postRequest.mockResolvedValueOnce(mockResponse);
348
+ // Act
349
+ const result = await testDataProvider.CreateTestRun(mockProject, testRunName, mockPlanId, testPointId);
350
+ // Assert
351
+ expect(result).toEqual(mockResponse);
352
+ expect(tfs_1.TFSServices.postRequest).toHaveBeenCalledWith(`${mockOrgUrl}${mockProject}/_apis/test/runs`, mockToken, 'Post', {
353
+ name: testRunName,
354
+ plan: { id: mockPlanId },
355
+ pointIds: [testPointId],
356
+ }, null);
357
+ });
358
+ it('should throw error when creation fails', async () => {
359
+ // Arrange
360
+ const testRunName = 'Test Run 1';
361
+ const testPointId = '12345';
362
+ const mockError = new Error('Creation failed');
363
+ tfs_1.TFSServices.postRequest.mockRejectedValueOnce(mockError);
364
+ // Act & Assert
365
+ await expect(testDataProvider.CreateTestRun(mockProject, testRunName, mockPlanId, testPointId)).rejects.toThrow('Error: Creation failed');
366
+ });
367
+ });
368
+ describe('UpdateTestCase', () => {
369
+ it('should update test case to Active state (0)', async () => {
370
+ // Arrange
371
+ const mockRunId = '12345';
372
+ const mockResponse = { data: { outcome: '0' } };
373
+ tfs_1.TFSServices.postRequest.mockResolvedValueOnce(mockResponse);
374
+ // Act
375
+ const result = await testDataProvider.UpdateTestCase(mockProject, mockRunId, 0);
376
+ // Assert
377
+ expect(result).toEqual(mockResponse);
378
+ expect(logger_1.default.info).toHaveBeenCalledWith('Reset test case to Active state ');
379
+ });
380
+ it('should update test case to Completed state (1)', async () => {
381
+ // Arrange
382
+ const mockRunId = '12345';
383
+ const mockResponse = { data: { state: 'Completed', outcome: '1' } };
384
+ tfs_1.TFSServices.postRequest.mockResolvedValueOnce(mockResponse);
385
+ // Act
386
+ const result = await testDataProvider.UpdateTestCase(mockProject, mockRunId, 1);
387
+ // Assert
388
+ expect(result).toEqual(mockResponse);
389
+ expect(logger_1.default.info).toHaveBeenCalledWith('Update test case to complite state ');
390
+ });
391
+ it('should update test case to Passed state (2)', async () => {
392
+ // Arrange
393
+ const mockRunId = '12345';
394
+ const mockResponse = { data: { state: 'Completed', outcome: '2' } };
395
+ tfs_1.TFSServices.postRequest.mockResolvedValueOnce(mockResponse);
396
+ // Act
397
+ const result = await testDataProvider.UpdateTestCase(mockProject, mockRunId, 2);
398
+ // Assert
399
+ expect(result).toEqual(mockResponse);
400
+ expect(logger_1.default.info).toHaveBeenCalledWith('Update test case to passed state ');
401
+ });
402
+ it('should update test case to Failed state (3)', async () => {
403
+ // Arrange
404
+ const mockRunId = '12345';
405
+ const mockResponse = { data: { state: 'Completed', outcome: '3' } };
406
+ tfs_1.TFSServices.postRequest.mockResolvedValueOnce(mockResponse);
407
+ // Act
408
+ const result = await testDataProvider.UpdateTestCase(mockProject, mockRunId, 3);
409
+ // Assert
410
+ expect(result).toEqual(mockResponse);
411
+ expect(logger_1.default.info).toHaveBeenCalledWith('Update test case to failed state ');
412
+ });
413
+ });
414
+ describe('UploadTestAttachment', () => {
415
+ it('should upload attachment to test run', async () => {
416
+ // Arrange
417
+ const runId = '12345';
418
+ const stream = 'base64encodeddata';
419
+ const fileName = 'test.png';
420
+ const comment = 'Test attachment';
421
+ const attachmentType = 'GeneralAttachment';
422
+ const mockResponse = { data: { id: 1 } };
423
+ tfs_1.TFSServices.postRequest.mockResolvedValueOnce(mockResponse);
424
+ // Act
425
+ const result = await testDataProvider.UploadTestAttachment(runId, mockProject, stream, fileName, comment, attachmentType);
426
+ // Assert
427
+ expect(result).toEqual(mockResponse);
428
+ expect(tfs_1.TFSServices.postRequest).toHaveBeenCalledWith(`${mockOrgUrl}${mockProject}/_apis/test/Runs/${runId}/attachments?api-version=5.0-preview.1`, mockToken, 'Post', { stream, fileName, comment, attachmentType }, null);
429
+ });
430
+ });
431
+ describe('GetTestRunById', () => {
432
+ it('should return test run by ID', async () => {
433
+ // Arrange
434
+ const runId = '12345';
435
+ const mockResponse = { id: runId, name: 'Test Run 1' };
436
+ // Act
437
+ const result = await testDataProvider.GetTestRunById(mockProject, runId);
438
+ // Assert
439
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalled();
440
+ });
441
+ });
442
+ describe('GetTestPointByTestCaseId', () => {
443
+ it('should return test points for a test case ID', async () => {
444
+ // Arrange
445
+ const mockResponse = { data: { value: [{ id: 1 }] } };
446
+ tfs_1.TFSServices.postRequest.mockResolvedValueOnce(mockResponse);
447
+ // Act
448
+ const result = await testDataProvider.GetTestPointByTestCaseId(mockProject, mockTestCaseId);
449
+ // Assert
450
+ expect(result).toEqual(mockResponse);
451
+ expect(tfs_1.TFSServices.postRequest).toHaveBeenCalledWith(`${mockOrgUrl}${mockProject}/_apis/test/points`, mockToken, 'Post', { PointsFilter: { TestcaseIds: [mockTestCaseId] } }, null);
452
+ });
453
+ });
454
+ describe('GetTestCasesBySuites', () => {
455
+ it('should return test cases for suites', async () => {
456
+ // Arrange
457
+ const mockSuiteData = [new helper_1.suiteData('Suite 1', '123', '456', 1)];
458
+ const mockTestCases = {
459
+ count: 1,
460
+ value: [{ testCase: { id: '101', url: 'http://test.com/101' } }],
461
+ };
462
+ const mockTestCaseDetails = {
463
+ id: 101,
464
+ fields: {
465
+ 'System.Title': 'Test Case 1',
466
+ 'System.AreaPath': 'Area/Path',
467
+ 'System.Description': 'Description',
468
+ 'Microsoft.VSTS.TCM.Steps': null,
469
+ },
470
+ };
471
+ tfs_1.TFSServices.getItemContent
472
+ .mockResolvedValueOnce({ testSuites: mockSuiteData })
473
+ .mockResolvedValueOnce(mockTestCases)
474
+ .mockResolvedValueOnce(mockTestCaseDetails);
475
+ helper_1.Helper.findSuitesRecursive.mockReturnValueOnce(mockSuiteData);
476
+ // Act
477
+ const result = await testDataProvider.GetTestCasesBySuites(mockProject, mockPlanId, mockSuiteId, false, false, false, false);
478
+ // Assert
479
+ expect(result.testCasesList).toBeDefined();
480
+ expect(result.requirementToTestCaseTraceMap).toBeDefined();
481
+ expect(result.testCaseToRequirementsTraceMap).toBeDefined();
482
+ });
483
+ it('should use pre-filtered suites when provided', async () => {
484
+ // Arrange
485
+ const preFilteredSuites = [new helper_1.suiteData('Suite 1', '123', '456', 1)];
486
+ const mockTestCases = {
487
+ count: 1,
488
+ value: [{ testCase: { id: '101', url: 'http://test.com/101' } }],
489
+ };
490
+ const mockTestCaseDetails = {
491
+ id: 101,
492
+ fields: {
493
+ 'System.Title': 'Test Case 1',
494
+ 'System.AreaPath': 'Area/Path',
495
+ 'System.Description': 'Description',
496
+ 'Microsoft.VSTS.TCM.Steps': null,
497
+ },
498
+ };
499
+ tfs_1.TFSServices.getItemContent
500
+ .mockResolvedValueOnce(mockTestCases)
501
+ .mockResolvedValueOnce(mockTestCaseDetails);
502
+ // Act
503
+ const result = await testDataProvider.GetTestCasesBySuites(mockProject, mockPlanId, mockSuiteId, false, false, false, false, undefined, undefined, preFilteredSuites);
504
+ // Assert
505
+ expect(result.testCasesList).toBeDefined();
506
+ });
507
+ it('should handle errors in suite processing', async () => {
508
+ // Arrange
509
+ const preFilteredSuites = [new helper_1.suiteData('Suite 1', '123', '456', 1)];
510
+ tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(new Error('API Error'));
511
+ // Act
512
+ const result = await testDataProvider.GetTestCasesBySuites(mockProject, mockPlanId, mockSuiteId, false, false, false, false, undefined, undefined, preFilteredSuites);
513
+ // Assert
514
+ expect(result.testCasesList).toEqual([]);
515
+ });
516
+ });
517
+ describe('StructureTestCase', () => {
518
+ it('should return empty array when no test cases', async () => {
519
+ // Arrange
520
+ const suite = new helper_1.suiteData('Suite 1', '123', '456', 1);
521
+ const testCases = { count: 0, value: [] };
522
+ // Act
523
+ const result = await testDataProvider.StructureTestCase(mockProject, testCases, suite, false, false, false, new Map(), new Map());
524
+ // Assert
525
+ expect(result).toEqual([]);
526
+ expect(logger_1.default.warn).toHaveBeenCalled();
527
+ });
528
+ it('should warn and return [] when no testCases are provided', async () => {
529
+ const res = await testDataProvider.StructureTestCase(mockProject, { value: [], count: 0 }, { id: '1', name: 'Suite 1' }, true, true, false, new Map(), new Map());
530
+ expect(res).toEqual([]);
531
+ expect(logger_1.default.warn).toHaveBeenCalledWith('No test cases found for suite: 1');
532
+ });
533
+ it('should parse steps, add requirement relation when enabled, and add mom relation when enabled', async () => {
534
+ const UtilsMock = require('../../utils/testStepParserHelper').default;
535
+ const parserInstance = UtilsMock.mock.results[0].value;
536
+ parserInstance.parseTestSteps.mockResolvedValueOnce([{ stepId: '1' }]);
537
+ const suite = { id: '1', name: 'Suite 1' };
538
+ const testCases = {
539
+ count: 1,
540
+ value: [{ testCase: { id: 123, url: 'https://example.com/testcase/123' } }],
541
+ };
542
+ jest.spyOn(testDataProvider, 'fetchWithCache').mockImplementation(async (...args) => {
543
+ const url = String(args[0] || '');
544
+ if (url.includes('testcase/123') && url.includes('?$expand=All')) {
545
+ return {
546
+ id: 123,
547
+ fields: {
548
+ 'System.Title': 'TC 123',
549
+ 'System.AreaPath': 'A',
550
+ 'System.Description': 'D',
551
+ 'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
552
+ },
553
+ relations: [
554
+ { url: 'https://example.com/_apis/wit/workItems/200' },
555
+ { url: 'https://example.com/not-work-items/ignore' },
556
+ { url: 'https://example.com/_apis/wit/workItems/201' },
557
+ ],
558
+ };
559
+ }
560
+ if (url.includes('/workItems/200')) {
561
+ return {
562
+ id: 200,
563
+ fields: {
564
+ 'System.WorkItemType': 'Requirement',
565
+ 'System.Title': 'REQ 200',
566
+ 'Custom.CustomerRequirementId': 'C-200',
567
+ },
568
+ _links: { html: { href: 'http://example.com/200' } },
569
+ };
570
+ }
571
+ if (url.includes('/workItems/201')) {
572
+ return {
573
+ id: 201,
574
+ fields: {
575
+ 'System.WorkItemType': 'Bug',
576
+ 'System.Title': 'BUG 201',
577
+ 'System.State': 'Active',
578
+ },
579
+ _links: { html: { href: 'http://example.com/201' } },
580
+ };
581
+ }
582
+ throw new Error(`unexpected url ${url}`);
583
+ });
584
+ const requirementToTestCaseTraceMap = new Map();
585
+ const testCaseToRequirementsTraceMap = new Map();
586
+ const res = await testDataProvider.StructureTestCase(mockProject, testCases, suite, true, true, true, requirementToTestCaseTraceMap, testCaseToRequirementsTraceMap);
587
+ expect(res).toHaveLength(1);
588
+ expect(res[0].steps).toEqual([{ stepId: '1' }]);
589
+ expect(res[0].relations).toEqual(expect.arrayContaining([
590
+ expect.objectContaining({ type: 'requirement', id: 200, customerId: 'C-200' }),
591
+ ]));
592
+ expect(res[0].relations).toEqual(expect.arrayContaining([expect.objectContaining({ type: 'Bug', id: 201 })]));
593
+ expect(requirementToTestCaseTraceMap.size).toBe(1);
594
+ expect(testCaseToRequirementsTraceMap.size).toBe(1);
595
+ });
596
+ it('should use stepResultDetailsMap when provided and skip parseTestSteps', async () => {
597
+ const UtilsMock = require('../../utils/testStepParserHelper').default;
598
+ const parserInstance = UtilsMock.mock.results[0].value;
599
+ parserInstance.parseTestSteps.mockClear();
600
+ const suite = { id: '1', name: 'Suite 1' };
601
+ const testCases = {
602
+ count: 1,
603
+ value: [{ testCase: { id: 123, url: 'https://example.com/testcase/123' } }],
604
+ };
605
+ const stepResultDetailsMap = new Map();
606
+ stepResultDetailsMap.set('123', {
607
+ testCaseRevision: 7,
608
+ stepList: [{ stepId: 'from-cache' }],
609
+ caseEvidenceAttachments: [{ id: 1 }],
610
+ });
611
+ jest.spyOn(testDataProvider, 'fetchWithCache').mockResolvedValueOnce({
612
+ id: 123,
613
+ fields: {
614
+ 'System.Title': 'TC 123',
615
+ 'System.AreaPath': 'A',
616
+ 'System.Description': 'D',
617
+ 'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
618
+ },
619
+ relations: [],
620
+ });
621
+ const res = await testDataProvider.StructureTestCase(mockProject, testCases, suite, true, false, false, new Map(), new Map(), stepResultDetailsMap);
622
+ expect(res).toHaveLength(1);
623
+ expect(res[0].steps).toEqual([{ stepId: 'from-cache' }]);
624
+ expect(res[0].caseEvidenceAttachments).toEqual([{ id: 1 }]);
625
+ expect(parserInstance.parseTestSteps).not.toHaveBeenCalled();
626
+ });
627
+ it('should log error and continue when fetching relation content fails', async () => {
628
+ const suite = { id: '1', name: 'Suite 1' };
629
+ const testCases = {
630
+ count: 1,
631
+ value: [{ testCase: { id: 123, url: 'https://example.com/testcase/123' } }],
632
+ };
633
+ jest.spyOn(testDataProvider, 'fetchWithCache').mockImplementation(async (...args) => {
634
+ const url = String(args[0] || '');
635
+ if (url.includes('testcase/123') && url.includes('?$expand=All')) {
636
+ return {
637
+ id: 123,
638
+ fields: {
639
+ 'System.Title': 'TC 123',
640
+ 'System.AreaPath': 'A',
641
+ 'System.Description': 'D',
642
+ 'Microsoft.VSTS.TCM.Steps': null,
643
+ },
644
+ relations: [{ url: 'https://example.com/_apis/wit/workItems/200' }],
645
+ };
646
+ }
647
+ if (url.includes('/workItems/200')) {
648
+ throw new Error('boom');
649
+ }
650
+ return null;
651
+ });
652
+ const res = await testDataProvider.StructureTestCase(mockProject, testCases, suite, true, true, false, new Map(), new Map());
653
+ expect(res).toHaveLength(1);
654
+ expect(logger_1.default.error).toHaveBeenCalledWith(expect.stringContaining('Failed to fetch relation content for URL https://example.com/_apis/wit/workItems/200'));
655
+ });
656
+ it('should append linked MOMs from testCaseToLinkedMomLookup when provided', async () => {
657
+ const suite = { id: '1', name: 'Suite 1' };
658
+ const testCases = {
659
+ count: 1,
660
+ value: [{ testCase: { id: 123, url: 'https://example.com/testcase/123' } }],
661
+ };
662
+ jest.spyOn(testDataProvider, 'fetchWithCache').mockResolvedValueOnce({
663
+ id: 123,
664
+ fields: {
665
+ 'System.Title': 'TC 123',
666
+ 'System.AreaPath': 'A',
667
+ 'System.Description': 'D',
668
+ 'Microsoft.VSTS.TCM.Steps': null,
669
+ },
670
+ relations: [],
671
+ });
672
+ const testCaseToLinkedMomLookup = new Map();
673
+ testCaseToLinkedMomLookup.set(123, new Set([
674
+ {
675
+ id: 900,
676
+ fields: {
677
+ 'System.WorkItemType': 'Task',
678
+ 'System.Title': 'Task 900',
679
+ 'System.State': 'Active',
680
+ },
681
+ _links: { html: { href: 'http://example.com/900' } },
682
+ },
683
+ ]));
684
+ const res = await testDataProvider.StructureTestCase(mockProject, testCases, suite, false, false, false, new Map(), new Map(), undefined, testCaseToLinkedMomLookup);
685
+ expect(res).toHaveLength(1);
686
+ expect(res[0].relations).toEqual(expect.arrayContaining([expect.objectContaining({ type: 'Task', id: 900 })]));
687
+ });
688
+ it('should return empty list when test case fetch fails', async () => {
689
+ const suite = { id: '1', name: 'Suite 1' };
690
+ const testCases = {
691
+ count: 1,
692
+ value: [{ testCase: { id: 123, url: 'https://example.com/testcase/123' } }],
693
+ };
694
+ jest.spyOn(testDataProvider, 'fetchWithCache').mockRejectedValueOnce(new Error('boom'));
695
+ const res = await testDataProvider.StructureTestCase(mockProject, testCases, suite, false, false, false, new Map(), new Map());
696
+ expect(res).toEqual([]);
697
+ expect(logger_1.default.error).toHaveBeenCalledWith('Error: ran into an issue while retrieving testCase 123');
698
+ });
699
+ });
700
+ describe('ParseSteps', () => {
701
+ it('should parse XML steps correctly', () => {
702
+ // Arrange
703
+ const stepsXml = `<steps>
704
+ <step>
705
+ <parameterizedString>Action 1</parameterizedString>
706
+ <parameterizedString>Expected 1</parameterizedString>
707
+ </step>
708
+ </steps>`;
709
+ // Act
710
+ const result = testDataProvider.ParseSteps(stepsXml);
711
+ // Assert - ParseSteps uses xml2js.parseString which is synchronous callback-based
712
+ // The result may be empty if parsing fails or steps format doesn't match
713
+ expect(Array.isArray(result)).toBe(true);
714
+ });
715
+ });
716
+ });
717
+ //# sourceMappingURL=testDataProvider.test.js.map