@elisra-devops/docgen-data-provider 1.136.6 → 1.136.7

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.
@@ -4,9 +4,6 @@ import { Query, Workitem } from '../../models/tfs-data';
4
4
  describe('Helper', () => {
5
5
  beforeEach(() => {
6
6
  // Reset static state before each test
7
- Helper.suitList = [];
8
- Helper.level = 1;
9
- Helper.first = true;
10
7
  Helper.levelList = [];
11
8
  });
12
9
 
@@ -241,6 +238,75 @@ describe('Helper', () => {
241
238
  // Assert
242
239
  expect(result.length).toBe(5);
243
240
  });
241
+
242
+ it('should emit siblings in array (tree) order even when their ids are not ascending', () => {
243
+ // Arrange - array order mirrors a manually reordered ADO tree: D, B, A, C
244
+ const suits = [
245
+ { id: '1', title: 'Root', parentSuiteId: 0 },
246
+ { id: '40', title: 'Suite D', parentSuiteId: '1' },
247
+ { id: '20', title: 'Suite B', parentSuiteId: '1' },
248
+ { id: '10', title: 'Suite A', parentSuiteId: '1' },
249
+ { id: '30', title: 'Suite C', parentSuiteId: '1' },
250
+ ];
251
+
252
+ // Act
253
+ const result = Helper.findSuitesRecursive(mockPlanId, mockUrl, mockProject, suits, '1', true);
254
+
255
+ // Assert
256
+ expect(result.map((s) => s.name)).toEqual(['Suite D', 'Suite B', 'Suite A', 'Suite C']);
257
+ });
258
+
259
+ it('should emit the selected nested suite first even when it appears last in the source array', () => {
260
+ // Arrange
261
+ const suits = [
262
+ { id: '3', title: 'Grandchild Suite', parentSuiteId: '2' },
263
+ { id: '1', title: 'Root Suite', parentSuiteId: 0 },
264
+ { id: '2', title: 'Child Suite 1', parentSuiteId: '1' },
265
+ ];
266
+
267
+ // Act
268
+ const result = Helper.findSuitesRecursive(mockPlanId, mockUrl, mockProject, suits, '2', true);
269
+
270
+ // Assert
271
+ expect(result.map((s) => s.name)).toEqual(['Child Suite 1', 'Grandchild Suite']);
272
+ });
273
+
274
+ it('should compute levels correctly regardless of source array position', () => {
275
+ // Arrange - grandchild appears before its parent in the array
276
+ const suits = [
277
+ { id: '3', title: 'Level 2', parentSuiteId: '2' },
278
+ { id: '1', title: 'Root', parentSuiteId: 0 },
279
+ { id: '2', title: 'Level 1', parentSuiteId: '1' },
280
+ ];
281
+
282
+ // Act
283
+ const result = Helper.findSuitesRecursive(mockPlanId, mockUrl, mockProject, suits, '1', true);
284
+
285
+ // Assert
286
+ expect(result.find((s) => s.name === 'Level 1')?.level).toBe(1);
287
+ expect(result.find((s) => s.name === 'Level 2')?.level).toBe(2);
288
+ });
289
+
290
+ it('should not leak state between sequential calls', () => {
291
+ // Arrange
292
+ const suitsA = [
293
+ { id: '1', title: 'Root A', parentSuiteId: 0 },
294
+ { id: '2', title: 'Child A', parentSuiteId: '1' },
295
+ ];
296
+ const suitsB = [
297
+ { id: '10', title: 'Root B', parentSuiteId: 0 },
298
+ { id: '20', title: 'Child B1', parentSuiteId: '10' },
299
+ { id: '30', title: 'Child B2', parentSuiteId: '10' },
300
+ ];
301
+
302
+ // Act
303
+ const resultA = Helper.findSuitesRecursive(mockPlanId, mockUrl, mockProject, suitsA, '1', true);
304
+ const resultB = Helper.findSuitesRecursive(mockPlanId, mockUrl, mockProject, suitsB, '10', true);
305
+
306
+ // Assert - resultB is unaffected by resultA's traversal
307
+ expect(resultA.map((s) => s.name)).toEqual(['Child A']);
308
+ expect(resultB.map((s) => s.name)).toEqual(['Child B1', 'Child B2']);
309
+ });
244
310
  });
245
311
 
