@elisra-devops/docgen-data-provider 1.106.0 → 1.108.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.
@@ -0,0 +1,741 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tfs_1 = require("../../helpers/tfs");
4
+ const TicketsDataProvider_1 = require("../../modules/TicketsDataProvider");
5
+ const logger_1 = require("../../utils/logger");
6
+ jest.mock('../../helpers/tfs');
7
+ jest.mock('../../utils/logger', () => ({
8
+ __esModule: true,
9
+ default: {
10
+ debug: jest.fn(),
11
+ info: jest.fn(),
12
+ warn: jest.fn(),
13
+ error: jest.fn(),
14
+ },
15
+ }));
16
+ describe('TicketsDataProvider historical queries', () => {
17
+ const orgUrl = 'https://dev.azure.com/org/';
18
+ const token = 'pat';
19
+ const project = 'team-project';
20
+ let provider;
21
+ beforeEach(() => {
22
+ jest.clearAllMocks();
23
+ provider = new TicketsDataProvider_1.default(orgUrl, token);
24
+ });
25
+ it('GetHistoricalQueries flattens shared query tree into a sorted list with explicit api-version', async () => {
26
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce({
27
+ id: 'root',
28
+ name: 'Shared Queries',
29
+ isFolder: true,
30
+ children: [
31
+ {
32
+ id: 'folder-b',
33
+ name: 'B',
34
+ isFolder: true,
35
+ children: [{ id: 'q-2', name: 'Second Query', isFolder: false }],
36
+ },
37
+ {
38
+ id: 'folder-a',
39
+ name: 'A',
40
+ isFolder: true,
41
+ children: [{ id: 'q-1', name: 'First Query', isFolder: false }],
42
+ },
43
+ ],
44
+ });
45
+ const result = await provider.GetHistoricalQueries(project);
46
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(`/${project}/_apis/wit/queries/Shared%20Queries?$depth=2&$expand=all&api-version=7.1`), token);
47
+ expect(result).toEqual([
48
+ { id: 'q-1', queryName: 'First Query', path: 'Shared Queries/A' },
49
+ { id: 'q-2', queryName: 'Second Query', path: 'Shared Queries/B' },
50
+ ]);
51
+ });
52
+ it('GetHistoricalQueries supports legacy response shape and falls back to default api-version', async () => {
53
+ tfs_1.TFSServices.getItemContent.mockImplementation(async (url) => {
54
+ if (url.includes('api-version=7.1') || url.includes('api-version=5.1')) {
55
+ throw {
56
+ response: {
57
+ status: 400,
58
+ data: { message: 'The requested api-version is not supported.' },
59
+ },
60
+ };
61
+ }
62
+ if (url.includes('/_apis/wit/queries/Shared%20Queries')) {
63
+ return {
64
+ value: [
65
+ {
66
+ id: 'q-legacy',
67
+ name: 'Legacy Query',
68
+ isFolder: false,
69
+ },
70
+ ],
71
+ };
72
+ }
73
+ throw new Error(`unexpected URL: ${url}`);
74
+ });
75
+ const result = await provider.GetHistoricalQueries(project);
76
+ expect(result).toEqual([{ id: 'q-legacy', queryName: 'Legacy Query', path: 'Shared Queries' }]);
77
+ });
78
+ it('GetHistoricalQueries retries with 5.1 when 7.1 returns 500', async () => {
79
+ tfs_1.TFSServices.getItemContent.mockImplementation(async (url) => {
80
+ if (url.includes('api-version=7.1')) {
81
+ throw {
82
+ response: {
83
+ status: 500,
84
+ data: { message: 'Internal Server Error' },
85
+ },
86
+ };
87
+ }
88
+ if (url.includes('api-version=5.1')) {
89
+ return {
90
+ id: 'root',
91
+ name: 'Shared Queries',
92
+ isFolder: true,
93
+ children: [{ id: 'q-51', name: 'V5 Query', isFolder: false }],
94
+ };
95
+ }
96
+ throw new Error(`unexpected URL: ${url}`);
97
+ });
98
+ const result = await provider.GetHistoricalQueries(project);
99
+ expect(result).toEqual([{ id: 'q-51', queryName: 'V5 Query', path: 'Shared Queries' }]);
100
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(`/${project}/_apis/wit/queries/Shared%20Queries?$depth=2&$expand=all&api-version=7.1`), token);
101
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(`/${project}/_apis/wit/queries/Shared%20Queries?$depth=2&$expand=all&api-version=5.1`), token);
102
+ });
103
+ it('GetHistoricalQueries treats "Shared Queries" alias as shared root', async () => {
104
+ tfs_1.TFSServices.getItemContent.mockResolvedValueOnce({
105
+ id: 'root',
106
+ name: 'Shared Queries',
107
+ isFolder: true,
108
+ children: [],
109
+ });
110
+ await provider.GetHistoricalQueries(project, 'Shared Queries');
111
+ expect(tfs_1.TFSServices.getItemContent).toHaveBeenCalledWith(expect.stringContaining(`/${project}/_apis/wit/queries/Shared%20Queries?$depth=2&$expand=all&api-version=7.1`), token);
112
+ });
113
+ it('GetHistoricalQueryResults executes WIQL with ASOF and returns as-of snapshot rows', async () => {
114
+ const asOfIso = '2026-01-01T10:00:00.000Z';
115
+ tfs_1.TFSServices.getItemContent.mockImplementation(async (url, _pat, method, data) => {
116
+ if (url.includes('/_apis/wit/queries/q-1') && url.includes('api-version=7.1')) {
117
+ return { name: 'Historical Q', wiql: 'SELECT [System.Id] FROM WorkItems' };
118
+ }
119
+ if (url.includes('/_apis/wit/wiql?') && url.includes('api-version=7.1') && method === 'post') {
120
+ expect(String((data === null || data === void 0 ? void 0 : data.query) || '')).toContain(`ASOF '${asOfIso}'`);
121
+ return { workItems: [{ id: 101 }, { id: 102 }] };
122
+ }
123
+ if (url.includes('/_apis/wit/workitemsbatch') &&
124
+ url.includes('api-version=7.1') &&
125
+ method === 'post') {
126
+ expect(data.asOf).toBe(asOfIso);
127
+ return {
128
+ value: [
129
+ {
130
+ id: 101,
131
+ rev: 3,
132
+ fields: {
133
+ 'System.WorkItemType': 'Requirement',
134
+ 'System.Title': 'Req title',
135
+ 'System.State': 'Active',
136
+ 'System.AreaPath': 'Proj\\Area',
137
+ 'System.IterationPath': 'Proj\\Iter',
138
+ 'System.ChangedDate': '2025-12-30T10:00:00Z',
139
+ },
140
+ relations: [],
141
+ },
142
+ {
143
+ id: 102,
144
+ rev: 8,
145
+ fields: {
146
+ 'System.WorkItemType': 'Bug',
147
+ 'System.Title': 'Bug title',
148
+ 'System.State': 'Closed',
149
+ 'System.AreaPath': 'Proj\\Area',
150
+ 'System.IterationPath': 'Proj\\Iter',
151
+ 'System.ChangedDate': '2025-12-31T10:00:00Z',
152
+ },
153
+ relations: [],
154
+ },
155
+ ],
156
+ };
157
+ }
158
+ throw new Error(`unexpected URL: ${url}`);
159
+ });
160
+ const result = await provider.GetHistoricalQueryResults('q-1', project, asOfIso);
161
+ const deprecatedWiqlByIdCallUsed = tfs_1.TFSServices.getItemContent.mock.calls.some((call) => String(call[0]).includes('/_apis/wit/wiql/q-1'));
162
+ expect(deprecatedWiqlByIdCallUsed).toBe(false);
163
+ expect(result.queryName).toBe('Historical Q');
164
+ expect(result.asOf).toBe(asOfIso);
165
+ expect(result.total).toBe(2);
166
+ expect(result.rows[0]).toEqual(expect.objectContaining({
167
+ id: 101,
168
+ workItemType: 'Requirement',
169
+ title: 'Req title',
170
+ versionId: 3,
171
+ }));
172
+ });
173
+ it('GetHistoricalQueryResults falls back to api-version 5.1 and chunks workitemsbatch by 200 IDs', async () => {
174
+ const asOfIso = '2026-01-01T10:00:00.000Z';
175
+ const allIds = Array.from({ length: 205 }, (_, idx) => idx + 1);
176
+ tfs_1.TFSServices.getItemContent.mockImplementation(async (url, _pat, method, data) => {
177
+ if (url.includes('/_apis/wit/queries/q-fallback') && url.includes('api-version=7.1')) {
178
+ throw {
179
+ response: {
180
+ status: 400,
181
+ data: { message: 'The requested api-version is not supported.' },
182
+ },
183
+ };
184
+ }
185
+ if (url.includes('/_apis/wit/queries/q-fallback') && url.includes('api-version=5.1')) {
186
+ return { name: 'Fallback Q', wiql: 'SELECT [System.Id] FROM WorkItems' };
187
+ }
188
+ if (url.includes('/_apis/wit/wiql?') && url.includes('api-version=5.1') && method === 'post') {
189
+ expect(String((data === null || data === void 0 ? void 0 : data.query) || '')).toContain(`ASOF '${asOfIso}'`);
190
+ return { workItems: allIds.map((id) => ({ id })) };
191
+ }
192
+ if (url.includes('/_apis/wit/workitemsbatch') &&
193
+ url.includes('api-version=5.1') &&
194
+ method === 'post') {
195
+ return {
196
+ value: (Array.isArray(data === null || data === void 0 ? void 0 : data.ids) ? data.ids : []).map((id) => ({
197
+ id,
198
+ rev: 1,
199
+ fields: {
200
+ 'System.WorkItemType': 'Bug',
201
+ 'System.Title': `Bug ${id}`,
202
+ 'System.State': 'Active',
203
+ 'System.AreaPath': 'Proj\\Area',
204
+ 'System.IterationPath': 'Proj\\Iter',
205
+ 'System.ChangedDate': '2025-12-31T10:00:00Z',
206
+ },
207
+ relations: [],
208
+ })),
209
+ };
210
+ }
211
+ throw new Error(`unexpected URL: ${url}`);
212
+ });
213
+ const result = await provider.GetHistoricalQueryResults('q-fallback', project, asOfIso);
214
+ expect(result.total).toBe(205);
215
+ const batchCalls = tfs_1.TFSServices.getItemContent.mock.calls.filter((call) => String(call[0]).includes('/_apis/wit/workitemsbatch') &&
216
+ String(call[0]).includes('api-version=5.1') &&
217
+ String(call[2]).toLowerCase() === 'post');
218
+ expect(batchCalls).toHaveLength(2);
219
+ expect(batchCalls[0][3].ids).toHaveLength(200);
220
+ expect(batchCalls[1][3].ids).toHaveLength(5);
221
+ });
222
+ it('GetHistoricalQueryResults falls back to per-item retrieval when workitemsbatch fails', async () => {
223
+ const asOfIso = '2026-01-01T10:00:00.000Z';
224
+ tfs_1.TFSServices.getItemContent.mockImplementation(async (url, _pat, method, data) => {
225
+ if (url.includes('/_apis/wit/queries/q-batch-fallback') && url.includes('api-version=7.1')) {
226
+ return { name: 'Batch Fallback Q', wiql: 'SELECT [System.Id] FROM WorkItems' };
227
+ }
228
+ if (url.includes('/_apis/wit/wiql?') && url.includes('api-version=7.1') && method === 'post') {
229
+ expect(String((data === null || data === void 0 ? void 0 : data.query) || '')).toContain(`ASOF '${asOfIso}'`);
230
+ return { workItems: [{ id: 101 }, { id: 102 }] };
231
+ }
232
+ if (url.includes('/_apis/wit/workitemsbatch') &&
233
+ url.includes('api-version=7.1') &&
234
+ method === 'post') {
235
+ throw {
236
+ response: {
237
+ status: 500,
238
+ data: { message: 'workitemsbatch failed' },
239
+ },
240
+ };
241
+ }
242
+ if (url.includes('/_apis/wit/workitems/101') && url.includes('api-version=7.1')) {
243
+ expect(url).toContain('$expand=Relations');
244
+ expect(url).toContain(`asOf=${encodeURIComponent(asOfIso)}`);
245
+ expect(url).not.toContain('fields=');
246
+ return {
247
+ id: 101,
248
+ rev: 3,
249
+ fields: {
250
+ 'System.WorkItemType': 'Requirement',
251
+ 'System.Title': 'Req 101',
252
+ 'System.State': 'Active',
253
+ 'System.AreaPath': 'Proj\\Area',
254
+ 'System.IterationPath': 'Proj\\Iter',
255
+ 'System.ChangedDate': '2025-12-30T10:00:00Z',
256
+ },
257
+ relations: [],
258
+ };
259
+ }
260
+ if (url.includes('/_apis/wit/workitems/102') && url.includes('api-version=7.1')) {
261
+ expect(url).toContain('$expand=Relations');
262
+ expect(url).toContain(`asOf=${encodeURIComponent(asOfIso)}`);
263
+ expect(url).not.toContain('fields=');
264
+ return {
265
+ id: 102,
266
+ rev: 4,
267
+ fields: {
268
+ 'System.WorkItemType': 'Bug',
269
+ 'System.Title': 'Bug 102',
270
+ 'System.State': 'Closed',
271
+ 'System.AreaPath': 'Proj\\Area',
272
+ 'System.IterationPath': 'Proj\\Iter',
273
+ 'System.ChangedDate': '2025-12-31T10:00:00Z',
274
+ },
275
+ relations: [],
276
+ };
277
+ }
278
+ throw new Error(`unexpected URL: ${url}`);
279
+ });
280
+ const result = await provider.GetHistoricalQueryResults('q-batch-fallback', project, asOfIso);
281
+ expect(result.total).toBe(2);
282
+ expect(result.rows.map((row) => row.id)).toEqual([101, 102]);
283
+ });
284
+ it('GetHistoricalQueryResults skips work items that do not exist at as-of time and logs warning', async () => {
285
+ const asOfIso = '2026-01-01T10:00:00.000Z';
286
+ tfs_1.TFSServices.getItemContent.mockImplementation(async (url, _pat, method, data) => {
287
+ if (url.includes('/_apis/wit/queries/q-missing-asof') && url.includes('api-version=7.1')) {
288
+ return { name: 'Missing AsOf Q', wiql: 'SELECT [System.Id] FROM WorkItems' };
289
+ }
290
+ if (url.includes('/_apis/wit/wiql?') && url.includes('api-version=7.1') && method === 'post') {
291
+ expect(String((data === null || data === void 0 ? void 0 : data.query) || '')).toContain(`ASOF '${asOfIso}'`);
292
+ return { workItems: [{ id: 101 }, { id: 102 }] };
293
+ }
294
+ if (url.includes('/_apis/wit/workitemsbatch') && method === 'post') {
295
+ throw {
296
+ response: {
297
+ status: 500,
298
+ data: { message: 'workitemsbatch failed' },
299
+ },
300
+ };
301
+ }
302
+ if (url.includes('/_apis/wit/workitems/101')) {
303
+ return {
304
+ id: 101,
305
+ rev: 3,
306
+ fields: {
307
+ 'System.WorkItemType': 'Requirement',
308
+ 'System.Title': 'Req 101',
309
+ 'System.State': 'Active',
310
+ 'System.AreaPath': 'Proj\\Area',
311
+ 'System.IterationPath': 'Proj\\Iter',
312
+ 'System.ChangedDate': '2025-12-30T10:00:00Z',
313
+ },
314
+ relations: [],
315
+ };
316
+ }
317
+ if (url.includes('/_apis/wit/workitems/102')) {
318
+ throw {
319
+ response: {
320
+ status: 404,
321
+ data: {
322
+ message: 'The work item 102 does not exist at time 12/31/2025 11:56:00 PM. It might have been deleted.',
323
+ },
324
+ },
325
+ };
326
+ }
327
+ throw new Error(`unexpected URL: ${url}`);
328
+ });
329
+ const result = await provider.GetHistoricalQueryResults('q-missing-asof', project, asOfIso);
330
+ expect(result.total).toBe(1);
331
+ expect(result.skippedWorkItemsCount).toBe(1);
332
+ expect(result.rows.map((row) => row.id)).toEqual([101]);
333
+ expect(logger_1.default.warn.mock.calls.some((call) => String(call[0]).includes('skipping work item 102'))).toBe(true);
334
+ });
335
+ it('GetHistoricalQueryResults returns empty result when all work items are missing at as-of time', async () => {
336
+ const asOfIso = '2026-01-01T10:00:00.000Z';
337
+ tfs_1.TFSServices.getItemContent.mockImplementation(async (url, _pat, method, data) => {
338
+ if (url.includes('/_apis/wit/queries/q-all-missing-asof') && url.includes('api-version=7.1')) {
339
+ return { name: 'All Missing AsOf Q', wiql: 'SELECT [System.Id] FROM WorkItems' };
340
+ }
341
+ if (url.includes('/_apis/wit/wiql?') && method === 'post') {
342
+ expect(String((data === null || data === void 0 ? void 0 : data.query) || '')).toContain(`ASOF '${asOfIso}'`);
343
+ return { workItems: [{ id: 201 }, { id: 202 }] };
344
+ }
345
+ if (url.includes('/_apis/wit/workitemsbatch') && method === 'post') {
346
+ throw {
347
+ response: {
348
+ status: 500,
349
+ data: { message: 'workitemsbatch failed' },
350
+ },
351
+ };
352
+ }
353
+ if (url.includes('/_apis/wit/workitems/201') || url.includes('/_apis/wit/workitems/202')) {
354
+ const id = url.includes('/201') ? 201 : 202;
355
+ throw {
356
+ response: {
357
+ status: 404,
358
+ data: {
359
+ message: `The work item ${id} does not exist at time 12/31/2025 11:56:00 PM.`,
360
+ },
361
+ },
362
+ };
363
+ }
364
+ throw new Error(`unexpected URL: ${url}`);
365
+ });
366
+ const result = await provider.GetHistoricalQueryResults('q-all-missing-asof', project, asOfIso);
367
+ expect(result.total).toBe(0);
368
+ expect(result.rows).toEqual([]);
369
+ expect(result.skippedWorkItemsCount).toBe(2);
370
+ });
371
+ it('GetHistoricalQueryResults still throws for non-missing historical errors', async () => {
372
+ const asOfIso = '2026-01-01T10:00:00.000Z';
373
+ tfs_1.TFSServices.getItemContent.mockImplementation(async (url, _pat, method, data) => {
374
+ if (url.includes('/_apis/wit/queries/q-auth-error') && url.includes('api-version=7.1')) {
375
+ return { name: 'Auth Error Q', wiql: 'SELECT [System.Id] FROM WorkItems' };
376
+ }
377
+ if (url.includes('/_apis/wit/wiql?') && method === 'post') {
378
+ expect(String((data === null || data === void 0 ? void 0 : data.query) || '')).toContain(`ASOF '${asOfIso}'`);
379
+ return { workItems: [{ id: 901 }] };
380
+ }
381
+ if (url.includes('/_apis/wit/workitemsbatch') && method === 'post') {
382
+ throw {
383
+ response: {
384
+ status: 500,
385
+ data: { message: 'workitemsbatch failed' },
386
+ },
387
+ };
388
+ }
389
+ if (url.includes('/_apis/wit/workitems/901')) {
390
+ throw {
391
+ response: {
392
+ status: 401,
393
+ data: { message: 'Unauthorized' },
394
+ },
395
+ };
396
+ }
397
+ throw new Error(`unexpected URL: ${url}`);
398
+ });
399
+ await expect(provider.GetHistoricalQueryResults('q-auth-error', project, asOfIso)).rejects.toEqual(expect.objectContaining({
400
+ response: expect.objectContaining({ status: 401 }),
401
+ }));
402
+ });
403
+ it('GetHistoricalQueryResults falls back to WIQL-by-id when inline WIQL fails', async () => {
404
+ const asOfIso = '2026-01-01T10:00:00.000Z';
405
+ tfs_1.TFSServices.getItemContent.mockImplementation(async (url, _pat, method, data) => {
406
+ if (url.includes('/_apis/wit/queries/q-inline-fallback') && url.includes('api-version=7.1')) {
407
+ return { name: 'Inline Fallback Q', wiql: 'SELECT [System.Id] FROM WorkItems' };
408
+ }
409
+ if (url.includes('/_apis/wit/wiql?') && url.includes('api-version=7.1') && method === 'post') {
410
+ expect(String((data === null || data === void 0 ? void 0 : data.query) || '')).toContain(`ASOF '${asOfIso}'`);
411
+ throw {
412
+ response: {
413
+ status: 500,
414
+ data: { message: 'inline wiql failed' },
415
+ },
416
+ };
417
+ }
418
+ if (url.includes('/_apis/wit/wiql/q-inline-fallback') && url.includes('api-version=7.1')) {
419
+ expect(url).toContain(`asOf=${encodeURIComponent(asOfIso)}`);
420
+ return { workItems: [{ id: 3001 }] };
421
+ }
422
+ if (url.includes('/_apis/wit/workitemsbatch') &&
423
+ url.includes('api-version=7.1') &&
424
+ method === 'post') {
425
+ return {
426
+ value: [
427
+ {
428
+ id: 3001,
429
+ rev: 1,
430
+ fields: {
431
+ 'System.WorkItemType': 'Requirement',
432
+ 'System.Title': 'Req 3001',
433
+ 'System.State': 'Active',
434
+ 'System.AreaPath': 'Proj\\Area',
435
+ 'System.IterationPath': 'Proj\\Iter',
436
+ 'System.ChangedDate': '2025-12-31T10:00:00Z',
437
+ },
438
+ relations: [],
439
+ },
440
+ ],
441
+ };
442
+ }
443
+ throw new Error(`unexpected URL: ${url}`);
444
+ });
445
+ const result = await provider.GetHistoricalQueryResults('q-inline-fallback', project, asOfIso);
446
+ expect(result.total).toBe(1);
447
+ expect(result.rows[0]).toEqual(expect.objectContaining({
448
+ id: 3001,
449
+ workItemType: 'Requirement',
450
+ title: 'Req 3001',
451
+ }));
452
+ });
453
+ it('CompareHistoricalQueryResults marks Added/Deleted/Changed/No changes using noise-control fields', async () => {
454
+ var _a, _b, _c, _d, _e, _f, _g;
455
+ const baselineIso = '2025-12-22T17:08:00.000Z';
456
+ const compareIso = '2025-12-28T08:57:00.000Z';
457
+ const baselineBatch = {
458
+ value: [
459
+ {
460
+ id: 11,
461
+ rev: 2,
462
+ fields: {
463
+ 'System.WorkItemType': 'Requirement',
464
+ 'System.Title': 'Req A',
465
+ 'System.State': 'Active',
466
+ 'System.Description': 'Old desc',
467
+ 'Elisra.TestPhase': 'FAT',
468
+ 'System.ChangedDate': baselineIso,
469
+ },
470
+ relations: [],
471
+ },
472
+ {
473
+ id: 23,
474
+ rev: 1,
475
+ fields: {
476
+ 'System.WorkItemType': 'Test Case',
477
+ 'System.Title': 'Case B',
478
+ 'System.State': 'Active',
479
+ 'System.Description': 'Case desc',
480
+ 'Microsoft.VSTS.TCM.Steps': '<steps>1</steps>',
481
+ 'Elisra.TestPhase': 'FAT',
482
+ 'System.ChangedDate': baselineIso,
483
+ },
484
+ relations: [{ id: 'l-1' }],
485
+ },
486
+ {
487
+ id: 58,
488
+ rev: 2,
489
+ fields: {
490
+ 'System.WorkItemType': 'Bug',
491
+ 'System.Title': 'Deleted bug',
492
+ 'System.State': 'New',
493
+ 'System.Description': 'x',
494
+ 'System.ChangedDate': baselineIso,
495
+ },
496
+ relations: [],
497
+ },
498
+ {
499
+ id: 813,
500
+ rev: 3,
501
+ fields: {
502
+ 'System.WorkItemType': 'Bug',
503
+ 'System.Title': 'No Change bug',
504
+ 'System.State': 'Active',
505
+ 'System.Description': 'same',
506
+ 'System.ChangedDate': baselineIso,
507
+ },
508
+ relations: [],
509
+ },
510
+ ],
511
+ };
512
+ const compareBatch = {
513
+ value: [
514
+ {
515
+ id: 11,
516
+ rev: 20,
517
+ fields: {
518
+ 'System.WorkItemType': 'Requirement',
519
+ 'System.Title': 'Req A',
520
+ 'System.State': 'Active',
521
+ 'System.Description': 'New desc',
522
+ 'Elisra.TestPhase': 'FAT; ATP',
523
+ 'System.ChangedDate': compareIso,
524
+ },
525
+ relations: [],
526
+ },
527
+ {
528
+ id: 23,
529
+ rev: 3,
530
+ fields: {
531
+ 'System.WorkItemType': 'Test Case',
532
+ 'System.Title': 'Case B',
533
+ 'System.State': 'Active',
534
+ 'System.Description': 'Case desc',
535
+ 'Microsoft.VSTS.TCM.Steps': '<steps>2</steps>',
536
+ 'Elisra.TestPhase': 'FAT',
537
+ 'System.ChangedDate': compareIso,
538
+ },
539
+ relations: [{ id: 'l-1' }, { id: 'l-2' }],
540
+ },
541
+ {
542
+ id: 814,
543
+ rev: 1,
544
+ fields: {
545
+ 'System.WorkItemType': 'Bug',
546
+ 'System.Title': 'Added bug',
547
+ 'System.State': 'New',
548
+ 'System.Description': 'new',
549
+ 'System.ChangedDate': compareIso,
550
+ },
551
+ relations: [],
552
+ },
553
+ {
554
+ id: 813,
555
+ rev: 9,
556
+ fields: {
557
+ 'System.WorkItemType': 'Bug',
558
+ 'System.Title': 'No Change bug',
559
+ 'System.State': 'Active',
560
+ 'System.Description': 'same',
561
+ 'System.ChangedDate': compareIso,
562
+ },
563
+ relations: [],
564
+ },
565
+ ],
566
+ };
567
+ tfs_1.TFSServices.getItemContent.mockImplementation(async (url, _pat, method, data) => {
568
+ if (url.includes('/_apis/wit/queries/q-compare') && url.includes('api-version=7.1')) {
569
+ return { name: 'Compare Query', wiql: 'SELECT [System.Id] FROM WorkItems' };
570
+ }
571
+ if (url.includes('/_apis/wit/wiql?') && url.includes('api-version=7.1') && method === 'post') {
572
+ const query = String((data === null || data === void 0 ? void 0 : data.query) || '');
573
+ if (query.includes(baselineIso)) {
574
+ return { workItems: [{ id: 11 }, { id: 23 }, { id: 58 }, { id: 813 }] };
575
+ }
576
+ if (query.includes(compareIso)) {
577
+ return { workItems: [{ id: 11 }, { id: 23 }, { id: 814 }, { id: 813 }] };
578
+ }
579
+ }
580
+ if (url.includes('/_apis/wit/workitemsbatch') && method === 'post' && (data === null || data === void 0 ? void 0 : data.asOf) === baselineIso) {
581
+ return baselineBatch;
582
+ }
583
+ if (url.includes('/_apis/wit/workitemsbatch') && method === 'post' && (data === null || data === void 0 ? void 0 : data.asOf) === compareIso) {
584
+ return compareBatch;
585
+ }
586
+ throw new Error(`unexpected URL: ${url}`);
587
+ });
588
+ const result = await provider.CompareHistoricalQueryResults('q-compare', project, baselineIso, compareIso);
589
+ const byId = new Map(result.rows.map((row) => [row.id, row]));
590
+ expect((_a = byId.get(11)) === null || _a === void 0 ? void 0 : _a.compareStatus).toBe('Changed');
591
+ expect((_b = byId.get(11)) === null || _b === void 0 ? void 0 : _b.changedFields).toEqual(expect.arrayContaining(['Description', 'Test Phase']));
592
+ expect((_c = byId.get(23)) === null || _c === void 0 ? void 0 : _c.compareStatus).toBe('Changed');
593
+ expect((_d = byId.get(23)) === null || _d === void 0 ? void 0 : _d.changedFields).toEqual(expect.arrayContaining(['Steps', 'Related Link Count']));
594
+ expect((_e = byId.get(58)) === null || _e === void 0 ? void 0 : _e.compareStatus).toBe('Deleted');
595
+ expect((_f = byId.get(814)) === null || _f === void 0 ? void 0 : _f.compareStatus).toBe('Added');
596
+ expect((_g = byId.get(813)) === null || _g === void 0 ? void 0 : _g.compareStatus).toBe('No changes');
597
+ expect(result.summary).toEqual({
598
+ addedCount: 1,
599
+ deletedCount: 1,
600
+ changedCount: 2,
601
+ noChangeCount: 1,
602
+ updatedCount: 2,
603
+ });
604
+ });
605
+ it('CompareHistoricalQueryResults supports missing work items on one side and reports Added/Deleted', async () => {
606
+ var _a, _b, _c;
607
+ const baselineIso = '2025-12-20T00:00:00.000Z';
608
+ const compareIso = '2025-12-30T00:00:00.000Z';
609
+ tfs_1.TFSServices.getItemContent.mockImplementation(async (url, _pat, method, data) => {
610
+ if (url.includes('/_apis/wit/queries/q-compare-missing-side') && url.includes('api-version=7.1')) {
611
+ return { name: 'Compare Missing Side', wiql: 'SELECT [System.Id] FROM WorkItems' };
612
+ }
613
+ if (url.includes('/_apis/wit/wiql?') && method === 'post') {
614
+ const query = String((data === null || data === void 0 ? void 0 : data.query) || '');
615
+ if (query.includes(baselineIso)) {
616
+ return { workItems: [{ id: 1001 }, { id: 1002 }, { id: 1003 }] };
617
+ }
618
+ if (query.includes(compareIso)) {
619
+ return { workItems: [{ id: 1001 }, { id: 1002 }, { id: 1003 }] };
620
+ }
621
+ }
622
+ if (url.includes('/_apis/wit/workitemsbatch') && method === 'post') {
623
+ throw {
624
+ response: {
625
+ status: 500,
626
+ data: { message: 'workitemsbatch failed' },
627
+ },
628
+ };
629
+ }
630
+ if (url.includes('/_apis/wit/workitems/1001')) {
631
+ return {
632
+ id: 1001,
633
+ rev: 1,
634
+ fields: {
635
+ 'System.WorkItemType': 'Requirement',
636
+ 'System.Title': 'Stable Item',
637
+ 'System.State': 'Active',
638
+ 'System.ChangedDate': compareIso,
639
+ },
640
+ relations: [],
641
+ };
642
+ }
643
+ if (url.includes(`asOf=${encodeURIComponent(baselineIso)}`) &&
644
+ url.includes('/_apis/wit/workitems/1002')) {
645
+ throw {
646
+ response: {
647
+ status: 404,
648
+ data: { message: 'The work item 1002 does not exist at time 12/20/2025 12:00:00 AM.' },
649
+ },
650
+ };
651
+ }
652
+ if (url.includes(`asOf=${encodeURIComponent(compareIso)}`) &&
653
+ url.includes('/_apis/wit/workitems/1002')) {
654
+ return {
655
+ id: 1002,
656
+ rev: 4,
657
+ fields: {
658
+ 'System.WorkItemType': 'Bug',
659
+ 'System.Title': 'Added Later',
660
+ 'System.State': 'New',
661
+ 'System.ChangedDate': compareIso,
662
+ },
663
+ relations: [],
664
+ };
665
+ }
666
+ if (url.includes(`asOf=${encodeURIComponent(baselineIso)}`) &&
667
+ url.includes('/_apis/wit/workitems/1003')) {
668
+ return {
669
+ id: 1003,
670
+ rev: 2,
671
+ fields: {
672
+ 'System.WorkItemType': 'Bug',
673
+ 'System.Title': 'Deleted Later',
674
+ 'System.State': 'Active',
675
+ 'System.ChangedDate': baselineIso,
676
+ },
677
+ relations: [],
678
+ };
679
+ }
680
+ if (url.includes(`asOf=${encodeURIComponent(compareIso)}`) &&
681
+ url.includes('/_apis/wit/workitems/1003')) {
682
+ throw {
683
+ response: {
684
+ status: 404,
685
+ data: { message: 'The work item 1003 does not exist at time 12/30/2025 12:00:00 AM.' },
686
+ },
687
+ };
688
+ }
689
+ throw new Error(`unexpected URL: ${url}`);
690
+ });
691
+ const result = await provider.CompareHistoricalQueryResults('q-compare-missing-side', project, baselineIso, compareIso);
692
+ const byId = new Map(result.rows.map((row) => [row.id, row]));
693
+ expect((_a = byId.get(1001)) === null || _a === void 0 ? void 0 : _a.compareStatus).toBe('No changes');
694
+ expect((_b = byId.get(1002)) === null || _b === void 0 ? void 0 : _b.compareStatus).toBe('Added');
695
+ expect((_c = byId.get(1003)) === null || _c === void 0 ? void 0 : _c.compareStatus).toBe('Deleted');
696
+ expect(result.skippedWorkItems).toEqual(expect.objectContaining({ baselineCount: 1, compareToCount: 1, totalDistinct: 2 }));
697
+ });
698
+ it('CompareHistoricalQueryResults excludes work items missing at both dates and can return empty set', async () => {
699
+ const baselineIso = '2025-01-01T00:00:00.000Z';
700
+ const compareIso = '2025-01-02T00:00:00.000Z';
701
+ tfs_1.TFSServices.getItemContent.mockImplementation(async (url, _pat, method, data) => {
702
+ if (url.includes('/_apis/wit/queries/q-compare-all-missing') && url.includes('api-version=7.1')) {
703
+ return { name: 'Compare All Missing', wiql: 'SELECT [System.Id] FROM WorkItems' };
704
+ }
705
+ if (url.includes('/_apis/wit/wiql?') && method === 'post') {
706
+ const query = String((data === null || data === void 0 ? void 0 : data.query) || '');
707
+ if (query.includes(baselineIso) || query.includes(compareIso)) {
708
+ return { workItems: [{ id: 777 }] };
709
+ }
710
+ }
711
+ if (url.includes('/_apis/wit/workitemsbatch') && method === 'post') {
712
+ throw {
713
+ response: {
714
+ status: 500,
715
+ data: { message: 'workitemsbatch failed' },
716
+ },
717
+ };
718
+ }
719
+ if (url.includes('/_apis/wit/workitems/777')) {
720
+ throw {
721
+ response: {
722
+ status: 404,
723
+ data: { message: 'The work item 777 does not exist at time 01/01/2025 12:00:00 AM.' },
724
+ },
725
+ };
726
+ }
727
+ throw new Error(`unexpected URL: ${url}`);
728
+ });
729
+ const result = await provider.CompareHistoricalQueryResults('q-compare-all-missing', project, baselineIso, compareIso);
730
+ expect(result.rows).toEqual([]);
731
+ expect(result.summary).toEqual({
732
+ addedCount: 0,
733
+ deletedCount: 0,
734
+ changedCount: 0,
735
+ noChangeCount: 0,
736
+ updatedCount: 0,
737
+ });
738
+ expect(result.skippedWorkItems).toEqual(expect.objectContaining({ baselineCount: 1, compareToCount: 1, totalDistinct: 1 }));
739
+ });
740
+ });
741
+ //# sourceMappingURL=ticketsDataProvider.historical.test.js.map