@elisra-devops/docgen-data-provider 1.103.0 → 1.104.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elisra-devops/docgen-data-provider",
3
- "version": "1.103.0",
3
+ "version": "1.104.0",
4
4
  "description": "A document generator data provider, aimed to retrive data from azure devops",
5
5
  "repository": {
6
6
  "type": "git",
@@ -826,6 +826,13 @@ export default class ResultDataProvider {
826
826
  }
827
827
 
828
828
  const preloadedStepXmlCount = stepsXmlByTestCase.size;
829
+ const latestWorkItemMapStats = await this.enrichMewpTestCaseMapsFromLatestWorkItems(
830
+ projectName,
831
+ [...allTestCaseIds],
832
+ stepsXmlByTestCase,
833
+ testCaseTitleMap,
834
+ testCaseDescriptionMap
835
+ );
829
836
  const fallbackStepLoadStats = await this.enrichMewpStepsXmlMapFromWorkItems(
830
837
  projectName,
831
838
  [...allTestCaseIds],
@@ -833,7 +840,11 @@ export default class ResultDataProvider {
833
840
  );
834
841
  logger.info(
835
842
  `MEWP internal validation steps source summary: testCases=${allTestCaseIds.size} ` +
836
- `fromSuitePayload=${preloadedStepXmlCount} fromWorkItemFallback=${fallbackStepLoadStats.loadedFromFallback} ` +
843
+ `fromSuitePayload=${preloadedStepXmlCount} ` +
844
+ `fromLatestWorkItem=${latestWorkItemMapStats.stepsLoadedFromLatest} ` +
845
+ `titleFromLatest=${latestWorkItemMapStats.titleLoadedFromLatest} ` +
846
+ `descriptionFromLatest=${latestWorkItemMapStats.descriptionLoadedFromLatest} ` +
847
+ `fromWorkItemFallback=${fallbackStepLoadStats.loadedFromFallback} ` +
837
848
  `stepsXmlAvailable=${stepsXmlByTestCase.size} unresolved=${fallbackStepLoadStats.unresolvedCount}`
838
849
  );
839
850
 
@@ -1744,6 +1755,81 @@ export default class ResultDataProvider {
1744
1755
  return map;
1745
1756
  }
1746
1757
 
1758
+ private async enrichMewpTestCaseMapsFromLatestWorkItems(
1759
+ projectName: string,
1760
+ testCaseIds: number[],
1761
+ stepsXmlByTestCase: Map<number, string>,
1762
+ testCaseTitleMap: Map<number, string>,
1763
+ testCaseDescriptionMap: Map<number, string>
1764
+ ): Promise<{ stepsLoadedFromLatest: number; titleLoadedFromLatest: number; descriptionLoadedFromLatest: number }> {
1765
+ const uniqueIds = [...new Set(testCaseIds)]
1766
+ .map((id) => Number(id))
1767
+ .filter((id) => Number.isFinite(id) && id > 0);
1768
+
1769
+ if (uniqueIds.length === 0) {
1770
+ return {
1771
+ stepsLoadedFromLatest: 0,
1772
+ titleLoadedFromLatest: 0,
1773
+ descriptionLoadedFromLatest: 0,
1774
+ };
1775
+ }
1776
+
1777
+ let stepsLoadedFromLatest = 0;
1778
+ let titleLoadedFromLatest = 0;
1779
+ let descriptionLoadedFromLatest = 0;
1780
+
1781
+ try {
1782
+ const latestWorkItems = await this.fetchWorkItemsByIds(projectName, uniqueIds, false);
1783
+ for (const workItem of latestWorkItems || []) {
1784
+ const id = Number(workItem?.id || 0);
1785
+ if (!Number.isFinite(id) || id <= 0) continue;
1786
+
1787
+ const fields = workItem?.fields || {};
1788
+ const latestStepsXml = this.extractStepsXmlFromFieldsMap(fields);
1789
+ if (latestStepsXml) {
1790
+ const previous = String(stepsXmlByTestCase.get(id) || '');
1791
+ if (previous !== latestStepsXml) {
1792
+ stepsLoadedFromLatest += 1;
1793
+ }
1794
+ stepsXmlByTestCase.set(id, latestStepsXml);
1795
+ }
1796
+
1797
+ const latestTitle = this.toMewpComparableText(
1798
+ this.getFieldValueByName(fields, 'System.Title') ?? this.getFieldValueByName(fields, 'Title')
1799
+ );
1800
+ if (latestTitle) {
1801
+ const previous = String(testCaseTitleMap.get(id) || '');
1802
+ if (previous !== latestTitle) {
1803
+ titleLoadedFromLatest += 1;
1804
+ }
1805
+ testCaseTitleMap.set(id, latestTitle);
1806
+ }
1807
+
1808
+ const latestDescription = this.toMewpComparableText(
1809
+ this.getFieldValueByName(fields, 'System.Description') ??
1810
+ this.getFieldValueByName(fields, 'Description')
1811
+ );
1812
+ if (latestDescription) {
1813
+ const previous = String(testCaseDescriptionMap.get(id) || '');
1814
+ if (previous !== latestDescription) {
1815
+ descriptionLoadedFromLatest += 1;
1816
+ }
1817
+ testCaseDescriptionMap.set(id, latestDescription);
1818
+ }
1819
+ }
1820
+ } catch (error: any) {
1821
+ logger.warn(
1822
+ `MEWP internal validation: failed to load latest test-case fields: ${error?.message || error}`
1823
+ );
1824
+ }
1825
+
1826
+ return {
1827
+ stepsLoadedFromLatest,
1828
+ titleLoadedFromLatest,
1829
+ descriptionLoadedFromLatest,
1830
+ };
1831
+ }
1832
+
1747
1833
  private extractMewpTestCaseId(runResult: any): number {
1748
1834
  const testCaseId = Number(runResult?.testCaseId || runResult?.testCase?.id || 0);
1749
1835
  return Number.isFinite(testCaseId) ? testCaseId : 0;
@@ -3505,6 +3505,100 @@ describe('ResultDataProvider', () => {
3505
3505
  })
3506
3506
  );
3507
3507
  });
