@elisra-devops/docgen-data-provider 1.63.13 → 1.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +26 -9
- package/.github/workflows/release.yml +9 -10
- package/bin/helpers/tfs.d.ts +3 -0
- package/bin/helpers/tfs.js +44 -7
- package/bin/helpers/tfs.js.map +1 -1
- package/bin/modules/GitDataProvider.d.ts +10 -0
- package/bin/modules/GitDataProvider.js +10 -0
- package/bin/modules/GitDataProvider.js.map +1 -1
- package/bin/modules/TestDataProvider.js +0 -1
- package/bin/modules/TestDataProvider.js.map +1 -1
- package/bin/modules/TicketsDataProvider.d.ts +63 -24
- package/bin/modules/TicketsDataProvider.js +216 -114
- package/bin/modules/TicketsDataProvider.js.map +1 -1
- package/bin/tests/helpers/helper.test.js +279 -0
- package/bin/tests/helpers/helper.test.js.map +1 -0
- package/bin/{helpers/test → tests/helpers}/tfs.test.js +312 -49
- package/bin/tests/helpers/tfs.test.js.map +1 -0
- package/bin/tests/index.test.js +25 -0
- package/bin/tests/index.test.js.map +1 -0
- package/bin/tests/models/tfs-data.test.js +160 -0
- package/bin/tests/models/tfs-data.test.js.map +1 -0
- package/bin/{modules/test → tests/modules}/JfrogDataProvider.test.js +9 -9
- package/bin/tests/modules/JfrogDataProvider.test.js.map +1 -0
- package/bin/tests/modules/ResultDataProvider.test.js +1942 -0
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -0
- package/bin/tests/modules/gitDataProvider.test.js +1888 -0
- package/bin/tests/modules/gitDataProvider.test.js.map +1 -0
- package/bin/{modules/test → tests/modules}/managmentDataProvider.test.js +13 -1
- package/bin/tests/modules/managmentDataProvider.test.js.map +1 -0
- package/bin/tests/modules/pipelineDataProvider.test.d.ts +1 -0
- package/bin/tests/modules/pipelineDataProvider.test.js +783 -0
- package/bin/tests/modules/pipelineDataProvider.test.js.map +1 -0
- package/bin/tests/modules/testDataProvider.test.d.ts +1 -0
- package/bin/tests/modules/testDataProvider.test.js +717 -0
- package/bin/tests/modules/testDataProvider.test.js.map +1 -0
- package/bin/tests/modules/ticketsDataProvider.test.d.ts +1 -0
- package/bin/tests/modules/ticketsDataProvider.test.js +1681 -0
- package/bin/tests/modules/ticketsDataProvider.test.js.map +1 -0
- package/bin/tests/utils/DataProviderUtils.test.d.ts +1 -0
- package/bin/tests/utils/DataProviderUtils.test.js +61 -0
- package/bin/tests/utils/DataProviderUtils.test.js.map +1 -0
- package/bin/tests/utils/testStepParserHelper.test.d.ts +1 -0
- package/bin/tests/utils/testStepParserHelper.test.js +359 -0
- package/bin/tests/utils/testStepParserHelper.test.js.map +1 -0
- package/package.json +9 -1
- package/src/helpers/tfs.ts +51 -7
- package/src/modules/GitDataProvider.ts +10 -0
- package/src/modules/TestDataProvider.ts +0 -1
- package/src/modules/TicketsDataProvider.ts +298 -141
- package/src/tests/helpers/helper.test.ts +337 -0
- package/src/tests/helpers/tfs.test.ts +1092 -0
- package/src/tests/index.test.ts +28 -0
- package/src/tests/models/tfs-data.test.ts +203 -0
- package/src/tests/modules/JfrogDataProvider.test.ts +167 -0
- package/src/tests/modules/ResultDataProvider.test.ts +2571 -0
- package/src/tests/modules/gitDataProvider.test.ts +2628 -0
- package/src/{modules/test → tests/modules}/managmentDataProvider.test.ts +33 -1
- package/src/tests/modules/pipelineDataProvider.test.ts +1038 -0
- package/src/tests/modules/testDataProvider.test.ts +1046 -0
- package/src/tests/modules/ticketsDataProvider.test.ts +2204 -0
- package/src/tests/utils/DataProviderUtils.test.ts +76 -0
- package/src/tests/utils/testStepParserHelper.test.ts +437 -0
- package/tsconfig.json +1 -0
- package/bin/helpers/test/tfs.test.js.map +0 -1
- package/bin/modules/test/JfrogDataProvider.test.js.map +0 -1
- package/bin/modules/test/ResultDataProvider.test.js +0 -444
- package/bin/modules/test/ResultDataProvider.test.js.map +0 -1
- package/bin/modules/test/gitDataProvider.test.js +0 -428
- package/bin/modules/test/gitDataProvider.test.js.map +0 -1
- package/bin/modules/test/managmentDataProvider.test.js.map +0 -1
- package/bin/modules/test/pipelineDataProvider.test.js +0 -237
- package/bin/modules/test/pipelineDataProvider.test.js.map +0 -1
- package/bin/modules/test/testDataProvider.test.js +0 -234
- package/bin/modules/test/testDataProvider.test.js.map +0 -1
- package/bin/modules/test/ticketsDataProvider.test.js +0 -348
- package/bin/modules/test/ticketsDataProvider.test.js.map +0 -1
- package/src/helpers/test/tfs.test.ts +0 -748
- package/src/modules/test/JfrogDataProvider.test.ts +0 -171
- package/src/modules/test/ResultDataProvider.test.ts +0 -542
- package/src/modules/test/gitDataProvider.test.ts +0 -645
- package/src/modules/test/pipelineDataProvider.test.ts +0 -292
- package/src/modules/test/testDataProvider.test.ts +0 -318
- package/src/modules/test/ticketsDataProvider.test.ts +0 -462
- /package/bin/{helpers/test/tfs.test.d.ts → tests/helpers/helper.test.d.ts} +0 -0
- /package/bin/{modules/test/JfrogDataProvider.test.d.ts → tests/helpers/tfs.test.d.ts} +0 -0
- /package/bin/{modules/test/ResultDataProvider.test.d.ts → tests/index.test.d.ts} +0 -0
- /package/bin/{modules/test/gitDataProvider.test.d.ts → tests/models/tfs-data.test.d.ts} +0 -0
- /package/bin/{modules/test/managmentDataProvider.test.d.ts → tests/modules/JfrogDataProvider.test.d.ts} +0 -0
- /package/bin/{modules/test/pipelineDataProvider.test.d.ts → tests/modules/ResultDataProvider.test.d.ts} +0 -0
- /package/bin/{modules/test/testDataProvider.test.d.ts → tests/modules/gitDataProvider.test.d.ts} +0 -0
- /package/bin/{modules/test/ticketsDataProvider.test.d.ts → tests/modules/managmentDataProvider.test.d.ts} +0 -0
|
@@ -0,0 +1,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
|