@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/bin/modules/ResultDataProvider.d.ts +1 -0
- package/bin/modules/ResultDataProvider.js +63 -1
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +75 -0
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/ResultDataProvider.ts +87 -1
- package/src/tests/modules/ResultDataProvider.test.ts +94 -0
package/package.json
CHANGED
|
@@ -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}
|
|
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', () => {
|