@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.
@@ -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 use sysrs root, exclude trace folders from system requirements and resolve both trace directions', async () => {
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 fetchFlatUpstreamQueriesSpy = jest
170
- .spyOn(ticketsDataProvider as any, 'fetchFlatUpstreamQueries')
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
- expect(findChildFolderByPossibleNamesSpy).toHaveBeenNthCalledWith(
196
- 1,
197
- sysRsRoot,
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).toHaveBeenNthCalledWith(1, subsystemToSystemFolder);
207
- expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenNthCalledWith(2, systemToSubsystemFolder);
208
- expect(fetchFlatUpstreamQueriesSpy).toHaveBeenCalledWith(subsystemToSystemFolder, [], ['requirement']);
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: { id: 'fwd-trace-tree' },
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 trace folders are missing (no sysRsRoot fallback scan)', async () => {
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 fetchFlatUpstreamQueriesSpy = jest
228
- .spyOn(ticketsDataProvider as any, 'fetchFlatUpstreamQueries')
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).toHaveBeenNthCalledWith(1, null);
252
- expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenNthCalledWith(2, null);
253
- // No fallback scan of sysRsRoot when the System-to-Customer folder is absent.
254
- expect(fetchFlatUpstreamQueriesSpy).not.toHaveBeenCalled();
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(