@elisra-devops/docgen-data-provider 1.93.0 → 1.95.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.
@@ -333,6 +333,21 @@ describe('ResultDataProvider', () => {
333
333
  testCaseUrl: 'https://dev.azure.com/organization/test-project/_workitems/edit/1',
334
334
  });
335
335
  });
336
+
337
+ it('should include pointAsOfTimestamp when lastUpdatedDate is available', () => {
338
+ const testPoint = {
339
+ testCaseReference: { id: 1, name: 'Test Case 1' },
340
+ lastUpdatedDate: '2025-01-01T12:34:56Z',
341
+ };
342
+
343
+ const result = (resultDataProvider as any).mapTestPoint(testPoint, mockProjectName);
344
+
345
+ expect(result).toEqual(
346
+ expect.objectContaining({
347
+ pointAsOfTimestamp: '2025-01-01T12:34:56.000Z',
348
+ })
349
+ );
350
+ });
336
351
  });
337
352
 
338
353
  describe('calculateGroupResultSummary', () => {
@@ -679,6 +694,34 @@ describe('ResultDataProvider', () => {
679
694
 
680
695
  // Assert
681
696
  expect(result).toEqual(mockTestCases.value);
697
+ expect(TFSServices.getItemContent).toHaveBeenCalledWith(
698
+ expect.stringContaining(
699
+ `/_apis/testplan/Plans/${mockTestPlanId}/Suites/${mockSuiteId}/TestCase?witFields=Microsoft.VSTS.TCM.Steps,System.Rev`
700
+ ),
701
+ mockToken
702
+ );
703
+ });
704
+ });
705
+
706
+ describe('resolveSuiteTestCaseRevision', () => {
707
+ it('should resolve System.Rev from workItemFields', () => {
708
+ const revision = (resultDataProvider as any).resolveSuiteTestCaseRevision({
709
+ workItem: {
710
+ workItemFields: [{ key: 'System.Rev', value: '12' }],
711
+ },
712
+ });
713
+
714
+ expect(revision).toBe(12);
715
+ });
716
+
717
+ it('should resolve System.Rev case-insensitively from workItem fields map', () => {
718
+ const revision = (resultDataProvider as any).resolveSuiteTestCaseRevision({
719
+ workItem: {
720
+ fields: { 'system.rev': 14 },
721
+ },
722
+ });
723
+
724
+ expect(revision).toBe(14);
682
725
  });
683
726
  });
684
727
 
