@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.
@@ -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 use sysrs root, exclude trace folders from system requirements and resolve both trace directions', async () => {
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
- 'System To Customer',
79
- 'System To Subsystem',
215
+ 'system to subsystem',
216
+ 'system-to-subsystem',
217
+ 'system subsystem',
80
218
  ]);
81
- expect(findChildFolderByPossibleNamesSpy).toHaveBeenNthCalledWith(1, sysRsRoot, expect.arrayContaining(['system to customer', 'subsystem to system', 'customer to system']));
82
- expect(findChildFolderByPossibleNamesSpy).toHaveBeenNthCalledWith(2, sysRsRoot, expect.arrayContaining(['system to subsystem', 'system-to-subsystem']));
83
- expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenNthCalledWith(1, subsystemToSystemFolder);
84
- expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenNthCalledWith(2, systemToSubsystemFolder);
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
- subsystemToSystemRequirementsQueries: { id: 'fwd-trace-tree' },
228
+ customerRequirementsQueries: { systemRequirementsQueryTree: { id: 'customer-tree' } },
229
+ subsystemToSystemRequirementsQueries: null,
88
230
  systemToSubsystemRequirementsQueries: { id: 'rev-trace-tree' },
89
231
  });
90
232
  });
91
- it('should fall back to root queries and return null trace trees when trace folders are missing', async () => {
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, 'findChildFolderByPossibleNames')
101
- .mockResolvedValueOnce(null)
102
- .mockResolvedValueOnce(null);
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
- 'System To Customer',
110
- 'System To Subsystem',
250
+ 'system to subsystem',
251
+ 'system-to-subsystem',
252
+ 'system subsystem',
111
253
  ]);
112
- expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenNthCalledWith(1, null);
113
- expect(fetchRequirementsTraceQueriesForFolderSpy).toHaveBeenNthCalledWith(2, null);
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());