@elisra-devops/docgen-data-provider 1.109.1 → 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 +2 -0
- package/bin/modules/TicketsDataProvider.js +72 -43
- package/bin/modules/TicketsDataProvider.js.map +1 -1
- package/bin/tests/modules/ticketsDataProvider.test.js +150 -34
- package/bin/tests/modules/ticketsDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/TicketsDataProvider.ts +94 -51
- package/src/tests/modules/ticketsDataProvider.test.ts +172 -39
|
@@ -153,11 +153,119 @@ describe('TicketsDataProvider', () => {
|
|
|
153
153
|
});
|
|
154
154
|
});
|
|
155
155
|
|
|
156
|
+
describe('fetchCustomerRequirementQueries', () => {
|
|
157
|
+
it('should return flat, tree, and oneHop query nodes without WIQL type filtering', async () => {
|
|
158
|
+
const root = {
|
|
159
|
+
id: 'root',
|
|
160
|
+
name: 'SYSRS',
|
|
161
|
+
isFolder: true,
|
|
162
|
+
hasChildren: true,
|
|
163
|
+
children: [
|
|
164
|
+
{
|
|
165
|
+
id: 'flat-query',
|
|
166
|
+
name: 'Flat Customer Pull',
|
|
167
|
+
isFolder: false,
|
|
168
|
+
hasChildren: false,
|
|
169
|
+
queryType: 'flat',
|
|
170
|
+
wiql: "SELECT * FROM WorkItems WHERE [System.WorkItemType] = 'Task'",
|
|
171
|
+
columns: [],
|
|
172
|
+
_links: { wiql: { href: 'flat-url' } },
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'tree-query',
|
|
176
|
+
name: 'Tree Customer Pull',
|
|
177
|
+
isFolder: false,
|
|
178
|
+
hasChildren: false,
|
|
179
|
+
queryType: 'tree',
|
|
180
|
+
wiql: "SELECT * FROM WorkItemLinks WHERE [System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward'",
|
|
181
|
+
columns: [],
|
|
182
|
+
_links: { wiql: { href: 'tree-url' } },
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: 'onehop-query',
|
|
186
|
+
name: 'OneHop Customer Pull',
|
|
187
|
+
isFolder: false,
|
|
188
|
+
hasChildren: false,
|
|
189
|
+
queryType: 'oneHop',
|
|
190
|
+
wiql: "SELECT * FROM WorkItemLinks WHERE [System.Links.LinkType] = 'System.LinkTypes.Related'",
|
|
191
|
+
columns: [],
|
|
192
|
+
_links: { wiql: { href: 'onehop-url' } },
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: 'unsupported-query',
|
|
196
|
+
name: 'Unsupported',
|
|
197
|
+
isFolder: false,
|
|
198
|
+
hasChildren: false,
|
|
199
|
+
queryType: 'invalid',
|
|
200
|
+
_links: { wiql: { href: 'unsupported-url' } },
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const result = await (ticketsDataProvider as any).fetchCustomerRequirementQueries(root, []);
|
|
206
|
+
|
|
207
|
+
expect(result.systemRequirementsQueryTree.children.map((child: any) => child.queryType)).toEqual([
|
|
208
|
+
'flat',
|
|
209
|
+
'tree',
|
|
210
|
+
'oneHop',
|
|
211
|
+
]);
|
|
212
|
+
expect(result.systemRequirementsQueryTree.children.map((child: any) => child.wiql.href)).toEqual([
|
|
213
|
+
'flat-url',
|
|
214
|
+
'tree-url',
|
|
215
|
+
'onehop-url',
|
|
216
|
+
]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should include system-to-subsystem queries because customer discovery has no folder exclusions', async () => {
|
|
220
|
+
const root = {
|
|
221
|
+
id: 'root',
|
|
222
|
+
name: 'SYSRS',
|
|
223
|
+
isFolder: true,
|
|
224
|
+
hasChildren: true,
|
|
225
|
+
children: [
|
|
226
|
+
{
|
|
227
|
+
id: 'excluded-folder',
|
|
228
|
+
name: 'System To Subsystem',
|
|
229
|
+
isFolder: true,
|
|
230
|
+
hasChildren: true,
|
|
231
|
+
children: [
|
|
232
|
+
{
|
|
233
|
+
id: 'system-to-subsystem-query',
|
|
234
|
+
name: 'System To Subsystem Query',
|
|
235
|
+
isFolder: false,
|
|
236
|
+
hasChildren: false,
|
|
237
|
+
queryType: 'tree',
|
|
238
|
+
_links: { wiql: { href: 'system-to-subsystem-url' } },
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
id: 'included-query',
|
|
244
|
+
name: 'Included Query',
|
|
245
|
+
isFolder: false,
|
|
246
|
+
hasChildren: false,
|
|
247
|
+
queryType: 'oneHop',
|
|
248
|
+
_links: { wiql: { href: 'included-url' } },
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const result = await (ticketsDataProvider as any).fetchCustomerRequirementQueries(root);
|
|
254
|
+
|
|
255
|
+
expect(result.systemRequirementsQueryTree.children).toHaveLength(2);
|
|
256
|
+
expect(result.systemRequirementsQueryTree.children[0].children[0]).toEqual(
|
|
257
|
+
expect.objectContaining({ id: 'system-to-subsystem-query', queryType: 'tree' }),
|
|
258
|
+
);
|
|
259
|
+
expect(result.systemRequirementsQueryTree.children[1]).toEqual(
|
|
260
|
+
expect.objectContaining({ id: 'included-query', queryType: 'oneHop' }),
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
156
265
|
describe('fetchSysRsQueries', () => {
|
|
157
|
-
it('should
|
|
266
|
+
it('should scan the dedicated sysrs folder for customer queries regardless of query type', async () => {
|
|
158
267
|
const rootQueries = { id: 'root' };
|
|
159
268
|
const sysRsRoot = { id: 'sysrs-root', name: 'SYSRS' };
|
|
160
|
-
const subsystemToSystemFolder = { id: 'fwd-folder', name: 'System To Customer' };
|
|
161
269
|
const systemToSubsystemFolder = { id: 'rev-folder', name: 'System To Subsystem' };
|
|
162
270
|
|
|
163
271
|
const getDocTypeRootSpy = jest
|
|
@@ -166,56 +274,49 @@ describe('TicketsDataProvider', () => {
|
|
|
166
274
|
const fetchSystemRequirementQueriesSpy = jest
|
|
167
275
|
.spyOn(ticketsDataProvider as any, 'fetchSystemRequirementQueries')
|
|
168
276
|
.mockResolvedValue({ systemRequirementsQueryTree: { id: 'system-tree' } });
|
|
169
|
-
const
|
|
170
|
-
.spyOn(ticketsDataProvider as any, '
|
|
277
|
+
const fetchCustomerRequirementQueriesSpy = jest
|
|
278
|
+
.spyOn(ticketsDataProvider as any, 'fetchCustomerRequirementQueries')
|
|
171
279
|
.mockResolvedValue({ systemRequirementsQueryTree: { id: 'customer-tree' } });
|
|
172
280
|
const findChildFolderByPossibleNamesSpy = jest
|
|
173
281
|
.spyOn(ticketsDataProvider as any, 'findChildFolderByPossibleNames')
|
|
174
|
-
.mockResolvedValueOnce(subsystemToSystemFolder)
|
|
175
282
|
.mockResolvedValueOnce(systemToSubsystemFolder);
|
|
176
283
|
const fetchRequirementsTraceQueriesForFolderSpy = jest
|
|
177
284
|
.spyOn(ticketsDataProvider as any, 'fetchRequirementsTraceQueriesForFolder')
|
|
178
|
-
.mockResolvedValueOnce({ id: 'fwd-trace-tree' })
|
|
179
285
|
.mockResolvedValueOnce({ id: 'rev-trace-tree' });
|
|
180
286
|
|
|
181
287
|
const result = await (ticketsDataProvider as any).fetchSysRsQueries(rootQueries);
|
|
182
288
|
|
|
183
289
|
expect(getDocTypeRootSpy).toHaveBeenCalledWith(rootQueries, 'sysrs');
|
|
290
|
+
// fetchSystemRequirementQueries only excludes the sub-system trace folder;
|
|
291
|
+
// the legacy System-to-Customer exclusion list is gone.
|
|
184
292
|
expect(fetchSystemRequirementQueriesSpy).toHaveBeenCalledWith(sysRsRoot, [
|
|
185
|
-
'system to customer',
|
|
186
|
-
'system-to-customer',
|
|
187
|
-
'system customer',
|
|
188
|
-
'subsystem to system',
|
|
189
|
-
'customer to system',
|
|
190
293
|
'system to subsystem',
|
|
191
294
|
'system-to-subsystem',
|
|
192
295
|
'system subsystem',
|
|
193
296
|
]);
|
|
194
297
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
expect.arrayContaining(['system to customer', 'subsystem to system', 'customer to system']),
|
|
199
|
-
);
|
|
200
|
-
expect(findChildFolderByPossibleNamesSpy).toHaveBeenNthCalledWith(
|
|
201
|
-
2,
|
|
298
|
+
// Only one folder lookup remains (sub-system trace); no System-to-Customer lookup.
|
|
299
|
+
expect(findChildFolderByPossibleNamesSpy).toHaveBeenCalledTimes(1);
|
|
300
|
+
expect(findChildFolderByPossibleNamesSpy).toHaveBeenCalledWith(
|
|
202
301
|
sysRsRoot,
|
|
203
302
|
expect.arrayContaining(['system to subsystem', 'system-to-subsystem']),
|
|
204
303
|
);
|
|
205
304
|
|
|
206
|
-
expect(fetchRequirementsTraceQueriesForFolderSpy).
|
|
207
|
-
expect(fetchRequirementsTraceQueriesForFolderSpy).
|
|
208
|
-
|
|
305
|
+
expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenCalledTimes(1);
|
|
306
|
+
expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenCalledWith(systemToSubsystemFolder);
|
|
307
|
+
|
|
308
|
+
// Customer picker scans the whole sysRsRoot with no query/folder exclusions.
|
|
309
|
+
expect(fetchCustomerRequirementQueriesSpy).toHaveBeenCalledWith(sysRsRoot);
|
|
209
310
|
|
|
210
311
|
expect(result).toEqual({
|
|
211
312
|
systemRequirementsQueries: { systemRequirementsQueryTree: { id: 'system-tree' } },
|
|
212
313
|
customerRequirementsQueries: { systemRequirementsQueryTree: { id: 'customer-tree' } },
|
|
213
|
-
subsystemToSystemRequirementsQueries:
|
|
314
|
+
subsystemToSystemRequirementsQueries: null,
|
|
214
315
|
systemToSubsystemRequirementsQueries: { id: 'rev-trace-tree' },
|
|
215
316
|
});
|
|
216
317
|
});
|
|
217
318
|
|
|
218
|
-
it('should return null customer/trace trees when
|
|
319
|
+
it('should return null customer/trace trees when the dedicated sysrs folder is missing (no fallback scan)', async () => {
|
|
219
320
|
const rootQueries = { id: 'root' };
|
|
220
321
|
|
|
221
322
|
jest
|
|
@@ -224,34 +325,26 @@ describe('TicketsDataProvider', () => {
|
|
|
224
325
|
const fetchSystemRequirementQueriesSpy = jest
|
|
225
326
|
.spyOn(ticketsDataProvider as any, 'fetchSystemRequirementQueries')
|
|
226
327
|
.mockResolvedValue({ systemRequirementsQueryTree: { id: 'system-tree' } });
|
|
227
|
-
const
|
|
228
|
-
.spyOn(ticketsDataProvider as any, '
|
|
328
|
+
const fetchCustomerRequirementQueriesSpy = jest
|
|
329
|
+
.spyOn(ticketsDataProvider as any, 'fetchCustomerRequirementQueries')
|
|
229
330
|
.mockResolvedValue({ systemRequirementsQueryTree: { id: 'should-not-be-used' } });
|
|
230
|
-
jest
|
|
231
|
-
.spyOn(ticketsDataProvider as any, 'findChildFolderByPossibleNames')
|
|
232
|
-
.mockResolvedValueOnce(null)
|
|
233
|
-
.mockResolvedValueOnce(null);
|
|
331
|
+
jest.spyOn(ticketsDataProvider as any, 'findChildFolderByPossibleNames').mockResolvedValueOnce(null);
|
|
234
332
|
const fetchRequirementsTraceQueriesForFolderSpy = jest
|
|
235
333
|
.spyOn(ticketsDataProvider as any, 'fetchRequirementsTraceQueriesForFolder')
|
|
236
|
-
.mockResolvedValueOnce(null)
|
|
237
334
|
.mockResolvedValueOnce(null);
|
|
238
335
|
|
|
239
336
|
const result = await (ticketsDataProvider as any).fetchSysRsQueries(rootQueries);
|
|
240
337
|
|
|
241
338
|
expect(fetchSystemRequirementQueriesSpy).toHaveBeenCalledWith(rootQueries, [
|
|
242
|
-
'system to customer',
|
|
243
|
-
'system-to-customer',
|
|
244
|
-
'system customer',
|
|
245
|
-
'subsystem to system',
|
|
246
|
-
'customer to system',
|
|
247
339
|
'system to subsystem',
|
|
248
340
|
'system-to-subsystem',
|
|
249
341
|
'system subsystem',
|
|
250
342
|
]);
|
|
251
|
-
expect(fetchRequirementsTraceQueriesForFolderSpy).
|
|
252
|
-
expect(fetchRequirementsTraceQueriesForFolderSpy).
|
|
253
|
-
// No fallback scan
|
|
254
|
-
|
|
343
|
+
expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenCalledTimes(1);
|
|
344
|
+
expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenCalledWith(null);
|
|
345
|
+
// No fallback scan when the dedicated sysrs folder is absent — otherwise
|
|
346
|
+
// unrelated flat queries from the Shared Queries root would leak in.
|
|
347
|
+
expect(fetchCustomerRequirementQueriesSpy).not.toHaveBeenCalled();
|
|
255
348
|
expect(result).toEqual({
|
|
256
349
|
systemRequirementsQueries: { systemRequirementsQueryTree: { id: 'system-tree' } },
|
|
257
350
|
customerRequirementsQueries: null,
|
|
@@ -1436,6 +1529,46 @@ describe('TicketsDataProvider', () => {
|
|
|
1436
1529
|
expect(result).toBeDefined();
|
|
1437
1530
|
});
|
|
1438
1531
|
|
|
1532
|
+
// Regression: WI_DEFAULT_FIELDS must include System.WorkItemType so that
|
|
1533
|
+
// downstream Requirement-type filters (e.g. the customer-coverage table)
|
|
1534
|
+
// do not drop every row because workItemType resolves to empty string.
|
|
1535
|
+
it('should request System.WorkItemType in default fields for flat queries and expose workItemType on parsed items', async () => {
|
|
1536
|
+
const mockWiqlHref = 'https://example.com/wiql';
|
|
1537
|
+
const mockQueryResult = {
|
|
1538
|
+
queryType: 'flat',
|
|
1539
|
+
workItems: [{ id: 42, url: 'https://example.com/wi/42' }],
|
|
1540
|
+
};
|
|
1541
|
+
const mockWiResponse = {
|
|
1542
|
+
fields: {
|
|
1543
|
+
'System.Title': 'Customer Requirement',
|
|
1544
|
+
'System.WorkItemType': 'Requirement',
|
|
1545
|
+
'System.Description': 'Desc',
|
|
1546
|
+
},
|
|
1547
|
+
_links: { html: { href: 'https://example.com/wi/42' } },
|
|
1548
|
+
};
|
|
1549
|
+
|
|
1550
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
1551
|
+
.mockResolvedValueOnce(mockQueryResult)
|
|
1552
|
+
.mockResolvedValueOnce(mockWiResponse);
|
|
1553
|
+
|
|
1554
|
+
const result: any = await ticketsDataProvider.GetQueryResultsFromWiql(
|
|
1555
|
+
mockWiqlHref,
|
|
1556
|
+
false,
|
|
1557
|
+
new Map<number, Set<any>>(),
|
|
1558
|
+
);
|
|
1559
|
+
|
|
1560
|
+
// The per-item fetch URL must request System.WorkItemType.
|
|
1561
|
+
const perItemCall = (TFSServices.getItemContent as jest.Mock).mock.calls.find(
|
|
1562
|
+
(call: any[]) => typeof call[0] === 'string' && call[0].startsWith('https://example.com/wi/42'),
|
|
1563
|
+
);
|
|
1564
|
+
expect(perItemCall).toBeDefined();
|
|
1565
|
+
expect(perItemCall[0]).toContain('System.WorkItemType');
|
|
1566
|
+
|
|
1567
|
+
// The parsed item must expose workItemType so downstream filters work.
|
|
1568
|
+
expect(Array.isArray(result)).toBe(true);
|
|
1569
|
+
expect(result[0]).toMatchObject({ id: 42, workItemType: 'Requirement' });
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1439
1572
|
it('should return undefined when queryType is not supported', async () => {
|
|
1440
1573
|
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({ queryType: 'Unknown' });
|
|
1441
1574
|
const res = await ticketsDataProvider.GetQueryResultsFromWiql(
|