@@ -837,7 +880,7 @@ describe('ResultDataProvider', () => {
837
880
  const result = await (resultDataProvider as any).fetchCrossTestPoints(mockProjectName, [1, 2]);
838
881
 
839
882
  expect(TFSServices.getItemContent).toHaveBeenCalledWith(
840
- 'https://example.com/points/2?witFields=Microsoft.VSTS.TCM.Steps&includePointDetails=true',
883
+ 'https://example.com/points/2?witFields=Microsoft.VSTS.TCM.Steps,System.Rev&includePointDetails=true',
841
884
  mockToken
842
885
  );
843
886
  expect(result).toHaveLength(2);
@@ -3862,7 +3905,9 @@ describe('ResultDataProvider', () => {
3862
3905
  true,
3863
3906
  [],
3864
3907
  false,
3865
- point
3908
+ point,
3909
+ false,
3910
+ true
3866
3911
  );
3867
3912
 
3868
3913
  expect(TFSServices.getItemContent).toHaveBeenCalledWith(
@@ -3874,6 +3919,196 @@ describe('ResultDataProvider', () => {
3874
3919
  expect(res).toEqual(expect.objectContaining({ testCaseRevision: 9 }));
3875
3920
  });
3876
3921
 
3922
+ it('should ignore point asOf timestamp when runless asOf mode is disabled', async () => {
3923
+ const point = {
3924
+ testCaseId: '123',
3925
+ testCaseName: 'TC 123',
3926
+ outcome: 'passed',
3927
+ pointAsOfTimestamp: '2025-01-01T12:34:56Z',
3928
+ suiteTestCase: {
3929
+ workItem: {
3930
+ id: 123,
3931
+ rev: 9,
3932
+ workItemFields: [{ key: 'Microsoft.VSTS.TCM.Steps', value: '<steps></steps>' }],
3933
+ },
3934
+ },
3935
+ testSuite: { id: '1', name: 'Suite' },
3936
+ };
3937
+
3938
+ (TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({
3939
+ id: 123,
3940
+ rev: 9,
3941
+ fields: {
3942
+ 'System.State': 'Active',
3943
+ 'System.CreatedDate': '2024-01-01T00:00:00',
3944
+ 'Microsoft.VSTS.TCM.Priority': 1,
3945
+ 'System.Title': 'Title 123',
3946
+ 'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
3947
+ },
3948
+ relations: null,
3949
+ });
3950
+
3951
+ const res = await (resultDataProvider as any).fetchResultDataBasedOnWiBase(
3952
+ mockProjectName,
3953
+ '0',
3954
+ '0',
3955
+ true,
3956
+ [],
3957
+ false,
3958
+ point
3959
+ );
3960
+
3961
+ const calledUrls = (TFSServices.getItemContent as jest.Mock).mock.calls.map((args: any[]) => String(args[0]));
3962
+ expect(calledUrls.some((url: string) => url.includes('/_apis/wit/workItems/123?asOf='))).toBe(false);
3963
+ expect(calledUrls.some((url: string) => url.includes('/_apis/wit/workItems/123/revisions/9'))).toBe(true);
3964
+ expect(res).toEqual(expect.objectContaining({ testCaseRevision: 9 }));
3965
+ });
3966
+
3967
+ it('should fetch no-run test case by asOf timestamp when pointAsOfTimestamp is available', async () => {
3968
+ (TFSServices.getItemContent as jest.Mock).mockReset();
3969
+ const point = {
3970
+ testCaseId: '123',
3971
+ testCaseName: 'TC 123',
3972
+ outcome: 'passed',
3973
+ pointAsOfTimestamp: '2025-01-01T12:34:56Z',
3974
+ suiteTestCase: {
3975
+ workItem: {
3976
+ id: 123,
3977
+ rev: 9,
3978
+ workItemFields: [{ key: 'Microsoft.VSTS.TCM.Steps', value: '<steps></steps>' }],
3979
+ },
3980
+ },
3981
+ testSuite: { id: '1', name: 'Suite' },
3982
+ };
3983
+
3984
+ (TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({
3985
+ id: 123,
3986
+ rev: 6,
3987
+ fields: {
3988
+ 'System.State': 'Active',
3989
+ 'System.CreatedDate': '2024-01-01T00:00:00',
3990
+ 'Microsoft.VSTS.TCM.Priority': 1,
3991
+ 'System.Title': 'Title 123',
3992
+ 'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
3993
+ },
3994
+ relations: null,
3995
+ });
3996
+
3997
+ const res = await (resultDataProvider as any).fetchResultDataBasedOnWiBase(
3998
+ mockProjectName,
3999
+ '0',
4000
+ '0',
4001
+ true,
4002
+ [],
4003
+ false,
4004
+ point,
4005
+ false,
4006
+ true
4007
+ );
4008
+
4009
+ const calledUrls = (TFSServices.getItemContent as jest.Mock).mock.calls.map((args: any[]) => String(args[0]));
4010
+ expect(calledUrls.some((url: string) => url.includes('/_apis/wit/workItems/123?asOf='))).toBe(true);
4011
+ expect(calledUrls.some((url: string) => url.includes('/revisions/9'))).toBe(false);
4012
+ expect(res).toEqual(expect.objectContaining({ testCaseRevision: 6 }));
4013
+ });
4014
+
4015
+ it('should fallback to suite revision when asOf fetch fails', async () => {
4016
+ (TFSServices.getItemContent as jest.Mock).mockReset();
4017
+ const point = {
4018
+ testCaseId: '456',
4019
+ testCaseName: 'TC 456',
4020
+ outcome: 'Not Run',
4021
+ pointAsOfTimestamp: '2025-02-01T00:00:00Z',
4022
+ suiteTestCase: {
4023
+ workItem: {
4024
+ id: 456,
4025
+ workItemFields: [{ key: 'System.Rev', value: '11' }],
4026
+ },
4027
+ },
4028
+ testSuite: { id: '1', name: 'Suite' },
4029
+ };
4030
+
4031
+ (TFSServices.getItemContent as jest.Mock)
4032
+ .mockRejectedValueOnce(new Error('asOf failed'))
4033
+ .mockResolvedValueOnce({
4034
+ id: 456,
4035
+ rev: 11,
4036
+ fields: {
4037
+ 'System.State': 'Active',
4038
+ 'System.CreatedDate': '2024-01-01T00:00:00',
4039
+ 'Microsoft.VSTS.TCM.Priority': 1,
4040
+ 'System.Title': 'Title 456',
4041
+ 'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
4042
+ },
4043
+ relations: [],
4044
+ });
4045
+
4046
+ const res = await (resultDataProvider as any).fetchResultDataBasedOnWiBase(
4047
+ mockProjectName,
4048
+ '0',
4049
+ '0',
4050
+ true,
4051
+ [],
4052
+ false,
4053
+ point,
4054
+ false,
4055
+ true
4056
+ );
4057
+
4058
+ const calledUrls = (TFSServices.getItemContent as jest.Mock).mock.calls.map((args: any[]) => String(args[0]));
4059
+ expect(calledUrls.some((url: string) => url.includes('/_apis/wit/workItems/456?asOf='))).toBe(true);
4060
+ expect(calledUrls.some((url: string) => url.includes('/_apis/wit/workItems/456/revisions/11'))).toBe(true);
4061
+ expect(res).toEqual(expect.objectContaining({ testCaseRevision: 11 }));
4062
+ });
4063
+
4064
+ it('should resolve no-run revision from System.Rev in suite test-case fields', async () => {
4065
+ (TFSServices.getItemContent as jest.Mock).mockReset();
4066
+ const point = {
4067
+ testCaseId: '321',
4068
+ testCaseName: 'TC 321',
4069
+ outcome: 'Not Run',
4070
+ suiteTestCase: {
4071
+ workItem: {
4072
+ id: 321,
4073
+ workItemFields: [
4074
+ { key: 'System.Rev', value: '13' },
4075
+ { key: 'Microsoft.VSTS.TCM.Steps', value: '<steps></steps>' },
4076
+ ],
4077
+ },
4078
+ },
4079
+ testSuite: { id: '1', name: 'Suite' },
4080
+ };
4081
+
4082
+ (TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({
4083
+ id: 321,
4084
+ rev: 13,
4085
+ fields: {
4086
+ 'System.State': 'Design',
4087
+ 'System.CreatedDate': '2024-05-01T00:00:00',
4088
+ 'Microsoft.VSTS.TCM.Priority': 1,
4089
+ 'System.Title': 'Title 321',
4090
+ 'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
4091
+ },
4092
+ relations: [],
4093
+ });
4094
+
4095
+ const res = await (resultDataProvider as any).fetchResultDataBasedOnWiBase(
4096
+ mockProjectName,
4097
+ '0',
4098
+ '0',
4099
+ true,
4100
+ [],
4101
+ false,
4102
+ point
4103
+ );
4104
+
4105
+ expect(TFSServices.getItemContent).toHaveBeenCalledWith(
4106
+ expect.stringContaining('/_apis/wit/workItems/321/revisions/13?$expand=all'),
4107
+ mockToken
4108
+ );
4109
+ expect(res).toEqual(expect.objectContaining({ testCaseRevision: 13 }));
4110
+ });
4111
+
3877
4112
  it('should append linked relations and filter testCaseWorkItemField when isTestReporter=true and isQueryMode=false', async () => {
3878
4113
  (TFSServices.getItemContent as jest.Mock).mockReset();
3879
4114
 
@@ -5045,6 +5280,35 @@ describe('ResultDataProvider', () => {
5045
5280
  expect(fetchStrategy).not.toHaveBeenCalled();
5046
5281
  expect(result).toEqual([]);
5047
5282
  });
5283
+
5284
+ it('should keep points without run/result IDs when test reporter mode is enabled', async () => {
5285
+ const testData = [
5286
+ {
5287
+ testSuiteId: 1,
5288
+ testPointsItems: [{ testCaseId: 10, lastRunId: 101, lastResultId: 201 }, { testCaseId: 11 }],
5289
+ },
5290
+ ];
5291
+ const fetchStrategy = jest
5292
+ .fn()
5293
+ .mockResolvedValueOnce({ testCaseId: 10 })
5294
+ .mockResolvedValueOnce({ testCaseId: 11 });
5295
+
5296
+ const result = await (resultDataProvider as any).fetchAllResultDataBase(
5297
+ testData,
5298
+ mockProjectName,
5299
+ true,
5300
+ fetchStrategy
5301
+ );
5302
+
5303
+ expect(fetchStrategy).toHaveBeenCalledTimes(2);
5304
+ expect(fetchStrategy).toHaveBeenNthCalledWith(
5305
+ 2,
5306
+ mockProjectName,
5307
+ 1,
5308
+ expect.objectContaining({ testCaseId: 11 })
5309
+ );
5310
+ expect(result).toEqual([{ testCaseId: 10 }, { testCaseId: 11 }]);
5311
+ });
5048
5312
  });
5049
5313
 
5050
5314
  describe('fetchResultDataBase', () => {
@@ -5071,6 +5335,26 @@ describe('ResultDataProvider', () => {
5071
5335
  expect(fetchResultMethod).toHaveBeenCalled();
5072
5336
  expect(result).toBeDefined();
5073
5337
  });
5338
+
5339
+ it('should call fetch method with runId/resultId as 0 when point has no run history', async () => {
5340
+ const point = { testCaseId: 15, lastRunId: undefined, lastResultId: undefined };
5341
+ const fetchResultMethod = jest.fn().mockResolvedValue({
5342
+ testCase: { id: 15, name: 'TC 15' },
5343
+ testSuite: { name: 'S' },
5344
+ iterationDetails: [],
5345
+ });
5346
+ const createResponseObject = jest.fn().mockReturnValue({ id: 15 });
5347
+
5348
+ await (resultDataProvider as any).fetchResultDataBase(
5349
+ mockProjectName,
5350
+ 'suite-no-runs',
5351
+ point,
5352
+ fetchResultMethod,
5353
+ createResponseObject
5354
+ );
5355
+
5356
+ expect(fetchResultMethod).toHaveBeenCalledWith(mockProjectName, '0', '0');
5357
+ });
5074
5358
  });
5075
5359
 
5076
5360
  describe('getCombinedResultsSummary', () => {
@@ -5460,6 +5744,86 @@ describe('ResultDataProvider', () => {
5460
5744
  })
5461
5745
  );
5462
5746
  });
5747
+
5748
+ it('should return test-level row with empty step fields when suite has no run history', async () => {
5749
+ jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan 12');
5750
+ jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([
5751
+ {
5752
+ testSuiteId: 300,
5753
+ suiteId: 300,
5754
+ suiteName: 'suite no runs',
5755
+ parentSuiteId: 100,
5756
+ parentSuiteName: 'Rel3',
5757
+ suitePath: 'Root/Rel3/suite no runs',
5758
+ testGroupName: 'suite no runs',
5759
+ },
5760
+ ]);
5761
+
5762
+ jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
5763
+ {
5764
+ testSuiteId: 300,
5765
+ suiteId: 300,
5766
+ suiteName: 'suite no runs',
5767
+ parentSuiteId: 100,
5768
+ parentSuiteName: 'Rel3',
5769
+ suitePath: 'Root/Rel3/suite no runs',
5770
+ testGroupName: 'suite no runs',
5771
+ testPointsItems: [
5772
+ {
5773
+ testCaseId: 55,
5774
+ testCaseName: 'TC 55',
5775
+ outcome: 'Not Run',
5776
+ testPointId: 9001,
5777
+ lastRunId: undefined,
5778
+ lastResultId: undefined,
5779
+ lastResultDetails: undefined,
5780
+ },
5781
+ ],
5782
+ testCasesItems: [
5783
+ {
5784
+ workItem: {
5785
+ id: 55,
5786
+ workItemFields: [{ key: 'System.Rev', value: 4 }],
5787
+ },
5788
+ },
5789
+ ],
5790
+ },
5791
+ ]);
5792
+
5793
+ jest.spyOn(resultDataProvider as any, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([
5794
+ {
5795
+ testCaseId: 55,
5796
+ testCase: { id: 55, name: 'TC 55' },
5797
+ testSuite: { name: 'suite no runs' },
5798
+ executionDate: '',
5799
+ testCaseResult: { resultMessage: 'Not Run', url: '' },
5800
+ customFields: {},
5801
+ runBy: '',
5802
+ iteration: undefined,
5803
+ lastRunId: undefined,
5804
+ lastResultId: undefined,
5805
+ },
5806
+ ]);
5807
+
5808
+ const result = await resultDataProvider.getTestReporterFlatResults(
5809
+ mockTestPlanId,
5810
+ mockProjectName,
5811
+ undefined,
5812
+ [],
5813
+ false
5814
+ );
5815
+
5816
+ expect(result.rows).toHaveLength(1);
5817
+ expect(result.rows[0]).toEqual(
5818
+ expect.objectContaining({
5819
+ testCaseId: 55,
5820
+ testRunId: undefined,
5821
+ testPointId: 9001,
5822
+ stepOutcome: undefined,
5823
+ stepStepIdentifier: '',
5824
+ })
5825
+ );
5826
+ });
5463
5827
  });
5464
5828
 
5465
5829
  describe('getCombinedResultsSummary - appendix branches', () => {