246
312
  describe('LevelBuilder', () => {
@@ -326,7 +326,7 @@ describe('TestDataProvider', () => {
326
326
  const result = await bearerProvider.GetTestSuitesForPlan(mockProject, mockPlanId);
327
327
 
328
328
  expect(TFSServices.getItemContentWithHeaders).toHaveBeenCalledWith(
329
- `${mockOrgUrl.replace(/\/+$/, '')}/${mockProject}/_apis/testplan/Plans/${mockPlanId}/suites?expand=children&api-version=7.0`,
329
+ `${mockOrgUrl.replace(/\/+$/, '')}/${mockProject}/_apis/testplan/Plans/${mockPlanId}/suites?expand=children&asTreeView=true&api-version=7.0`,
330
330
  mockBearerToken,
331
331
  'get',
332
332
  {},
@@ -368,7 +368,7 @@ describe('TestDataProvider', () => {
368
368
  const result = await bearerProvider.GetTestSuitesForPlan(mockProject, mockPlanId);
369
369
 
370
370
  expect(TFSServices.getItemContentWithHeaders).toHaveBeenCalledWith(
371
- `${mockOrgUrl.replace(/\/+$/, '')}/${mockProject}/_apis/testplan/Plans/${mockPlanId}/suites?expand=children&api-version=7.0`,
371
+ `${mockOrgUrl.replace(/\/+$/, '')}/${mockProject}/_apis/testplan/Plans/${mockPlanId}/suites?expand=children&asTreeView=true&api-version=7.0`,
372
372
  mockBearerToken,
373
373
  'get',
374
374
  {},
@@ -407,6 +407,7 @@ describe('TestDataProvider', () => {
407
407
  const calledUrl = (TFSServices.getItemContentWithHeaders as jest.Mock).mock.calls[0][0] as string;
408
408
  expect(calledUrl).toContain(`/_apis/testplan/Plans/${mockPlanId}/suites`);
409
409
  expect(calledUrl).toContain('expand=children');
410
+ expect(calledUrl).toContain('asTreeView=true');
410
411
  expect(calledUrl).toContain('api-version=7.0');
411
412
  expect(calledUrl).not.toContain('includeChildren');
412
413
  });
@@ -480,6 +481,82 @@ describe('TestDataProvider', () => {
480
481
  expect(callCount).toBeGreaterThan(0);
481
482
  expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('reached MAX_PAGES'));
482
483
  });
484
+
485
+ it('should flatten a nested asTreeView=true payload into DFS pre-order', async () => {
486
+ const bearerProvider = new TestDataProvider(mockOrgUrl, mockBearerToken);
487
+ const treePayload = {
488
+ value: [
489
+ {
490
+ id: 1,
491
+ name: 'Root',
492
+ children: [
493
+ { id: 20, name: 'Suite B', parentSuite: { id: 1 } },
494
+ {
495
+ id: 10,
496
+ name: 'Suite A',
497
+ parentSuite: { id: 1 },
498
+ children: [{ id: 11, name: 'Suite A.1', parentSuite: { id: 10 } }],
499
+ },
500
+ ],
501
+ },
502
+ ],
503
+ count: 1,
504
+ };
505
+ (TFSServices.getItemContentWithHeaders as jest.Mock).mockResolvedValueOnce({
506
+ data: treePayload,
507
+ headers: {},
508
+ });
509
+
510
+ const result = await bearerProvider.GetTestSuitesForPlan(mockProject, mockPlanId);
511
+
512
+ expect(result.testSuites.map((s: any) => s.id)).toEqual([1, 20, 10, 11]);
513
+ expect(result.testSuites.map((s: any) => s.treeOrder)).toEqual([0, 1, 2, 3]);
514
+ });
515
+
516
+ it('should derive tree order from a flat list whose elements carry populated children[], without duplicating suites', async () => {
517
+ const bearerProvider = new TestDataProvider(mockOrgUrl, mockBearerToken);
518
+ // Server ignored/does-not-support asTreeView and returned a flat, id-ordered list,
519
+ // but each suite still carries its own children[] in display order.
520
+ const flatWithChildren = {
521
+ value: [
522
+ { id: 10, name: 'Suite A', parentSuite: { id: 1 }, children: [{ id: 11, name: 'Suite A.1' }] },
523
+ { id: 11, name: 'Suite A.1', parentSuite: { id: 10 } },
524
+ { id: 1, name: 'Root', children: [{ id: 10, name: 'Suite A' }, { id: 20, name: 'Suite B' }] },
525
+ { id: 20, name: 'Suite B', parentSuite: { id: 1 } },
526
+ ],
527
+ count: 4,
528
+ };
529
+ (TFSServices.getItemContentWithHeaders as jest.Mock).mockResolvedValueOnce({
530
+ data: flatWithChildren,
531
+ headers: {},
532
+ });
533
+
534
+ const result = await bearerProvider.GetTestSuitesForPlan(mockProject, mockPlanId);
535
+
536
+ expect(result.testSuites.map((s: any) => s.id)).toEqual([10, 11, 1, 20]);
537
+ expect(result.testSuites).toHaveLength(4);
538
+ });
539
+
540
+ it('should leave order unchanged for a flat list with no children field (regression guard)', async () => {
541
+ const bearerProvider = new TestDataProvider(mockOrgUrl, mockBearerToken);
542
+ const flatPayload = {
543
+ value: [
544
+ { id: 40, name: 'Suite D' },
545
+ { id: 20, name: 'Suite B' },
546
+ { id: 10, name: 'Suite A' },
547
+ ],
548
+ count: 3,
549
+ };
550
+ (TFSServices.getItemContentWithHeaders as jest.Mock).mockResolvedValueOnce({
551
+ data: flatPayload,
552
+ headers: {},
553
+ });
554
+
555
+ const result = await bearerProvider.GetTestSuitesForPlan(mockProject, mockPlanId);
556
+
557
+ expect(result.testSuites.map((s: any) => s.id)).toEqual([40, 20, 10]);
558
+ expect(result.testSuites.map((s: any) => s.treeOrder)).toEqual([0, 1, 2]);
559
+ });
483
560
  });
484
561
 
485
562
  describe('PAT-token branch — regression guard', () => {
@@ -548,7 +625,7 @@ describe('TestDataProvider', () => {
548
625
  const result = await bearerProvider.GetTestSuiteById(mockProject, mockPlanId, mockSuiteId, true);
549
626
 
550
627
  expect(TFSServices.getItemContentWithHeaders).toHaveBeenCalledWith(
551
- `${mockOrgUrl.replace(/\/+$/, '')}/${mockProject}/_apis/testplan/Plans/${mockPlanId}/suites?expand=children&api-version=7.0`,
628
+ `${mockOrgUrl.replace(/\/+$/, '')}/${mockProject}/_apis/testplan/Plans/${mockPlanId}/suites?expand=children&asTreeView=true&api-version=7.0`,
552
629
  mockBearerToken,
553
630
  'get',
554
631
  {},
@@ -672,6 +749,31 @@ describe('TestDataProvider', () => {
672
749
  expect(res).toEqual([{ id: '10' }, { id: '11' }]);
673
750
  });
674
751
 
752
+ it('should emit multiple hierarchies in tree order, not in suiteIdsFilter order', async () => {
753
+ jest.spyOn(testDataProvider, 'GetTestSuitesForPlan').mockResolvedValueOnce({
754
+ testSuites: [
755
+ { id: 1, parentSuiteId: 0, treeOrder: 0 },
756
+ { id: 2, parentSuiteId: 0, treeOrder: 1 },
757
+ { id: 10, parentSuiteId: 1, treeOrder: 2 },
758
+ { id: 11, parentSuiteId: 2, treeOrder: 3 },
759
+ ],
760
+ } as any);
761
+
762
+ // Caller lists the second hierarchy's suite (11) before the first (10),
763
+ // but suite 10 (treeOrder 2) precedes suite 11 (treeOrder 3) in the tree.
764
+ const suiteIdsFilter = [11, 10];
765
+ const getByIdSpy = jest
766
+ .spyOn(testDataProvider, 'GetTestSuiteById')
767
+ .mockResolvedValueOnce([{ id: '10' }])
768
+ .mockResolvedValueOnce([{ id: '11' }]);
769
+
770
+ const res = await testDataProvider.GetTestSuitesByPlan(mockProject, mockPlanId, true, suiteIdsFilter);
771
+
772
+ expect(getByIdSpy).toHaveBeenNthCalledWith(1, mockProject, mockPlanId, '10', true, suiteIdsFilter);
773
+ expect(getByIdSpy).toHaveBeenNthCalledWith(2, mockProject, mockPlanId, '11', true, suiteIdsFilter);
774
+ expect(res).toEqual([{ id: '10' }, { id: '11' }]);
775
+ });
776
+
675
777
  it('should fallback to first suite when no top-level suites can be determined', async () => {
676
778
  jest.spyOn(testDataProvider, 'GetTestSuitesForPlan').mockResolvedValueOnce({
677
779
  testSuites: [{ id: 1, parentSuiteId: 0 }],