@elisra-devops/docgen-data-provider 1.101.0 → 1.103.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 +34 -0
- package/bin/modules/ResultDataProvider.js +213 -90
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +335 -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 +229 -115
- package/src/tests/modules/ResultDataProvider.test.ts +389 -1
|
@@ -24,6 +24,7 @@ export interface MewpL2RequirementWorkItem {
|
|
|
24
24
|
requirementId: string;
|
|
25
25
|
baseKey: string;
|
|
26
26
|
title: string;
|
|
27
|
+
owner: string;
|
|
27
28
|
subSystem: string;
|
|
28
29
|
responsibility: string;
|
|
29
30
|
linkedTestCaseIds: number[];
|
|
@@ -35,6 +36,7 @@ export interface MewpL2RequirementFamily {
|
|
|
35
36
|
requirementId: string;
|
|
36
37
|
baseKey: string;
|
|
37
38
|
title: string;
|
|
39
|
+
owner: string;
|
|
38
40
|
subSystem: string;
|
|
39
41
|
responsibility: string;
|
|
40
42
|
linkedTestCaseIds: number[];
|
|
@@ -71,7 +73,10 @@ export interface MewpCoverageBugCell {
|
|
|
71
73
|
export type MewpCoverageL3L4Cell = MewpL3L4Pair;
|
|
72
74
|
export interface MewpCoverageRow {
|
|
73
75
|
'L2 REQ ID': string;
|
|
76
|
+
'SR #': string;
|
|
74
77
|
'L2 REQ Title': string;
|
|
78
|
+
'L2 REQ Full Title'?: string;
|
|
79
|
+
'L2 Owner': string;
|
|
75
80
|
'L2 SubSystem': string;
|
|
76
81
|
'L2 Run Status': MewpRunStatus;
|
|
77
82
|
'Bug ID': number | '';
|
|
@@ -26,6 +26,8 @@ export default class ResultDataProvider {
|
|
|
26
26
|
private static readonly MEWP_INTERNAL_VALIDATION_TRACE_TAG;
|
|
27
27
|
private static readonly MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG;
|
|
28
28
|
private static readonly MEWP_INTERNAL_VALIDATION_SUMMARY_TAG;
|
|
29
|
+
private static readonly MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_REF;
|
|
30
|
+
private static readonly MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_HEADER_PATTERN;
|
|
29
31
|
private static readonly MEWP_L2_COVERAGE_COLUMNS;
|
|
30
32
|
private static readonly INTERNAL_VALIDATION_COLUMNS;
|
|
31
33
|
orgUrl: string;
|
|
@@ -90,6 +92,7 @@ export default class ResultDataProvider {
|
|
|
90
92
|
private formatLogValue;
|
|
91
93
|
private buildTaggedLogMessage;
|
|
92
94
|
private createMewpCoverageRow;
|
|
95
|
+
private deriveMewpRequirementDisplayName;
|
|
93
96
|
private createEmptyMewpCoverageBugCell;
|
|
94
97
|
private createEmptyMewpCoverageL3L4Cell;
|
|
95
98
|
private buildMewpCoverageL3L4Rows;
|
|
@@ -104,6 +107,15 @@ export default class ResultDataProvider {
|
|
|
104
107
|
private isExternalStateInScope;
|
|
105
108
|
private invertBaseRequirementLinks;
|
|
106
109
|
private buildMewpTestCaseTitleMap;
|
|
110
|
+
/**
|
|
111
|
+
* Builds a lookup of test case id -> test case description using suite payload data.
|
|
112
|
+
*
|
|
113
|
+
* Resolution order:
|
|
114
|
+
* 1) direct description fields on test-case payload (`testCase.description` / `workItem.description`)
|
|
115
|
+
* 2) `System.Description` / `Description` in work-item fields map
|
|
116
|
+
* 3) `System.Description` / `Description` from work-item field list (`workItemFields`)
|
|
117
|
+
*/
|
|
118
|
+
private buildMewpTestCaseDescriptionMap;
|
|
107
119
|
private extractMewpTestCaseId;
|
|
108
120
|
private buildTestCaseStepsXmlMap;
|
|
109
121
|
private extractStepsXmlFromTestCaseItem;
|
|
@@ -115,6 +127,27 @@ export default class ResultDataProvider {
|
|
|
115
127
|
private resolveRequirementStatusForWindow;
|
|
116
128
|
private extractRequirementCodesFromText;
|
|
117
129
|
private extractRequirementMentionsFromExpectedSteps;
|
|
130
|
+
/**
|
|
131
|
+
* Extracts SR requirement mentions from the "assumptions" section inside a test-case description.
|
|
132
|
+
*
|
|
133
|
+
* Notes:
|
|
134
|
+
* - Heading detection is case-insensitive and keyed by "assumptions".
|
|
135
|
+
* - Parsing is scoped to that section only and stops when a likely next section heading is reached
|
|
136
|
+
* after at least one requirement-bearing line was collected.
|
|
137
|
+
* - Returned stepRef is a synthetic marker ("Assumptions") so downstream discrepancy output can
|
|
138
|
+
* clearly attribute source to description-level assumptions.
|
|
139
|
+
*/
|
|
140
|
+
private extractRequirementMentionsFromAssumptionsDescription;
|
|
141
|
+
/**
|
|
142
|
+
* Normalizes raw HTML/plain description content into comparable text lines.
|
|
143
|
+
* This makes section/title heuristics resilient to formatting differences.
|
|
144
|
+
*/
|
|
145
|
+
private normalizeMewpDescriptionLines;
|
|
146
|
+
/**
|
|
147
|
+
* Heuristic detector for description section headings used to stop assumptions parsing.
|
|
148
|
+
* A line is considered a heading when it is short/title-like and does not itself contain SR codes.
|
|
149
|
+
*/
|
|
150
|
+
private isLikelyMewpDescriptionSectionHeading;
|
|
118
151
|
private extractRequirementCodesFromExpectedText;
|
|
119
152
|
private extractRequirementCandidatesFromToken;
|
|
120
153
|
private expandRequirementTokenByComma;
|
|
@@ -138,6 +171,7 @@ export default class ResultDataProvider {
|
|
|
138
171
|
private extractLinkedWorkItemIdsFromRelations;
|
|
139
172
|
private extractMewpRequirementIdentifier;
|
|
140
173
|
private deriveMewpResponsibility;
|
|
174
|
+
private deriveMewpRequirementOwner;
|
|
141
175
|
private deriveMewpTestCaseResponsibility;
|
|
142
176
|
private deriveMewpSubSystem;
|
|
143
177
|
private resolveBugResponsibility;
|
|
@@ -517,6 +517,7 @@ class ResultDataProvider {
|
|
|
517
517
|
const rows = [];
|
|
518
518
|
const stepsXmlByTestCase = this.buildTestCaseStepsXmlMap(testData);
|
|
519
519
|
const testCaseTitleMap = this.buildMewpTestCaseTitleMap(testData);
|
|
520
|
+
const testCaseDescriptionMap = this.buildMewpTestCaseDescriptionMap(testData);
|
|
520
521
|
const allTestCaseIds = new Set();
|
|
521
522
|
for (const suite of testData || []) {
|
|
522
523
|
const testCasesItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testCasesItems) ? suite.testCasesItems : [];
|
|
@@ -562,7 +563,13 @@ class ResultDataProvider {
|
|
|
562
563
|
: [];
|
|
563
564
|
const executableSteps = parsedSteps.filter((step) => !(step === null || step === void 0 ? void 0 : step.isSharedStepTitle));
|
|
564
565
|
diagnostics.totalParsedSteps += executableSteps.length;
|
|
565
|
-
|
|
566
|
+
// Direction A/B mentions can come from two sources:
|
|
567
|
+
// 1) Expected Result in executable steps.
|
|
568
|
+
// 2) "Assumptions" section in the test-case description.
|
|
569
|
+
const stepMentionEntries = this.extractRequirementMentionsFromExpectedSteps(parsedSteps, true);
|
|
570
|
+
const descriptionText = testCaseDescriptionMap.get(testCaseId) || '';
|
|
571
|
+
const assumptionsMentionEntries = this.extractRequirementMentionsFromAssumptionsDescription(descriptionText, true);
|
|
572
|
+
const mentionEntries = [...stepMentionEntries, ...assumptionsMentionEntries];
|
|
566
573
|
diagnostics.totalStepsWithMentions += mentionEntries.length;
|
|
567
574
|
const mentionedL2Only = new Set();
|
|
568
575
|
const mentionedCodeFirstStep = new Map();
|
|
@@ -601,19 +608,6 @@ class ResultDataProvider {
|
|
|
601
608
|
mentionedCodesByBase.set(baseKey, new Set());
|
|
602
609
|
mentionedCodesByBase.get(baseKey).add(code);
|
|
603
610
|
}
|
|
604
|
-
if (traceCurrentTestCase) {
|
|
605
|
-
logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
606
|
-
event: 'test-case-start',
|
|
607
|
-
tc: testCaseId,
|
|
608
|
-
parsedSteps: executableSteps.length,
|
|
609
|
-
stepsWithMentions: mentionEntries.length,
|
|
610
|
-
mentionedCodes: [...mentionedL2Only].sort((a, b) => this.compareMewpRequirementCodes(a, b)).join('; ') ||
|
|
611
|
-
'<none>',
|
|
612
|
-
linkedCodesInTestCase: [...linkedFullCodes]
|
|
613
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
614
|
-
.join('; ') || '<none>',
|
|
615
|
-
}));
|
|
616
|
-
}
|
|
617
611
|
// Direction A logic:
|
|
618
612
|
// 1) Base mention ("SR0054") is parent-level and considered covered only when
|
|
619
613
|
// the whole family is covered across scoped test cases:
|
|
@@ -636,81 +630,30 @@ class ResultDataProvider {
|
|
|
636
630
|
const requiredFamilyMembers = specificFamilyMembers.length > 0 ? specificFamilyMembers : normalizedFamilyMembers;
|
|
637
631
|
// Base mention ("SR0054") requires full family coverage across selected test cases.
|
|
638
632
|
if (hasBaseMention) {
|
|
639
|
-
const missingRequiredFamilyMembers = requiredFamilyMembers.filter((memberCode) => !familyLinkedCodes.has(memberCode));
|
|
640
633
|
const isWholeFamilyCovered = requiredFamilyMembers.every((memberCode) => familyLinkedCodes.has(memberCode));
|
|
641
634
|
if (!isWholeFamilyCovered) {
|
|
642
635
|
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
643
636
|
}
|
|
644
|
-
if (traceCurrentTestCase) {
|
|
645
|
-
logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
646
|
-
event: 'base-family-coverage',
|
|
647
|
-
tc: testCaseId,
|
|
648
|
-
base: baseKey,
|
|
649
|
-
baseMention: true,
|
|
650
|
-
requiredFamily: requiredFamilyMembers
|
|
651
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
652
|
-
.join('; ') || '<none>',
|
|
653
|
-
linkedAcrossScope: [...familyLinkedCodes]
|
|
654
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
655
|
-
.join('; ') || '<none>',
|
|
656
|
-
missingRequired: missingRequiredFamilyMembers
|
|
657
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
658
|
-
.join('; ') || '<none>',
|
|
659
|
-
covered: isWholeFamilyCovered,
|
|
660
|
-
}));
|
|
661
|
-
}
|
|
662
637
|
}
|
|
663
638
|
// Specific mention ("SR0054-1") validates as exact-match only across scoped test cases.
|
|
664
639
|
const missingSpecificMembers = mentionedSpecificMembers.filter((code) => !familyLinkedCodes.has(code));
|
|
665
640
|
for (const code of missingSpecificMembers) {
|
|
666
641
|
missingSpecificMentionedNoFamily.add(code);
|
|
667
642
|
}
|
|
668
|
-
if (traceCurrentTestCase && mentionedSpecificMembers.length > 0) {
|
|
669
|
-
logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
670
|
-
event: 'specific-members-check',
|
|
671
|
-
tc: testCaseId,
|
|
672
|
-
base: baseKey,
|
|
673
|
-
specificMentioned: mentionedSpecificMembers
|
|
674
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
675
|
-
.join('; ') || '<none>',
|
|
676
|
-
specificMissing: missingSpecificMembers
|
|
677
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
678
|
-
.join('; ') || '<none>',
|
|
679
|
-
}));
|
|
680
|
-
}
|
|
681
643
|
continue;
|
|
682
644
|
}
|
|
683
645
|
// Fallback path when family data is unavailable for this base key.
|
|
684
|
-
const fallbackMissingSpecific = [];
|
|
685
|
-
let fallbackMissingBase = false;
|
|
686
646
|
for (const code of mentionedCodes) {
|
|
687
647
|
const hasSpecificSuffix = /-\d+$/.test(code);
|
|
688
648
|
if (hasSpecificSuffix) {
|
|
689
649
|
if (!linkedFullCodesAcrossTestCases.has(code)) {
|
|
690
650
|
missingSpecificMentionedNoFamily.add(code);
|
|
691
|
-
fallbackMissingSpecific.push(code);
|
|
692
651
|
}
|
|
693
652
|
}
|
|
694
653
|
else if (!linkedBaseKeysAcrossTestCases.has(baseKey)) {
|
|
695
654
|
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
696
|
-
fallbackMissingBase = true;
|
|
697
655
|
}
|
|
698
656
|
}
|
|
699
|
-
if (traceCurrentTestCase) {
|
|
700
|
-
logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
701
|
-
event: 'fallback-path',
|
|
702
|
-
tc: testCaseId,
|
|
703
|
-
base: baseKey,
|
|
704
|
-
fallbackUsed: true,
|
|
705
|
-
mentioned: mentionedCodesList
|
|
706
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
707
|
-
.join('; ') || '<none>',
|
|
708
|
-
missingSpecific: fallbackMissingSpecific
|
|
709
|
-
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
710
|
-
.join('; ') || '<none>',
|
|
711
|
-
missingBase: fallbackMissingBase,
|
|
712
|
-
}));
|
|
713
|
-
}
|
|
714
657
|
}
|
|
715
658
|
// Direction B is family-based: if any member of a family is mentioned in Expected Result,
|
|
716
659
|
// linked members of that same family are not considered "linked but not mentioned".
|
|
@@ -750,14 +693,6 @@ class ResultDataProvider {
|
|
|
750
693
|
const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
|
|
751
694
|
appendMentionedButNotLinked(baseKey, stepRef);
|
|
752
695
|
}
|
|
753
|
-
if (traceCurrentTestCase) {
|
|
754
|
-
logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
755
|
-
event: 'direction-a-summary',
|
|
756
|
-
tc: testCaseId,
|
|
757
|
-
missingSpecific: sortedMissingSpecificMentionedNoFamily.join('; ') || '<none>',
|
|
758
|
-
missingBase: sortedMissingBaseWhenFamilyUncovered.join('; ') || '<none>',
|
|
759
|
-
}));
|
|
760
|
-
}
|
|
761
696
|
const sortedExtraLinked = [...new Set(extraLinked)]
|
|
762
697
|
.map((code) => this.normalizeMewpRequirementCodeWithSuffix(code))
|
|
763
698
|
.filter((code) => !!code)
|
|
@@ -792,18 +727,20 @@ class ResultDataProvider {
|
|
|
792
727
|
const validationStatus = mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
|
|
793
728
|
if (validationStatus === 'Fail')
|
|
794
729
|
diagnostics.failingRows += 1;
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
730
|
+
if (traceCurrentTestCase) {
|
|
731
|
+
logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG, {
|
|
732
|
+
testCaseId,
|
|
733
|
+
parsedSteps: executableSteps.length,
|
|
734
|
+
stepsWithMentions: mentionEntries.length,
|
|
735
|
+
customerIdsFound: mentionedL2Only.size,
|
|
736
|
+
linkedRequirements: linkedFullCodes.size,
|
|
737
|
+
mentionedButNotLinked: sortedMissingSpecificMentionedNoFamily.length +
|
|
738
|
+
sortedMissingBaseWhenFamilyUncovered.length,
|
|
739
|
+
linkedButNotMentioned: sortedExtraLinked.length,
|
|
740
|
+
status: validationStatus,
|
|
741
|
+
customerIdSample: [...mentionedL2Only].slice(0, 5).join(', '),
|
|
742
|
+
}));
|
|
743
|
+
}
|
|
807
744
|
rows.push({
|
|
808
745
|
'Test Case ID': testCaseId,
|
|
809
746
|
'Test Case Title': String(testCaseTitleMap.get(testCaseId) || '').trim(),
|
|
@@ -921,11 +858,17 @@ class ResultDataProvider {
|
|
|
921
858
|
createMewpCoverageRow(requirement, runStatus, bug, linkedL3L4) {
|
|
922
859
|
const l2ReqIdNumeric = Number((requirement === null || requirement === void 0 ? void 0 : requirement.workItemId) || 0);
|
|
923
860
|
const l2ReqId = l2ReqIdNumeric > 0 ? String(l2ReqIdNumeric) : '';
|
|
861
|
+
const srNumber = this.normalizeMewpRequirementCodeWithSuffix((requirement === null || requirement === void 0 ? void 0 : requirement.requirementId) || '');
|
|
924
862
|
const l2ReqTitle = this.toMewpComparableText(requirement.title);
|
|
863
|
+
const reqName = this.deriveMewpRequirementDisplayName(srNumber, l2ReqTitle);
|
|
864
|
+
const l2Owner = this.toMewpComparableText(requirement.owner);
|
|
925
865
|
const l2SubSystem = this.toMewpComparableText(requirement.subSystem);
|
|
926
866
|
return {
|
|
927
867
|
'L2 REQ ID': l2ReqId,
|
|
928
|
-
'
|
|
868
|
+
'SR #': srNumber,
|
|
869
|
+
'L2 REQ Title': reqName,
|
|
870
|
+
'L2 REQ Full Title': l2ReqTitle,
|
|
871
|
+
'L2 Owner': l2Owner,
|
|
929
872
|
'L2 SubSystem': l2SubSystem,
|
|
930
873
|
'L2 Run Status': runStatus,
|
|
931
874
|
'Bug ID': Number.isFinite(Number(bug === null || bug === void 0 ? void 0 : bug.id)) && Number(bug === null || bug === void 0 ? void 0 : bug.id) > 0 ? Number(bug === null || bug === void 0 ? void 0 : bug.id) : '',
|
|
@@ -937,6 +880,20 @@ class ResultDataProvider {
|
|
|
937
880
|
'L4 REQ Title': String((linkedL3L4 === null || linkedL3L4 === void 0 ? void 0 : linkedL3L4.l4Title) || '').trim(),
|
|
938
881
|
};
|
|
939
882
|
}
|
|
883
|
+
deriveMewpRequirementDisplayName(requirementCode, title) {
|
|
884
|
+
const normalizedTitle = this.toMewpComparableText(title);
|
|
885
|
+
if (!normalizedTitle)
|
|
886
|
+
return '';
|
|
887
|
+
const normalizedCode = this.normalizeMewpRequirementCodeWithSuffix(requirementCode || '');
|
|
888
|
+
if (!normalizedCode)
|
|
889
|
+
return normalizedTitle;
|
|
890
|
+
const escapedCode = normalizedCode.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
891
|
+
const codePrefixPattern = new RegExp(`^${escapedCode}(?:\\s*[:\\-–—]\\s*|\\s+)`, 'i');
|
|
892
|
+
const withoutCodePrefix = normalizedTitle.replace(codePrefixPattern, '').trim();
|
|
893
|
+
if (!withoutCodePrefix)
|
|
894
|
+
return normalizedTitle;
|
|
895
|
+
return withoutCodePrefix;
|
|
896
|
+
}
|
|
940
897
|
createEmptyMewpCoverageBugCell() {
|
|
941
898
|
return { id: '', title: '', responsibility: '' };
|
|
942
899
|
}
|
|
@@ -1192,7 +1149,9 @@ class ResultDataProvider {
|
|
|
1192
1149
|
const baseKey = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
|
|
1193
1150
|
if (!baseKey)
|
|
1194
1151
|
continue;
|
|
1195
|
-
const
|
|
1152
|
+
const rawOwner = this.toMewpComparableText(requirement === null || requirement === void 0 ? void 0 : requirement.owner);
|
|
1153
|
+
const normalized = this.resolveMewpResponsibility(rawOwner) ||
|
|
1154
|
+
this.resolveMewpResponsibility(this.toMewpComparableText(requirement === null || requirement === void 0 ? void 0 : requirement.responsibility));
|
|
1196
1155
|
if (!normalized)
|
|
1197
1156
|
continue;
|
|
1198
1157
|
const existing = out.get(baseKey) || '';
|
|
@@ -1289,6 +1248,48 @@ class ResultDataProvider {
|
|
|
1289
1248
|
}
|
|
1290
1249
|
return map;
|
|
1291
1250
|
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Builds a lookup of test case id -> test case description using suite payload data.
|
|
1253
|
+
*
|
|
1254
|
+
* Resolution order:
|
|
1255
|
+
* 1) direct description fields on test-case payload (`testCase.description` / `workItem.description`)
|
|
1256
|
+
* 2) `System.Description` / `Description` in work-item fields map
|
|
1257
|
+
* 3) `System.Description` / `Description` from work-item field list (`workItemFields`)
|
|
1258
|
+
*/
|
|
1259
|
+
buildMewpTestCaseDescriptionMap(testData) {
|
|
1260
|
+
var _a, _b, _c, _d;
|
|
1261
|
+
const map = new Map();
|
|
1262
|
+
const readDescriptionFromFields = (fields) => {
|
|
1263
|
+
var _a;
|
|
1264
|
+
const value = (_a = this.getFieldValueByName(fields, 'System.Description')) !== null && _a !== void 0 ? _a : this.getFieldValueByName(fields, 'Description');
|
|
1265
|
+
return this.toMewpComparableText(value);
|
|
1266
|
+
};
|
|
1267
|
+
for (const suite of testData || []) {
|
|
1268
|
+
const testCasesItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testCasesItems) ? suite.testCasesItems : [];
|
|
1269
|
+
for (const testCase of testCasesItems) {
|
|
1270
|
+
const id = Number(((_a = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _a === void 0 ? void 0 : _a.id) || (testCase === null || testCase === void 0 ? void 0 : testCase.testCaseId) || (testCase === null || testCase === void 0 ? void 0 : testCase.id));
|
|
1271
|
+
if (!Number.isFinite(id) || id <= 0 || map.has(id))
|
|
1272
|
+
continue;
|
|
1273
|
+
const fromDirect = this.toMewpComparableText((testCase === null || testCase === void 0 ? void 0 : testCase.description) || ((_b = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _b === void 0 ? void 0 : _b.description));
|
|
1274
|
+
if (fromDirect) {
|
|
1275
|
+
map.set(id, fromDirect);
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
const fieldsFromMap = ((_c = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _c === void 0 ? void 0 : _c.fields) || {};
|
|
1279
|
+
const fromFieldsMap = readDescriptionFromFields(fieldsFromMap);
|
|
1280
|
+
if (fromFieldsMap) {
|
|
1281
|
+
map.set(id, fromFieldsMap);
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1284
|
+
const fieldsFromList = this.extractWorkItemFieldsMap((_d = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _d === void 0 ? void 0 : _d.workItemFields);
|
|
1285
|
+
const fromFieldsList = readDescriptionFromFields(fieldsFromList);
|
|
1286
|
+
if (fromFieldsList) {
|
|
1287
|
+
map.set(id, fromFieldsList);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
return map;
|
|
1292
|
+
}
|
|
1292
1293
|
extractMewpTestCaseId(runResult) {
|
|
1293
1294
|
var _a;
|
|
1294
1295
|
const testCaseId = Number((runResult === null || runResult === void 0 ? void 0 : runResult.testCaseId) || ((_a = runResult === null || runResult === void 0 ? void 0 : runResult.testCase) === null || _a === void 0 ? void 0 : _a.id) || 0);
|
|
@@ -1479,6 +1480,102 @@ class ResultDataProvider {
|
|
|
1479
1480
|
}
|
|
1480
1481
|
return out;
|
|
1481
1482
|
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Extracts SR requirement mentions from the "assumptions" section inside a test-case description.
|
|
1485
|
+
*
|
|
1486
|
+
* Notes:
|
|
1487
|
+
* - Heading detection is case-insensitive and keyed by "assumptions".
|
|
1488
|
+
* - Parsing is scoped to that section only and stops when a likely next section heading is reached
|
|
1489
|
+
* after at least one requirement-bearing line was collected.
|
|
1490
|
+
* - Returned stepRef is a synthetic marker ("Assumptions") so downstream discrepancy output can
|
|
1491
|
+
* clearly attribute source to description-level assumptions.
|
|
1492
|
+
*/
|
|
1493
|
+
extractRequirementMentionsFromAssumptionsDescription(description, includeSuffix) {
|
|
1494
|
+
const lines = this.normalizeMewpDescriptionLines(description);
|
|
1495
|
+
if (lines.length === 0)
|
|
1496
|
+
return [];
|
|
1497
|
+
const assumptionsHeaderIndex = lines.findIndex((line) => ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_HEADER_PATTERN.test(line));
|
|
1498
|
+
if (assumptionsHeaderIndex < 0)
|
|
1499
|
+
return [];
|
|
1500
|
+
const assumptionCodes = new Set();
|
|
1501
|
+
let hasCollectedRequirementCodes = false;
|
|
1502
|
+
for (let index = assumptionsHeaderIndex + 1; index < lines.length; index += 1) {
|
|
1503
|
+
const rawLine = String(lines[index] || '').trim();
|
|
1504
|
+
if (!rawLine)
|
|
1505
|
+
continue;
|
|
1506
|
+
const line = rawLine.replace(/^[-*•]+\s*/, '').trim();
|
|
1507
|
+
if (!line)
|
|
1508
|
+
continue;
|
|
1509
|
+
const lineCodes = this.extractRequirementCodesFromExpectedText(line, includeSuffix);
|
|
1510
|
+
if (lineCodes.size > 0) {
|
|
1511
|
+
for (const code of lineCodes)
|
|
1512
|
+
assumptionCodes.add(code);
|
|
1513
|
+
hasCollectedRequirementCodes = true;
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
if (hasCollectedRequirementCodes && this.isLikelyMewpDescriptionSectionHeading(line)) {
|
|
1517
|
+
break;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
if (assumptionCodes.size === 0)
|
|
1521
|
+
return [];
|
|
1522
|
+
return [{ stepRef: ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_REF, codes: assumptionCodes }];
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Normalizes raw HTML/plain description content into comparable text lines.
|
|
1526
|
+
* This makes section/title heuristics resilient to formatting differences.
|
|
1527
|
+
*/
|
|
1528
|
+
normalizeMewpDescriptionLines(description) {
|
|
1529
|
+
const raw = String(description || '');
|
|
1530
|
+
if (!raw)
|
|
1531
|
+
return [];
|
|
1532
|
+
const normalized = raw
|
|
1533
|
+
.replace(/\r\n?/g, '\n')
|
|
1534
|
+
.replace(/<\s*br\s*\/?>/gi, '\n')
|
|
1535
|
+
// Underlined sections are commonly used as inline HTML titles in MEWP test-case descriptions.
|
|
1536
|
+
// Treat underline tags as boundaries so compact forms like <u><b>Title</b></u><p>... preserve section split.
|
|
1537
|
+
.replace(/<\s*u\b[^>]*>/gi, '\n')
|
|
1538
|
+
.replace(/<\/\s*u\s*>/gi, '\n')
|
|
1539
|
+
.replace(/<\s*li\b[^>]*>/gi, '\n- ')
|
|
1540
|
+
.replace(/<\/\s*(p|div|li|ul|ol|tr|td|h[1-6])\s*>/gi, '\n')
|
|
1541
|
+
.replace(/ | | /gi, ' ')
|
|
1542
|
+
.replace(/</gi, '<')
|
|
1543
|
+
.replace(/>/gi, '>')
|
|
1544
|
+
.replace(/&/gi, '&')
|
|
1545
|
+
.replace(/"/gi, '"')
|
|
1546
|
+
.replace(/'|'/gi, "'")
|
|
1547
|
+
.replace(/<[^>]*>/g, ' ')
|
|
1548
|
+
.replace(/[\u200B-\u200D\uFEFF]/g, '')
|
|
1549
|
+
.replace(/[ \t]+/g, ' ');
|
|
1550
|
+
return normalized
|
|
1551
|
+
.split('\n')
|
|
1552
|
+
.map((line) => String(line || '').trim())
|
|
1553
|
+
.filter((line) => !!line);
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Heuristic detector for description section headings used to stop assumptions parsing.
|
|
1557
|
+
* A line is considered a heading when it is short/title-like and does not itself contain SR codes.
|
|
1558
|
+
*/
|
|
1559
|
+
isLikelyMewpDescriptionSectionHeading(line) {
|
|
1560
|
+
const normalized = String(line || '')
|
|
1561
|
+
.replace(/^[-*•]+\s*/, '')
|
|
1562
|
+
.trim();
|
|
1563
|
+
if (!normalized)
|
|
1564
|
+
return false;
|
|
1565
|
+
if (ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_HEADER_PATTERN.test(normalized))
|
|
1566
|
+
return false;
|
|
1567
|
+
if (this.extractRequirementCodesFromExpectedText(normalized, true).size > 0)
|
|
1568
|
+
return false;
|
|
1569
|
+
const compact = normalized.replace(/[.:;,\-–—]+$/, '').trim();
|
|
1570
|
+
if (!compact)
|
|
1571
|
+
return false;
|
|
1572
|
+
const wordCount = compact.split(/\s+/).filter((item) => !!item).length;
|
|
1573
|
+
if (wordCount === 0 || wordCount > 12)
|
|
1574
|
+
return false;
|
|
1575
|
+
if (/[.;!?]/.test(compact))
|
|
1576
|
+
return false;
|
|
1577
|
+
return /^[a-z0-9\s()&/+_'-]+$/i.test(compact);
|
|
1578
|
+
}
|
|
1482
1579
|
extractRequirementCodesFromExpectedText(text, includeSuffix) {
|
|
1483
1580
|
const out = new Set();
|
|
1484
1581
|
const source = this.normalizeRequirementStepText(text);
|
|
@@ -1648,6 +1745,7 @@ class ResultDataProvider {
|
|
|
1648
1745
|
requirementId,
|
|
1649
1746
|
baseKey: this.toRequirementKey(requirementId),
|
|
1650
1747
|
title: this.toMewpComparableText((fields === null || fields === void 0 ? void 0 : fields['System.Title']) || (wi === null || wi === void 0 ? void 0 : wi.title)),
|
|
1748
|
+
owner: this.deriveMewpRequirementOwner(fields),
|
|
1651
1749
|
subSystem: this.deriveMewpSubSystem(fields),
|
|
1652
1750
|
responsibility: this.deriveMewpResponsibility(fields),
|
|
1653
1751
|
linkedTestCaseIds: this.extractLinkedTestCaseIdsFromRequirement((wi === null || wi === void 0 ? void 0 : wi.relations) || []),
|
|
@@ -1690,6 +1788,8 @@ class ResultDataProvider {
|
|
|
1690
1788
|
score += 2;
|
|
1691
1789
|
if (String((item === null || item === void 0 ? void 0 : item.title) || '').trim())
|
|
1692
1790
|
score += 1;
|
|
1791
|
+
if (String((item === null || item === void 0 ? void 0 : item.owner) || '').trim())
|
|
1792
|
+
score += 1;
|
|
1693
1793
|
if (String((item === null || item === void 0 ? void 0 : item.subSystem) || '').trim())
|
|
1694
1794
|
score += 1;
|
|
1695
1795
|
if (String((item === null || item === void 0 ? void 0 : item.responsibility) || '').trim())
|
|
@@ -1723,14 +1823,15 @@ class ResultDataProvider {
|
|
|
1723
1823
|
}
|
|
1724
1824
|
return [...families.entries()]
|
|
1725
1825
|
.map(([baseKey, family]) => {
|
|
1726
|
-
var _a, _b, _c, _d, _e;
|
|
1826
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1727
1827
|
return ({
|
|
1728
1828
|
workItemId: Number(((_a = family === null || family === void 0 ? void 0 : family.representative) === null || _a === void 0 ? void 0 : _a.workItemId) || 0),
|
|
1729
1829
|
requirementId: String(((_b = family === null || family === void 0 ? void 0 : family.representative) === null || _b === void 0 ? void 0 : _b.requirementId) || baseKey),
|
|
1730
1830
|
baseKey,
|
|
1731
1831
|
title: String(((_c = family === null || family === void 0 ? void 0 : family.representative) === null || _c === void 0 ? void 0 : _c.title) || ''),
|
|
1732
|
-
|
|
1733
|
-
|
|
1832
|
+
owner: String(((_d = family === null || family === void 0 ? void 0 : family.representative) === null || _d === void 0 ? void 0 : _d.owner) || ''),
|
|
1833
|
+
subSystem: String(((_e = family === null || family === void 0 ? void 0 : family.representative) === null || _e === void 0 ? void 0 : _e.subSystem) || ''),
|
|
1834
|
+
responsibility: String(((_f = family === null || family === void 0 ? void 0 : family.representative) === null || _f === void 0 ? void 0 : _f.responsibility) || ''),
|
|
1734
1835
|
linkedTestCaseIds: [...family.linkedTestCaseIds].sort((a, b) => a - b),
|
|
1735
1836
|
});
|
|
1736
1837
|
})
|
|
@@ -2159,6 +2260,24 @@ class ResultDataProvider {
|
|
|
2159
2260
|
}
|
|
2160
2261
|
return '';
|
|
2161
2262
|
}
|
|
2263
|
+
// L2 owner is sourced only from requirement SAPWBS fields.
|
|
2264
|
+
deriveMewpRequirementOwner(fields) {
|
|
2265
|
+
const directCandidates = [fields === null || fields === void 0 ? void 0 : fields['Custom.SAPWBS'], fields === null || fields === void 0 ? void 0 : fields['SAPWBS']];
|
|
2266
|
+
for (const candidate of directCandidates) {
|
|
2267
|
+
const normalized = this.toMewpComparableText(candidate);
|
|
2268
|
+
if (normalized)
|
|
2269
|
+
return normalized;
|
|
2270
|
+
}
|
|
2271
|
+
for (const [key, value] of Object.entries(fields || {})) {
|
|
2272
|
+
const normalizedKey = String(key || '').toLowerCase();
|
|
2273
|
+
if (!normalizedKey.includes('sapwbs'))
|
|
2274
|
+
continue;
|
|
2275
|
+
const normalizedValue = this.toMewpComparableText(value);
|
|
2276
|
+
if (normalizedValue)
|
|
2277
|
+
return normalizedValue;
|
|
2278
|
+
}
|
|
2279
|
+
return '';
|
|
2280
|
+
}
|
|
2162
2281
|
// Test-case responsibility must come from test-case path context (not SAPWBS).
|
|
2163
2282
|
deriveMewpTestCaseResponsibility(fields) {
|
|
2164
2283
|
const areaPathCandidates = [
|
|
@@ -4511,9 +4630,13 @@ class ResultDataProvider {
|
|
|
4511
4630
|
ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG = '[MEWP][InternalValidation][Trace]';
|
|
4512
4631
|
ResultDataProvider.MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG = '[MEWP][InternalValidation][Diagnostics]';
|
|
4513
4632
|
ResultDataProvider.MEWP_INTERNAL_VALIDATION_SUMMARY_TAG = '[MEWP][InternalValidation][Summary]';
|
|
4633
|
+
ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_REF = 'Assumptions';
|
|
4634
|
+
ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_HEADER_PATTERN = /assumptions/i;
|
|
4514
4635
|
ResultDataProvider.MEWP_L2_COVERAGE_COLUMNS = [
|
|
4515
4636
|
'L2 REQ ID',
|
|
4637
|
+
'SR #',
|
|
4516
4638
|
'L2 REQ Title',
|
|
4639
|
+
'L2 Owner',
|
|
4517
4640
|
'L2 SubSystem',
|
|
4518
4641
|
'L2 Run Status',
|
|
4519
4642
|
'Bug ID',
|