@elisra-devops/docgen-data-provider 1.85.0 → 1.87.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 +98 -29
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +285 -1
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/ResultDataProvider.ts +105 -27
- package/src/tests/modules/ResultDataProvider.test.ts +330 -1
package/package.json
CHANGED
|
@@ -848,18 +848,6 @@ export default class ResultDataProvider {
|
|
|
848
848
|
[...mentionedL2Only].map((code) => this.toRequirementKey(code)).filter((code) => !!code)
|
|
849
849
|
);
|
|
850
850
|
|
|
851
|
-
const expectedFamilyCodes = new Set<string>();
|
|
852
|
-
for (const baseKey of mentionedBaseKeys) {
|
|
853
|
-
const familyCodes = requirementFamilies.get(baseKey);
|
|
854
|
-
if (familyCodes?.size) {
|
|
855
|
-
familyCodes.forEach((code) => expectedFamilyCodes.add(code));
|
|
856
|
-
} else {
|
|
857
|
-
for (const code of mentionedL2Only) {
|
|
858
|
-
if (this.toRequirementKey(code) === baseKey) expectedFamilyCodes.add(code);
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
|
|
863
851
|
const linkedFullCodesRaw = linkedRequirementsByTestCase.get(testCaseId)?.fullCodes || new Set<string>();
|
|
864
852
|
const linkedFullCodes =
|
|
865
853
|
scopedRequirementKeys?.size && linkedFullCodesRaw.size > 0
|
|
@@ -873,14 +861,69 @@ export default class ResultDataProvider {
|
|
|
873
861
|
[...linkedFullCodes].map((code) => this.toRequirementKey(code)).filter((code) => !!code)
|
|
874
862
|
);
|
|
875
863
|
|
|
876
|
-
const
|
|
864
|
+
const mentionedCodesByBase = new Map<string, Set<string>>();
|
|
865
|
+
for (const code of mentionedL2Only) {
|
|
877
866
|
const baseKey = this.toRequirementKey(code);
|
|
878
|
-
if (!baseKey)
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
867
|
+
if (!baseKey) continue;
|
|
868
|
+
if (!mentionedCodesByBase.has(baseKey)) mentionedCodesByBase.set(baseKey, new Set<string>());
|
|
869
|
+
mentionedCodesByBase.get(baseKey)!.add(code);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Context-based direction A logic:
|
|
873
|
+
// 1) If no member of family is linked -> report only base SR (Step X: SR0054).
|
|
874
|
+
// 2) If family is partially linked -> report only specific missing members.
|
|
875
|
+
// 3) If family fully linked -> report nothing for that family.
|
|
876
|
+
const missingBaseWhenFamilyUncovered = new Set<string>();
|
|
877
|
+
const missingSpecificMentionedNoFamily = new Set<string>();
|
|
878
|
+
const missingFamilyMembers = new Set<string>();
|
|
879
|
+
for (const [baseKey, mentionedCodes] of mentionedCodesByBase.entries()) {
|
|
880
|
+
const familyCodes = requirementFamilies.get(baseKey);
|
|
881
|
+
const mentionedCodesList = [...mentionedCodes];
|
|
882
|
+
const hasBaseMention = mentionedCodesList.some((code) => !/-\d+$/.test(code));
|
|
883
|
+
const mentionedSpecificMembers = mentionedCodesList.filter((code) => /-\d+$/.test(code));
|
|
884
|
+
|
|
885
|
+
if (familyCodes?.size) {
|
|
886
|
+
// Base mention ("SR0054") validates against child coverage when children exist.
|
|
887
|
+
// If no child variants exist, fallback to the single standalone requirement code.
|
|
888
|
+
if (hasBaseMention) {
|
|
889
|
+
const familyCodesList = [...familyCodes];
|
|
890
|
+
const childFamilyCodes = familyCodesList.filter((code) => /-\d+$/.test(code));
|
|
891
|
+
const targetFamilyCodes = childFamilyCodes.length > 0 ? childFamilyCodes : familyCodesList;
|
|
892
|
+
const missingInTargetFamily = targetFamilyCodes.filter((code) => !linkedFullCodes.has(code));
|
|
893
|
+
|
|
894
|
+
if (missingInTargetFamily.length > 0) {
|
|
895
|
+
const hasAnyLinkedInFamily = familyCodesList.some((code) => linkedFullCodes.has(code));
|
|
896
|
+
if (!hasAnyLinkedInFamily) {
|
|
897
|
+
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
898
|
+
} else {
|
|
899
|
+
for (const code of missingInTargetFamily) {
|
|
900
|
+
missingFamilyMembers.add(code);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Specific mention ("SR0054-1") validates as exact-match only.
|
|
908
|
+
for (const code of mentionedSpecificMembers) {
|
|
909
|
+
if (!linkedFullCodes.has(code)) {
|
|
910
|
+
missingSpecificMentionedNoFamily.add(code);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Fallback path when family data is unavailable for this base key.
|
|
917
|
+
for (const code of mentionedCodes) {
|
|
918
|
+
const hasSpecificSuffix = /-\d+$/.test(code);
|
|
919
|
+
if (hasSpecificSuffix) {
|
|
920
|
+
if (!linkedFullCodes.has(code)) missingSpecificMentionedNoFamily.add(code);
|
|
921
|
+
} else if (!linkedBaseKeys.has(baseKey)) {
|
|
922
|
+
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
884
927
|
// Direction B is family-based: if any member of a family is mentioned in Expected Result,
|
|
885
928
|
// linked members of that same family are not considered "linked but not mentioned".
|
|
886
929
|
const extraLinked = [...linkedFullCodes].filter((code) => {
|
|
@@ -899,13 +942,22 @@ export default class ResultDataProvider {
|
|
|
899
942
|
mentionedButNotLinkedByStep.get(normalizedStepRef)!.add(normalizedRequirementId);
|
|
900
943
|
};
|
|
901
944
|
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
|
|
945
|
+
const sortedMissingSpecificMentionedNoFamily = [...missingSpecificMentionedNoFamily].sort((a, b) =>
|
|
946
|
+
a.localeCompare(b)
|
|
947
|
+
);
|
|
948
|
+
const sortedMissingBaseWhenFamilyUncovered = [...missingBaseWhenFamilyUncovered].sort((a, b) =>
|
|
949
|
+
a.localeCompare(b)
|
|
950
|
+
);
|
|
951
|
+
const sortedMissingFamilyMembers = [...missingFamilyMembers].sort((a, b) => a.localeCompare(b));
|
|
952
|
+
for (const code of sortedMissingSpecificMentionedNoFamily) {
|
|
905
953
|
const stepRef = mentionedCodeFirstStep.get(code) || 'Step ?';
|
|
906
954
|
appendMentionedButNotLinked(code, stepRef);
|
|
907
955
|
}
|
|
908
|
-
for (const
|
|
956
|
+
for (const baseKey of sortedMissingBaseWhenFamilyUncovered) {
|
|
957
|
+
const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
|
|
958
|
+
appendMentionedButNotLinked(baseKey, stepRef);
|
|
959
|
+
}
|
|
960
|
+
for (const code of sortedMissingFamilyMembers) {
|
|
909
961
|
const baseKey = this.toRequirementKey(code);
|
|
910
962
|
const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
|
|
911
963
|
appendMentionedButNotLinked(code, stepRef);
|
|
@@ -929,11 +981,11 @@ export default class ResultDataProvider {
|
|
|
929
981
|
return String(a[0]).localeCompare(String(b[0]));
|
|
930
982
|
})
|
|
931
983
|
.map(([stepRef, requirementIds]) => {
|
|
932
|
-
const
|
|
933
|
-
return `${stepRef}: ${
|
|
984
|
+
const groupedRequirementList = this.formatRequirementCodesGroupedByFamily(requirementIds);
|
|
985
|
+
return `${stepRef}: ${groupedRequirementList}`;
|
|
934
986
|
})
|
|
935
987
|
.join('; ');
|
|
936
|
-
const linkedButNotMentioned =
|
|
988
|
+
const linkedButNotMentioned = this.formatRequirementCodesGroupedByFamily(sortedExtraLinked);
|
|
937
989
|
const validationStatus: 'Pass' | 'Fail' =
|
|
938
990
|
mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
|
|
939
991
|
if (validationStatus === 'Fail') diagnostics.failingRows += 1;
|
|
@@ -941,7 +993,11 @@ export default class ResultDataProvider {
|
|
|
941
993
|
`MEWP internal validation parse diagnostics: ` +
|
|
942
994
|
`testCaseId=${testCaseId} parsedSteps=${executableSteps.length} ` +
|
|
943
995
|
`stepsWithMentions=${mentionEntries.length} customerIdsFound=${mentionedL2Only.size} ` +
|
|
944
|
-
`linkedRequirements=${linkedFullCodes.size} mentionedButNotLinked=${
|
|
996
|
+
`linkedRequirements=${linkedFullCodes.size} mentionedButNotLinked=${
|
|
997
|
+
sortedMissingSpecificMentionedNoFamily.length +
|
|
998
|
+
sortedMissingBaseWhenFamilyUncovered.length +
|
|
999
|
+
sortedMissingFamilyMembers.length
|
|
1000
|
+
} ` +
|
|
945
1001
|
`linkedButNotMentioned=${sortedExtraLinked.length} status=${validationStatus} ` +
|
|
946
1002
|
`customerIdSample='${[...mentionedL2Only].slice(0, 5).join(', ')}'`
|
|
947
1003
|
);
|
|
@@ -2700,6 +2756,28 @@ export default class ResultDataProvider {
|
|
|
2700
2756
|
return `SR${match[1]}`;
|
|
2701
2757
|
}
|
|
2702
2758
|
|
|
2759
|
+
private formatRequirementCodesGroupedByFamily(codes: Iterable<string>): string {
|
|
2760
|
+
const byBaseKey = new Map<string, Set<string>>();
|
|
2761
|
+
for (const rawCode of codes || []) {
|
|
2762
|
+
const normalizedCode = this.normalizeMewpRequirementCodeWithSuffix(String(rawCode || ''));
|
|
2763
|
+
if (!normalizedCode) continue;
|
|
2764
|
+
const baseKey = this.toRequirementKey(normalizedCode) || normalizedCode;
|
|
2765
|
+
if (!byBaseKey.has(baseKey)) byBaseKey.set(baseKey, new Set<string>());
|
|
2766
|
+
byBaseKey.get(baseKey)!.add(normalizedCode);
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
if (byBaseKey.size === 0) return '';
|
|
2770
|
+
|
|
2771
|
+
return [...byBaseKey.entries()]
|
|
2772
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
2773
|
+
.map(([baseKey, members]) => {
|
|
2774
|
+
const sortedMembers = [...members].sort((a, b) => a.localeCompare(b));
|
|
2775
|
+
if (sortedMembers.length <= 1) return sortedMembers[0];
|
|
2776
|
+
return `${baseKey}: ${sortedMembers.join(', ')}`;
|
|
2777
|
+
})
|
|
2778
|
+
.join('; ');
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2703
2781
|
private toMewpComparableText(value: any): string {
|
|
2704
2782
|
if (value === null || value === undefined) return '';
|
|
2705
2783
|
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
@@ -1986,6 +1986,226 @@ describe('ResultDataProvider', () => {
|
|
|
1986
1986
|
);
|
|
1987
1987
|
});
|
|
1988
1988
|
|
|
1989
|
+
it('should pass when a base SR mention is fully covered by its only linked child', async () => {
|
|
1990
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
1991
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
1992
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
|
|
1993
|
+
{
|
|
1994
|
+
testPointsItems: [{ testCaseId: 402, testCaseName: 'TC 402 - Single child covered' }],
|
|
1995
|
+
testCasesItems: [
|
|
1996
|
+
{
|
|
1997
|
+
workItem: {
|
|
1998
|
+
id: 402,
|
|
1999
|
+
workItemFields: [{ key: 'Steps', value: '<steps id=\"mock-steps-tc-402\"></steps>' }],
|
|
2000
|
+
},
|
|
2001
|
+
},
|
|
2002
|
+
],
|
|
2003
|
+
},
|
|
2004
|
+
]);
|
|
2005
|
+
jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
|
|
2006
|
+
{
|
|
2007
|
+
workItemId: 9101,
|
|
2008
|
+
requirementId: 'SR0054',
|
|
2009
|
+
baseKey: 'SR0054',
|
|
2010
|
+
title: 'SR0054 parent',
|
|
2011
|
+
responsibility: 'ESUK',
|
|
2012
|
+
linkedTestCaseIds: [],
|
|
2013
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
2014
|
+
},
|
|
2015
|
+
{
|
|
2016
|
+
workItemId: 9102,
|
|
2017
|
+
requirementId: 'SR0054-1',
|
|
2018
|
+
baseKey: 'SR0054',
|
|
2019
|
+
title: 'SR0054 child 1',
|
|
2020
|
+
responsibility: 'ESUK',
|
|
2021
|
+
linkedTestCaseIds: [],
|
|
2022
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
2023
|
+
},
|
|
2024
|
+
]);
|
|
2025
|
+
jest.spyOn(resultDataProvider as any, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(
|
|
2026
|
+
new Map([
|
|
2027
|
+
[
|
|
2028
|
+
402,
|
|
2029
|
+
{
|
|
2030
|
+
baseKeys: new Set(['SR0054']),
|
|
2031
|
+
fullCodes: new Set(['SR0054-1']),
|
|
2032
|
+
},
|
|
2033
|
+
],
|
|
2034
|
+
])
|
|
2035
|
+
);
|
|
2036
|
+
jest.spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
|
|
2037
|
+
{
|
|
2038
|
+
stepId: '3',
|
|
2039
|
+
stepPosition: '3',
|
|
2040
|
+
action: 'Validate family root',
|
|
2041
|
+
expected: 'SR0054',
|
|
2042
|
+
isSharedStepTitle: false,
|
|
2043
|
+
},
|
|
2044
|
+
]);
|
|
2045
|
+
|
|
2046
|
+
const result = await (resultDataProvider as any).getMewpInternalValidationFlatResults(
|
|
2047
|
+
'123',
|
|
2048
|
+
mockProjectName,
|
|
2049
|
+
[1]
|
|
2050
|
+
);
|
|
2051
|
+
|
|
2052
|
+
expect(result.rows).toHaveLength(1);
|
|
2053
|
+
expect(result.rows[0]).toEqual(
|
|
2054
|
+
expect.objectContaining({
|
|
2055
|
+
'Test Case ID': 402,
|
|
2056
|
+
'Mentioned but Not Linked': '',
|
|
2057
|
+
'Linked but Not Mentioned': '',
|
|
2058
|
+
'Validation Status': 'Pass',
|
|
2059
|
+
})
|
|
2060
|
+
);
|
|
2061
|
+
});
|
|
2062
|
+
|
|
2063
|
+
it('should group linked-but-not-mentioned requirements by SR family', async () => {
|
|
2064
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
2065
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
2066
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
|
|
2067
|
+
{
|
|
2068
|
+
testPointsItems: [{ testCaseId: 403, testCaseName: 'TC 403 - Linked only' }],
|
|
2069
|
+
testCasesItems: [
|
|
2070
|
+
{
|
|
2071
|
+
workItem: {
|
|
2072
|
+
id: 403,
|
|
2073
|
+
workItemFields: [{ key: 'Steps', value: '<steps id=\"mock-steps-tc-403\"></steps>' }],
|
|
2074
|
+
},
|
|
2075
|
+
},
|
|
2076
|
+
],
|
|
2077
|
+
},
|
|
2078
|
+
]);
|
|
2079
|
+
jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
|
|
2080
|
+
{
|
|
2081
|
+
workItemId: 9201,
|
|
2082
|
+
requirementId: 'SR0054-1',
|
|
2083
|
+
baseKey: 'SR0054',
|
|
2084
|
+
title: 'SR0054 child 1',
|
|
2085
|
+
responsibility: 'ESUK',
|
|
2086
|
+
linkedTestCaseIds: [],
|
|
2087
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
2088
|
+
},
|
|
2089
|
+
{
|
|
2090
|
+
workItemId: 9202,
|
|
2091
|
+
requirementId: 'SR0054-2',
|
|
2092
|
+
baseKey: 'SR0054',
|
|
2093
|
+
title: 'SR0054 child 2',
|
|
2094
|
+
responsibility: 'ESUK',
|
|
2095
|
+
linkedTestCaseIds: [],
|
|
2096
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
2097
|
+
},
|
|
2098
|
+
{
|
|
2099
|
+
workItemId: 9203,
|
|
2100
|
+
requirementId: 'SR0100-1',
|
|
2101
|
+
baseKey: 'SR0100',
|
|
2102
|
+
title: 'SR0100 child 1',
|
|
2103
|
+
responsibility: 'ESUK',
|
|
2104
|
+
linkedTestCaseIds: [],
|
|
2105
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
2106
|
+
},
|
|
2107
|
+
]);
|
|
2108
|
+
jest.spyOn(resultDataProvider as any, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(
|
|
2109
|
+
new Map([
|
|
2110
|
+
[
|
|
2111
|
+
403,
|
|
2112
|
+
{
|
|
2113
|
+
baseKeys: new Set(['SR0054', 'SR0100']),
|
|
2114
|
+
fullCodes: new Set(['SR0054-1', 'SR0054-2', 'SR0100-1']),
|
|
2115
|
+
},
|
|
2116
|
+
],
|
|
2117
|
+
])
|
|
2118
|
+
);
|
|
2119
|
+
jest.spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
|
|
2120
|
+
{
|
|
2121
|
+
stepId: '1',
|
|
2122
|
+
stepPosition: '1',
|
|
2123
|
+
action: 'Action only',
|
|
2124
|
+
expected: '',
|
|
2125
|
+
isSharedStepTitle: false,
|
|
2126
|
+
},
|
|
2127
|
+
]);
|
|
2128
|
+
|
|
2129
|
+
const result = await (resultDataProvider as any).getMewpInternalValidationFlatResults(
|
|
2130
|
+
'123',
|
|
2131
|
+
mockProjectName,
|
|
2132
|
+
[1]
|
|
2133
|
+
);
|
|
2134
|
+
|
|
2135
|
+
expect(result.rows).toHaveLength(1);
|
|
2136
|
+
expect(String(result.rows[0]['Mentioned but Not Linked'] || '')).toBe('');
|
|
2137
|
+
expect(String(result.rows[0]['Linked but Not Mentioned'] || '')).toContain('SR0054: SR0054-1, SR0054-2');
|
|
2138
|
+
expect(String(result.rows[0]['Linked but Not Mentioned'] || '')).toContain('SR0100-1');
|
|
2139
|
+
});
|
|
2140
|
+
|
|
2141
|
+
it('should report only base SR when an entire mentioned family is uncovered', async () => {
|
|
2142
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
2143
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
2144
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
|
|
2145
|
+
{
|
|
2146
|
+
testPointsItems: [{ testCaseId: 401, testCaseName: 'TC 401 - Family uncovered' }],
|
|
2147
|
+
testCasesItems: [
|
|
2148
|
+
{
|
|
2149
|
+
workItem: {
|
|
2150
|
+
id: 401,
|
|
2151
|
+
workItemFields: [{ key: 'Steps', value: '<steps id=\"mock-steps-tc-401\"></steps>' }],
|
|
2152
|
+
},
|
|
2153
|
+
},
|
|
2154
|
+
],
|
|
2155
|
+
},
|
|
2156
|
+
]);
|
|
2157
|
+
jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
|
|
2158
|
+
{
|
|
2159
|
+
workItemId: 9001,
|
|
2160
|
+
requirementId: 'SR0054-1',
|
|
2161
|
+
baseKey: 'SR0054',
|
|
2162
|
+
title: 'SR0054 child 1',
|
|
2163
|
+
responsibility: 'ESUK',
|
|
2164
|
+
linkedTestCaseIds: [],
|
|
2165
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
2166
|
+
},
|
|
2167
|
+
{
|
|
2168
|
+
workItemId: 9002,
|
|
2169
|
+
requirementId: 'SR0054-2',
|
|
2170
|
+
baseKey: 'SR0054',
|
|
2171
|
+
title: 'SR0054 child 2',
|
|
2172
|
+
responsibility: 'ESUK',
|
|
2173
|
+
linkedTestCaseIds: [],
|
|
2174
|
+
areaPath: 'MEWP\\Customer Requirements\\Level 2',
|
|
2175
|
+
},
|
|
2176
|
+
]);
|
|
2177
|
+
jest.spyOn(resultDataProvider as any, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(
|
|
2178
|
+
new Map([[401, { baseKeys: new Set<string>(), fullCodes: new Set<string>() }]])
|
|
2179
|
+
);
|
|
2180
|
+
jest.spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
|
|
2181
|
+
{
|
|
2182
|
+
stepId: '3',
|
|
2183
|
+
stepPosition: '3',
|
|
2184
|
+
action: 'Validate family root',
|
|
2185
|
+
expected: 'SR0054',
|
|
2186
|
+
isSharedStepTitle: false,
|
|
2187
|
+
},
|
|
2188
|
+
]);
|
|
2189
|
+
|
|
2190
|
+
const result = await (resultDataProvider as any).getMewpInternalValidationFlatResults(
|
|
2191
|
+
'123',
|
|
2192
|
+
mockProjectName,
|
|
2193
|
+
[1]
|
|
2194
|
+
);
|
|
2195
|
+
|
|
2196
|
+
expect(result.rows).toHaveLength(1);
|
|
2197
|
+
expect(result.rows[0]).toEqual(
|
|
2198
|
+
expect.objectContaining({
|
|
2199
|
+
'Test Case ID': 401,
|
|
2200
|
+
'Mentioned but Not Linked': 'Step 3: SR0054',
|
|
2201
|
+
'Linked but Not Mentioned': '',
|
|
2202
|
+
'Validation Status': 'Fail',
|
|
2203
|
+
})
|
|
2204
|
+
);
|
|
2205
|
+
expect(String(result.rows[0]['Mentioned but Not Linked'] || '')).not.toContain('SR0054-1');
|
|
2206
|
+
expect(String(result.rows[0]['Mentioned but Not Linked'] || '')).not.toContain('SR0054-2');
|
|
2207
|
+
});
|
|
2208
|
+
|
|
1989
2209
|
it('should not duplicate Direction A discrepancy when same requirement is repeated in multiple steps', async () => {
|
|
1990
2210
|
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
|
|
1991
2211
|
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
|
|
@@ -2325,7 +2545,7 @@ describe('ResultDataProvider', () => {
|
|
|
2325
2545
|
expect(byTestCase.get(201)).toEqual(
|
|
2326
2546
|
expect.objectContaining({
|
|
2327
2547
|
'Test Case Title': 'TC 201 - Mixed discrepancies',
|
|
2328
|
-
'Mentioned but Not Linked': 'Step 1: SR0095-3
|
|
2548
|
+
'Mentioned but Not Linked': 'Step 1: SR0095-3; SR0511: SR0511-1, SR0511-2',
|
|
2329
2549
|
'Linked but Not Mentioned': 'SR8888',
|
|
2330
2550
|
'Validation Status': 'Fail',
|
|
2331
2551
|
})
|
|
@@ -2662,6 +2882,115 @@ describe('ResultDataProvider', () => {
|
|
|
2662
2882
|
});
|
|
2663
2883
|
});
|
|
2664
2884
|
|
|
2885
|
+
describe('buildLinkedRequirementsByTestCase', () => {
|
|
2886
|
+
it('should map linked requirements only for supported test-case requirement relation types', async () => {
|
|
2887
|
+
const requirements = [
|
|
2888
|
+
{
|
|
2889
|
+
workItemId: 7001,
|
|
2890
|
+
requirementId: 'SR0054-1',
|
|
2891
|
+
baseKey: 'SR0054',
|
|
2892
|
+
linkedTestCaseIds: [],
|
|
2893
|
+
},
|
|
2894
|
+
];
|
|
2895
|
+
const testData = [
|
|
2896
|
+
{
|
|
2897
|
+
testCasesItems: [{ workItem: { id: 1001 } }],
|
|
2898
|
+
testPointsItems: [],
|
|
2899
|
+
},
|
|
2900
|
+
];
|
|
2901
|
+
|
|
2902
|
+
const fetchByIdsSpy = jest
|
|
2903
|
+
.spyOn(resultDataProvider as any, 'fetchWorkItemsByIds')
|
|
2904
|
+
.mockImplementation(async (...args: any[]) => {
|
|
2905
|
+
const ids = Array.isArray(args?.[1]) ? args[1] : [];
|
|
2906
|
+
const includeRelations = !!args?.[2];
|
|
2907
|
+
if (includeRelations) {
|
|
2908
|
+
return [
|
|
2909
|
+
{
|
|
2910
|
+
id: 1001,
|
|
2911
|
+
relations: [
|
|
2912
|
+
{
|
|
2913
|
+
rel: 'Microsoft.VSTS.Common.TestedBy-Reverse',
|
|
2914
|
+
url: 'https://dev.azure.com/org/project/_apis/wit/workItems/7001',
|
|
2915
|
+
},
|
|
2916
|
+
{
|
|
2917
|
+
rel: 'System.LinkTypes.Related',
|
|
2918
|
+
url: 'https://dev.azure.com/org/project/_apis/wit/workItems/7001',
|
|
2919
|
+
},
|
|
2920
|
+
],
|
|
2921
|
+
},
|
|
2922
|
+
];
|
|
2923
|
+
}
|
|
2924
|
+
return ids.map((id) => ({
|
|
2925
|
+
id,
|
|
2926
|
+
fields: {
|
|
2927
|
+
'System.WorkItemType': id === 7001 ? 'Requirement' : 'Test Case',
|
|
2928
|
+
},
|
|
2929
|
+
}));
|
|
2930
|
+
});
|
|
2931
|
+
|
|
2932
|
+
const linked = await (resultDataProvider as any).buildLinkedRequirementsByTestCase(
|
|
2933
|
+
requirements,
|
|
2934
|
+
testData,
|
|
2935
|
+
mockProjectName
|
|
2936
|
+
);
|
|
2937
|
+
|
|
2938
|
+
expect(fetchByIdsSpy).toHaveBeenCalled();
|
|
2939
|
+
expect(linked.get(1001)?.baseKeys?.has('SR0054')).toBe(true);
|
|
2940
|
+
expect(linked.get(1001)?.fullCodes?.has('SR0054-1')).toBe(true);
|
|
2941
|
+
});
|
|
2942
|
+
|
|
2943
|
+
it('should ignore unsupported relation types when linking test case to requirements', async () => {
|
|
2944
|
+
const requirements = [
|
|
2945
|
+
{
|
|
2946
|
+
workItemId: 7002,
|
|
2947
|
+
requirementId: 'SR0099-1',
|
|
2948
|
+
baseKey: 'SR0099',
|
|
2949
|
+
linkedTestCaseIds: [],
|
|
2950
|
+
},
|
|
2951
|
+
];
|
|
2952
|
+
const testData = [
|
|
2953
|
+
{
|
|
2954
|
+
testCasesItems: [{ workItem: { id: 1002 } }],
|
|
2955
|
+
testPointsItems: [],
|
|
2956
|
+
},
|
|
2957
|
+
];
|
|
2958
|
+
|
|
2959
|
+
jest.spyOn(resultDataProvider as any, 'fetchWorkItemsByIds').mockImplementation(async (...args: any[]) => {
|
|
2960
|
+
const ids = Array.isArray(args?.[1]) ? args[1] : [];
|
|
2961
|
+
const includeRelations = !!args?.[2];
|
|
2962
|
+
if (includeRelations) {
|
|
2963
|
+
return [
|
|
2964
|
+
{
|
|
2965
|
+
id: 1002,
|
|
2966
|
+
relations: [
|
|
2967
|
+
{
|
|
2968
|
+
rel: 'System.LinkTypes.Related',
|
|
2969
|
+
url: 'https://dev.azure.com/org/project/_apis/wit/workItems/7002',
|
|
2970
|
+
},
|
|
2971
|
+
],
|
|
2972
|
+
},
|
|
2973
|
+
];
|
|
2974
|
+
}
|
|
2975
|
+
return ids.map((id) => ({
|
|
2976
|
+
id,
|
|
2977
|
+
fields: {
|
|
2978
|
+
'System.WorkItemType': id === 7002 ? 'Requirement' : 'Test Case',
|
|
2979
|
+
},
|
|
2980
|
+
}));
|
|
2981
|
+
});
|
|
2982
|
+
|
|
2983
|
+
const linked = await (resultDataProvider as any).buildLinkedRequirementsByTestCase(
|
|
2984
|
+
requirements,
|
|
2985
|
+
testData,
|
|
2986
|
+
mockProjectName
|
|
2987
|
+
);
|
|
2988
|
+
|
|
2989
|
+
expect(linked.get(1002)?.baseKeys?.has('SR0099')).toBe(false);
|
|
2990
|
+
expect(linked.get(1002)?.fullCodes?.has('SR0099-1')).toBe(false);
|
|
2991
|
+
});
|
|
2992
|
+
});
|
|
2993
|
+
|
|
2665
2994
|
describe('MEWP rel fallback scoping', () => {
|
|
2666
2995
|
it('should fallback to previous Rel run when latest selected Rel has no run for a test case', async () => {
|
|
2667
2996
|
const suites = [
|