@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.
Files changed (91) hide show
  1. package/.github/workflows/ci.yml +26 -9
  2. package/.github/workflows/release.yml +9 -10
  3. package/bin/helpers/tfs.d.ts +3 -0
  4. package/bin/helpers/tfs.js +44 -7
  5. package/bin/helpers/tfs.js.map +1 -1
  6. package/bin/modules/GitDataProvider.d.ts +10 -0
  7. package/bin/modules/GitDataProvider.js +10 -0
  8. package/bin/modules/GitDataProvider.js.map +1 -1
  9. package/bin/modules/TestDataProvider.js +0 -1
  10. package/bin/modules/TestDataProvider.js.map +1 -1
  11. package/bin/modules/TicketsDataProvider.d.ts +63 -24
  12. package/bin/modules/TicketsDataProvider.js +216 -114
  13. package/bin/modules/TicketsDataProvider.js.map +1 -1
  14. package/bin/tests/helpers/helper.test.js +279 -0
  15. package/bin/tests/helpers/helper.test.js.map +1 -0
  16. package/bin/{helpers/test → tests/helpers}/tfs.test.js +312 -49
  17. package/bin/tests/helpers/tfs.test.js.map +1 -0
  18. package/bin/tests/index.test.js +25 -0
  19. package/bin/tests/index.test.js.map +1 -0
  20. package/bin/tests/models/tfs-data.test.js +160 -0
  21. package/bin/tests/models/tfs-data.test.js.map +1 -0
  22. package/bin/{modules/test → tests/modules}/JfrogDataProvider.test.js +9 -9
  23. package/bin/tests/modules/JfrogDataProvider.test.js.map +1 -0
  24. package/bin/tests/modules/ResultDataProvider.test.js +1942 -0
  25. package/bin/tests/modules/ResultDataProvider.test.js.map +1 -0
  26. package/bin/tests/modules/gitDataProvider.test.js +1888 -0
  27. package/bin/tests/modules/gitDataProvider.test.js.map +1 -0
  28. package/bin/{modules/test → tests/modules}/managmentDataProvider.test.js +13 -1
  29. package/bin/tests/modules/managmentDataProvider.test.js.map +1 -0
  30. package/bin/tests/modules/pipelineDataProvider.test.d.ts +1 -0
  31. package/bin/tests/modules/pipelineDataProvider.test.js +783 -0
  32. package/bin/tests/modules/pipelineDataProvider.test.js.map +1 -0
  33. package/bin/tests/modules/testDataProvider.test.d.ts +1 -0
  34. package/bin/tests/modules/testDataProvider.test.js +717 -0
  35. package/bin/tests/modules/testDataProvider.test.js.map +1 -0
  36. package/bin/tests/modules/ticketsDataProvider.test.d.ts +1 -0
  37. package/bin/tests/modules/ticketsDataProvider.test.js +1681 -0
  38. package/bin/tests/modules/ticketsDataProvider.test.js.map +1 -0
  39. package/bin/tests/utils/DataProviderUtils.test.d.ts +1 -0
  40. package/bin/tests/utils/DataProviderUtils.test.js +61 -0
  41. package/bin/tests/utils/DataProviderUtils.test.js.map +1 -0
  42. package/bin/tests/utils/testStepParserHelper.test.d.ts +1 -0
  43. package/bin/tests/utils/testStepParserHelper.test.js +359 -0
  44. package/bin/tests/utils/testStepParserHelper.test.js.map +1 -0
  45. package/package.json +9 -1
  46. package/src/helpers/tfs.ts +51 -7
  47. package/src/modules/GitDataProvider.ts +10 -0
  48. package/src/modules/TestDataProvider.ts +0 -1
  49. package/src/modules/TicketsDataProvider.ts +298 -141
  50. package/src/tests/helpers/helper.test.ts +337 -0
  51. package/src/tests/helpers/tfs.test.ts +1092 -0
  52. package/src/tests/index.test.ts +28 -0
  53. package/src/tests/models/tfs-data.test.ts +203 -0
  54. package/src/tests/modules/JfrogDataProvider.test.ts +167 -0
  55. package/src/tests/modules/ResultDataProvider.test.ts +2571 -0
  56. package/src/tests/modules/gitDataProvider.test.ts +2628 -0
  57. package/src/{modules/test → tests/modules}/managmentDataProvider.test.ts +33 -1
  58. package/src/tests/modules/pipelineDataProvider.test.ts +1038 -0
  59. package/src/tests/modules/testDataProvider.test.ts +1046 -0
  60. package/src/tests/modules/ticketsDataProvider.test.ts +2204 -0
  61. package/src/tests/utils/DataProviderUtils.test.ts +76 -0
  62. package/src/tests/utils/testStepParserHelper.test.ts +437 -0
  63. package/tsconfig.json +1 -0
  64. package/bin/helpers/test/tfs.test.js.map +0 -1
  65. package/bin/modules/test/JfrogDataProvider.test.js.map +0 -1
  66. package/bin/modules/test/ResultDataProvider.test.js +0 -444
  67. package/bin/modules/test/ResultDataProvider.test.js.map +0 -1
  68. package/bin/modules/test/gitDataProvider.test.js +0 -428
  69. package/bin/modules/test/gitDataProvider.test.js.map +0 -1
  70. package/bin/modules/test/managmentDataProvider.test.js.map +0 -1
  71. package/bin/modules/test/pipelineDataProvider.test.js +0 -237
  72. package/bin/modules/test/pipelineDataProvider.test.js.map +0 -1
  73. package/bin/modules/test/testDataProvider.test.js +0 -234
  74. package/bin/modules/test/testDataProvider.test.js.map +0 -1
  75. package/bin/modules/test/ticketsDataProvider.test.js +0 -348
  76. package/bin/modules/test/ticketsDataProvider.test.js.map +0 -1
  77. package/src/helpers/test/tfs.test.ts +0 -748
  78. package/src/modules/test/JfrogDataProvider.test.ts +0 -171
  79. package/src/modules/test/ResultDataProvider.test.ts +0 -542
  80. package/src/modules/test/gitDataProvider.test.ts +0 -645
  81. package/src/modules/test/pipelineDataProvider.test.ts +0 -292
  82. package/src/modules/test/testDataProvider.test.ts +0 -318
  83. package/src/modules/test/ticketsDataProvider.test.ts +0 -462
  84. /package/bin/{helpers/test/tfs.test.d.ts → tests/helpers/helper.test.d.ts} +0 -0
  85. /package/bin/{modules/test/JfrogDataProvider.test.d.ts → tests/helpers/tfs.test.d.ts} +0 -0
  86. /package/bin/{modules/test/ResultDataProvider.test.d.ts → tests/index.test.d.ts} +0 -0
  87. /package/bin/{modules/test/gitDataProvider.test.d.ts → tests/models/tfs-data.test.d.ts} +0 -0
  88. /package/bin/{modules/test/managmentDataProvider.test.d.ts → tests/modules/JfrogDataProvider.test.d.ts} +0 -0
  89. /package/bin/{modules/test/pipelineDataProvider.test.d.ts → tests/modules/ResultDataProvider.test.d.ts} +0 -0
  90. /package/bin/{modules/test/testDataProvider.test.d.ts → tests/modules/gitDataProvider.test.d.ts} +0 -0
  91. /package/bin/{modules/test/ticketsDataProvider.test.d.ts → tests/modules/managmentDataProvider.test.d.ts} +0 -0
