@elisra-devops/docgen-data-provider 1.109.0 → 1.110.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/bin/modules/TicketsDataProvider.d.ts +9 -1
- package/bin/modules/TicketsDataProvider.js +93 -45
- package/bin/modules/TicketsDataProvider.js.map +1 -1
- package/bin/tests/modules/ticketsDataProvider.test.js +196 -21
- package/bin/tests/modules/ticketsDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/TicketsDataProvider.ts +138 -43
- package/src/tests/modules/ticketsDataProvider.test.ts +242 -26
|
@@ -52,11 +52,145 @@ describe('TicketsDataProvider', () => {
|
|
|
52
52
|
expect(structureSpy.mock.calls[0][3]).toEqual(['epic', 'feature', 'requirement', 'task']);
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
|
+
describe('fetchFlatUpstreamQueries', () => {
|
|
56
|
+
it('should allow only Requirement flat queries and exclude oneHop/tree query types', async () => {
|
|
57
|
+
const structureSpy = jest
|
|
58
|
+
.spyOn(ticketsDataProvider, 'structureFetchedQueries')
|
|
59
|
+
.mockResolvedValue({ tree1: { id: 't1' }, tree2: null });
|
|
60
|
+
await ticketsDataProvider.fetchFlatUpstreamQueries({ hasChildren: false }, []);
|
|
61
|
+
expect(structureSpy.mock.calls[0][3]).toEqual(['requirement']);
|
|
62
|
+
expect(structureSpy.mock.calls[0][7]).toBe(false);
|
|
63
|
+
expect(structureSpy.mock.calls[0][9]).toBe(true);
|
|
64
|
+
expect(structureSpy.mock.calls[0][10]).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
it('should allow Requirement flat queries for fallback discovery', async () => {
|
|
67
|
+
const structureSpy = jest
|
|
68
|
+
.spyOn(ticketsDataProvider, 'structureFetchedQueries')
|
|
69
|
+
.mockResolvedValue({ tree1: { id: 't1' }, tree2: null });
|
|
70
|
+
await ticketsDataProvider.fetchFlatUpstreamQueries({ hasChildren: false }, ['system to subsystem', 'system-to-subsystem', 'system subsystem'], ['requirement']);
|
|
71
|
+
expect(structureSpy.mock.calls[0][3]).toEqual(['requirement']);
|
|
72
|
+
expect(structureSpy.mock.calls[0][8]).toEqual([
|
|
73
|
+
'system to subsystem',
|
|
74
|
+
'system-to-subsystem',
|
|
75
|
+
'system subsystem',
|
|
76
|
+
]);
|
|
77
|
+
expect(structureSpy.mock.calls[0][9]).toBe(true);
|
|
78
|
+
expect(structureSpy.mock.calls[0][10]).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
it('should let structureFetchedQueries exclude oneHop leaves only for flat-only callers', async () => {
|
|
81
|
+
const result = await ticketsDataProvider.structureFetchedQueries({
|
|
82
|
+
id: 'one-hop',
|
|
83
|
+
hasChildren: false,
|
|
84
|
+
isFolder: false,
|
|
85
|
+
queryType: 'oneHop',
|
|
86
|
+
wiql: "[Source].[System.WorkItemType] = 'Requirement'",
|
|
87
|
+
}, false, null, ['requirement'], [], undefined, undefined, false, [], true, false);
|
|
88
|
+
expect(result).toEqual({ tree1: null, tree2: null });
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('fetchCustomerRequirementQueries', () => {
|
|
92
|
+
it('should return flat, tree, and oneHop query nodes without WIQL type filtering', async () => {
|
|
93
|
+
const root = {
|
|
94
|
+
id: 'root',
|
|
95
|
+
name: 'SYSRS',
|
|
96
|
+
isFolder: true,
|
|
97
|
+
hasChildren: true,
|
|
98
|
+
children: [
|
|
99
|
+
{
|
|
100
|
+
id: 'flat-query',
|
|
101
|
+
name: 'Flat Customer Pull',
|
|
102
|
+
isFolder: false,
|
|
103
|
+
hasChildren: false,
|
|
104
|
+
queryType: 'flat',
|
|
105
|
+
wiql: "SELECT * FROM WorkItems WHERE [System.WorkItemType] = 'Task'",
|
|
106
|
+
columns: [],
|
|
107
|
+
_links: { wiql: { href: 'flat-url' } },
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'tree-query',
|
|
111
|
+
name: 'Tree Customer Pull',
|
|
112
|
+
isFolder: false,
|
|
113
|
+
hasChildren: false,
|
|
114
|
+
queryType: 'tree',
|
|
115
|
+
wiql: "SELECT * FROM WorkItemLinks WHERE [System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward'",
|
|
116
|
+
columns: [],
|
|
117
|
+
_links: { wiql: { href: 'tree-url' } },
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'onehop-query',
|
|
121
|
+
name: 'OneHop Customer Pull',
|
|
122
|
+
isFolder: false,
|
|
123
|
+
hasChildren: false,
|
|
124
|
+
queryType: 'oneHop',
|
|
125
|
+
wiql: "SELECT * FROM WorkItemLinks WHERE [System.Links.LinkType] = 'System.LinkTypes.Related'",
|
|
126
|
+
columns: [],
|
|
127
|
+
_links: { wiql: { href: 'onehop-url' } },
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'unsupported-query',
|
|
131
|
+
name: 'Unsupported',
|
|
132
|
+
isFolder: false,
|
|
133
|
+
hasChildren: false,
|
|
134
|
+
queryType: 'invalid',
|
|
135
|
+
_links: { wiql: { href: 'unsupported-url' } },
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
const result = await ticketsDataProvider.fetchCustomerRequirementQueries(root, []);
|
|
140
|
+
expect(result.systemRequirementsQueryTree.children.map((child) => child.queryType)).toEqual([
|
|
141
|
+
'flat',
|
|
142
|
+
'tree',
|
|
143
|
+
'oneHop',
|
|
144
|
+
]);
|
|
145
|
+
expect(result.systemRequirementsQueryTree.children.map((child) => child.wiql.href)).toEqual([
|
|
146
|
+
'flat-url',
|
|
147
|
+
'tree-url',
|
|
148
|
+
'onehop-url',
|
|
149
|
+
]);
|
|
150
|
+
});
|
|
151
|
+
it('should include system-to-subsystem queries because customer discovery has no folder exclusions', async () => {
|
|
152
|
+
const root = {
|
|
153
|
+
id: 'root',
|
|
154
|
+
name: 'SYSRS',
|
|
155
|
+
isFolder: true,
|
|
156
|
+
hasChildren: true,
|
|
157
|
+
children: [
|
|
158
|
+
{
|
|
159
|
+
id: 'excluded-folder',
|
|
160
|
+
name: 'System To Subsystem',
|
|
161
|
+
isFolder: true,
|
|
162
|
+
hasChildren: true,
|
|
163
|
+
children: [
|
|
164
|
+
{
|
|
165
|
+
id: 'system-to-subsystem-query',
|
|
166
|
+
name: 'System To Subsystem Query',
|
|
167
|
+
isFolder: false,
|
|
168
|
+
hasChildren: false,
|
|
169
|
+
queryType: 'tree',
|
|
170
|
+
_links: { wiql: { href: 'system-to-subsystem-url' } },
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'included-query',
|
|
176
|
+
name: 'Included Query',
|
|
177
|
+
isFolder: false,
|
|
178
|
+
hasChildren: false,
|
|
179
|
+
queryType: 'oneHop',
|
|
180
|
+
_links: { wiql: { href: 'included-url' } },
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
const result = await ticketsDataProvider.fetchCustomerRequirementQueries(root);
|
|
185
|
+
expect(result.systemRequirementsQueryTree.children).toHaveLength(2);
|
|
186
|
+
expect(result.systemRequirementsQueryTree.children[0].children[0]).toEqual(expect.objectContaining({ id: 'system-to-subsystem-query', queryType: 'tree' }));
|
|
187
|
+
expect(result.systemRequirementsQueryTree.children[1]).toEqual(expect.objectContaining({ id: 'included-query', queryType: 'oneHop' }));
|
|
188
|
+
});
|
|
189
|
+
});
|
|
55
190
|
describe('fetchSysRsQueries', () => {
|
|
56
|
-
it('should
|
|
191
|
+
it('should scan the dedicated sysrs folder for customer queries regardless of query type', async () => {
|
|
57
192
|
const rootQueries = { id: 'root' };
|
|
58
193
|
const sysRsRoot = { id: 'sysrs-root', name: 'SYSRS' };
|
|
59
|
-
const subsystemToSystemFolder = { id: 'fwd-folder', name: 'System To Customer' };
|
|
60
194
|
const systemToSubsystemFolder = { id: 'rev-folder', name: 'System To Subsystem' };
|
|
61
195
|
const getDocTypeRootSpy = jest
|
|
62
196
|
.spyOn(ticketsDataProvider, 'getDocTypeRoot')
|
|
@@ -64,31 +198,39 @@ describe('TicketsDataProvider', () => {
|
|
|
64
198
|
const fetchSystemRequirementQueriesSpy = jest
|
|
65
199
|
.spyOn(ticketsDataProvider, 'fetchSystemRequirementQueries')
|
|
66
200
|
.mockResolvedValue({ systemRequirementsQueryTree: { id: 'system-tree' } });
|
|
201
|
+
const fetchCustomerRequirementQueriesSpy = jest
|
|
202
|
+
.spyOn(ticketsDataProvider, 'fetchCustomerRequirementQueries')
|
|
203
|
+
.mockResolvedValue({ systemRequirementsQueryTree: { id: 'customer-tree' } });
|
|
67
204
|
const findChildFolderByPossibleNamesSpy = jest
|
|
68
205
|
.spyOn(ticketsDataProvider, 'findChildFolderByPossibleNames')
|
|
69
|
-
.mockResolvedValueOnce(subsystemToSystemFolder)
|
|
70
206
|
.mockResolvedValueOnce(systemToSubsystemFolder);
|
|
71
207
|
const fetchRequirementsTraceQueriesForFolderSpy = jest
|
|
72
208
|
.spyOn(ticketsDataProvider, 'fetchRequirementsTraceQueriesForFolder')
|
|
73
|
-
.mockResolvedValueOnce({ id: 'fwd-trace-tree' })
|
|
74
209
|
.mockResolvedValueOnce({ id: 'rev-trace-tree' });
|
|
75
210
|
const result = await ticketsDataProvider.fetchSysRsQueries(rootQueries);
|
|
76
211
|
expect(getDocTypeRootSpy).toHaveBeenCalledWith(rootQueries, 'sysrs');
|
|
212
|
+
// fetchSystemRequirementQueries only excludes the sub-system trace folder;
|
|
213
|
+
// the legacy System-to-Customer exclusion list is gone.
|
|
77
214
|
expect(fetchSystemRequirementQueriesSpy).toHaveBeenCalledWith(sysRsRoot, [
|
|
78
|
-
'
|
|
79
|
-
'
|
|
215
|
+
'system to subsystem',
|
|
216
|
+
'system-to-subsystem',
|
|
217
|
+
'system subsystem',
|
|
80
218
|
]);
|
|
81
|
-
|
|
82
|
-
expect(findChildFolderByPossibleNamesSpy).
|
|
83
|
-
expect(
|
|
84
|
-
expect(fetchRequirementsTraceQueriesForFolderSpy).
|
|
219
|
+
// Only one folder lookup remains (sub-system trace); no System-to-Customer lookup.
|
|
220
|
+
expect(findChildFolderByPossibleNamesSpy).toHaveBeenCalledTimes(1);
|
|
221
|
+
expect(findChildFolderByPossibleNamesSpy).toHaveBeenCalledWith(sysRsRoot, expect.arrayContaining(['system to subsystem', 'system-to-subsystem']));
|
|
222
|
+
expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenCalledTimes(1);
|
|
223
|
+
expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenCalledWith(systemToSubsystemFolder);
|
|
224
|
+
// Customer picker scans the whole sysRsRoot with no query/folder exclusions.
|
|
225
|
+
expect(fetchCustomerRequirementQueriesSpy).toHaveBeenCalledWith(sysRsRoot);
|
|
85
226
|
expect(result).toEqual({
|
|
86
227
|
systemRequirementsQueries: { systemRequirementsQueryTree: { id: 'system-tree' } },
|
|
87
|
-
|
|
228
|
+
customerRequirementsQueries: { systemRequirementsQueryTree: { id: 'customer-tree' } },
|
|
229
|
+
subsystemToSystemRequirementsQueries: null,
|
|
88
230
|
systemToSubsystemRequirementsQueries: { id: 'rev-trace-tree' },
|
|
89
231
|
});
|
|
90
232
|
});
|
|
91
|
-
it('should
|
|
233
|
+
it('should return null customer/trace trees when the dedicated sysrs folder is missing (no fallback scan)', async () => {
|
|
92
234
|
const rootQueries = { id: 'root' };
|
|
93
235
|
jest
|
|
94
236
|
.spyOn(ticketsDataProvider, 'getDocTypeRoot')
|
|
@@ -96,23 +238,27 @@ describe('TicketsDataProvider', () => {
|
|
|
96
238
|
const fetchSystemRequirementQueriesSpy = jest
|
|
97
239
|
.spyOn(ticketsDataProvider, 'fetchSystemRequirementQueries')
|
|
98
240
|
.mockResolvedValue({ systemRequirementsQueryTree: { id: 'system-tree' } });
|
|
99
|
-
jest
|
|
100
|
-
.spyOn(ticketsDataProvider, '
|
|
101
|
-
.
|
|
102
|
-
|
|
241
|
+
const fetchCustomerRequirementQueriesSpy = jest
|
|
242
|
+
.spyOn(ticketsDataProvider, 'fetchCustomerRequirementQueries')
|
|
243
|
+
.mockResolvedValue({ systemRequirementsQueryTree: { id: 'should-not-be-used' } });
|
|
244
|
+
jest.spyOn(ticketsDataProvider, 'findChildFolderByPossibleNames').mockResolvedValueOnce(null);
|
|
103
245
|
const fetchRequirementsTraceQueriesForFolderSpy = jest
|
|
104
246
|
.spyOn(ticketsDataProvider, 'fetchRequirementsTraceQueriesForFolder')
|
|
105
|
-
.mockResolvedValueOnce(null)
|
|
106
247
|
.mockResolvedValueOnce(null);
|
|
107
248
|
const result = await ticketsDataProvider.fetchSysRsQueries(rootQueries);
|
|
108
249
|
expect(fetchSystemRequirementQueriesSpy).toHaveBeenCalledWith(rootQueries, [
|
|
109
|
-
'
|
|
110
|
-
'
|
|
250
|
+
'system to subsystem',
|
|
251
|
+
'system-to-subsystem',
|
|
252
|
+
'system subsystem',
|
|
111
253
|
]);
|
|
112
|
-
expect(fetchRequirementsTraceQueriesForFolderSpy).
|
|
113
|
-
expect(fetchRequirementsTraceQueriesForFolderSpy).
|
|
254
|
+
expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenCalledTimes(1);
|
|
255
|
+
expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenCalledWith(null);
|
|
256
|
+
// No fallback scan when the dedicated sysrs folder is absent — otherwise
|
|
257
|
+
// unrelated flat queries from the Shared Queries root would leak in.
|
|
258
|
+
expect(fetchCustomerRequirementQueriesSpy).not.toHaveBeenCalled();
|
|
114
259
|
expect(result).toEqual({
|
|
115
260
|
systemRequirementsQueries: { systemRequirementsQueryTree: { id: 'system-tree' } },
|
|
261
|
+
customerRequirementsQueries: null,
|
|
116
262
|
subsystemToSystemRequirementsQueries: null,
|
|
117
263
|
systemToSubsystemRequirementsQueries: null,
|
|
118
264
|
});
|
|
@@ -1028,6 +1174,35 @@ describe('TicketsDataProvider', () => {
|
|
|
1028
1174
|
// Assert
|
|
1029
1175
|
expect(result).toBeDefined();
|
|
1030
1176
|
});
|
|
1177
|
+
// Regression: WI_DEFAULT_FIELDS must include System.WorkItemType so that
|
|
1178
|
+
// downstream Requirement-type filters (e.g. the customer-coverage table)
|
|
1179
|
+
// do not drop every row because workItemType resolves to empty string.
|
|
1180
|
+
it('should request System.WorkItemType in default fields for flat queries and expose workItemType on parsed items', async () => {
|
|
1181
|
+
const mockWiqlHref = 'https://example.com/wiql';
|
|
1182
|
+
const mockQueryResult = {
|
|
1183
|
+
queryType: 'flat',
|
|
1184
|
+
workItems: [{ id: 42, url: 'https://example.com/wi/42' }],
|
|
1185
|
+
};
|
|
1186
|
+
const mockWiResponse = {
|
|
1187
|
+
fields: {
|
|
1188
|
+
'System.Title': 'Customer Requirement',
|
|
1189
|
+
'System.WorkItemType': 'Requirement',
|
|
1190
|
+
'System.Description': 'Desc',
|
|
1191
|
+
},
|
|
1192
|
+
_links: { html: { href: 'https://example.com/wi/42' } },
|
|
1193
|
+
};
|
|
1194
|
+
tfs_1.TFSServices.getItemContent
|
|
1195
|
+
.mockResolvedValueOnce(mockQueryResult)
|
|
1196
|
+
.mockResolvedValueOnce(mockWiResponse);
|
|
1197
|
+
const result = await ticketsDataProvider.GetQueryResultsFromWiql(mockWiqlHref, false, new Map());
|
|
1198
|
+
// The per-item fetch URL must request System.WorkItemType.
|
|
1199
|
+
const perItemCall = tfs_1.TFSServices.getItemContent.mock.calls.find((call) => typeof call[0] === 'string' && call[0].startsWith('https://example.com/wi/42'));
|
|
1200
|
+
expect(perItemCall).toBeDefined();
|
|
1201
|
+
expect(perItemCall[0]).toContain('System.WorkItemType');
|
|
1202
|
+
// The parsed item must expose workItemType so downstream filters work.
|
|
1203
|
+
expect(Array.isArray(result)).toBe(true);
|
|
1204
|
+
expect(result[0]).toMatchObject({ id: 42, workItemType: 'Requirement' });
|
|
1205
|
+
});
|
|
1031
1206
|
it('should return undefined when queryType is not supported', async () => {
|
|
1032
1207
|
tfs_1.TFSServices.getItemContent.mockResolvedValueOnce({ queryType: 'Unknown' });
|
|
1033
1208
|
const res = await ticketsDataProvider.GetQueryResultsFromWiql('https://example.com/wiql', false, new Map());
|