@elisra-devops/docgen-data-provider 1.102.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/models/mewp-reporting.d.ts +5 -0
- package/bin/modules/ResultDataProvider.d.ts +3 -0
- package/bin/modules/ResultDataProvider.js +128 -90
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +114 -1
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/models/mewp-reporting.ts +5 -0
- package/src/modules/ResultDataProvider.ts +154 -115
- package/src/tests/modules/ResultDataProvider.test.ts +151 -1
package/package.json
CHANGED
|
@@ -28,6 +28,7 @@ export interface MewpL2RequirementWorkItem {
|
|
|
28
28
|
requirementId: string;
|
|
29
29
|
baseKey: string;
|
|
30
30
|
title: string;
|
|
31
|
+
owner: string;
|
|
31
32
|
subSystem: string;
|
|
32
33
|
responsibility: string;
|
|
33
34
|
linkedTestCaseIds: number[];
|
|
@@ -40,6 +41,7 @@ export interface MewpL2RequirementFamily {
|
|
|
40
41
|
requirementId: string;
|
|
41
42
|
baseKey: string;
|
|
42
43
|
title: string;
|
|
44
|
+
owner: string;
|
|
43
45
|
subSystem: string;
|
|
44
46
|
responsibility: string;
|
|
45
47
|
linkedTestCaseIds: number[];
|
|
@@ -84,7 +86,10 @@ export type MewpCoverageL3L4Cell = MewpL3L4Pair;
|
|
|
84
86
|
|
|
85
87
|
export interface MewpCoverageRow {
|
|
86
88
|
'L2 REQ ID': string;
|
|
89
|
+
'SR #': string;
|
|
87
90
|
'L2 REQ Title': string;
|
|
91
|
+
'L2 REQ Full Title'?: string;
|
|
92
|
+
'L2 Owner': string;
|
|
88
93
|
'L2 SubSystem': string;
|
|
89
94
|
'L2 Run Status': MewpRunStatus;
|
|
90
95
|
'Bug ID': number | '';
|
|
@@ -61,7 +61,9 @@ export default class ResultDataProvider {
|
|
|
61
61
|
|
|
62
62
|
private static readonly MEWP_L2_COVERAGE_COLUMNS = [
|
|
63
63
|
'L2 REQ ID',
|
|
64
|
+
'SR #',
|
|
64
65
|
'L2 REQ Title',
|
|
66
|
+
'L2 Owner',
|
|
65
67
|
'L2 SubSystem',
|
|
66
68
|
'L2 Run Status',
|
|
67
69
|
'Bug ID',
|
|
@@ -824,6 +826,13 @@ export default class ResultDataProvider {
|
|
|
824
826
|
}
|
|
825
827
|
|
|
826
828
|
const preloadedStepXmlCount = stepsXmlByTestCase.size;
|
|
829
|
+
const latestWorkItemMapStats = await this.enrichMewpTestCaseMapsFromLatestWorkItems(
|
|
830
|
+
projectName,
|
|
831
|
+
[...allTestCaseIds],
|
|
832
|
+
stepsXmlByTestCase,
|
|
833
|
+
testCaseTitleMap,
|
|
834
|
+
testCaseDescriptionMap
|
|
835
|
+
);
|
|
827
836
|
const fallbackStepLoadStats = await this.enrichMewpStepsXmlMapFromWorkItems(
|
|
828
837
|
projectName,
|
|
829
838
|
[...allTestCaseIds],
|
|
@@ -831,7 +840,11 @@ export default class ResultDataProvider {
|
|
|
831
840
|
);
|
|
832
841
|
logger.info(
|
|
833
842
|
`MEWP internal validation steps source summary: testCases=${allTestCaseIds.size} ` +
|
|
834
|
-
`fromSuitePayload=${preloadedStepXmlCount}
|
|
843
|
+
`fromSuitePayload=${preloadedStepXmlCount} ` +
|
|
844
|
+
`fromLatestWorkItem=${latestWorkItemMapStats.stepsLoadedFromLatest} ` +
|
|
845
|
+
`titleFromLatest=${latestWorkItemMapStats.titleLoadedFromLatest} ` +
|
|
846
|
+
`descriptionFromLatest=${latestWorkItemMapStats.descriptionLoadedFromLatest} ` +
|
|
847
|
+
`fromWorkItemFallback=${fallbackStepLoadStats.loadedFromFallback} ` +
|
|
835
848
|
`stepsXmlAvailable=${stepsXmlByTestCase.size} unresolved=${fallbackStepLoadStats.unresolvedCount}`
|
|
836
849
|
);
|
|
837
850
|
|
|
@@ -915,24 +928,6 @@ export default class ResultDataProvider {
|
|
|
915
928
|
if (!mentionedCodesByBase.has(baseKey)) mentionedCodesByBase.set(baseKey, new Set<string>());
|
|
916
929
|
mentionedCodesByBase.get(baseKey)!.add(code);
|
|
917
930
|
}
|
|
918
|
-
if (traceCurrentTestCase) {
|
|
919
|
-
logger.debug(
|
|
920
|
-
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
921
|
-
event: 'test-case-start',
|
|
922
|
-
tc: testCaseId,
|
|
923
|
-
parsedSteps: executableSteps.length,
|
|
924
|
-
stepsWithMentions: mentionEntries.length,
|
|
925
|
-
mentionedCodes:
|
|
926
|
-
[...mentionedL2Only].sort((a, b) => this.compareMewpRequirementCodes(a, b)).join('; ') ||
|
|
927
|
-
'<none>',
|
|
928
|
-
linkedCodesInTestCase:
|
|
929
|
-
[...linkedFullCodes]
|
|
930
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
931
|
-
.join('; ') || '<none>',
|
|
932
|
-
})
|
|
933
|
-
);
|
|
934
|
-
}
|
|
935
|
-
|
|
936
931
|
// Direction A logic:
|
|
937
932
|
// 1) Base mention ("SR0054") is parent-level and considered covered only when
|
|
938
933
|
// the whole family is covered across scoped test cases:
|
|
@@ -958,38 +953,12 @@ export default class ResultDataProvider {
|
|
|
958
953
|
|
|
959
954
|
// Base mention ("SR0054") requires full family coverage across selected test cases.
|
|
960
955
|
if (hasBaseMention) {
|
|
961
|
-
const missingRequiredFamilyMembers = requiredFamilyMembers.filter(
|
|
962
|
-
(memberCode) => !familyLinkedCodes.has(memberCode)
|
|
963
|
-
);
|
|
964
956
|
const isWholeFamilyCovered = requiredFamilyMembers.every((memberCode) =>
|
|
965
957
|
familyLinkedCodes.has(memberCode)
|
|
966
958
|
);
|
|
967
959
|
if (!isWholeFamilyCovered) {
|
|
968
960
|
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
969
961
|
}
|
|
970
|
-
if (traceCurrentTestCase) {
|
|
971
|
-
logger.debug(
|
|
972
|
-
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
973
|
-
event: 'base-family-coverage',
|
|
974
|
-
tc: testCaseId,
|
|
975
|
-
base: baseKey,
|
|
976
|
-
baseMention: true,
|
|
977
|
-
requiredFamily:
|
|
978
|
-
requiredFamilyMembers
|
|
979
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
980
|
-
.join('; ') || '<none>',
|
|
981
|
-
linkedAcrossScope:
|
|
982
|
-
[...familyLinkedCodes]
|
|
983
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
984
|
-
.join('; ') || '<none>',
|
|
985
|
-
missingRequired:
|
|
986
|
-
missingRequiredFamilyMembers
|
|
987
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
988
|
-
.join('; ') || '<none>',
|
|
989
|
-
covered: isWholeFamilyCovered,
|
|
990
|
-
})
|
|
991
|
-
);
|
|
992
|
-
}
|
|
993
962
|
}
|
|
994
963
|
|
|
995
964
|
// Specific mention ("SR0054-1") validates as exact-match only across scoped test cases.
|
|
@@ -999,60 +968,20 @@ export default class ResultDataProvider {
|
|
|
999
968
|
for (const code of missingSpecificMembers) {
|
|
1000
969
|
missingSpecificMentionedNoFamily.add(code);
|
|
1001
970
|
}
|
|
1002
|
-
if (traceCurrentTestCase && mentionedSpecificMembers.length > 0) {
|
|
1003
|
-
logger.debug(
|
|
1004
|
-
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
1005
|
-
event: 'specific-members-check',
|
|
1006
|
-
tc: testCaseId,
|
|
1007
|
-
base: baseKey,
|
|
1008
|
-
specificMentioned:
|
|
1009
|
-
mentionedSpecificMembers
|
|
1010
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
1011
|
-
.join('; ') || '<none>',
|
|
1012
|
-
specificMissing:
|
|
1013
|
-
missingSpecificMembers
|
|
1014
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
1015
|
-
.join('; ') || '<none>',
|
|
1016
|
-
})
|
|
1017
|
-
);
|
|
1018
|
-
}
|
|
1019
971
|
continue;
|
|
1020
972
|
}
|
|
1021
973
|
|
|
1022
974
|
// Fallback path when family data is unavailable for this base key.
|
|
1023
|
-
const fallbackMissingSpecific: string[] = [];
|
|
1024
|
-
let fallbackMissingBase = false;
|
|
1025
975
|
for (const code of mentionedCodes) {
|
|
1026
976
|
const hasSpecificSuffix = /-\d+$/.test(code);
|
|
1027
977
|
if (hasSpecificSuffix) {
|
|
1028
978
|
if (!linkedFullCodesAcrossTestCases.has(code)) {
|
|
1029
979
|
missingSpecificMentionedNoFamily.add(code);
|
|
1030
|
-
fallbackMissingSpecific.push(code);
|
|
1031
980
|
}
|
|
1032
981
|
} else if (!linkedBaseKeysAcrossTestCases.has(baseKey)) {
|
|
1033
982
|
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
1034
|
-
fallbackMissingBase = true;
|
|
1035
983
|
}
|
|
1036
984
|
}
|
|
1037
|
-
if (traceCurrentTestCase) {
|
|
1038
|
-
logger.debug(
|
|
1039
|
-
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
1040
|
-
event: 'fallback-path',
|
|
1041
|
-
tc: testCaseId,
|
|
1042
|
-
base: baseKey,
|
|
1043
|
-
fallbackUsed: true,
|
|
1044
|
-
mentioned:
|
|
1045
|
-
mentionedCodesList
|
|
1046
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
1047
|
-
.join('; ') || '<none>',
|
|
1048
|
-
missingSpecific:
|
|
1049
|
-
fallbackMissingSpecific
|
|
1050
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
1051
|
-
.join('; ') || '<none>',
|
|
1052
|
-
missingBase: fallbackMissingBase,
|
|
1053
|
-
})
|
|
1054
|
-
);
|
|
1055
|
-
}
|
|
1056
985
|
}
|
|
1057
986
|
|
|
1058
987
|
// Direction B is family-based: if any member of a family is mentioned in Expected Result,
|
|
@@ -1098,17 +1027,6 @@ export default class ResultDataProvider {
|
|
|
1098
1027
|
const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
|
|
1099
1028
|
appendMentionedButNotLinked(baseKey, stepRef);
|
|
1100
1029
|
}
|
|
1101
|
-
if (traceCurrentTestCase) {
|
|
1102
|
-
logger.debug(
|
|
1103
|
-
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
1104
|
-
event: 'direction-a-summary',
|
|
1105
|
-
tc: testCaseId,
|
|
1106
|
-
missingSpecific: sortedMissingSpecificMentionedNoFamily.join('; ') || '<none>',
|
|
1107
|
-
missingBase: sortedMissingBaseWhenFamilyUncovered.join('; ') || '<none>',
|
|
1108
|
-
})
|
|
1109
|
-
);
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
1030
|
const sortedExtraLinked = [...new Set(extraLinked)]
|
|
1113
1031
|
.map((code) => this.normalizeMewpRequirementCodeWithSuffix(code))
|
|
1114
1032
|
.filter((code) => !!code)
|
|
@@ -1146,21 +1064,23 @@ export default class ResultDataProvider {
|
|
|
1146
1064
|
const validationStatus: 'Pass' | 'Fail' =
|
|
1147
1065
|
mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
|
|
1148
1066
|
if (validationStatus === 'Fail') diagnostics.failingRows += 1;
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1067
|
+
if (traceCurrentTestCase) {
|
|
1068
|
+
logger.debug(
|
|
1069
|
+
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG, {
|
|
1070
|
+
testCaseId,
|
|
1071
|
+
parsedSteps: executableSteps.length,
|
|
1072
|
+
stepsWithMentions: mentionEntries.length,
|
|
1073
|
+
customerIdsFound: mentionedL2Only.size,
|
|
1074
|
+
linkedRequirements: linkedFullCodes.size,
|
|
1075
|
+
mentionedButNotLinked:
|
|
1076
|
+
sortedMissingSpecificMentionedNoFamily.length +
|
|
1077
|
+
sortedMissingBaseWhenFamilyUncovered.length,
|
|
1078
|
+
linkedButNotMentioned: sortedExtraLinked.length,
|
|
1079
|
+
status: validationStatus,
|
|
1080
|
+
customerIdSample: [...mentionedL2Only].slice(0, 5).join(', '),
|
|
1081
|
+
})
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1164
1084
|
|
|
1165
1085
|
rows.push({
|
|
1166
1086
|
'Test Case ID': testCaseId,
|
|
@@ -1309,7 +1229,7 @@ export default class ResultDataProvider {
|
|
|
1309
1229
|
private createMewpCoverageRow(
|
|
1310
1230
|
requirement: Pick<
|
|
1311
1231
|
MewpL2RequirementFamily,
|
|
1312
|
-
'workItemId' | 'requirementId' | 'title' | 'subSystem' | 'responsibility'
|
|
1232
|
+
'workItemId' | 'requirementId' | 'title' | 'owner' | 'subSystem' | 'responsibility'
|
|
1313
1233
|
>,
|
|
1314
1234
|
runStatus: MewpRunStatus,
|
|
1315
1235
|
bug: MewpCoverageBugCell,
|
|
@@ -1317,12 +1237,18 @@ export default class ResultDataProvider {
|
|
|
1317
1237
|
): MewpCoverageRow {
|
|
1318
1238
|
const l2ReqIdNumeric = Number(requirement?.workItemId || 0);
|
|
1319
1239
|
const l2ReqId = l2ReqIdNumeric > 0 ? String(l2ReqIdNumeric) : '';
|
|
1240
|
+
const srNumber = this.normalizeMewpRequirementCodeWithSuffix(requirement?.requirementId || '');
|
|
1320
1241
|
const l2ReqTitle = this.toMewpComparableText(requirement.title);
|
|
1242
|
+
const reqName = this.deriveMewpRequirementDisplayName(srNumber, l2ReqTitle);
|
|
1243
|
+
const l2Owner = this.toMewpComparableText(requirement.owner);
|
|
1321
1244
|
const l2SubSystem = this.toMewpComparableText(requirement.subSystem);
|
|
1322
1245
|
|
|
1323
1246
|
return {
|
|
1324
1247
|
'L2 REQ ID': l2ReqId,
|
|
1325
|
-
'
|
|
1248
|
+
'SR #': srNumber,
|
|
1249
|
+
'L2 REQ Title': reqName,
|
|
1250
|
+
'L2 REQ Full Title': l2ReqTitle,
|
|
1251
|
+
'L2 Owner': l2Owner,
|
|
1326
1252
|
'L2 SubSystem': l2SubSystem,
|
|
1327
1253
|
'L2 Run Status': runStatus,
|
|
1328
1254
|
'Bug ID': Number.isFinite(Number(bug?.id)) && Number(bug?.id) > 0 ? Number(bug?.id) : '',
|
|
@@ -1335,6 +1261,20 @@ export default class ResultDataProvider {
|
|
|
1335
1261
|
};
|
|
1336
1262
|
}
|
|
1337
1263
|
|
|
1264
|
+
private deriveMewpRequirementDisplayName(requirementCode: string, title: string): string {
|
|
1265
|
+
const normalizedTitle = this.toMewpComparableText(title);
|
|
1266
|
+
if (!normalizedTitle) return '';
|
|
1267
|
+
|
|
1268
|
+
const normalizedCode = this.normalizeMewpRequirementCodeWithSuffix(requirementCode || '');
|
|
1269
|
+
if (!normalizedCode) return normalizedTitle;
|
|
1270
|
+
|
|
1271
|
+
const escapedCode = normalizedCode.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1272
|
+
const codePrefixPattern = new RegExp(`^${escapedCode}(?:\\s*[:\\-–—]\\s*|\\s+)`, 'i');
|
|
1273
|
+
const withoutCodePrefix = normalizedTitle.replace(codePrefixPattern, '').trim();
|
|
1274
|
+
if (!withoutCodePrefix) return normalizedTitle;
|
|
1275
|
+
return withoutCodePrefix;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1338
1278
|
private createEmptyMewpCoverageBugCell(): MewpCoverageBugCell {
|
|
1339
1279
|
return { id: '' as '', title: '', responsibility: '' };
|
|
1340
1280
|
}
|
|
@@ -1655,14 +1595,17 @@ export default class ResultDataProvider {
|
|
|
1655
1595
|
}
|
|
1656
1596
|
|
|
1657
1597
|
private buildRequirementSapWbsByBaseKey(
|
|
1658
|
-
requirements: Array<Pick<MewpL2RequirementWorkItem, 'baseKey' | 'responsibility'>>
|
|
1598
|
+
requirements: Array<Pick<MewpL2RequirementWorkItem, 'baseKey' | 'owner' | 'responsibility'>>
|
|
1659
1599
|
): Map<string, string> {
|
|
1660
1600
|
const out = new Map<string, string>();
|
|
1661
1601
|
for (const requirement of requirements || []) {
|
|
1662
1602
|
const baseKey = String(requirement?.baseKey || '').trim();
|
|
1663
1603
|
if (!baseKey) continue;
|
|
1664
1604
|
|
|
1665
|
-
const
|
|
1605
|
+
const rawOwner = this.toMewpComparableText(requirement?.owner);
|
|
1606
|
+
const normalized =
|
|
1607
|
+
this.resolveMewpResponsibility(rawOwner) ||
|
|
1608
|
+
this.resolveMewpResponsibility(this.toMewpComparableText(requirement?.responsibility));
|
|
1666
1609
|
if (!normalized) continue;
|
|
1667
1610
|
|
|
1668
1611
|
const existing = out.get(baseKey) || '';
|
|
@@ -1812,6 +1755,81 @@ export default class ResultDataProvider {
|
|
|
1812
1755
|
return map;
|
|
1813
1756
|
}
|
|
1814
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
|
+
|
|
1815
1833
|
private extractMewpTestCaseId(runResult: any): number {
|
|
1816
1834
|
const testCaseId = Number(runResult?.testCaseId || runResult?.testCase?.id || 0);
|
|
1817
1835
|
return Number.isFinite(testCaseId) ? testCaseId : 0;
|
|
@@ -2299,6 +2317,7 @@ export default class ResultDataProvider {
|
|
|
2299
2317
|
requirementId,
|
|
2300
2318
|
baseKey: this.toRequirementKey(requirementId),
|
|
2301
2319
|
title: this.toMewpComparableText(fields?.['System.Title'] || wi?.title),
|
|
2320
|
+
owner: this.deriveMewpRequirementOwner(fields),
|
|
2302
2321
|
subSystem: this.deriveMewpSubSystem(fields),
|
|
2303
2322
|
responsibility: this.deriveMewpResponsibility(fields),
|
|
2304
2323
|
linkedTestCaseIds: this.extractLinkedTestCaseIdsFromRequirement(wi?.relations || []),
|
|
@@ -2348,6 +2367,7 @@ export default class ResultDataProvider {
|
|
|
2348
2367
|
if (areaPath.includes('\\customer requirements\\level 2')) score += 3;
|
|
2349
2368
|
if (!areaPath.includes('\\mop')) score += 2;
|
|
2350
2369
|
if (String(item?.title || '').trim()) score += 1;
|
|
2370
|
+
if (String(item?.owner || '').trim()) score += 1;
|
|
2351
2371
|
if (String(item?.subSystem || '').trim()) score += 1;
|
|
2352
2372
|
if (String(item?.responsibility || '').trim()) score += 1;
|
|
2353
2373
|
return score;
|
|
@@ -2384,6 +2404,7 @@ export default class ResultDataProvider {
|
|
|
2384
2404
|
requirementId: String(family?.representative?.requirementId || baseKey),
|
|
2385
2405
|
baseKey,
|
|
2386
2406
|
title: String(family?.representative?.title || ''),
|
|
2407
|
+
owner: String(family?.representative?.owner || ''),
|
|
2387
2408
|
subSystem: String(family?.representative?.subSystem || ''),
|
|
2388
2409
|
responsibility: String(family?.representative?.responsibility || ''),
|
|
2389
2410
|
linkedTestCaseIds: [...family.linkedTestCaseIds].sort((a, b) => a - b),
|
|
@@ -2835,6 +2856,24 @@ export default class ResultDataProvider {
|
|
|
2835
2856
|
return '';
|
|
2836
2857
|
}
|
|
2837
2858
|
|
|
2859
|
+
// L2 owner is sourced only from requirement SAPWBS fields.
|
|
2860
|
+
private deriveMewpRequirementOwner(fields: Record<string, any>): string {
|
|
2861
|
+
const directCandidates = [fields?.['Custom.SAPWBS'], fields?.['SAPWBS']];
|
|
2862
|
+
for (const candidate of directCandidates) {
|
|
2863
|
+
const normalized = this.toMewpComparableText(candidate);
|
|
2864
|
+
if (normalized) return normalized;
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
for (const [key, value] of Object.entries(fields || {})) {
|
|
2868
|
+
const normalizedKey = String(key || '').toLowerCase();
|
|
2869
|
+
if (!normalizedKey.includes('sapwbs')) continue;
|
|
2870
|
+
const normalizedValue = this.toMewpComparableText(value);
|
|
2871
|
+
if (normalizedValue) return normalizedValue;
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
return '';
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2838
2877
|
// Test-case responsibility must come from test-case path context (not SAPWBS).
|
|
2839
2878
|
private deriveMewpTestCaseResponsibility(fields: Record<string, any>): string {
|
|
2840
2879
|
const areaPathCandidates = [
|
|
@@ -1126,6 +1126,62 @@ describe('ResultDataProvider', () => {
|
|
|
1126
1126
|
});
|
|
1127
1127
|
|
|
1128
1128
|
describe('getMewpL2CoverageFlatResults', () => {
|
|
1129
|
+
it('should split SR prefix into SR # + L2 REQ Title while preserving full title', () => {
|
|
1130
|
+
const row = (resultDataProvider as any).createMewpCoverageRow(
|
|
1131
|
+
{
|
|
1132
|
+
workItemId: 5054,
|
|
1133
|
+
requirementId: 'SR0054',
|
|
1134
|
+
title: 'SR0054 - Engine startup coverage',
|
|
1135
|
+
owner: 'ESUK',
|
|
1136
|
+
subSystem: 'Propulsion',
|
|
1137
|
+
responsibility: 'ESUK',
|
|
1138
|
+
},
|
|
1139
|
+
'Pass',
|
|
1140
|
+
{ id: '', title: '', responsibility: '' },
|
|
1141
|
+
{ l3Id: '', l3Title: '', l4Id: '', l4Title: '' }
|
|
1142
|
+
);
|
|
1143
|
+
|
|
1144
|
+
expect(row).toEqual(
|
|
1145
|
+
expect.objectContaining({
|
|
1146
|
+
'L2 REQ ID': '5054',
|
|
1147
|
+
'SR #': 'SR0054',
|
|
1148
|
+
'L2 REQ Title': 'Engine startup coverage',
|
|
1149
|
+
'L2 REQ Full Title': 'SR0054 - Engine startup coverage',
|
|
1150
|
+
'L2 Owner': 'ESUK',
|
|
1151
|
+
'L2 SubSystem': 'Propulsion',
|
|
1152
|
+
})
|
|
1153
|
+
);
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
it('should keep full title when title is only the SR token', () => {
|
|
1157
|
+
const row = (resultDataProvider as any).createMewpCoverageRow(
|
|
1158
|
+
{
|
|
1159
|
+
workItemId: 9999,
|
|
1160
|
+
requirementId: 'SR9999',
|
|
1161
|
+
title: 'SR9999',
|
|
1162
|
+
owner: 'IL',
|
|
1163
|
+
subSystem: '',
|
|
1164
|
+
responsibility: 'IL',
|
|
1165
|
+
},
|
|
1166
|
+
'Not Run',
|
|
1167
|
+
{ id: '', title: '', responsibility: '' },
|
|
1168
|
+
{ l3Id: '', l3Title: '', l4Id: '', l4Title: '' }
|
|
1169
|
+
);
|
|
1170
|
+
|
|
1171
|
+
expect(row['L2 REQ Title']).toBe('SR9999');
|
|
1172
|
+
expect(row['L2 REQ Full Title']).toBe('SR9999');
|
|
1173
|
+
expect(row['L2 Owner']).toBe('IL');
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
it('should resolve L2 run status precedence: Fail > Not Run > Pass', () => {
|
|
1177
|
+
const resolve = (resultDataProvider as any).resolveMewpL2RunStatus.bind(resultDataProvider as any);
|
|
1178
|
+
|
|
1179
|
+
expect(resolve({ passed: 2, failed: 1, notRun: 3, hasAnyTestCase: true })).toBe('Fail');
|
|
1180
|
+
expect(resolve({ passed: 5, failed: 0, notRun: 1, hasAnyTestCase: true })).toBe('Not Run');
|
|
1181
|
+
expect(resolve({ passed: 1, failed: 0, notRun: 0, hasAnyTestCase: true })).toBe('Pass');
|
|
1182
|
+
expect(resolve({ passed: 0, failed: 0, notRun: 0, hasAnyTestCase: true })).toBe('Not Run');
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1129
1185
|
it('should fetch MEWP scoped test data from selected suites', async () => {
|
|
1130
1186
|
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
1131
1187
|
const scopedSpy = jest
|
|
@@ -1234,7 +1290,7 @@ describe('ResultDataProvider', () => {
|
|
|
1234
1290
|
expect(result).toEqual(
|
|
1235
1291
|
expect.objectContaining({
|
|
1236
1292
|
sheetName: expect.stringContaining('MEWP L2 Coverage'),
|
|
1237
|
-
columnOrder: expect.arrayContaining(['L2 REQ ID', 'L2 REQ Title', 'L2 Run Status']),
|
|
1293
|
+
columnOrder: expect.arrayContaining(['L2 REQ ID', 'SR #', 'L2 REQ Title', 'L2 Owner', 'L2 Run Status']),
|
|
1238
1294
|
})
|
|
1239
1295
|
);
|
|
1240
1296
|
|
|
@@ -3449,6 +3505,100 @@ describe('ResultDataProvider', () => {
|
|
|
3449
3505
|
})
|
|
3450
3506
|
);
|
|
3451
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
|
+
});
|
|
3452
3602
|
});
|
|
3453
3603
|
|
|
3454
3604
|
describe('buildLinkedRequirementsByTestCase', () => {
|