@@ -0,0 +1,1888 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tfs_1 = require("../../helpers/tfs");
4
+ const GitDataProvider_1 = require("../../modules/GitDataProvider");
5
+ const logger_1 = require("../../utils/logger");
6
+ jest.mock('../../helpers/tfs');
7
+ jest.mock('../../utils/logger');
8
+ describe('GitDataProvider - GetCommitForPipeline', () => {
9
+ let gitDataProvider;
10
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
11
+ const mockToken = 'mock-token';
12
+ const mockProjectId = 'project-123';
13
+ const mockBuildId = 456;
14
+ beforeEach(() => {
15
+ jest.clearAllMocks();
16
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
17
+ });
18
+ it('should return the sourceVersion from build information', async () => {
19
+ // Arrange
20
+ const mockCommitSha = 'abc123def456';
21
+ const mockResponse = {
22
+ id: mockBuildId,
23
+ sourceVersion: mockCommitSha,
24
+ status: 'completed',
25
+ };
26
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
27
+ // Act
28
+ const result = await gitDataProvider.GetCommitForPipeline(mockProjectId, mockBuildId);
29
+ // Assert
30
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}${mockProjectId}/_apis/build/builds/${mockBuildId}`, mockToken, 'get');
31
+ expect(result).toBe(mockCommitSha);
32
+ });
33
+ it('should throw an error if the API call fails', async () => {
34
+ // Arrange
35
+ const expectedError = new Error('API call failed');
36
+ tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(expectedError);
37
+ // Act & Assert
38
+ await expect(gitDataProvider.GetCommitForPipeline(mockProjectId, mockBuildId)).rejects.toThrow('API call failed');
39
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}${mockProjectId}/_apis/build/builds/${mockBuildId}`, mockToken, 'get');
40
+ });
41
+ it('should return undefined if the response does not contain sourceVersion', async () => {
42
+ // Arrange
43
+ const mockResponse = {
44
+ id: mockBuildId,
45
+ status: 'completed',
46
+ // No sourceVersion property
47
+ };
48
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
49
+ // Act
50
+ const result = await gitDataProvider.GetCommitForPipeline(mockProjectId, mockBuildId);
51
+ // Assert
52
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}${mockProjectId}/_apis/build/builds/${mockBuildId}`, mockToken, 'get');
53
+ expect(result).toBeUndefined();
54
+ });
55
+ it('should correctly construct URL with given project ID and build ID', async () => {
56
+ // Arrange
57
+ const customProjectId = 'custom-project';
58
+ const customBuildId = 789;
59
+ const mockCommitSha = 'xyz789abc';
60
+ const mockResponse = { sourceVersion: mockCommitSha };
61
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
62
+ // Act
63
+ await gitDataProvider.GetCommitForPipeline(customProjectId, customBuildId);
64
+ // Assert
65
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}${customProjectId}/_apis/build/builds/${customBuildId}`, mockToken, 'get');
66
+ });
67
+ it('should handle different organization URLs correctly', async () => {
68
+ // Arrange
69
+ const altOrgUrl = 'https://dev.azure.com/different-org/';
70
+ const altGitDataProvider = new GitDataProvider_1.default(altOrgUrl, mockToken);
71
+ const mockResponse = { sourceVersion: 'commit-sha' };
72
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
73
+ // Act
74
+ await altGitDataProvider.GetCommitForPipeline(mockProjectId, mockBuildId);
75
+ // Assert
76
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${altOrgUrl}${mockProjectId}/_apis/build/builds/${mockBuildId}`, mockToken, 'get');
77
+ });
78
+ });
79
+ describe('GitDataProvider - GetTeamProjectGitReposList', () => {
80
+ let gitDataProvider;
81
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
82
+ const mockToken = 'mock-token';
83
+ const mockTeamProject = 'project-123';
84
+ beforeEach(() => {
85
+ jest.clearAllMocks();
86
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
87
+ });
88
+ it('should return sorted repositories when API call succeeds', async () => {
89
+ // Arrange
90
+ const mockRepos = {
91
+ value: [
92
+ { id: 'repo2', name: 'ZRepo' },
93
+ { id: 'repo1', name: 'ARepo' },
94
+ { id: 'repo3', name: 'MRepo' },
95
+ ],
96
+ };
97
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockRepos);
98
+ // Act
99
+ const result = await gitDataProvider.GetTeamProjectGitReposList(mockTeamProject);
100
+ // Assert
101
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}/${mockTeamProject}/_apis/git/repositories`, mockToken, 'get');
102
+ expect(result).toHaveLength(3);
103
+ expect(result[0].name).toBe('ARepo');
104
+ expect(result[1].name).toBe('MRepo');
105
+ expect(result[2].name).toBe('ZRepo');
106
+ expect(logger_1.default.debug).toHaveBeenCalledWith(expect.stringContaining(`fetching repos list for team project - ${mockTeamProject}`));
107
+ });
108
+ it('should return empty array when no repositories exist', async () => {
109
+ // Arrange
110
+ const mockEmptyRepos = { value: [] };
111
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockEmptyRepos);
112
+ // Act
113
+ const result = await gitDataProvider.GetTeamProjectGitReposList(mockTeamProject);
114
+ // Assert
115
+ expect(result).toEqual([]);
116
+ });
117
+ it('should handle API errors appropriately', async () => {
118
+ // Arrange
119
+ const mockError = new Error('API Error');
120
+ tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(mockError);
121
+ // Act & Assert
122
+ await expect(gitDataProvider.GetTeamProjectGitReposList(mockTeamProject)).rejects.toThrow('API Error');
123
+ });
124
+ });
125
+ describe('GitDataProvider - GetGitRepoFromRepoId', () => {
126
+ let gitDataProvider;
127
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
128
+ const mockToken = 'mock-token';
129
+ beforeEach(() => {
130
+ jest.clearAllMocks();
131
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
132
+ });
133
+ it('should fetch repository by id', async () => {
134
+ const mockRepoId = 'repo-123';
135
+ const mockResponse = { id: mockRepoId, name: 'Repo' };
136
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
137
+ const result = await gitDataProvider.GetGitRepoFromRepoId(mockRepoId);
138
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}_apis/git/repositories/${mockRepoId}`, mockToken, 'get');
139
+ expect(result).toEqual(mockResponse);
140
+ });
141
+ });
142
+ describe('GitDataProvider - GetTag', () => {
143
+ let gitDataProvider;
144
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
145
+ const mockToken = 'mock-token';
146
+ const mockGitRepoUrl = 'https://dev.azure.com/orgname/project/_apis/git/repositories/repo-id';
147
+ beforeEach(() => {
148
+ jest.clearAllMocks();
149
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
150
+ });
151
+ it('should return tag info when tag exists', async () => {
152
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce({
153
+ value: [
154
+ {
155
+ name: 'refs/tags/v1.0.0',
156
+ objectId: 'abc123',
157
+ peeledObjectId: 'def456',
158
+ },
159
+ ],
160
+ });
161
+ const result = await gitDataProvider.GetTag(mockGitRepoUrl, 'v1.0.0');
162
+ expect(result).toEqual(expect.objectContaining({
163
+ name: 'v1.0.0',
164
+ objectId: 'abc123',
165
+ peeledObjectId: 'def456',
166
+ }));
167
+ });
168
+ it('should return null when no matching tag found', async () => {
169
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce({
170
+ value: [{ name: 'refs/tags/other-tag', objectId: 'abc123' }],
171
+ });
172
+ const result = await gitDataProvider.GetTag(mockGitRepoUrl, 'v1.0.0');
173
+ expect(result).toBeNull();
174
+ });
175
+ });
176
+ describe('GitDataProvider - GetFileFromGitRepo', () => {
177
+ let gitDataProvider;
178
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
179
+ const mockToken = 'mock-token';
180
+ const mockProjectName = 'project-123';
181
+ const mockRepoId = 'repo-456';
182
+ const mockFileName = 'README.md';
183
+ const mockVersion = { version: 'main', versionType: 'branch' };
184
+ beforeEach(() => {
185
+ jest.clearAllMocks();
186
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
187
+ });
188
+ it('should return file content when file exists', async () => {
189
+ // Arrange
190
+ const mockContent = 'This is a test readme file';
191
+ const mockResponse = { content: mockContent };
192
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
193
+ // Act
194
+ const result = await gitDataProvider.GetFileFromGitRepo(mockProjectName, mockRepoId, mockFileName, mockVersion);
195
+ // Assert
196
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(`${mockOrgUrl}${mockProjectName}/_apis/git/repositories/${mockRepoId}/items`), mockToken, 'get', {}, {}, false);
197
+ expect(result).toBe(mockContent);
198
+ });
199
+ it('should handle special characters in version by encoding them', async () => {
200
+ // Arrange
201
+ const specialVersion = { version: 'feature/branch#123', versionType: 'branch' };
202
+ const mockResponse = { content: 'content' };
203
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
204
+ // Act
205
+ await gitDataProvider.GetFileFromGitRepo(mockProjectName, mockRepoId, mockFileName, specialVersion);
206
+ // Assert
207
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining('versionDescriptor.version=feature%2Fbranch%23123'), expect.anything(), expect.anything(), expect.anything(), expect.anything(), expect.anything());
208
+ });
209
+ it('should use custom gitRepoUrl if provided', async () => {
210
+ // Arrange
211
+ const mockCustomUrl = 'https://custom.git.url';
212
+ const mockResponse = { content: 'content' };
213
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
214
+ // Act
215
+ await gitDataProvider.GetFileFromGitRepo(mockProjectName, mockRepoId, mockFileName, mockVersion, mockCustomUrl);
216
+ // Assert
217
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(mockCustomUrl), expect.anything(), expect.anything(), expect.anything(), expect.anything(), expect.anything());
218
+ });
219
+ it('should return undefined when file does not exist', async () => {
220
+ // Arrange
221
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce({});
222
+ // Act
223
+ const result = await gitDataProvider.GetFileFromGitRepo(mockProjectName, mockRepoId, mockFileName, mockVersion);
224
+ // Assert
225
+ expect(result).toBeUndefined();
226
+ });
227
+ it('should log warning and return undefined when error occurs', async () => {
228
+ // Arrange
229
+ const mockError = new Error('File not found');
230
+ tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(mockError);
231
+ // Act
232
+ const result = await gitDataProvider.GetFileFromGitRepo(mockProjectName, mockRepoId, mockFileName, mockVersion);
233
+ // Assert
234
+ expect(logger_1.default.warn).toHaveBeenCalledWith(expect.stringContaining(`File ${mockFileName} could not be read: ${mockError.message}`));
235
+ expect(result).toBeUndefined();
236
+ });
237
+ });
238
+ describe('GitDataProvider - CheckIfItemExist', () => {
239
+ let gitDataProvider;
240
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
241
+ const mockToken = 'mock-token';
242
+ const mockGitApiUrl = 'https://dev.azure.com/orgname/project/_apis/git/repositories/repo-id';
243
+ const mockItemPath = 'path/to/file.txt';
244
+ const mockVersion = { version: 'main', versionType: 'branch' };
245
+ beforeEach(() => {
246
+ jest.clearAllMocks();
247
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
248
+ });
249
+ it('should return true when item exists', async () => {
250
+ // Arrange
251
+ const mockResponse = { path: mockItemPath, content: 'content' };
252
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
253
+ // Act
254
+ const result = await gitDataProvider.CheckIfItemExist(mockGitApiUrl, mockItemPath, mockVersion);
255
+ // Assert
256
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(`${mockGitApiUrl}/items?path=${mockItemPath}`), mockToken, 'get', {}, {}, false);
257
+ expect(result).toBe(true);
258
+ });
259
+ it('should return false when item does not exist', async () => {
260
+ // Arrange
261
+ tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(new Error('Not found'));
262
+ // Act
263
+ const result = await gitDataProvider.CheckIfItemExist(mockGitApiUrl, mockItemPath, mockVersion);
264
+ // Assert
265
+ expect(result).toBe(false);
266
+ });
267
+ it('should return false when API returns null', async () => {
268
+ // Arrange
269
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(null);
270
+ // Act
271
+ const result = await gitDataProvider.CheckIfItemExist(mockGitApiUrl, mockItemPath, mockVersion);
272
+ // Assert
273
+ expect(result).toBe(false);
274
+ });
275
+ it('should handle special characters in version', async () => {
276
+ // Arrange
277
+ const specialVersion = { version: 'feature/branch#123', versionType: 'branch' };
278
+ const mockResponse = { path: mockItemPath };
279
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
280
+ // Act
281
+ await gitDataProvider.CheckIfItemExist(mockGitApiUrl, mockItemPath, specialVersion);
282
+ // Assert
283
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining('versionDescriptor.version=feature%2Fbranch%23123'), expect.anything(), expect.anything(), expect.anything(), expect.anything(), expect.anything());
284
+ });
285
+ });
286
+ describe('GitDataProvider - GetPullRequestsInCommitRangeWithoutLinkedItems', () => {
287
+ let gitDataProvider;
288
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
289
+ const mockToken = 'mock-token';
290
+ const mockProjectId = 'project-123';
291
+ const mockRepoId = 'repo-456';
292
+ beforeEach(() => {
293
+ jest.clearAllMocks();
294
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
295
+ });
296
+ it('should return filtered pull requests matching commit ids', async () => {
297
+ // Arrange
298
+ const mockCommits = {
299
+ value: [{ commitId: 'commit-1' }, { commitId: 'commit-2' }],
300
+ };
301
+ const mockPullRequests = {
302
+ count: 3,
303
+ value: [
304
+ {
305
+ pullRequestId: 101,
306
+ title: 'PR 1',
307
+ createdBy: { displayName: 'User 1' },
308
+ creationDate: '2023-01-01',
309
+ closedDate: '2023-01-02',
310
+ description: 'Description 1',
311
+ lastMergeCommit: { commitId: 'commit-1' },
312
+ },
313
+ {
314
+ pullRequestId: 102,
315
+ title: 'PR 2',
316
+ createdBy: { displayName: 'User 2' },
317
+ creationDate: '2023-02-01',
318
+ closedDate: '2023-02-02',
319
+ description: 'Description 2',
320
+ lastMergeCommit: { commitId: 'commit-3' }, // Not in our commit range
321
+ },
322
+ {
323
+ pullRequestId: 103,
324
+ title: 'PR 3',
325
+ createdBy: { displayName: 'User 3' },
326
+ creationDate: '2023-03-01',
327
+ closedDate: '2023-03-02',
328
+ description: 'Description 3',
329
+ lastMergeCommit: { commitId: 'commit-2' },
330
+ },
331
+ ],
332
+ };
333
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockPullRequests);
334
+ // Act
335
+ const result = await gitDataProvider.GetPullRequestsInCommitRangeWithoutLinkedItems(mockProjectId, mockRepoId, mockCommits);
336
+ // Assert
337
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(`${mockOrgUrl}${mockProjectId}/_apis/git/repositories/${mockRepoId}/pullrequests`), mockToken, 'get');
338
+ expect(result).toHaveLength(2);
339
+ expect(result[0].pullRequestId).toBe(101);
340
+ expect(result[1].pullRequestId).toBe(103);
341
+ expect(logger_1.default.info).toHaveBeenCalledWith(expect.stringContaining('filtered in commit range 2 pullrequests'));
342
+ });
343
+ it('should return empty array when no matching pull requests', async () => {
344
+ // Arrange
345
+ const mockCommits = {
346
+ value: [
347
+ { commitId: 'commit-999' }, // Not matching any PRs
348
+ ],
349
+ };
350
+ const mockPullRequests = {
351
+ count: 2,
352
+ value: [
353
+ {
354
+ pullRequestId: 101,
355
+ lastMergeCommit: { commitId: 'commit-1' },
356
+ },
357
+ {
358
+ pullRequestId: 102,
359
+ lastMergeCommit: { commitId: 'commit-2' },
360
+ },
361
+ ],
362
+ };
363
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockPullRequests);
364
+ // Act
365
+ const result = await gitDataProvider.GetPullRequestsInCommitRangeWithoutLinkedItems(mockProjectId, mockRepoId, mockCommits);
366
+ // Assert
367
+ expect(result).toHaveLength(0);
368
+ });
369
+ it('should handle API errors appropriately', async () => {
370
+ // Arrange
371
+ const mockCommits = { value: [{ commitId: 'commit-1' }] };
372
+ const mockError = new Error('API Error');
373
+ tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(mockError);
374
+ // Act & Assert
375
+ await expect(gitDataProvider.GetPullRequestsInCommitRangeWithoutLinkedItems(mockProjectId, mockRepoId, mockCommits)).rejects.toThrow('API Error');
376
+ });
377
+ });
378
+ describe('GitDataProvider - GetBranch', () => {
379
+ let gitDataProvider;
380
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
381
+ const mockToken = 'mock-token';
382
+ const mockGitRepoUrl = 'https://dev.azure.com/orgname/project/_apis/git/repositories/repo-id';
383
+ beforeEach(() => {
384
+ jest.clearAllMocks();
385
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
386
+ });
387
+ it('should return branch info when branch exists', async () => {
388
+ // Arrange
389
+ const branchName = 'main';
390
+ const mockResponse = {
391
+ value: [{ name: 'refs/heads/main', objectId: 'abc123' }],
392
+ };
393
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
394
+ // Act
395
+ const result = await gitDataProvider.GetBranch(mockGitRepoUrl, branchName);
396
+ // Assert
397
+ expect(result).toEqual({ name: 'refs/heads/main', objectId: 'abc123' });
398
+ });
399
+ it('should return null when branch does not exist', async () => {
400
+ // Arrange
401
+ const mockResponse = { value: [] };
402
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
403
+ // Act
404
+ const result = await gitDataProvider.GetBranch(mockGitRepoUrl, 'nonexistent');
405
+ // Assert
406
+ expect(result).toBeNull();
407
+ });
408
+ it('should return null when response is empty', async () => {
409
+ // Arrange
410
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(null);
411
+ // Act
412
+ const result = await gitDataProvider.GetBranch(mockGitRepoUrl, 'main');
413
+ // Assert
414
+ expect(result).toBeNull();
415
+ });
416
+ });
417
+ describe('GitDataProvider - GetGitRepoFromPrId', () => {
418
+ let gitDataProvider;
419
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
420
+ const mockToken = 'mock-token';
421
+ beforeEach(() => {
422
+ jest.clearAllMocks();
423
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
424
+ });
425
+ it('should fetch PR by ID', async () => {
426
+ // Arrange
427
+ const prId = 123;
428
+ const mockResponse = { pullRequestId: prId, title: 'Test PR' };
429
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
430
+ // Act
431
+ const result = await gitDataProvider.GetGitRepoFromPrId(prId);
432
+ // Assert
433
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}_apis/git/pullrequests/${prId}`, mockToken, 'get');
434
+ expect(result).toEqual(mockResponse);
435
+ });
436
+ });
437
+ describe('GitDataProvider - GetPullRequestCommits', () => {
438
+ let gitDataProvider;
439
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
440
+ const mockToken = 'mock-token';
441
+ beforeEach(() => {
442
+ jest.clearAllMocks();
443
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
444
+ });
445
+ it('should fetch PR commits', async () => {
446
+ // Arrange
447
+ const repoId = 'repo-123';
448
+ const prId = 456;
449
+ const mockResponse = { value: [{ commitId: 'abc123' }] };
450
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
451
+ // Act
452
+ const result = await gitDataProvider.GetPullRequestCommits(repoId, prId);
453
+ // Assert
454
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}_apis/git/repositories/${repoId}/pullRequests/${prId}/commits`, mockToken, 'get');
455
+ expect(result).toEqual(mockResponse);
456
+ });
457
+ });
458
+ describe('GitDataProvider - GetCommitByCommitId', () => {
459
+ let gitDataProvider;
460
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
461
+ const mockToken = 'mock-token';
462
+ beforeEach(() => {
463
+ jest.clearAllMocks();
464
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
465
+ });
466
+ it('should fetch commit by SHA', async () => {
467
+ // Arrange
468
+ const projectId = 'project-123';
469
+ const repoId = 'repo-456';
470
+ const commitSha = 'abc123def456';
471
+ const mockResponse = { commitId: commitSha, comment: 'Test commit' };
472
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
473
+ // Act
474
+ const result = await gitDataProvider.GetCommitByCommitId(projectId, repoId, commitSha);
475
+ // Assert
476
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(`${mockOrgUrl}${projectId}/_apis/git/repositories/${repoId}/commits/${commitSha}`, mockToken, 'get');
477
+ expect(result).toEqual(mockResponse);
478
+ });
479
+ });
480
+ describe('GitDataProvider - GetItemsForPipelinesRange', () => {
481
+ let gitDataProvider;
482
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
483
+ const mockToken = 'mock-token';
484
+ beforeEach(() => {
485
+ jest.clearAllMocks();
486
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
487
+ });
488
+ it('should fetch items in pipeline range', async () => {
489
+ // Arrange
490
+ const projectId = 'project-123';
491
+ const fromBuildId = 100;
492
+ const toBuildId = 200;
493
+ const mockWorkItemsResponse = {
494
+ count: 1,
495
+ value: [{ id: 1 }],
496
+ };
497
+ const mockWorkItemResponse = { id: 1, fields: { 'System.Title': 'Test' } };
498
+ tfs_1.TFSServices.getItemContent
499
+ .mockResolvedValueOnce(mockWorkItemsResponse)
500
+ .mockResolvedValueOnce(mockWorkItemResponse);
501
+ // Act
502
+ const result = await gitDataProvider.GetItemsForPipelinesRange(projectId, fromBuildId, toBuildId);
503
+ // Assert
504
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(`fromBuildId=${fromBuildId}&toBuildId=${toBuildId}`), mockToken, 'get');
505
+ expect(result).toHaveLength(1);
506
+ });
507
+ });
508
+ describe('GitDataProvider - GetCommitsInDateRange', () => {
509
+ let gitDataProvider;
510
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
511
+ const mockToken = 'mock-token';
512
+ beforeEach(() => {
513
+ jest.clearAllMocks();
514
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
515
+ });
516
+ it('should fetch commits in date range without branch', async () => {
517
+ // Arrange
518
+ const projectId = 'project-123';
519
+ const repoId = 'repo-456';
520
+ const fromDate = '2023-01-01';
521
+ const toDate = '2023-12-31';
522
+ const mockResponse = { value: [{ commitId: 'abc123' }] };
523
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
524
+ // Act
525
+ const result = await gitDataProvider.GetCommitsInDateRange(projectId, repoId, fromDate, toDate);
526
+ // Assert
527
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(`fromDate=${fromDate}`), mockToken, 'get');
528
+ expect(result).toEqual(mockResponse);
529
+ });
530
+ it('should fetch commits in date range with branch', async () => {
531
+ // Arrange
532
+ const projectId = 'project-123';
533
+ const repoId = 'repo-456';
534
+ const fromDate = '2023-01-01';
535
+ const toDate = '2023-12-31';
536
+ const branchName = 'main';
537
+ const mockResponse = { value: [{ commitId: 'abc123' }] };
538
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
539
+ // Act
540
+ const result = await gitDataProvider.GetCommitsInDateRange(projectId, repoId, fromDate, toDate, branchName);
541
+ // Assert
542
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(`itemVersion.version=${branchName}`), mockToken, 'get');
543
+ });
544
+ });
545
+ describe('GitDataProvider - GetCommitsInCommitRange', () => {
546
+ let gitDataProvider;
547
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
548
+ const mockToken = 'mock-token';
549
+ beforeEach(() => {
550
+ jest.clearAllMocks();
551
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
552
+ });
553
+ it('should fetch commits between two commit SHAs', async () => {
554
+ // Arrange
555
+ const projectId = 'project-123';
556
+ const repoId = 'repo-456';
557
+ const fromSha = 'abc123';
558
+ const toSha = 'def456';
559
+ const mockResponse = { value: [{ commitId: 'xyz789' }] };
560
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
561
+ // Act
562
+ const result = await gitDataProvider.GetCommitsInCommitRange(projectId, repoId, fromSha, toSha);
563
+ // Assert
564
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(`fromCommitId=${fromSha}&searchCriteria.toCommitId=${toSha}`), mockToken, 'get');
565
+ expect(result).toEqual(mockResponse);
566
+ });
567
+ });
568
+ describe('GitDataProvider - CreatePullRequestComment', () => {
569
+ let gitDataProvider;
570
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
571
+ const mockToken = 'mock-token';
572
+ beforeEach(() => {
573
+ jest.clearAllMocks();
574
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
575
+ });
576
+ it('should create a PR comment thread', async () => {
577
+ // Arrange
578
+ const projectName = 'project-123';
579
+ const repoId = 'repo-456';
580
+ const prId = 789;
581
+ const threads = { comments: [{ content: 'Test comment' }] };
582
+ const mockResponse = { id: 1, comments: threads.comments };
583
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
584
+ // Act
585
+ const result = await gitDataProvider.CreatePullRequestComment(projectName, repoId, prId, threads);
586
+ // Assert
587
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(`pullRequests/${prId}/threads`), mockToken, 'post', threads, null);
588
+ expect(result).toEqual(mockResponse);
589
+ });
590
+ });
591
+ describe('GitDataProvider - GetPullRequestComments', () => {
592
+ let gitDataProvider;
593
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
594
+ const mockToken = 'mock-token';
595
+ beforeEach(() => {
596
+ jest.clearAllMocks();
597
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
598
+ });
599
+ it('should fetch PR comments', async () => {
600
+ // Arrange
601
+ const projectName = 'project-123';
602
+ const repoId = 'repo-456';
603
+ const prId = 789;
604
+ const mockResponse = { value: [{ id: 1, comments: [] }] };
605
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
606
+ // Act
607
+ const result = await gitDataProvider.GetPullRequestComments(projectName, repoId, prId);
608
+ // Assert
609
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(`pullRequests/${prId}/threads`), mockToken, 'get', null, null);
610
+ expect(result).toEqual(mockResponse);
611
+ });
612
+ });
613
+ describe('GitDataProvider - GetCommitsForRepo', () => {
614
+ let gitDataProvider;
615
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
616
+ const mockToken = 'mock-token';
617
+ beforeEach(() => {
618
+ jest.clearAllMocks();
619
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
620
+ });
621
+ it('should fetch commits for repo with version identifier', async () => {
622
+ // Arrange
623
+ const projectName = 'project-123';
624
+ const repoId = 'repo-456';
625
+ const versionId = 'main';
626
+ const mockResponse = {
627
+ count: 2,
628
+ value: [
629
+ { commitId: 'abc123', comment: 'First commit', committer: { date: '2023-01-01' } },
630
+ { commitId: 'def456', comment: 'Second commit', author: { date: '2023-01-02' } },
631
+ ],
632
+ };
633
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
634
+ // Act
635
+ const result = await gitDataProvider.GetCommitsForRepo(projectName, repoId, versionId);
636
+ // Assert
637
+ expect(result).toHaveLength(2);
638
+ expect(result[0].name).toContain('abc123');
639
+ expect(result[0].value).toBe('abc123');
640
+ });
641
+ it('should return empty array when no commits', async () => {
642
+ // Arrange
643
+ const mockResponse = { count: 0, value: [] };
644
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
645
+ // Act
646
+ const result = await gitDataProvider.GetCommitsForRepo('project', 'repo');
647
+ // Assert
648
+ expect(result).toEqual([]);
649
+ });
650
+ });
651
+ describe('GitDataProvider - GetPullRequestsForRepo', () => {
652
+ let gitDataProvider;
653
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
654
+ const mockToken = 'mock-token';
655
+ beforeEach(() => {
656
+ jest.clearAllMocks();
657
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
658
+ });
659
+ it('should fetch PRs for repo', async () => {
660
+ // Arrange
661
+ const projectName = 'project-123';
662
+ const repoId = 'repo-456';
663
+ const mockResponse = { count: 2, value: [{ pullRequestId: 1 }, { pullRequestId: 2 }] };
664
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
665
+ // Act
666
+ const result = await gitDataProvider.GetPullRequestsForRepo(projectName, repoId);
667
+ // Assert
668
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining('pullrequests?status=completed'), mockToken, 'get', null, null);
669
+ expect(result).toEqual(mockResponse);
670
+ });
671
+ });
672
+ describe('GitDataProvider - GetRepoBranches', () => {
673
+ let gitDataProvider;
674
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
675
+ const mockToken = 'mock-token';
676
+ beforeEach(() => {
677
+ jest.clearAllMocks();
678
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
679
+ });
680
+ it('should fetch repo branches', async () => {
681
+ // Arrange
682
+ const projectName = 'project-123';
683
+ const repoId = 'repo-456';
684
+ const mockResponse = { value: [{ name: 'refs/heads/main' }, { name: 'refs/heads/develop' }] };
685
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
686
+ // Act
687
+ const result = await gitDataProvider.GetRepoBranches(projectName, repoId);
688
+ // Assert
689
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining('refs?searchCriteria.$top=1000&filter=heads'), mockToken, 'get', null, null);
690
+ expect(result).toEqual(mockResponse);
691
+ });
692
+ });
693
+ describe('GitDataProvider - GetPullRequestsLinkedItemsInCommitRange', () => {
694
+ let gitDataProvider;
695
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
696
+ const mockToken = 'mock-token';
697
+ beforeEach(() => {
698
+ jest.clearAllMocks();
699
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
700
+ });
701
+ it('should fetch and filter PRs with linked work items', async () => {
702
+ // Arrange
703
+ const projectId = 'project-123';
704
+ const repoId = 'repo-456';
705
+ const commitRange = { value: [{ commitId: 'commit-1' }] };
706
+ const mockPRsResponse = {
707
+ count: 1,
708
+ value: [
709
+ {
710
+ pullRequestId: 101,
711
+ lastMergeCommit: { commitId: 'commit-1' },
712
+ _links: { workItems: { href: 'https://example.com/workitems' } },
713
+ },
714
+ ],
715
+ };
716
+ const mockWorkItemsResponse = {
717
+ count: 1,
718
+ value: [{ id: 1 }],
719
+ };
720
+ const mockPopulatedWI = { id: 1, fields: { 'System.Title': 'Test WI' } };
721
+ tfs_1.TFSServices.getItemContent
722
+ .mockResolvedValueOnce(mockPRsResponse)
723
+ .mockResolvedValueOnce(mockWorkItemsResponse)
724
+ .mockResolvedValueOnce(mockPopulatedWI);
725
+ // Act
726
+ const result = await gitDataProvider.GetPullRequestsLinkedItemsInCommitRange(projectId, repoId, commitRange);
727
+ // Assert
728
+ expect(result).toHaveLength(1);
729
+ expect(result[0].workItem.id).toBe(1);
730
+ expect(result[0].pullrequest.pullRequestId).toBe(101);
731
+ });
732
+ it('should handle PRs without linked work items', async () => {
733
+ // Arrange
734
+ const projectId = 'project-123';
735
+ const repoId = 'repo-456';
736
+ const commitRange = { value: [{ commitId: 'commit-1' }] };
737
+ const mockPRsResponse = {
738
+ count: 1,
739
+ value: [
740
+ {
741
+ pullRequestId: 101,
742
+ lastMergeCommit: { commitId: 'commit-1' },
743
+ _links: {},
744
+ },
745
+ ],
746
+ };
747
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockPRsResponse);
748
+ // Act
749
+ const result = await gitDataProvider.GetPullRequestsLinkedItemsInCommitRange(projectId, repoId, commitRange);
750
+ // Assert
751
+ expect(result).toHaveLength(0);
752
+ });
753
+ });
754
+ describe('GitDataProvider - GetItemsInCommitRange', () => {
755
+ let gitDataProvider;
756
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
757
+ const mockToken = 'mock-token';
758
+ beforeEach(() => {
759
+ jest.clearAllMocks();
760
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
761
+ });
762
+ it('should process commits with work items', async () => {
763
+ // Arrange
764
+ const projectId = 'project-123';
765
+ const repoId = 'repo-456';
766
+ const commitRange = {
767
+ value: [
768
+ {
769
+ commitId: 'commit-1',
770
+ workItems: [{ id: 1 }],
771
+ },
772
+ ],
773
+ };
774
+ const mockPopulatedWI = { id: 1, fields: { 'System.Title': 'Test WI' } };
775
+ const mockPRsResponse = { count: 0, value: [] };
776
+ tfs_1.TFSServices.getItemContent
777
+ .mockResolvedValueOnce(mockPopulatedWI)
778
+ .mockResolvedValueOnce(mockPRsResponse);
779
+ // Act
780
+ const result = await gitDataProvider.GetItemsInCommitRange(projectId, repoId, commitRange, null, false);
781
+ // Assert
782
+ expect(result.commitChangesArray).toHaveLength(1);
783
+ expect(result.commitsWithNoRelations).toHaveLength(0);
784
+ });
785
+ it('should include unlinked commits when flag is true', async () => {
786
+ // Arrange
787
+ const projectId = 'project-123';
788
+ const repoId = 'repo-456';
789
+ const commitRange = {
790
+ value: [
791
+ {
792
+ commitId: 'commit-1',
793
+ workItems: [],
794
+ committer: { date: '2023-01-01', name: 'Test User' },
795
+ comment: 'Test commit',
796
+ remoteUrl: 'https://example.com/commit/1',
797
+ },
798
+ ],
799
+ };
800
+ const mockPRsResponse = { count: 0, value: [] };
801
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockPRsResponse);
802
+ // Act
803
+ const result = await gitDataProvider.GetItemsInCommitRange(projectId, repoId, commitRange, null, true);
804
+ // Assert
805
+ expect(result.commitChangesArray).toHaveLength(0);
806
+ expect(result.commitsWithNoRelations).toHaveLength(1);
807
+ expect(result.commitsWithNoRelations[0].commitId).toBe('commit-1');
808
+ });
809
+ });
810
+ describe('GitDataProvider - getItemsForPipelineRange', () => {
811
+ let gitDataProvider;
812
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
813
+ const mockToken = 'mock-token';
814
+ beforeEach(() => {
815
+ jest.clearAllMocks();
816
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
817
+ });
818
+ it('should throw error when extended commits is empty', async () => {
819
+ // Arrange
820
+ const teamProject = 'project-123';
821
+ const extendedCommits = [];
822
+ const targetRepo = { url: 'https://example.com/repo' };
823
+ const addedWorkItemByIdSet = new Set();
824
+ // Act
825
+ const result = await gitDataProvider.getItemsForPipelineRange(teamProject, extendedCommits, targetRepo, addedWorkItemByIdSet);
826
+ // Assert - should log error and return empty arrays
827
+ expect(logger_1.default.error).toHaveBeenCalledWith('extended commits cannot be empty');
828
+ expect(result.commitChangesArray).toHaveLength(0);
829
+ });
830
+ it('should process commits with work items', async () => {
831
+ // Arrange
832
+ const teamProject = 'project-123';
833
+ const extendedCommits = [
834
+ {
835
+ commit: {
836
+ commitId: 'commit-1',
837
+ workItems: [{ id: 1 }],
838
+ committer: { date: '2023-01-01', name: 'Test User' },
839
+ comment: 'Test commit',
840
+ },
841
+ },
842
+ ];
843
+ const targetRepo = { url: 'https://example.com/repo' };
844
+ const addedWorkItemByIdSet = new Set();
845
+ const mockRepoData = {
846
+ _links: { web: { href: 'https://example.com/repo-web' } },
847
+ project: { id: 'project-id' },
848
+ };
849
+ const mockPopulatedWI = { id: 1, fields: { 'System.Title': 'Test WI' } };
850
+ tfs_1.TFSServices.getItemContent
851
+ .mockResolvedValueOnce(mockRepoData)
852
+ .mockResolvedValueOnce(mockPopulatedWI);
853
+ // Act
854
+ const result = await gitDataProvider.getItemsForPipelineRange(teamProject, extendedCommits, targetRepo, addedWorkItemByIdSet);
855
+ // Assert
856
+ expect(result.commitChangesArray).toHaveLength(1);
857
+ expect(result.commitChangesArray[0].workItem.id).toBe(1);
858
+ });
859
+ it('should include unlinked commits when flag is true', async () => {
860
+ // Arrange
861
+ const teamProject = 'project-123';
862
+ const extendedCommits = [
863
+ {
864
+ commit: {
865
+ commitId: 'commit-1',
866
+ workItems: [],
867
+ committer: { date: '2023-01-01', name: 'Test User' },
868
+ comment: 'Test commit',
869
+ remoteUrl: 'https://example.com/commit/1',
870
+ },
871
+ },
872
+ ];
873
+ const targetRepo = { url: 'https://example.com/repo' };
874
+ const addedWorkItemByIdSet = new Set();
875
+ const mockRepoData = {
876
+ _links: { web: { href: 'https://example.com/repo-web' } },
877
+ project: { id: 'project-id' },
878
+ };
879
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockRepoData);
880
+ // Act
881
+ const result = await gitDataProvider.getItemsForPipelineRange(teamProject, extendedCommits, targetRepo, addedWorkItemByIdSet, undefined, true);
882
+ // Assert
883
+ expect(result.commitsWithNoRelations).toHaveLength(1);
884
+ expect(result.commitsWithNoRelations[0].commitId).toBe('commit-1');
885
+ });
886
+ it('should not add duplicate work items', async () => {
887
+ // Arrange
888
+ const teamProject = 'project-123';
889
+ const extendedCommits = [
890
+ { commit: { commitId: 'commit-1', workItems: [{ id: 1 }] } },
891
+ { commit: { commitId: 'commit-2', workItems: [{ id: 1 }] } }, // Same WI
892
+ ];
893
+ const targetRepo = { url: 'https://example.com/repo' };
894
+ const addedWorkItemByIdSet = new Set();
895
+ const mockRepoData = {
896
+ _links: { web: { href: 'https://example.com/repo-web' } },
897
+ project: { id: 'project-id' },
898
+ };
899
+ const mockPopulatedWI = { id: 1, fields: { 'System.Title': 'Test WI' } };
900
+ tfs_1.TFSServices.getItemContent
901
+ .mockResolvedValueOnce(mockRepoData)
902
+ .mockResolvedValueOnce(mockPopulatedWI)
903
+ .mockResolvedValueOnce(mockPopulatedWI);
904
+ // Act
905
+ const result = await gitDataProvider.getItemsForPipelineRange(teamProject, extendedCommits, targetRepo, addedWorkItemByIdSet);
906
+ // Assert - should only have 1 work item (no duplicates)
907
+ expect(result.commitChangesArray).toHaveLength(1);
908
+ });
909
+ });
910
+ describe('GitDataProvider - GetItemsInPullRequestRange', () => {
911
+ let gitDataProvider;
912
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
913
+ const mockToken = 'mock-token';
914
+ beforeEach(() => {
915
+ jest.clearAllMocks();
916
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
917
+ });
918
+ it('should fetch items from PRs in range', async () => {
919
+ // Arrange
920
+ const projectId = 'project-123';
921
+ const repoId = 'repo-456';
922
+ const prIds = [101];
923
+ const mockPRsResponse = {
924
+ count: 1,
925
+ value: [{ pullRequestId: 101, _links: { workItems: { href: 'https://example.com/wi1' } } }],
926
+ };
927
+ const mockWIResponse = { count: 1, value: [{ id: 1 }] };
928
+ const mockPopulatedWI = { id: 1, fields: { 'System.Title': 'WI 1' } };
929
+ tfs_1.TFSServices.getItemContent
930
+ .mockResolvedValueOnce(mockPRsResponse)
931
+ .mockResolvedValueOnce(mockWIResponse)
932
+ .mockResolvedValueOnce(mockPopulatedWI);
933
+ // Act
934
+ const result = await gitDataProvider.GetItemsInPullRequestRange(projectId, repoId, prIds);
935
+ // Assert
936
+ expect(result).toHaveLength(1);
937
+ expect(result[0].workItem.id).toBe(1);
938
+ });
939
+ });
940
+ describe('GitDataProvider - GetRepoTagsWithCommits', () => {
941
+ let gitDataProvider;
942
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
943
+ const mockToken = 'mock-token';
944
+ beforeEach(() => {
945
+ jest.clearAllMocks();
946
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
947
+ });
948
+ it('should return empty array when response is null', async () => {
949
+ // Arrange
950
+ const repoApiUrl = 'https://dev.azure.com/org/project/_apis/git/repositories/repo-id';
951
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(null);
952
+ // Act
953
+ const result = await gitDataProvider.GetRepoTagsWithCommits(repoApiUrl);
954
+ // Assert
955
+ expect(result).toEqual([]);
956
+ });
957
+ it('should return empty array when no tags exist', async () => {
958
+ // Arrange
959
+ const repoApiUrl = 'https://dev.azure.com/org/project/_apis/git/repositories/repo-id';
960
+ const mockTagsResponse = { count: 0, value: [] };
961
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockTagsResponse);
962
+ // Act
963
+ const result = await gitDataProvider.GetRepoTagsWithCommits(repoApiUrl);
964
+ // Assert
965
+ expect(result).toEqual([]);
966
+ });
967
+ });
968
+ describe('GitDataProvider - GetCommitBatch', () => {
969
+ let gitDataProvider;
970
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
971
+ const mockToken = 'mock-token';
972
+ beforeEach(() => {
973
+ jest.clearAllMocks();
974
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
975
+ // Mock postRequest
976
+ tfs_1.TFSServices.postRequest = jest.fn();
977
+ });
978
+ it('should fetch commits in batches', async () => {
979
+ // Arrange
980
+ const gitUrl = 'https://dev.azure.com/org/project/_apis/git/repositories/repo-id';
981
+ const itemVersion = { version: 'main', versionType: 'branch' };
982
+ const compareVersion = { version: 'v1.0.0', versionType: 'tag' };
983
+ const mockCommitsResponse = {
984
+ data: {
985
+ count: 1,
986
+ value: [
987
+ {
988
+ commitId: 'abc123',
989
+ committer: { name: 'Test User', date: '2023-01-01T00:00:00Z' },
990
+ comment: 'Test commit',
991
+ },
992
+ ],
993
+ },
994
+ };
995
+ const mockEmptyResponse = { data: { count: 0, value: [] } };
996
+ tfs_1.TFSServices.postRequest
997
+ .mockResolvedValueOnce(mockCommitsResponse)
998
+ .mockResolvedValueOnce(mockEmptyResponse);
999
+ // Act
1000
+ const result = await gitDataProvider.GetCommitBatch(gitUrl, itemVersion, compareVersion);
1001
+ // Assert
1002
+ expect(result).toHaveLength(1);
1003
+ expect(result[0].commit.commitId).toBe('abc123');
1004
+ expect(result[0].committerName).toBe('Test User');
1005
+ });
1006
+ it('should handle empty commits response', async () => {
1007
+ // Arrange
1008
+ const gitUrl = 'https://dev.azure.com/org/project/_apis/git/repositories/repo-id';
1009
+ const itemVersion = { version: 'main', versionType: 'branch' };
1010
+ const compareVersion = { version: 'v1.0.0', versionType: 'tag' };
1011
+ const mockEmptyResponse = { data: { count: 0, value: [] } };
1012
+ tfs_1.TFSServices.postRequest.mockResolvedValueOnce(mockEmptyResponse);
1013
+ // Act
1014
+ const result = await gitDataProvider.GetCommitBatch(gitUrl, itemVersion, compareVersion);
1015
+ // Assert
1016
+ expect(result).toHaveLength(0);
1017
+ });
1018
+ it('should include specificItemPath when provided', async () => {
1019
+ // Arrange
1020
+ const gitUrl = 'https://dev.azure.com/org/project/_apis/git/repositories/repo-id';
1021
+ const itemVersion = { version: 'main', versionType: 'branch' };
1022
+ const compareVersion = { version: 'v1.0.0', versionType: 'tag' };
1023
+ const specificItemPath = '/src/file.ts';
1024
+ const mockEmptyResponse = { data: { count: 0, value: [] } };
1025
+ tfs_1.TFSServices.postRequest.mockResolvedValueOnce(mockEmptyResponse);
1026
+ // Act
1027
+ await gitDataProvider.GetCommitBatch(gitUrl, itemVersion, compareVersion, specificItemPath);
1028
+ // Assert
1029
+ expect(tfs_1.TFSServices.postRequest).toHaveBeenCalledWith(expect.any(String), mockToken, undefined, expect.objectContaining({
1030
+ itemPath: specificItemPath,
1031
+ historyMode: 'fullHistory',
1032
+ }));
1033
+ });
1034
+ });
1035
+ describe('GitDataProvider - getSubmodulesData', () => {
1036
+ let gitDataProvider;
1037
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
1038
+ const mockToken = 'mock-token';
1039
+ beforeEach(() => {
1040
+ jest.clearAllMocks();
1041
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
1042
+ });
1043
+ it('should return empty array when no .gitmodules file exists', async () => {
1044
+ // Arrange
1045
+ const projectName = 'project-123';
1046
+ const repoId = 'repo-456';
1047
+ const targetVersion = { version: 'main', versionType: 'branch' };
1048
+ const sourceVersion = { version: 'v1.0.0', versionType: 'tag' };
1049
+ const allCommitsExtended = [];
1050
+ // Mock GetFileFromGitRepo to return undefined (no .gitmodules)
1051
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce({});
1052
+ // Act
1053
+ const result = await gitDataProvider.getSubmodulesData(projectName, repoId, targetVersion, sourceVersion, allCommitsExtended);
1054
+ // Assert
1055
+ expect(result).toEqual([]);
1056
+ });
1057
+ });
1058
+ describe('GitDataProvider - GetRepoTagsWithCommits (extended)', () => {
1059
+ let gitDataProvider;
1060
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
1061
+ const mockToken = 'mock-token';
1062
+ beforeEach(() => {
1063
+ jest.clearAllMocks();
1064
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
1065
+ });
1066
+ it('should fetch tags with commit dates', async () => {
1067
+ // Arrange
1068
+ const repoApiUrl = 'https://dev.azure.com/org/project/_apis/git/repositories/repo-id';
1069
+ const mockTagsResponse = {
1070
+ count: 1,
1071
+ value: [{ name: 'refs/tags/v1.0.0', objectId: 'abc123', peeledObjectId: 'def456' }],
1072
+ };
1073
+ const mockCommitResponse = { committer: { date: '2023-01-01' } };
1074
+ tfs_1.TFSServices.getItemContent
1075
+ .mockResolvedValueOnce(mockTagsResponse)
1076
+ .mockResolvedValueOnce(mockCommitResponse);
1077
+ // Act
1078
+ const result = await gitDataProvider.GetRepoTagsWithCommits(repoApiUrl);
1079
+ // Assert
1080
+ expect(result).toHaveLength(1);
1081
+ expect(result[0].name).toBe('v1.0.0');
1082
+ expect(result[0].commitId).toBe('def456');
1083
+ expect(result[0].date).toBe('2023-01-01');
1084
+ });
1085
+ it('should skip tags without commitId', async () => {
1086
+ // Arrange
1087
+ const repoApiUrl = 'https://dev.azure.com/org/project/_apis/git/repositories/repo-id';
1088
+ const mockTagsResponse = {
1089
+ count: 2,
1090
+ value: [
1091
+ { name: 'refs/tags/v1.0.0' }, // No objectId or peeledObjectId
1092
+ { name: 'refs/tags/v2.0.0', objectId: 'abc123' },
1093
+ ],
1094
+ };
1095
+ const mockCommitResponse = { committer: { date: '2023-01-01' } };
1096
+ tfs_1.TFSServices.getItemContent
1097
+ .mockResolvedValueOnce(mockTagsResponse)
1098
+ .mockResolvedValueOnce(mockCommitResponse);
1099
+ // Act
1100
+ const result = await gitDataProvider.GetRepoTagsWithCommits(repoApiUrl);
1101
+ // Assert
1102
+ expect(result).toHaveLength(1);
1103
+ expect(result[0].name).toBe('v2.0.0');
1104
+ });
1105
+ it('should handle commit fetch failure gracefully', async () => {
1106
+ // Arrange
1107
+ const repoApiUrl = 'https://dev.azure.com/org/project/_apis/git/repositories/repo-id';
1108
+ const mockTagsResponse = {
1109
+ count: 1,
1110
+ value: [{ name: 'refs/tags/v1.0.0', objectId: 'abc123' }],
1111
+ };
1112
+ tfs_1.TFSServices.getItemContent
1113
+ .mockResolvedValueOnce(mockTagsResponse)
1114
+ .mockRejectedValueOnce(new Error('Commit not found'));
1115
+ // Act
1116
+ const result = await gitDataProvider.GetRepoTagsWithCommits(repoApiUrl);
1117
+ // Assert
1118
+ expect(result).toHaveLength(1);
1119
+ expect(result[0].date).toBeUndefined();
1120
+ });
1121
+ it('should use author date when committer date is missing', async () => {
1122
+ // Arrange
1123
+ const repoApiUrl = 'https://dev.azure.com/org/project/_apis/git/repositories/repo-id';
1124
+ const mockTagsResponse = {
1125
+ count: 1,
1126
+ value: [{ name: 'refs/tags/v1.0.0', objectId: 'abc123' }],
1127
+ };
1128
+ const mockCommitResponse = { author: { date: '2023-02-01' } }; // No committer
1129
+ tfs_1.TFSServices.getItemContent
1130
+ .mockResolvedValueOnce(mockTagsResponse)
1131
+ .mockResolvedValueOnce(mockCommitResponse);
1132
+ // Act
1133
+ const result = await gitDataProvider.GetRepoTagsWithCommits(repoApiUrl);
1134
+ // Assert
1135
+ expect(result[0].date).toBe('2023-02-01');
1136
+ });
1137
+ });
1138
+ describe('GitDataProvider - createLinkedRelatedItemsForSVD (via GetItemsInCommitRange)', () => {
1139
+ let gitDataProvider;
1140
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
1141
+ const mockToken = 'mock-token';
1142
+ beforeEach(() => {
1143
+ jest.clearAllMocks();
1144
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
1145
+ });
1146
+ it('should create linked items for requirements with Affects relationship', async () => {
1147
+ // Arrange
1148
+ const projectId = 'project-123';
1149
+ const repoId = 'repo-456';
1150
+ const commitRange = {
1151
+ value: [
1152
+ {
1153
+ commitId: 'commit-1',
1154
+ workItems: [{ id: 1 }],
1155
+ },
1156
+ ],
1157
+ };
1158
+ const linkedWiOptions = {
1159
+ isEnabled: true,
1160
+ linkedWiTypes: 'reqOnly',
1161
+ linkedWiRelationship: 'affectsOnly',
1162
+ };
1163
+ const mockPopulatedWI = {
1164
+ id: 1,
1165
+ fields: { 'System.Title': 'Test WI' },
1166
+ relations: [
1167
+ {
1168
+ url: 'https://example.com/workItems/2',
1169
+ rel: 'System.LinkTypes.Affects-Forward',
1170
+ attributes: { name: 'Affects' },
1171
+ },
1172
+ ],
1173
+ };
1174
+ const mockRelatedWI = {
1175
+ id: 2,
1176
+ fields: {
1177
+ 'System.WorkItemType': 'Requirement',
1178
+ 'System.Title': 'Related Requirement',
1179
+ },
1180
+ _links: { html: { href: 'https://example.com/wi/2' } },
1181
+ };
1182
+ const mockPRsResponse = { count: 0, value: [] };
1183
+ tfs_1.TFSServices.getItemContent
1184
+ .mockResolvedValueOnce(mockPopulatedWI)
1185
+ .mockResolvedValueOnce(mockRelatedWI)
1186
+ .mockResolvedValueOnce(mockPRsResponse);
1187
+ // Act
1188
+ const result = await gitDataProvider.GetItemsInCommitRange(projectId, repoId, commitRange, linkedWiOptions, false);
1189
+ // Assert
1190
+ expect(result.commitChangesArray).toHaveLength(1);
1191
+ expect(result.commitChangesArray[0].linkedItems).toBeDefined();
1192
+ });
1193
+ it('should filter out non-workItem relations', async () => {
1194
+ // Arrange
1195
+ const projectId = 'project-123';
1196
+ const repoId = 'repo-456';
1197
+ const commitRange = {
1198
+ value: [
1199
+ {
1200
+ commitId: 'commit-1',
1201
+ workItems: [{ id: 1 }],
1202
+ },
1203
+ ],
1204
+ };
1205
+ const linkedWiOptions = {
1206
+ isEnabled: true,
1207
+ linkedWiTypes: 'both',
1208
+ linkedWiRelationship: 'both',
1209
+ };
1210
+ const mockPopulatedWI = {
1211
+ id: 1,
1212
+ fields: { 'System.Title': 'Test WI' },
1213
+ relations: [
1214
+ {
1215
+ url: 'https://example.com/attachments/file.txt', // Not a workItem
1216
+ rel: 'AttachedFile',
1217
+ attributes: {},
1218
+ },
1219
+ ],
1220
+ };
1221
+ const mockPRsResponse = { count: 0, value: [] };
1222
+ tfs_1.TFSServices.getItemContent
1223
+ .mockResolvedValueOnce(mockPopulatedWI)
1224
+ .mockResolvedValueOnce(mockPRsResponse);
1225
+ // Act
1226
+ const result = await gitDataProvider.GetItemsInCommitRange(projectId, repoId, commitRange, linkedWiOptions, false);
1227
+ // Assert
1228
+ expect(result.commitChangesArray).toHaveLength(1);
1229
+ expect(result.commitChangesArray[0].linkedItems).toEqual([]);
1230
+ });
1231
+ it('should handle linkedWiTypes=none', async () => {
1232
+ // Arrange
1233
+ const projectId = 'project-123';
1234
+ const repoId = 'repo-456';
1235
+ const commitRange = {
1236
+ value: [
1237
+ {
1238
+ commitId: 'commit-1',
1239
+ workItems: [{ id: 1 }],
1240
+ },
1241
+ ],
1242
+ };
1243
+ const linkedWiOptions = {
1244
+ isEnabled: true,
1245
+ linkedWiTypes: 'none',
1246
+ linkedWiRelationship: 'both',
1247
+ };
1248
+ const mockPopulatedWI = {
1249
+ id: 1,
1250
+ fields: { 'System.Title': 'Test WI' },
1251
+ relations: [
1252
+ {
1253
+ url: 'https://example.com/workItems/2',
1254
+ rel: 'System.LinkTypes.Affects-Forward',
1255
+ attributes: { name: 'Affects' },
1256
+ },
1257
+ ],
1258
+ };
1259
+ const mockPRsResponse = { count: 0, value: [] };
1260
+ tfs_1.TFSServices.getItemContent
1261
+ .mockResolvedValueOnce(mockPopulatedWI)
1262
+ .mockResolvedValueOnce(mockPRsResponse);
1263
+ // Act
1264
+ const result = await gitDataProvider.GetItemsInCommitRange(projectId, repoId, commitRange, linkedWiOptions, false);
1265
+ // Assert
1266
+ expect(result.commitChangesArray[0].linkedItems).toEqual([]);
1267
+ });
1268
+ it('should handle Feature type with featureOnly option', async () => {
1269
+ // Arrange
1270
+ const projectId = 'project-123';
1271
+ const repoId = 'repo-456';
1272
+ const commitRange = {
1273
+ value: [
1274
+ {
1275
+ commitId: 'commit-1',
1276
+ workItems: [{ id: 1 }],
1277
+ },
1278
+ ],
1279
+ };
1280
+ const linkedWiOptions = {
1281
+ isEnabled: true,
1282
+ linkedWiTypes: 'featureOnly',
1283
+ linkedWiRelationship: 'coversOnly',
1284
+ };
1285
+ const mockPopulatedWI = {
1286
+ id: 1,
1287
+ fields: { 'System.Title': 'Test WI' },
1288
+ relations: [
1289
+ {
1290
+ url: 'https://example.com/workItems/2',
1291
+ rel: 'System.LinkTypes.CoveredBy-Forward',
1292
+ attributes: { name: 'CoveredBy' },
1293
+ },
1294
+ ],
1295
+ };
1296
+ const mockRelatedWI = {
1297
+ id: 2,
1298
+ fields: {
1299
+ 'System.WorkItemType': 'Feature',
1300
+ 'System.Title': 'Related Feature',
1301
+ },
1302
+ _links: { html: { href: 'https://example.com/wi/2' } },
1303
+ };
1304
+ const mockPRsResponse = { count: 0, value: [] };
1305
+ tfs_1.TFSServices.getItemContent
1306
+ .mockResolvedValueOnce(mockPopulatedWI)
1307
+ .mockResolvedValueOnce(mockRelatedWI)
1308
+ .mockResolvedValueOnce(mockPRsResponse);
1309
+ // Act
1310
+ const result = await gitDataProvider.GetItemsInCommitRange(projectId, repoId, commitRange, linkedWiOptions, false);
1311
+ // Assert
1312
+ expect(result.commitChangesArray).toHaveLength(1);
1313
+ });
1314
+ });
1315
+ describe('GitDataProvider - GetRepoReferences (extended)', () => {
1316
+ let gitDataProvider;
1317
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
1318
+ const mockToken = 'mock-token';
1319
+ beforeEach(() => {
1320
+ jest.clearAllMocks();
1321
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
1322
+ });
1323
+ it('should sort tags by commit date (most recent first)', async () => {
1324
+ // Arrange
1325
+ const projectId = 'project-123';
1326
+ const repoId = 'repo-456';
1327
+ const mockTagsResponse = {
1328
+ count: 2,
1329
+ value: [
1330
+ { name: 'refs/tags/v1.0.0', objectId: 'abc123' },
1331
+ { name: 'refs/tags/v2.0.0', objectId: 'def456', peeledObjectId: 'ghi789' },
1332
+ ],
1333
+ };
1334
+ const mockCommit1 = { committer: { date: '2023-01-01' } };
1335
+ const mockCommit2 = { committer: { date: '2023-06-01' } };
1336
+ tfs_1.TFSServices.getItemContent
1337
+ .mockResolvedValueOnce(mockTagsResponse)
1338
+ .mockResolvedValueOnce(mockCommit1)
1339
+ .mockResolvedValueOnce(mockCommit2);
1340
+ // Act
1341
+ const result = await gitDataProvider.GetRepoReferences(projectId, repoId, 'tag');
1342
+ // Assert
1343
+ expect(result).toHaveLength(2);
1344
+ // v2.0.0 should be first (more recent)
1345
+ expect(result[0].name).toBe('v2.0.0');
1346
+ expect(result[1].name).toBe('v1.0.0');
1347
+ });
1348
+ it('should sort branches by commit date', async () => {
1349
+ // Arrange
1350
+ const projectId = 'project-123';
1351
+ const repoId = 'repo-456';
1352
+ const mockBranchesResponse = {
1353
+ count: 2,
1354
+ value: [
1355
+ { name: 'refs/heads/main', objectId: 'abc123' },
1356
+ { name: 'refs/heads/develop', objectId: 'def456' },
1357
+ ],
1358
+ };
1359
+ const mockCommit1 = { committer: { date: '2023-01-01' } };
1360
+ const mockCommit2 = { committer: { date: '2023-06-01' } };
1361
+ tfs_1.TFSServices.getItemContent
1362
+ .mockResolvedValueOnce(mockBranchesResponse)
1363
+ .mockResolvedValueOnce(mockCommit1)
1364
+ .mockResolvedValueOnce(mockCommit2);
1365
+ // Act
1366
+ const result = await gitDataProvider.GetRepoReferences(projectId, repoId, 'branch');
1367
+ // Assert
1368
+ expect(result).toHaveLength(2);
1369
+ // develop should be first (more recent)
1370
+ expect(result[0].name).toBe('develop');
1371
+ expect(result[1].name).toBe('main');
1372
+ });
1373
+ it('should handle commit resolution failure in tag sorting', async () => {
1374
+ // Arrange
1375
+ const projectId = 'project-123';
1376
+ const repoId = 'repo-456';
1377
+ const mockTagsResponse = {
1378
+ count: 1,
1379
+ value: [{ name: 'refs/tags/v1.0.0', objectId: 'abc123' }],
1380
+ };
1381
+ tfs_1.TFSServices.getItemContent
1382
+ .mockResolvedValueOnce(mockTagsResponse)
1383
+ .mockRejectedValueOnce(new Error('Commit not found'));
1384
+ // Act
1385
+ const result = await gitDataProvider.GetRepoReferences(projectId, repoId, 'tag');
1386
+ // Assert
1387
+ expect(result).toHaveLength(1);
1388
+ expect(result[0].date).toBeUndefined();
1389
+ });
1390
+ it('should set date undefined when tag commit has no committer/author date', async () => {
1391
+ const projectId = 'project-123';
1392
+ const repoId = 'repo-456';
1393
+ const mockTagsResponse = {
1394
+ count: 1,
1395
+ value: [{ name: 'refs/tags/v0.0.1', objectId: 'abc123' }],
1396
+ };
1397
+ tfs_1.TFSServices.getItemContent
1398
+ .mockResolvedValueOnce(mockTagsResponse)
1399
+ .mockResolvedValueOnce({});
1400
+ const result = await gitDataProvider.GetRepoReferences(projectId, repoId, 'tag');
1401
+ expect(result).toHaveLength(1);
1402
+ expect(result[0]).toEqual(expect.objectContaining({ name: 'v0.0.1', date: undefined }));
1403
+ });
1404
+ it('should set date undefined when branch commit cannot be resolved to a date', async () => {
1405
+ const projectId = 'project-123';
1406
+ const repoId = 'repo-456';
1407
+ const mockBranchesResponse = {
1408
+ count: 1,
1409
+ value: [{ name: 'refs/heads/feature', objectId: 'abc123' }],
1410
+ };
1411
+ tfs_1.TFSServices.getItemContent
1412
+ .mockResolvedValueOnce(mockBranchesResponse)
1413
+ .mockResolvedValueOnce(null);
1414
+ const result = await gitDataProvider.GetRepoReferences(projectId, repoId, 'branch');
1415
+ expect(result).toHaveLength(1);
1416
+ expect(result[0]).toEqual(expect.objectContaining({ name: 'feature', date: undefined }));
1417
+ });
1418
+ });
1419
+ describe('GitDataProvider - duplicate work item removal', () => {
1420
+ let gitDataProvider;
1421
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
1422
+ const mockToken = 'mock-token';
1423
+ beforeEach(() => {
1424
+ jest.clearAllMocks();
1425
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
1426
+ });
1427
+ it('should remove duplicate work items in GetItemsInCommitRange', async () => {
1428
+ // Arrange
1429
+ const projectId = 'project-123';
1430
+ const repoId = 'repo-456';
1431
+ const commitRange = {
1432
+ value: [
1433
+ { commitId: 'commit-1', workItems: [{ id: 1 }] },
1434
+ { commitId: 'commit-2', workItems: [{ id: 1 }] },
1435
+ ],
1436
+ };
1437
+ const mockPopulatedWI = { id: 1, fields: { 'System.Title': 'Test WI' } };
1438
+ const mockPRsResponse = { count: 0, value: [] };
1439
+ tfs_1.TFSServices.getItemContent
1440
+ .mockResolvedValueOnce(mockPopulatedWI)
1441
+ .mockResolvedValueOnce(mockPopulatedWI)
1442
+ .mockResolvedValueOnce(mockPRsResponse);
1443
+ // Act
1444
+ const result = await gitDataProvider.GetItemsInCommitRange(projectId, repoId, commitRange, null, false);
1445
+ // Assert
1446
+ expect(result.commitChangesArray.length).toBeLessThanOrEqual(2);
1447
+ });
1448
+ });
1449
+ describe('GitDataProvider - version encoding edge cases', () => {
1450
+ let gitDataProvider;
1451
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
1452
+ const mockToken = 'mock-token';
1453
+ beforeEach(() => {
1454
+ jest.clearAllMocks();
1455
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
1456
+ });
1457
+ it('should handle version with null gracefully in GetFileFromGitRepo', async () => {
1458
+ // Arrange
1459
+ const projectName = 'project-123';
1460
+ const repoId = 'repo-456';
1461
+ const fileName = 'test.txt';
1462
+ const version = { version: null, versionType: 'branch' };
1463
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce({ content: 'file content' });
1464
+ // Act
1465
+ const result = await gitDataProvider.GetFileFromGitRepo(projectName, repoId, fileName, version);
1466
+ // Assert
1467
+ expect(result).toBe('file content');
1468
+ });
1469
+ it('should handle version with null gracefully in CheckIfItemExist', async () => {
1470
+ // Arrange
1471
+ const gitApiUrl = 'https://dev.azure.com/org/project/_apis/git/repositories/repo-id';
1472
+ const itemPath = 'path/to/file.txt';
1473
+ const version = { version: null, versionType: 'branch' };
1474
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce({ path: itemPath });
1475
+ // Act
1476
+ const result = await gitDataProvider.CheckIfItemExist(gitApiUrl, itemPath, version);
1477
+ // Assert
1478
+ expect(result).toBe(true);
1479
+ });
1480
+ });
1481
+ describe('GitDataProvider - GetCommitsForRepo edge cases', () => {
1482
+ let gitDataProvider;
1483
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
1484
+ const mockToken = 'mock-token';
1485
+ beforeEach(() => {
1486
+ jest.clearAllMocks();
1487
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
1488
+ });
1489
+ it('should handle commits without committer date', async () => {
1490
+ // Arrange
1491
+ const projectName = 'project-123';
1492
+ const repoId = 'repo-456';
1493
+ const mockResponse = {
1494
+ count: 1,
1495
+ value: [{ commitId: 'abc123', comment: 'Test commit' }],
1496
+ };
1497
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
1498
+ // Act
1499
+ const result = await gitDataProvider.GetCommitsForRepo(projectName, repoId);
1500
+ // Assert
1501
+ expect(result).toHaveLength(1);
1502
+ expect(result[0].date).toBeUndefined();
1503
+ });
1504
+ it('should fetch commits without version identifier', async () => {
1505
+ // Arrange
1506
+ const projectName = 'project-123';
1507
+ const repoId = 'repo-456';
1508
+ const mockResponse = {
1509
+ count: 1,
1510
+ value: [{ commitId: 'abc123', comment: 'Test', committer: { date: '2023-01-01' } }],
1511
+ };
1512
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockResponse);
1513
+ // Act
1514
+ const result = await gitDataProvider.GetCommitsForRepo(projectName, repoId, '');
1515
+ // Assert
1516
+ expect(result).toHaveLength(1);
1517
+ });
1518
+ });
1519
+ describe('GitDataProvider - GetItemsInPullRequestRange edge cases', () => {
1520
+ let gitDataProvider;
1521
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
1522
+ const mockToken = 'mock-token';
1523
+ beforeEach(() => {
1524
+ jest.clearAllMocks();
1525
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
1526
+ });
1527
+ it('should handle PR without workItems link', async () => {
1528
+ // Arrange
1529
+ const projectId = 'project-123';
1530
+ const repoId = 'repo-456';
1531
+ const prIds = [101];
1532
+ const mockPRsResponse = {
1533
+ count: 1,
1534
+ value: [{ pullRequestId: 101, _links: {} }],
1535
+ };
1536
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce(mockPRsResponse);
1537
+ // Act
1538
+ const result = await gitDataProvider.GetItemsInPullRequestRange(projectId, repoId, prIds);
1539
+ // Assert
1540
+ expect(result).toHaveLength(0);
1541
+ });
1542
+ it('should handle errors when fetching work items', async () => {
1543
+ // Arrange
1544
+ const projectId = 'project-123';
1545
+ const repoId = 'repo-456';
1546
+ const prIds = [101];
1547
+ const mockPRsResponse = {
1548
+ count: 1,
1549
+ value: [{ pullRequestId: 101, _links: { workItems: { href: 'https://example.com/wi' } } }],
1550
+ };
1551
+ tfs_1.TFSServices.getItemContent
1552
+ .mockResolvedValueOnce(mockPRsResponse)
1553
+ .mockRejectedValueOnce(new Error('Failed to fetch'));
1554
+ // Act
1555
+ const result = await gitDataProvider.GetItemsInPullRequestRange(projectId, repoId, prIds);
1556
+ // Assert
1557
+ expect(result).toHaveLength(0);
1558
+ expect(logger_1.default.error).toHaveBeenCalled();
1559
+ });
1560
+ });
1561
+ describe('GitDataProvider - getSubmodulesData', () => {
1562
+ let gitDataProvider;
1563
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
1564
+ const mockToken = 'mock-token';
1565
+ beforeEach(() => {
1566
+ jest.clearAllMocks();
1567
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
1568
+ });
1569
+ it('should parse .gitmodules file and return submodule data', async () => {
1570
+ // Arrange
1571
+ const projectName = 'project-123';
1572
+ const repoId = 'repo-456';
1573
+ const targetVersion = { version: 'main', versionType: 'branch' };
1574
+ const sourceVersion = { version: 'v1.0.0', versionType: 'tag' };
1575
+ const allCommitsExtended = [];
1576
+ const gitModulesContent = `[submodule "libs/common"]
1577
+ path = libs/common
1578
+ url = https://dev.azure.com/org/project/_git/common-lib`;
1579
+ // Mock GetFileFromGitRepo calls
1580
+ tfs_1.TFSServices.getItemContent
1581
+ .mockResolvedValueOnce({ content: gitModulesContent }) // .gitmodules file
1582
+ .mockResolvedValueOnce({ content: 'target-sha-123' }) // target SHA
1583
+ .mockResolvedValueOnce({ content: 'source-sha-456' }); // source SHA
1584
+ // Act
1585
+ const result = await gitDataProvider.getSubmodulesData(projectName, repoId, targetVersion, sourceVersion, allCommitsExtended);
1586
+ // Assert
1587
+ expect(result).toHaveLength(1);
1588
+ expect(result[0].gitSubModuleName).toBe('libs_common');
1589
+ expect(result[0].targetSha1).toBe('target-sha-123');
1590
+ expect(result[0].sourceSha1).toBe('source-sha-456');
1591
+ });
1592
+ it('should handle .gitmodules with CRLF line endings', async () => {
1593
+ // Arrange
1594
+ const projectName = 'project-123';
1595
+ const repoId = 'repo-456';
1596
+ const targetVersion = { version: 'main', versionType: 'branch' };
1597
+ const sourceVersion = { version: 'v1.0.0', versionType: 'tag' };
1598
+ const allCommitsExtended = [];
1599
+ const gitModulesContent = `[submodule "libs/common"]\r\n\tpath = libs/common\r\n\turl = https://example.com/repo`;
1600
+ tfs_1.TFSServices.getItemContent
1601
+ .mockResolvedValueOnce({ content: gitModulesContent })
1602
+ .mockResolvedValueOnce({ content: 'target-sha' })
1603
+ .mockResolvedValueOnce({ content: 'source-sha' });
1604
+ // Act
1605
+ const result = await gitDataProvider.getSubmodulesData(projectName, repoId, targetVersion, sourceVersion, allCommitsExtended);
1606
+ // Assert
1607
+ expect(result).toHaveLength(1);
1608
+ });
1609
+ it('should handle relative URL paths in submodules', async () => {
1610
+ // Arrange
1611
+ const projectName = 'project-123';
1612
+ const repoId = 'repo-456';
1613
+ const targetVersion = { version: 'main', versionType: 'branch' };
1614
+ const sourceVersion = { version: 'v1.0.0', versionType: 'tag' };
1615
+ const allCommitsExtended = [];
1616
+ const gitModulesContent = `[submodule "libs/common"]
1617
+ path = libs/common
1618
+ url = ../common-lib`;
1619
+ tfs_1.TFSServices.getItemContent
1620
+ .mockResolvedValueOnce({ content: gitModulesContent })
1621
+ .mockResolvedValueOnce({ content: 'target-sha' })
1622
+ .mockResolvedValueOnce({ content: 'source-sha' });
1623
+ // Act
1624
+ const result = await gitDataProvider.getSubmodulesData(projectName, repoId, targetVersion, sourceVersion, allCommitsExtended);
1625
+ // Assert
1626
+ expect(result).toHaveLength(1);
1627
+ expect(result[0].gitSubRepoUrl).toContain('common-lib');
1628
+ });
1629
+ it('should skip submodule when source SHA not found', async () => {
1630
+ // Arrange
1631
+ const projectName = 'project-123';
1632
+ const repoId = 'repo-456';
1633
+ const targetVersion = { version: 'main', versionType: 'branch' };
1634
+ const sourceVersion = { version: 'v1.0.0', versionType: 'tag' };
1635
+ const allCommitsExtended = [];
1636
+ const gitModulesContent = `[submodule "libs/common"]
1637
+ path = libs/common
1638
+ url = https://example.com/repo`;
1639
+ tfs_1.TFSServices.getItemContent
1640
+ .mockResolvedValueOnce({ content: gitModulesContent })
1641
+ .mockResolvedValueOnce({ content: 'target-sha' })
1642
+ .mockResolvedValueOnce({ content: undefined }); // source not found
1643
+ // Act
1644
+ const result = await gitDataProvider.getSubmodulesData(projectName, repoId, targetVersion, sourceVersion, allCommitsExtended);
1645
+ // Assert
1646
+ expect(result).toHaveLength(0);
1647
+ expect(logger_1.default.warn).toHaveBeenCalled();
1648
+ });
1649
+ it('should skip submodule when target SHA not found', async () => {
1650
+ // Arrange
1651
+ const projectName = 'project-123';
1652
+ const repoId = 'repo-456';
1653
+ const targetVersion = { version: 'main', versionType: 'branch' };
1654
+ const sourceVersion = { version: 'v1.0.0', versionType: 'tag' };
1655
+ const allCommitsExtended = [];
1656
+ const gitModulesContent = `[submodule "libs/common"]
1657
+ path = libs/common
1658
+ url = https://example.com/repo`;
1659
+ tfs_1.TFSServices.getItemContent
1660
+ .mockResolvedValueOnce({ content: gitModulesContent })
1661
+ .mockResolvedValueOnce({ content: undefined }) // target not found
1662
+ .mockResolvedValueOnce({ content: 'source-sha' });
1663
+ // Act
1664
+ const result = await gitDataProvider.getSubmodulesData(projectName, repoId, targetVersion, sourceVersion, allCommitsExtended);
1665
+ // Assert
1666
+ expect(result).toHaveLength(0);
1667
+ expect(logger_1.default.warn).toHaveBeenCalled();
1668
+ });
1669
+ it('should skip submodule when source and target SHA are the same', async () => {
1670
+ // Arrange
1671
+ const projectName = 'project-123';
1672
+ const repoId = 'repo-456';
1673
+ const targetVersion = { version: 'main', versionType: 'branch' };
1674
+ const sourceVersion = { version: 'v1.0.0', versionType: 'tag' };
1675
+ const allCommitsExtended = [];
1676
+ const gitModulesContent = `[submodule "libs/common"]
1677
+ path = libs/common
1678
+ url = https://example.com/repo`;
1679
+ tfs_1.TFSServices.getItemContent
1680
+ .mockResolvedValueOnce({ content: gitModulesContent })
1681
+ .mockResolvedValueOnce({ content: 'same-sha' })
1682
+ .mockResolvedValueOnce({ content: 'same-sha' });
1683
+ // Act
1684
+ const result = await gitDataProvider.getSubmodulesData(projectName, repoId, targetVersion, sourceVersion, allCommitsExtended);
1685
+ // Assert
1686
+ expect(result).toHaveLength(0);
1687
+ expect(logger_1.default.warn).toHaveBeenCalled();
1688
+ });
1689
+ it('should search commits for source SHA when not found initially', async () => {
1690
+ // Arrange
1691
+ const projectName = 'project-123';
1692
+ const repoId = 'repo-456';
1693
+ const targetVersion = { version: 'main', versionType: 'branch' };
1694
+ const sourceVersion = { version: 'v1.0.0', versionType: 'tag' };
1695
+ const allCommitsExtended = [{ commit: { commitId: 'commit-1' } }, { commit: { commitId: 'commit-2' } }];
1696
+ const gitModulesContent = `[submodule "libs/common"]
1697
+ path = libs/common
1698
+ url = https://example.com/repo`;
1699
+ tfs_1.TFSServices.getItemContent
1700
+ .mockResolvedValueOnce({ content: gitModulesContent })
1701
+ .mockResolvedValueOnce({ content: 'target-sha' })
1702
+ .mockResolvedValueOnce({ content: undefined }) // source not found initially
1703
+ .mockResolvedValueOnce({ content: 'source-sha-from-commit' }); // found in commit search
1704
+ // Act
1705
+ const result = await gitDataProvider.getSubmodulesData(projectName, repoId, targetVersion, sourceVersion, allCommitsExtended);
1706
+ // Assert
1707
+ expect(result).toHaveLength(1);
1708
+ expect(result[0].sourceSha1).toBe('source-sha-from-commit');
1709
+ });
1710
+ it('should handle commits with direct commitId property', async () => {
1711
+ // Arrange
1712
+ const projectName = 'project-123';
1713
+ const repoId = 'repo-456';
1714
+ const targetVersion = { version: 'main', versionType: 'branch' };
1715
+ const sourceVersion = { version: 'v1.0.0', versionType: 'tag' };
1716
+ const allCommitsExtended = [{ commitId: 'direct-commit-1' }, { commitId: 'direct-commit-2' }];
1717
+ const gitModulesContent = `[submodule "libs/common"]
1718
+ path = libs/common
1719
+ url = https://example.com/repo`;
1720
+ tfs_1.TFSServices.getItemContent
1721
+ .mockResolvedValueOnce({ content: gitModulesContent })
1722
+ .mockResolvedValueOnce({ content: 'target-sha' })
1723
+ .mockResolvedValueOnce({ content: undefined })
1724
+ .mockResolvedValueOnce({ content: 'source-sha' });
1725
+ // Act
1726
+ const result = await gitDataProvider.getSubmodulesData(projectName, repoId, targetVersion, sourceVersion, allCommitsExtended);
1727
+ // Assert
1728
+ expect(result).toHaveLength(1);
1729
+ });
1730
+ it('should warn when commit not found in extended commits', async () => {
1731
+ // Arrange
1732
+ const projectName = 'project-123';
1733
+ const repoId = 'repo-456';
1734
+ const targetVersion = { version: 'main', versionType: 'branch' };
1735
+ const sourceVersion = { version: 'v1.0.0', versionType: 'tag' };
1736
+ const allCommitsExtended = [
1737
+ { noCommitId: true }, // No commitId or commit property
1738
+ { noCommitId: true },
1739
+ ];
1740
+ const gitModulesContent = `[submodule "libs/common"]
1741
+ path = libs/common
1742
+ url = https://example.com/repo`;
1743
+ tfs_1.TFSServices.getItemContent
1744
+ .mockResolvedValueOnce({ content: gitModulesContent })
1745
+ .mockResolvedValueOnce({ content: 'target-sha' })
1746
+ .mockResolvedValueOnce({ content: undefined });
1747
+ // Act
1748
+ const result = await gitDataProvider.getSubmodulesData(projectName, repoId, targetVersion, sourceVersion, allCommitsExtended);
1749
+ // Assert
1750
+ expect(result).toHaveLength(0);
1751
+ expect(logger_1.default.warn).toHaveBeenCalled();
1752
+ });
1753
+ it('should handle errors gracefully', async () => {
1754
+ // Arrange
1755
+ const projectName = 'project-123';
1756
+ const repoId = 'repo-456';
1757
+ const targetVersion = { version: 'main', versionType: 'branch' };
1758
+ const sourceVersion = { version: 'v1.0.0', versionType: 'tag' };
1759
+ const allCommitsExtended = [];
1760
+ tfs_1.TFSServices.getItemContent.mockRejectedValueOnce(new Error('API error'));
1761
+ // Act
1762
+ const result = await gitDataProvider.getSubmodulesData(projectName, repoId, targetVersion, sourceVersion, allCommitsExtended);
1763
+ // Assert - returns empty array when .gitmodules fetch fails
1764
+ expect(result).toEqual([]);
1765
+ });
1766
+ it('should handle multiple submodules', async () => {
1767
+ // Arrange
1768
+ const projectName = 'project-123';
1769
+ const repoId = 'repo-456';
1770
+ const targetVersion = { version: 'main', versionType: 'branch' };
1771
+ const sourceVersion = { version: 'v1.0.0', versionType: 'tag' };
1772
+ const allCommitsExtended = [];
1773
+ const gitModulesContent = `[submodule "libs/common"]
1774
+ path = libs/common
1775
+ url = https://example.com/common
1776
+ [submodule "libs/utils"]
1777
+ path = libs/utils
1778
+ url = https://example.com/utils`;
1779
+ tfs_1.TFSServices.getItemContent
1780
+ .mockResolvedValueOnce({ content: gitModulesContent })
1781
+ .mockResolvedValueOnce({ content: 'target-sha-1' })
1782
+ .mockResolvedValueOnce({ content: 'source-sha-1' })
1783
+ .mockResolvedValueOnce({ content: 'target-sha-2' })
1784
+ .mockResolvedValueOnce({ content: 'source-sha-2' });
1785
+ // Act
1786
+ const result = await gitDataProvider.getSubmodulesData(projectName, repoId, targetVersion, sourceVersion, allCommitsExtended);
1787
+ // Assert
1788
+ expect(result).toHaveLength(2);
1789
+ expect(result[0].gitSubModuleName).toBe('libs_common');
1790
+ expect(result[1].gitSubModuleName).toBe('libs_utils');
1791
+ });
1792
+ });
1793
+ describe('GitDataProvider - createLinkedRelatedItemsForSVD', () => {
1794
+ let gitDataProvider;
1795
+ const mockOrgUrl = 'https://dev.azure.com/orgname/';
1796
+ const mockToken = 'mock-token';
1797
+ beforeEach(() => {
1798
+ jest.clearAllMocks();
1799
+ gitDataProvider = new GitDataProvider_1.default(mockOrgUrl, mockToken);
1800
+ });
1801
+ it('should add Requirement when linkedWiTypes=reqOnly and linkedWiRelationship=affectsOnly', async () => {
1802
+ jest.spyOn(gitDataProvider.ticketsDataProvider, 'GetWorkItemByUrl').mockResolvedValueOnce({
1803
+ id: 10,
1804
+ fields: { 'System.WorkItemType': 'Requirement', 'System.Title': 'Req' },
1805
+ _links: { html: { href: 'http://example.com/10' } },
1806
+ });
1807
+ const res = await gitDataProvider.createLinkedRelatedItemsForSVD({ isEnabled: true, linkedWiTypes: 'reqOnly', linkedWiRelationship: 'affectsOnly' }, {
1808
+ id: 1,
1809
+ relations: [
1810
+ {
1811
+ url: 'https://example.com/_apis/wit/workItems/10',
1812
+ rel: 'Affects',
1813
+ attributes: { name: 'Affects' },
1814
+ },
1815
+ ],
1816
+ });
1817
+ expect(res).toHaveLength(1);
1818
+ expect(res[0]).toEqual(expect.objectContaining({
1819
+ id: 10,
1820
+ wiType: 'Requirement',
1821
+ relationType: 'Affects',
1822
+ title: 'Req',
1823
+ url: 'http://example.com/10',
1824
+ }));
1825
+ });
1826
+ it('should add Feature when linkedWiTypes=featureOnly and linkedWiRelationship=coversOnly', async () => {
1827
+ jest.spyOn(gitDataProvider.ticketsDataProvider, 'GetWorkItemByUrl').mockResolvedValueOnce({
1828
+ id: 11,
1829
+ fields: { 'System.WorkItemType': 'Feature', 'System.Title': 'Feat' },
1830
+ _links: { html: { href: 'http://example.com/11' } },
1831
+ });
1832
+ const res = await gitDataProvider.createLinkedRelatedItemsForSVD({ isEnabled: true, linkedWiTypes: 'featureOnly', linkedWiRelationship: 'coversOnly' }, {
1833
+ id: 2,
1834
+ relations: [
1835
+ {
1836
+ url: 'https://example.com/_apis/wit/workItems/11',
1837
+ rel: 'CoveredBy',
1838
+ attributes: { name: 'CoveredBy' },
1839
+ },
1840
+ ],
1841
+ });
1842
+ expect(res).toHaveLength(1);
1843
+ expect(res[0]).toEqual(expect.objectContaining({
1844
+ id: 11,
1845
+ wiType: 'Feature',
1846
+ relationType: 'CoveredBy',
1847
+ title: 'Feat',
1848
+ url: 'http://example.com/11',
1849
+ }));
1850
+ });
1851
+ it('should add items when linkedWiTypes=both and linkedWiRelationship=both', async () => {
1852
+ jest.spyOn(gitDataProvider.ticketsDataProvider, 'GetWorkItemByUrl').mockResolvedValueOnce({
1853
+ id: 12,
1854
+ fields: { 'System.WorkItemType': 'Requirement', 'System.Title': 'Req 12' },
1855
+ _links: { html: { href: 'http://example.com/12' } },
1856
+ });
1857
+ const res = await gitDataProvider.createLinkedRelatedItemsForSVD({ isEnabled: true, linkedWiTypes: 'both', linkedWiRelationship: 'both' }, {
1858
+ id: 3,
1859
+ relations: [
1860
+ {
1861
+ url: 'https://example.com/_apis/wit/workItems/12',
1862
+ rel: 'Affects',
1863
+ attributes: { name: 'Affects' },
1864
+ },
1865
+ ],
1866
+ });
1867
+ expect(res).toHaveLength(1);
1868
+ });
1869
+ it('should skip when linkedWiTypes is enabled but item type does not match', async () => {
1870
+ jest.spyOn(gitDataProvider.ticketsDataProvider, 'GetWorkItemByUrl').mockResolvedValueOnce({
1871
+ id: 13,
1872
+ fields: { 'System.WorkItemType': 'Task', 'System.Title': 'Task 13' },
1873
+ _links: { html: { href: 'http://example.com/13' } },
1874
+ });
1875
+ const res = await gitDataProvider.createLinkedRelatedItemsForSVD({ isEnabled: true, linkedWiTypes: 'reqOnly', linkedWiRelationship: 'both' }, {
1876
+ id: 4,
1877
+ relations: [
1878
+ {
1879
+ url: 'https://example.com/_apis/wit/workItems/13',
1880
+ rel: 'Affects',
1881
+ attributes: { name: 'Affects' },
1882
+ },
1883
+ ],
1884
+ });
1885
+ expect(res).toEqual([]);
1886
+ });
1887
+ });
1888
+ //# sourceMappingURL=gitDataProvider.test.js.map