@elisra-devops/docgen-data-provider 1.89.0 → 1.91.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 +0 -4
- package/bin/modules/ResultDataProvider.d.ts +4 -3
- package/bin/modules/ResultDataProvider.js +103 -137
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +97 -3
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/models/mewp-reporting.ts +0 -5
- package/src/modules/ResultDataProvider.ts +106 -153
- package/src/tests/modules/ResultDataProvider.test.ts +112 -3
package/package.json
CHANGED
|
@@ -15,11 +15,6 @@ export interface MewpExternalFileRef {
|
|
|
15
15
|
export interface MewpCoverageRequestOptions {
|
|
16
16
|
externalBugsFile?: MewpExternalFileRef | null;
|
|
17
17
|
externalL3L4File?: MewpExternalFileRef | null;
|
|
18
|
-
debugMode?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface MewpInternalValidationRequestOptions {
|
|
22
|
-
debugMode?: boolean;
|
|
23
18
|
}
|
|
24
19
|
|
|
25
20
|
export interface MewpRequirementStepSummary {
|
|
@@ -13,7 +13,6 @@ import type {
|
|
|
13
13
|
MewpExternalTableValidationResult,
|
|
14
14
|
MewpCoverageRow,
|
|
15
15
|
MewpInternalValidationFlatPayload,
|
|
16
|
-
MewpInternalValidationRequestOptions,
|
|
17
16
|
MewpInternalValidationRow,
|
|
18
17
|
MewpL2RequirementFamily,
|
|
19
18
|
MewpL2RequirementWorkItem,
|
|
@@ -548,9 +547,6 @@ export default class ResultDataProvider {
|
|
|
548
547
|
const parsedDefinitionStepsByTestCase = new Map<number, TestSteps[]>();
|
|
549
548
|
const testCaseStepsXmlMap = this.buildTestCaseStepsXmlMap(testData);
|
|
550
549
|
const runResults = await this.fetchAllResultDataTestReporter(testData, projectName, [], false, false);
|
|
551
|
-
if (options?.debugMode) {
|
|
552
|
-
this.logMewpRunScenarioDebugMatrix(runResults, `coverage plan=${testPlanId}`);
|
|
553
|
-
}
|
|
554
550
|
for (const runResult of runResults) {
|
|
555
551
|
const testCaseId = this.extractMewpTestCaseId(runResult);
|
|
556
552
|
const rawActionResults = Array.isArray(runResult?.iteration?.actionResults)
|
|
@@ -736,8 +732,7 @@ export default class ResultDataProvider {
|
|
|
736
732
|
testPlanId: string,
|
|
737
733
|
projectName: string,
|
|
738
734
|
selectedSuiteIds: number[] | undefined,
|
|
739
|
-
linkedQueryRequest?: any
|
|
740
|
-
options?: MewpInternalValidationRequestOptions
|
|
735
|
+
linkedQueryRequest?: any
|
|
741
736
|
): Promise<MewpInternalValidationFlatPayload> {
|
|
742
737
|
const defaultPayload: MewpInternalValidationFlatPayload = {
|
|
743
738
|
sheetName: `MEWP Internal Validation - Plan ${testPlanId}`,
|
|
@@ -767,6 +762,37 @@ export default class ResultDataProvider {
|
|
|
767
762
|
allRequirements,
|
|
768
763
|
scopedRequirementKeys?.size ? scopedRequirementKeys : undefined
|
|
769
764
|
);
|
|
765
|
+
const linkedFullCodesByTestCase = new Map<number, Set<string>>();
|
|
766
|
+
const linkedFamilyCodesAcrossTestCases = new Map<string, Set<string>>();
|
|
767
|
+
const linkedFullCodesAcrossTestCases = new Set<string>();
|
|
768
|
+
const linkedBaseKeysAcrossTestCases = new Set<string>();
|
|
769
|
+
|
|
770
|
+
for (const [linkedTestCaseId, links] of linkedRequirementsByTestCase.entries()) {
|
|
771
|
+
const rawFullCodes = links?.fullCodes || new Set<string>();
|
|
772
|
+
const filteredFullCodes =
|
|
773
|
+
scopedRequirementKeys?.size && rawFullCodes.size > 0
|
|
774
|
+
? new Set<string>(
|
|
775
|
+
[...rawFullCodes].filter((code) =>
|
|
776
|
+
scopedRequirementKeys.has(this.toRequirementKey(code))
|
|
777
|
+
)
|
|
778
|
+
)
|
|
779
|
+
: rawFullCodes;
|
|
780
|
+
linkedFullCodesByTestCase.set(linkedTestCaseId, filteredFullCodes);
|
|
781
|
+
|
|
782
|
+
for (const code of filteredFullCodes) {
|
|
783
|
+
const normalizedCode = this.normalizeMewpRequirementCodeWithSuffix(code);
|
|
784
|
+
if (!normalizedCode) continue;
|
|
785
|
+
linkedFullCodesAcrossTestCases.add(normalizedCode);
|
|
786
|
+
|
|
787
|
+
const baseKey = this.toRequirementKey(normalizedCode);
|
|
788
|
+
if (!baseKey) continue;
|
|
789
|
+
linkedBaseKeysAcrossTestCases.add(baseKey);
|
|
790
|
+
if (!linkedFamilyCodesAcrossTestCases.has(baseKey)) {
|
|
791
|
+
linkedFamilyCodesAcrossTestCases.set(baseKey, new Set<string>());
|
|
792
|
+
}
|
|
793
|
+
linkedFamilyCodesAcrossTestCases.get(baseKey)!.add(normalizedCode);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
770
796
|
|
|
771
797
|
const rows: MewpInternalValidationRow[] = [];
|
|
772
798
|
const stepsXmlByTestCase = this.buildTestCaseStepsXmlMap(testData);
|
|
@@ -796,16 +822,6 @@ export default class ResultDataProvider {
|
|
|
796
822
|
`fromSuitePayload=${preloadedStepXmlCount} fromWorkItemFallback=${fallbackStepLoadStats.loadedFromFallback} ` +
|
|
797
823
|
`stepsXmlAvailable=${stepsXmlByTestCase.size} unresolved=${fallbackStepLoadStats.unresolvedCount}`
|
|
798
824
|
);
|
|
799
|
-
if (options?.debugMode) {
|
|
800
|
-
const debugRunResults = await this.fetchAllResultDataTestReporter(
|
|
801
|
-
testData,
|
|
802
|
-
projectName,
|
|
803
|
-
[],
|
|
804
|
-
false,
|
|
805
|
-
false
|
|
806
|
-
);
|
|
807
|
-
this.logMewpRunScenarioDebugMatrix(debugRunResults, `internal-validation plan=${testPlanId}`);
|
|
808
|
-
}
|
|
809
825
|
|
|
810
826
|
const validL2BaseKeys = new Set<string>([...requirementFamilies.keys()]);
|
|
811
827
|
const diagnostics = {
|
|
@@ -859,18 +875,7 @@ export default class ResultDataProvider {
|
|
|
859
875
|
[...mentionedL2Only].map((code) => this.toRequirementKey(code)).filter((code) => !!code)
|
|
860
876
|
);
|
|
861
877
|
|
|
862
|
-
const
|
|
863
|
-
const linkedFullCodes =
|
|
864
|
-
scopedRequirementKeys?.size && linkedFullCodesRaw.size > 0
|
|
865
|
-
? new Set<string>(
|
|
866
|
-
[...linkedFullCodesRaw].filter((code) =>
|
|
867
|
-
scopedRequirementKeys.has(this.toRequirementKey(code))
|
|
868
|
-
)
|
|
869
|
-
)
|
|
870
|
-
: linkedFullCodesRaw;
|
|
871
|
-
const linkedBaseKeys = new Set<string>(
|
|
872
|
-
[...linkedFullCodes].map((code) => this.toRequirementKey(code)).filter((code) => !!code)
|
|
873
|
-
);
|
|
878
|
+
const linkedFullCodes = linkedFullCodesByTestCase.get(testCaseId) || new Set<string>();
|
|
874
879
|
|
|
875
880
|
const mentionedCodesByBase = new Map<string, Set<string>>();
|
|
876
881
|
for (const code of mentionedL2Only) {
|
|
@@ -880,13 +885,12 @@ export default class ResultDataProvider {
|
|
|
880
885
|
mentionedCodesByBase.get(baseKey)!.add(code);
|
|
881
886
|
}
|
|
882
887
|
|
|
883
|
-
//
|
|
884
|
-
// 1)
|
|
885
|
-
//
|
|
886
|
-
//
|
|
888
|
+
// Direction A logic:
|
|
889
|
+
// 1) Base mention ("SR0054") is parent-level only and considered covered
|
|
890
|
+
// if any member of that family is linked across scoped test cases.
|
|
891
|
+
// 2) Child mention ("SR0054-1") is exact-match and checked across scoped test cases.
|
|
887
892
|
const missingBaseWhenFamilyUncovered = new Set<string>();
|
|
888
893
|
const missingSpecificMentionedNoFamily = new Set<string>();
|
|
889
|
-
const missingFamilyMembers = new Set<string>();
|
|
890
894
|
for (const [baseKey, mentionedCodes] of mentionedCodesByBase.entries()) {
|
|
891
895
|
const familyCodes = requirementFamilies.get(baseKey);
|
|
892
896
|
const mentionedCodesList = [...mentionedCodes];
|
|
@@ -894,30 +898,18 @@ export default class ResultDataProvider {
|
|
|
894
898
|
const mentionedSpecificMembers = mentionedCodesList.filter((code) => /-\d+$/.test(code));
|
|
895
899
|
|
|
896
900
|
if (familyCodes?.size) {
|
|
897
|
-
|
|
898
|
-
|
|
901
|
+
const familyLinkedCodes = linkedFamilyCodesAcrossTestCases.get(baseKey) || new Set<string>();
|
|
902
|
+
|
|
903
|
+
// Base mention ("SR0054") is satisfied by any linked member in the same family.
|
|
899
904
|
if (hasBaseMention) {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
const targetFamilyCodes = childFamilyCodes.length > 0 ? childFamilyCodes : familyCodesList;
|
|
903
|
-
const missingInTargetFamily = targetFamilyCodes.filter((code) => !linkedFullCodes.has(code));
|
|
904
|
-
|
|
905
|
-
if (missingInTargetFamily.length > 0) {
|
|
906
|
-
const hasAnyLinkedInFamily = familyCodesList.some((code) => linkedFullCodes.has(code));
|
|
907
|
-
if (!hasAnyLinkedInFamily) {
|
|
908
|
-
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
909
|
-
} else {
|
|
910
|
-
for (const code of missingInTargetFamily) {
|
|
911
|
-
missingFamilyMembers.add(code);
|
|
912
|
-
}
|
|
913
|
-
}
|
|
905
|
+
if (familyLinkedCodes.size === 0) {
|
|
906
|
+
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
914
907
|
}
|
|
915
|
-
continue;
|
|
916
908
|
}
|
|
917
909
|
|
|
918
|
-
// Specific mention ("SR0054-1") validates as exact-match only.
|
|
910
|
+
// Specific mention ("SR0054-1") validates as exact-match only across scoped test cases.
|
|
919
911
|
for (const code of mentionedSpecificMembers) {
|
|
920
|
-
if (!
|
|
912
|
+
if (!familyLinkedCodes.has(code)) {
|
|
921
913
|
missingSpecificMentionedNoFamily.add(code);
|
|
922
914
|
}
|
|
923
915
|
}
|
|
@@ -928,10 +920,10 @@ export default class ResultDataProvider {
|
|
|
928
920
|
for (const code of mentionedCodes) {
|
|
929
921
|
const hasSpecificSuffix = /-\d+$/.test(code);
|
|
930
922
|
if (hasSpecificSuffix) {
|
|
931
|
-
if (!
|
|
923
|
+
if (!linkedFullCodesAcrossTestCases.has(code)) {
|
|
932
924
|
missingSpecificMentionedNoFamily.add(code);
|
|
933
925
|
}
|
|
934
|
-
} else if (!
|
|
926
|
+
} else if (!linkedBaseKeysAcrossTestCases.has(baseKey)) {
|
|
935
927
|
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
936
928
|
}
|
|
937
929
|
}
|
|
@@ -961,7 +953,6 @@ export default class ResultDataProvider {
|
|
|
961
953
|
const sortedMissingBaseWhenFamilyUncovered = [...missingBaseWhenFamilyUncovered].sort((a, b) =>
|
|
962
954
|
a.localeCompare(b)
|
|
963
955
|
);
|
|
964
|
-
const sortedMissingFamilyMembers = [...missingFamilyMembers].sort((a, b) => a.localeCompare(b));
|
|
965
956
|
for (const code of sortedMissingSpecificMentionedNoFamily) {
|
|
966
957
|
const stepRef = mentionedCodeFirstStep.get(code) || 'Step ?';
|
|
967
958
|
appendMentionedButNotLinked(code, stepRef);
|
|
@@ -970,11 +961,6 @@ export default class ResultDataProvider {
|
|
|
970
961
|
const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
|
|
971
962
|
appendMentionedButNotLinked(baseKey, stepRef);
|
|
972
963
|
}
|
|
973
|
-
for (const code of sortedMissingFamilyMembers) {
|
|
974
|
-
const baseKey = this.toRequirementKey(code);
|
|
975
|
-
const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
|
|
976
|
-
appendMentionedButNotLinked(code, stepRef);
|
|
977
|
-
}
|
|
978
964
|
|
|
979
965
|
const sortedExtraLinked = [...new Set(extraLinked)]
|
|
980
966
|
.map((code) => this.normalizeMewpRequirementCodeWithSuffix(code))
|
|
@@ -994,11 +980,11 @@ export default class ResultDataProvider {
|
|
|
994
980
|
return String(a[0]).localeCompare(String(b[0]));
|
|
995
981
|
})
|
|
996
982
|
.map(([stepRef, requirementIds]) => {
|
|
997
|
-
|
|
998
|
-
return `${stepRef}: ${groupedRequirementList}`;
|
|
983
|
+
return this.formatStepScopedRequirementGroups(stepRef, requirementIds);
|
|
999
984
|
})
|
|
1000
|
-
.join('\n')
|
|
1001
|
-
|
|
985
|
+
.join('\n')
|
|
986
|
+
.trim();
|
|
987
|
+
const linkedButNotMentioned = this.formatRequirementCodesGroupedByFamily(sortedExtraLinked).trim();
|
|
1002
988
|
const validationStatus: 'Pass' | 'Fail' =
|
|
1003
989
|
mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
|
|
1004
990
|
if (validationStatus === 'Fail') diagnostics.failingRows += 1;
|
|
@@ -1008,8 +994,7 @@ export default class ResultDataProvider {
|
|
|
1008
994
|
`stepsWithMentions=${mentionEntries.length} customerIdsFound=${mentionedL2Only.size} ` +
|
|
1009
995
|
`linkedRequirements=${linkedFullCodes.size} mentionedButNotLinked=${
|
|
1010
996
|
sortedMissingSpecificMentionedNoFamily.length +
|
|
1011
|
-
sortedMissingBaseWhenFamilyUncovered.length
|
|
1012
|
-
sortedMissingFamilyMembers.length
|
|
997
|
+
sortedMissingBaseWhenFamilyUncovered.length
|
|
1013
998
|
} ` +
|
|
1014
999
|
`linkedButNotMentioned=${sortedExtraLinked.length} status=${validationStatus} ` +
|
|
1015
1000
|
`customerIdSample='${[...mentionedL2Only].slice(0, 5).join(', ')}'`
|
|
@@ -2630,6 +2615,49 @@ export default class ResultDataProvider {
|
|
|
2630
2615
|
return `SR${match[1]}`;
|
|
2631
2616
|
}
|
|
2632
2617
|
|
|
2618
|
+
private compareMewpRequirementCodes(a: string, b: string): number {
|
|
2619
|
+
const normalizeComparableCode = (value: string): { base: number; hasSuffix: number; suffix: number; raw: string } => {
|
|
2620
|
+
const normalizedCode = this.normalizeMewpRequirementCodeWithSuffix(value);
|
|
2621
|
+
const match = /^SR(\d+)(?:-(\d+))?$/i.exec(normalizedCode);
|
|
2622
|
+
if (!match) {
|
|
2623
|
+
return {
|
|
2624
|
+
base: Number.POSITIVE_INFINITY,
|
|
2625
|
+
hasSuffix: 1,
|
|
2626
|
+
suffix: Number.POSITIVE_INFINITY,
|
|
2627
|
+
raw: String(value || ''),
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
return {
|
|
2632
|
+
base: Number(match[1]),
|
|
2633
|
+
hasSuffix: match[2] ? 1 : 0,
|
|
2634
|
+
suffix: match[2] ? Number(match[2]) : -1,
|
|
2635
|
+
raw: normalizedCode,
|
|
2636
|
+
};
|
|
2637
|
+
};
|
|
2638
|
+
|
|
2639
|
+
const left = normalizeComparableCode(a);
|
|
2640
|
+
const right = normalizeComparableCode(b);
|
|
2641
|
+
|
|
2642
|
+
if (left.base !== right.base) return left.base - right.base;
|
|
2643
|
+
if (left.hasSuffix !== right.hasSuffix) return left.hasSuffix - right.hasSuffix;
|
|
2644
|
+
if (left.suffix !== right.suffix) return left.suffix - right.suffix;
|
|
2645
|
+
return left.raw.localeCompare(right.raw);
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
private formatStepScopedRequirementGroups(stepRef: string, requirementIds: Iterable<string>): string {
|
|
2649
|
+
const groupedRequirementList = this.formatRequirementCodesGroupedByFamily(requirementIds);
|
|
2650
|
+
if (!groupedRequirementList) return `${stepRef}:`;
|
|
2651
|
+
|
|
2652
|
+
const groupedLines = groupedRequirementList
|
|
2653
|
+
.split('\n')
|
|
2654
|
+
.map((line) => String(line || '').trim())
|
|
2655
|
+
.filter((line) => line.length > 0);
|
|
2656
|
+
|
|
2657
|
+
if (groupedLines.length <= 1) return `${stepRef}: ${groupedLines[0] || ''}`.trim();
|
|
2658
|
+
return `${stepRef}:\n${groupedLines.map((line) => `- ${line}`).join('\n')}`.trim();
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2633
2661
|
private formatRequirementCodesGroupedByFamily(codes: Iterable<string>): string {
|
|
2634
2662
|
const byBaseKey = new Map<string, Set<string>>();
|
|
2635
2663
|
for (const rawCode of codes || []) {
|
|
@@ -2643,11 +2671,17 @@ export default class ResultDataProvider {
|
|
|
2643
2671
|
if (byBaseKey.size === 0) return '';
|
|
2644
2672
|
|
|
2645
2673
|
return [...byBaseKey.entries()]
|
|
2646
|
-
.sort((a, b) => a[0]
|
|
2674
|
+
.sort((a, b) => this.compareMewpRequirementCodes(a[0], b[0]))
|
|
2647
2675
|
.map(([baseKey, members]) => {
|
|
2648
|
-
const sortedMembers = [...members].sort((a, b) =>
|
|
2676
|
+
const sortedMembers = [...members].sort((a, b) => this.compareMewpRequirementCodes(a, b));
|
|
2649
2677
|
if (sortedMembers.length <= 1) return sortedMembers[0];
|
|
2650
|
-
|
|
2678
|
+
|
|
2679
|
+
const nonBaseMembers = sortedMembers.filter((member) => member !== baseKey);
|
|
2680
|
+
if (nonBaseMembers.length > 0) {
|
|
2681
|
+
return `${baseKey}: ${nonBaseMembers.join(', ')}`;
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
return baseKey;
|
|
2651
2685
|
})
|
|
2652
2686
|
.join('\n');
|
|
2653
2687
|
}
|
|
@@ -3873,79 +3907,6 @@ export default class ResultDataProvider {
|
|
|
3873
3907
|
return this.fetchResultDataBasedOnWiBase(projectName, runId, resultId);
|
|
3874
3908
|
}
|
|
3875
3909
|
|
|
3876
|
-
private logMewpRunScenarioDebugMatrix(runResults: any[], contextLabel: string): void {
|
|
3877
|
-
const results = Array.isArray(runResults) ? runResults : [];
|
|
3878
|
-
const matrix = {
|
|
3879
|
-
total: results.length,
|
|
3880
|
-
passOrFailWithActionResults: 0,
|
|
3881
|
-
runWithNoActionResults: 0,
|
|
3882
|
-
notApplicable: 0,
|
|
3883
|
-
noRunHistoryActive: 0,
|
|
3884
|
-
other: 0,
|
|
3885
|
-
};
|
|
3886
|
-
const samples = {
|
|
3887
|
-
passOrFailWithActionResults: [] as number[],
|
|
3888
|
-
runWithNoActionResults: [] as number[],
|
|
3889
|
-
notApplicable: [] as number[],
|
|
3890
|
-
noRunHistoryActive: [] as number[],
|
|
3891
|
-
other: [] as number[],
|
|
3892
|
-
};
|
|
3893
|
-
|
|
3894
|
-
const pushSample = (bucket: keyof typeof samples, id: number) => {
|
|
3895
|
-
if (!Number.isFinite(id) || id <= 0) return;
|
|
3896
|
-
if (samples[bucket].length >= 5) return;
|
|
3897
|
-
samples[bucket].push(id);
|
|
3898
|
-
};
|
|
3899
|
-
|
|
3900
|
-
for (const item of results) {
|
|
3901
|
-
const testCaseId = Number(item?.testCaseId || item?.testCase?.id || 0);
|
|
3902
|
-
const hasRun = Number(item?.lastRunId || 0) > 0 && Number(item?.lastResultId || 0) > 0;
|
|
3903
|
-
const rawOutcome = String(item?._debugTestOutcome || '').trim().toLowerCase();
|
|
3904
|
-
const rawState = String(item?._debugTestCaseState || '').trim().toLowerCase();
|
|
3905
|
-
const originalActionResultsCount = Number(item?._debugOriginalActionResultsCount ?? -1);
|
|
3906
|
-
|
|
3907
|
-
if (rawOutcome === 'notapplicable' || rawOutcome === 'not applicable') {
|
|
3908
|
-
matrix.notApplicable += 1;
|
|
3909
|
-
pushSample('notApplicable', testCaseId);
|
|
3910
|
-
continue;
|
|
3911
|
-
}
|
|
3912
|
-
|
|
3913
|
-
if (hasRun && (rawOutcome === 'passed' || rawOutcome === 'failed') && originalActionResultsCount > 0) {
|
|
3914
|
-
matrix.passOrFailWithActionResults += 1;
|
|
3915
|
-
pushSample('passOrFailWithActionResults', testCaseId);
|
|
3916
|
-
continue;
|
|
3917
|
-
}
|
|
3918
|
-
|
|
3919
|
-
if (hasRun && originalActionResultsCount === 0) {
|
|
3920
|
-
matrix.runWithNoActionResults += 1;
|
|
3921
|
-
pushSample('runWithNoActionResults', testCaseId);
|
|
3922
|
-
continue;
|
|
3923
|
-
}
|
|
3924
|
-
|
|
3925
|
-
if (!hasRun && rawState === 'active') {
|
|
3926
|
-
matrix.noRunHistoryActive += 1;
|
|
3927
|
-
pushSample('noRunHistoryActive', testCaseId);
|
|
3928
|
-
continue;
|
|
3929
|
-
}
|
|
3930
|
-
|
|
3931
|
-
matrix.other += 1;
|
|
3932
|
-
pushSample('other', testCaseId);
|
|
3933
|
-
}
|
|
3934
|
-
|
|
3935
|
-
logger.info(
|
|
3936
|
-
`MEWP run debug matrix (${contextLabel}): total=${matrix.total}; ` +
|
|
3937
|
-
`passOrFailWithActionResults=${matrix.passOrFailWithActionResults}; ` +
|
|
3938
|
-
`runWithNoActionResults=${matrix.runWithNoActionResults}; ` +
|
|
3939
|
-
`notApplicable=${matrix.notApplicable}; ` +
|
|
3940
|
-
`noRunHistoryActive=${matrix.noRunHistoryActive}; other=${matrix.other}; ` +
|
|
3941
|
-
`samplePassFail=${samples.passOrFailWithActionResults.join(',') || '-'}; ` +
|
|
3942
|
-
`sampleNoAction=${samples.runWithNoActionResults.join(',') || '-'}; ` +
|
|
3943
|
-
`sampleNA=${samples.notApplicable.join(',') || '-'}; ` +
|
|
3944
|
-
`sampleNoRunActive=${samples.noRunHistoryActive.join(',') || '-'}; ` +
|
|
3945
|
-
`sampleOther=${samples.other.join(',') || '-'}`
|
|
3946
|
-
);
|
|
3947
|
-
}
|
|
3948
|
-
|
|
3949
3910
|
/**
|
|
3950
3911
|
* Converts a run status string into a human-readable format.
|
|
3951
3912
|
*
|
|
@@ -4381,11 +4342,6 @@ export default class ResultDataProvider {
|
|
|
4381
4342
|
resultData.iterationDetails.push(iteration);
|
|
4382
4343
|
}
|
|
4383
4344
|
|
|
4384
|
-
const originalActionResultsCount = Array.isArray(iteration?.actionResults)
|
|
4385
|
-
? iteration.actionResults.length
|
|
4386
|
-
: 0;
|
|
4387
|
-
resultData._debugOriginalActionResultsCount = originalActionResultsCount;
|
|
4388
|
-
|
|
4389
4345
|
if (resultData.stepsResultXml && iteration) {
|
|
4390
4346
|
const actionResults = Array.isArray(iteration.actionResults) ? iteration.actionResults : [];
|
|
4391
4347
|
const actionResultsWithSharedModels = actionResults.filter(
|
|
@@ -5098,7 +5054,7 @@ export default class ResultDataProvider {
|
|
|
5098
5054
|
resultData.iterationDetails?.length > 0
|
|
5099
5055
|
? resultData.iterationDetails[resultData.iterationDetails?.length - 1]
|
|
5100
5056
|
: undefined;
|
|
5101
|
-
const
|
|
5057
|
+
const testOutcome = this.getTestOutcome(resultData);
|
|
5102
5058
|
|
|
5103
5059
|
if (!resultData?.testCase || !resultData?.testSuite) {
|
|
5104
5060
|
logger.debug(
|
|
@@ -5132,9 +5088,6 @@ export default class ResultDataProvider {
|
|
|
5132
5088
|
relatedCRs: resultData.relatedCRs || undefined,
|
|
5133
5089
|
lastRunResult: undefined as any,
|
|
5134
5090
|
customFields: {}, // Create an object to store custom fields
|
|
5135
|
-
_debugTestOutcome: debugOutcome,
|
|
5136
|
-
_debugTestCaseState: String(resultData?.state || ''),
|
|
5137
|
-
_debugOriginalActionResultsCount: Number(resultData?._debugOriginalActionResultsCount ?? -1),
|
|
5138
5091
|
};
|
|
5139
5092
|
|
|
5140
5093
|
// Process all custom fields from resultData.filteredFields
|
|
@@ -5156,12 +5109,12 @@ export default class ResultDataProvider {
|
|
|
5156
5109
|
case 'testCaseResult':
|
|
5157
5110
|
if (lastRunId === undefined || lastResultId === undefined) {
|
|
5158
5111
|
resultDataResponse.testCaseResult = {
|
|
5159
|
-
resultMessage: `${this.convertRunStatus(
|
|
5112
|
+
resultMessage: `${this.convertRunStatus(testOutcome)}`,
|
|
5160
5113
|
url: '',
|
|
5161
5114
|
};
|
|
5162
5115
|
} else {
|
|
5163
5116
|
resultDataResponse.testCaseResult = {
|
|
5164
|
-
resultMessage: `${this.convertRunStatus(
|
|
5117
|
+
resultMessage: `${this.convertRunStatus(testOutcome)} in Run ${lastRunId}`,
|
|
5165
5118
|
url: `${this.orgUrl}${projectName}/_testManagement/runs?runId=${lastRunId}&_a=resultSummary&resultId=${lastResultId}`,
|
|
5166
5119
|
};
|
|
5167
5120
|
}
|
|
@@ -1937,7 +1937,7 @@ describe('ResultDataProvider', () => {
|
|
|
1937
1937
|
);
|
|
1938
1938
|
});
|
|
1939
1939
|
|
|
1940
|
-
it('should emit Direction A rows
|
|
1940
|
+
it('should emit Direction A rows only for specifically mentioned child requirements', async () => {
|
|
1941
1941
|
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
1942
1942
|
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
1943
1943
|
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
|
|
@@ -1981,7 +1981,7 @@ describe('ResultDataProvider', () => {
|
|
|
1981
1981
|
stepId: '2',
|
|
1982
1982
|
stepPosition: '2',
|
|
1983
1983
|
action: '',
|
|
1984
|
-
expected: 'SR0001
|
|
1984
|
+
expected: 'SR0001-1',
|
|
1985
1985
|
isSharedStepTitle: false,
|
|
1986
1986
|
},
|
|
1987
1987
|
]);
|
|
@@ -2077,6 +2077,115 @@ describe('ResultDataProvider', () => {
|
|
|
2077
2077
|
);
|
|
2078
2078
|
});
|
|
2079
2079
|
|
|
2080
|
+
it('should support cross-test-case family coverage when siblings are linked on different test cases', async () => {
|
|
2081
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
2082
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
2083
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
|
|
2084
|
+
{
|
|
2085
|
+
testPointsItems: [
|
|
2086
|
+
{ testCaseId: 501, testCaseName: 'TC 501 - sibling 1' },
|
|
2087
|
+
{ testCaseId: 502, testCaseName: 'TC 502 - sibling 2' },
|
|
2088
|
+
],
|
|
2089
|
+
testCasesItems: [
|
|
2090
|
+
{
|
|
2091
|
+
workItem: {
|
|
2092
|
+
id: 501,
|
|
2093
|
+
workItemFields: [{ key: 'Steps', value: '<steps id=\"mock-steps-tc-501\"></steps>' }],
|
|
2094
|
+
},
|
|
2095
|
+
},
|
|
2096
|
+
{
|
|
2097
|
+
workItem: {
|
|
2098
|
+
id: 502,
|
|
2099
|
+
workItemFields: [{ key: 'Steps', value: '<steps id=\"mock-steps-tc-502\"></steps>' }],
|
|
2100
|
+
},
|
|
2101
|
+
},
|
|
2102
|
+
],
|
|
2103
|
+
},
|
|
2104
|
+
]);
|
|
2105
|
+
jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
|
|
2106
|
+
{
|
|
2107
|
+
workItemId: 9301,
|
|
2108
|
+
requirementId: 'SR0054-1',
|
|
2109
|
+
baseKey: 'SR0054',
|
|
2110
|
+
title: 'SR0054 child 1',
|
|
2111
|
+
responsibility: 'ESUK',
|
|
2112
|
+
linkedTestCaseIds: [],
|
|
2113
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
2114
|
+
},
|
|
2115
|
+
{
|
|
2116
|
+
workItemId: 9302,
|
|
2117
|
+
requirementId: 'SR0054-2',
|
|
2118
|
+
baseKey: 'SR0054',
|
|
2119
|
+
title: 'SR0054 child 2',
|
|
2120
|
+
responsibility: 'ESUK',
|
|
2121
|
+
linkedTestCaseIds: [],
|
|
2122
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
2123
|
+
},
|
|
2124
|
+
]);
|
|
2125
|
+
jest.spyOn(resultDataProvider as any, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(
|
|
2126
|
+
new Map([
|
|
2127
|
+
[
|
|
2128
|
+
501,
|
|
2129
|
+
{
|
|
2130
|
+
baseKeys: new Set(['SR0054']),
|
|
2131
|
+
fullCodes: new Set(['SR0054-1']),
|
|
2132
|
+
},
|
|
2133
|
+
],
|
|
2134
|
+
[
|
|
2135
|
+
502,
|
|
2136
|
+
{
|
|
2137
|
+
baseKeys: new Set(['SR0054']),
|
|
2138
|
+
fullCodes: new Set(['SR0054-2']),
|
|
2139
|
+
},
|
|
2140
|
+
],
|
|
2141
|
+
])
|
|
2142
|
+
);
|
|
2143
|
+
jest
|
|
2144
|
+
.spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps')
|
|
2145
|
+
.mockResolvedValueOnce([
|
|
2146
|
+
{
|
|
2147
|
+
stepId: '1',
|
|
2148
|
+
stepPosition: '1',
|
|
2149
|
+
action: 'Parent mention on first test case',
|
|
2150
|
+
expected: 'SR0054',
|
|
2151
|
+
isSharedStepTitle: false,
|
|
2152
|
+
},
|
|
2153
|
+
])
|
|
2154
|
+
.mockResolvedValueOnce([
|
|
2155
|
+
{
|
|
2156
|
+
stepId: '1',
|
|
2157
|
+
stepPosition: '1',
|
|
2158
|
+
action: 'Parent mention on second test case',
|
|
2159
|
+
expected: 'SR0054',
|
|
2160
|
+
isSharedStepTitle: false,
|
|
2161
|
+
},
|
|
2162
|
+
]);
|
|
2163
|
+
|
|
2164
|
+
const result = await (resultDataProvider as any).getMewpInternalValidationFlatResults(
|
|
2165
|
+
'123',
|
|
2166
|
+
mockProjectName,
|
|
2167
|
+
[1]
|
|
2168
|
+
);
|
|
2169
|
+
|
|
2170
|
+
const byTestCase = new Map<number, any>(
|
|
2171
|
+
result.rows.map((row: any) => [Number(row['Test Case ID']), row])
|
|
2172
|
+
);
|
|
2173
|
+
expect(byTestCase.get(501)).toEqual(
|
|
2174
|
+
expect.objectContaining({
|
|
2175
|
+
'Mentioned but Not Linked': '',
|
|
2176
|
+
'Linked but Not Mentioned': '',
|
|
2177
|
+
'Validation Status': 'Pass',
|
|
2178
|
+
})
|
|
2179
|
+
);
|
|
2180
|
+
expect(byTestCase.get(502)).toEqual(
|
|
2181
|
+
expect.objectContaining({
|
|
2182
|
+
'Mentioned but Not Linked': '',
|
|
2183
|
+
'Linked but Not Mentioned': '',
|
|
2184
|
+
'Validation Status': 'Pass',
|
|
2185
|
+
})
|
|
2186
|
+
);
|
|
2187
|
+
});
|
|
2188
|
+
|
|
2080
2189
|
it('should group linked-but-not-mentioned requirements by SR family', async () => {
|
|
2081
2190
|
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
2082
2191
|
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
@@ -2562,7 +2671,7 @@ describe('ResultDataProvider', () => {
|
|
|
2562
2671
|
expect(byTestCase.get(201)).toEqual(
|
|
2563
2672
|
expect.objectContaining({
|
|
2564
2673
|
'Test Case Title': 'TC 201 - Mixed discrepancies',
|
|
2565
|
-
'Mentioned but Not Linked': 'Step 1: SR0095-3
|
|
2674
|
+
'Mentioned but Not Linked': 'Step 1: SR0095-3',
|
|
2566
2675
|
'Linked but Not Mentioned': 'SR8888',
|
|
2567
2676
|
'Validation Status': 'Fail',
|
|
2568
2677
|
})
|