3508
+
3509
+ it('should prefer latest test-case revision fields (title/description/steps) in internal validation flow', async () => {
3510
+ jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
3511
+ jest.spyOn(resultDataProvider as any, 'fetchMewpScopedTestData').mockResolvedValueOnce([
3512
+ {
3513
+ testPointsItems: [{ testCaseId: 777, testCaseName: 'TC 777 (snapshot)' }],
3514
+ testCasesItems: [
3515
+ {
3516
+ workItem: {
3517
+ id: 777,
3518
+ workItemFields: [
3519
+ { key: 'System.Title', value: 'TC 777 (snapshot title)' },
3520
+ {
3521
+ key: 'Microsoft.VSTS.TCM.Steps',
3522
+ value:
3523
+ '<steps><step id="2" type="ActionStep"><parameterizedString isformatted="true">Action</parameterizedString><parameterizedString isformatted="true">SR0001</parameterizedString></step></steps>',
3524
+ },
3525
+ { key: 'System.Description', value: '<p>Snapshot description</p>' },
3526
+ ],
3527
+ },
3528
+ },
3529
+ ],
3530
+ },
3531
+ ]);
3532
+ jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
3533
+ {
3534
+ workItemId: 9777,
3535
+ requirementId: 'SR7777',
3536
+ baseKey: 'SR7777',
3537
+ title: 'Req 7777',
3538
+ responsibility: 'ESUK',
3539
+ linkedTestCaseIds: [777],
3540
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
3541
+ },
3542
+ ]);
3543
+ jest.spyOn(resultDataProvider as any, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(
3544
+ new Map([
3545
+ [
3546
+ 777,
3547
+ {
3548
+ baseKeys: new Set(['SR7777']),
3549
+ fullCodes: new Set(['SR7777']),
3550
+ },
3551
+ ],
3552
+ ])
3553
+ );
3554
+ const fetchWorkItemsByIdsSpy = jest
3555
+ .spyOn(resultDataProvider as any, 'fetchWorkItemsByIds')
3556
+ .mockResolvedValueOnce([
3557
+ {
3558
+ id: 777,
3559
+ fields: {
3560
+ 'System.Title': 'TC 777 (latest title)',
3561
+ 'System.Description':
3562
+ '<p><b><u>Trial specific assumptions, constraints, dependencies and requirements</u></b></p><p>SR7777</p>',
3563
+ 'Microsoft.VSTS.TCM.Steps':
3564
+ '<steps><step id="2" type="ActionStep"><parameterizedString isformatted="true">Action</parameterizedString><parameterizedString isformatted="true">SR7777</parameterizedString></step></steps>',
3565
+ },
3566
+ },
3567
+ ]);
3568
+ jest.spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps').mockImplementation(
3569
+ async (...args: any[]) => [
3570
+ {
3571
+ stepId: '1',
3572
+ stepPosition: '1',
3573
+ action: 'Action',
3574
+ expected: String(args?.[0] || '').includes('SR7777') ? 'SR7777' : 'SR0001',
3575
+ isSharedStepTitle: false,
3576
+ },
3577
+ ]
3578
+ );
3579
+
3580
+ const result = await (resultDataProvider as any).getMewpInternalValidationFlatResults(
3581
+ '123',
3582
+ mockProjectName,
3583
+ [1]
3584
+ );
3585
+
3586
+ expect(fetchWorkItemsByIdsSpy).toHaveBeenCalledWith(mockProjectName, [777], false);
3587
+ expect((resultDataProvider as any).testStepParserHelper.parseTestSteps).toHaveBeenCalledWith(
3588
+ expect.stringContaining('SR7777'),
3589
+ expect.any(Map)
3590
+ );
3591
+ expect(result.rows).toHaveLength(1);
3592
+ expect(result.rows[0]).toEqual(
3593
+ expect.objectContaining({
3594
+ 'Test Case ID': 777,
3595
+ 'Test Case Title': 'TC 777 (latest title)',
3596
+ 'Mentioned but Not Linked': '',
3597
+ 'Linked but Not Mentioned': '',
3598
+ 'Validation Status': 'Pass',
3599
+ })
3600
+ );
3601
+ });
3508
3602
  });
3509
3603
 
3510
3604
  describe('buildLinkedRequirementsByTestCase', () => {