@elisra-devops/docgen-data-provider 1.92.0 → 1.94.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 +9 -1
- package/bin/modules/ResultDataProvider.js +149 -31
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +147 -2
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/ResultDataProvider.ts +177 -27
- package/src/tests/modules/ResultDataProvider.test.ts +206 -2
package/package.json
CHANGED
|
@@ -52,6 +52,11 @@ const pLimit = require('p-limit');
|
|
|
52
52
|
* Instantiate the class with the organization URL and token, and use the provided methods to fetch and process test data.
|
|
53
53
|
*/
|
|
54
54
|
export default class ResultDataProvider {
|
|
55
|
+
private static readonly MEWP_INTERNAL_VALIDATION_TRACE_TAG = '[MEWP][InternalValidation][Trace]';
|
|
56
|
+
private static readonly MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG =
|
|
57
|
+
'[MEWP][InternalValidation][Diagnostics]';
|
|
58
|
+
private static readonly MEWP_INTERNAL_VALIDATION_SUMMARY_TAG = '[MEWP][InternalValidation][Summary]';
|
|
59
|
+
|
|
55
60
|
private static readonly MEWP_L2_COVERAGE_COLUMNS = [
|
|
56
61
|
'L2 REQ ID',
|
|
57
62
|
'L2 REQ Title',
|
|
@@ -732,7 +737,10 @@ export default class ResultDataProvider {
|
|
|
732
737
|
testPlanId: string,
|
|
733
738
|
projectName: string,
|
|
734
739
|
selectedSuiteIds: number[] | undefined,
|
|
735
|
-
linkedQueryRequest?: any
|
|
740
|
+
linkedQueryRequest?: any,
|
|
741
|
+
options?: {
|
|
742
|
+
debugMode?: boolean;
|
|
743
|
+
}
|
|
736
744
|
): Promise<MewpInternalValidationFlatPayload> {
|
|
737
745
|
const defaultPayload: MewpInternalValidationFlatPayload = {
|
|
738
746
|
sheetName: `MEWP Internal Validation - Plan ${testPlanId}`,
|
|
@@ -832,9 +840,19 @@ export default class ResultDataProvider {
|
|
|
832
840
|
testCasesWithoutMentionedCustomerIds: 0,
|
|
833
841
|
failingRows: 0,
|
|
834
842
|
};
|
|
843
|
+
const traceInternalValidation = options?.debugMode === true;
|
|
844
|
+
if (traceInternalValidation) {
|
|
845
|
+
logger.info(
|
|
846
|
+
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
847
|
+
mode: 'enabled',
|
|
848
|
+
source: 'ui-debug-mode',
|
|
849
|
+
})
|
|
850
|
+
);
|
|
851
|
+
}
|
|
835
852
|
|
|
836
853
|
for (const testCaseId of [...allTestCaseIds].sort((a, b) => a - b)) {
|
|
837
854
|
diagnostics.totalTestCases += 1;
|
|
855
|
+
const traceCurrentTestCase = traceInternalValidation;
|
|
838
856
|
const stepsXml = stepsXmlByTestCase.get(testCaseId) || '';
|
|
839
857
|
const parsedSteps =
|
|
840
858
|
stepsXml && String(stepsXml).trim() !== ''
|
|
@@ -884,6 +902,23 @@ export default class ResultDataProvider {
|
|
|
884
902
|
if (!mentionedCodesByBase.has(baseKey)) mentionedCodesByBase.set(baseKey, new Set<string>());
|
|
885
903
|
mentionedCodesByBase.get(baseKey)!.add(code);
|
|
886
904
|
}
|
|
905
|
+
if (traceCurrentTestCase) {
|
|
906
|
+
logger.debug(
|
|
907
|
+
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
908
|
+
event: 'test-case-start',
|
|
909
|
+
tc: testCaseId,
|
|
910
|
+
parsedSteps: executableSteps.length,
|
|
911
|
+
stepsWithMentions: mentionEntries.length,
|
|
912
|
+
mentionedCodes:
|
|
913
|
+
[...mentionedL2Only].sort((a, b) => this.compareMewpRequirementCodes(a, b)).join('; ') ||
|
|
914
|
+
'<none>',
|
|
915
|
+
linkedCodesInTestCase:
|
|
916
|
+
[...linkedFullCodes]
|
|
917
|
+
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
918
|
+
.join('; ') || '<none>',
|
|
919
|
+
})
|
|
920
|
+
);
|
|
921
|
+
}
|
|
887
922
|
|
|
888
923
|
// Direction A logic:
|
|
889
924
|
// 1) Base mention ("SR0054") is parent-level and considered covered only when
|
|
@@ -901,43 +936,110 @@ export default class ResultDataProvider {
|
|
|
901
936
|
|
|
902
937
|
if (familyCodes?.size) {
|
|
903
938
|
const familyLinkedCodes = linkedFamilyCodesAcrossTestCases.get(baseKey) || new Set<string>();
|
|
939
|
+
const normalizedFamilyMembers = [...familyCodes]
|
|
940
|
+
.map((code) => this.normalizeMewpRequirementCodeWithSuffix(code))
|
|
941
|
+
.filter((code) => !!code);
|
|
942
|
+
const specificFamilyMembers = normalizedFamilyMembers.filter((code) => /-\d+$/.test(code));
|
|
943
|
+
const requiredFamilyMembers =
|
|
944
|
+
specificFamilyMembers.length > 0 ? specificFamilyMembers : normalizedFamilyMembers;
|
|
904
945
|
|
|
905
946
|
// Base mention ("SR0054") requires full family coverage across selected test cases.
|
|
906
947
|
if (hasBaseMention) {
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
const specificFamilyMembers = normalizedFamilyMembers.filter((code) => /-\d+$/.test(code));
|
|
911
|
-
const requiredFamilyMembers =
|
|
912
|
-
specificFamilyMembers.length > 0 ? specificFamilyMembers : normalizedFamilyMembers;
|
|
948
|
+
const missingRequiredFamilyMembers = requiredFamilyMembers.filter(
|
|
949
|
+
(memberCode) => !familyLinkedCodes.has(memberCode)
|
|
950
|
+
);
|
|
913
951
|
const isWholeFamilyCovered = requiredFamilyMembers.every((memberCode) =>
|
|
914
952
|
familyLinkedCodes.has(memberCode)
|
|
915
953
|
);
|
|
916
954
|
if (!isWholeFamilyCovered) {
|
|
917
955
|
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
918
956
|
}
|
|
957
|
+
if (traceCurrentTestCase) {
|
|
958
|
+
logger.debug(
|
|
959
|
+
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
960
|
+
event: 'base-family-coverage',
|
|
961
|
+
tc: testCaseId,
|
|
962
|
+
base: baseKey,
|
|
963
|
+
baseMention: true,
|
|
964
|
+
requiredFamily:
|
|
965
|
+
requiredFamilyMembers
|
|
966
|
+
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
967
|
+
.join('; ') || '<none>',
|
|
968
|
+
linkedAcrossScope:
|
|
969
|
+
[...familyLinkedCodes]
|
|
970
|
+
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
971
|
+
.join('; ') || '<none>',
|
|
972
|
+
missingRequired:
|
|
973
|
+
missingRequiredFamilyMembers
|
|
974
|
+
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
975
|
+
.join('; ') || '<none>',
|
|
976
|
+
covered: isWholeFamilyCovered,
|
|
977
|
+
})
|
|
978
|
+
);
|
|
979
|
+
}
|
|
919
980
|
}
|
|
920
981
|
|
|
921
982
|
// Specific mention ("SR0054-1") validates as exact-match only across scoped test cases.
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
983
|
+
const missingSpecificMembers = mentionedSpecificMembers.filter(
|
|
984
|
+
(code) => !familyLinkedCodes.has(code)
|
|
985
|
+
);
|
|
986
|
+
for (const code of missingSpecificMembers) {
|
|
987
|
+
missingSpecificMentionedNoFamily.add(code);
|
|
988
|
+
}
|
|
989
|
+
if (traceCurrentTestCase && mentionedSpecificMembers.length > 0) {
|
|
990
|
+
logger.debug(
|
|
991
|
+
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
992
|
+
event: 'specific-members-check',
|
|
993
|
+
tc: testCaseId,
|
|
994
|
+
base: baseKey,
|
|
995
|
+
specificMentioned:
|
|
996
|
+
mentionedSpecificMembers
|
|
997
|
+
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
998
|
+
.join('; ') || '<none>',
|
|
999
|
+
specificMissing:
|
|
1000
|
+
missingSpecificMembers
|
|
1001
|
+
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
1002
|
+
.join('; ') || '<none>',
|
|
1003
|
+
})
|
|
1004
|
+
);
|
|
926
1005
|
}
|
|
927
1006
|
continue;
|
|
928
1007
|
}
|
|
929
1008
|
|
|
930
1009
|
// Fallback path when family data is unavailable for this base key.
|
|
1010
|
+
const fallbackMissingSpecific: string[] = [];
|
|
1011
|
+
let fallbackMissingBase = false;
|
|
931
1012
|
for (const code of mentionedCodes) {
|
|
932
1013
|
const hasSpecificSuffix = /-\d+$/.test(code);
|
|
933
1014
|
if (hasSpecificSuffix) {
|
|
934
1015
|
if (!linkedFullCodesAcrossTestCases.has(code)) {
|
|
935
1016
|
missingSpecificMentionedNoFamily.add(code);
|
|
1017
|
+
fallbackMissingSpecific.push(code);
|
|
936
1018
|
}
|
|
937
1019
|
} else if (!linkedBaseKeysAcrossTestCases.has(baseKey)) {
|
|
938
1020
|
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
1021
|
+
fallbackMissingBase = true;
|
|
939
1022
|
}
|
|
940
1023
|
}
|
|
1024
|
+
if (traceCurrentTestCase) {
|
|
1025
|
+
logger.debug(
|
|
1026
|
+
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
1027
|
+
event: 'fallback-path',
|
|
1028
|
+
tc: testCaseId,
|
|
1029
|
+
base: baseKey,
|
|
1030
|
+
fallbackUsed: true,
|
|
1031
|
+
mentioned:
|
|
1032
|
+
mentionedCodesList
|
|
1033
|
+
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
1034
|
+
.join('; ') || '<none>',
|
|
1035
|
+
missingSpecific:
|
|
1036
|
+
fallbackMissingSpecific
|
|
1037
|
+
.sort((a, b) => this.compareMewpRequirementCodes(a, b))
|
|
1038
|
+
.join('; ') || '<none>',
|
|
1039
|
+
missingBase: fallbackMissingBase,
|
|
1040
|
+
})
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
941
1043
|
}
|
|
942
1044
|
|
|
943
1045
|
// Direction B is family-based: if any member of a family is mentioned in Expected Result,
|
|
@@ -983,6 +1085,16 @@ export default class ResultDataProvider {
|
|
|
983
1085
|
const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
|
|
984
1086
|
appendMentionedButNotLinked(baseKey, stepRef);
|
|
985
1087
|
}
|
|
1088
|
+
if (traceCurrentTestCase) {
|
|
1089
|
+
logger.debug(
|
|
1090
|
+
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
|
|
1091
|
+
event: 'direction-a-summary',
|
|
1092
|
+
tc: testCaseId,
|
|
1093
|
+
missingSpecific: sortedMissingSpecificMentionedNoFamily.join('; ') || '<none>',
|
|
1094
|
+
missingBase: sortedMissingBaseWhenFamilyUncovered.join('; ') || '<none>',
|
|
1095
|
+
})
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
986
1098
|
|
|
987
1099
|
const sortedExtraLinked = [...new Set(extraLinked)]
|
|
988
1100
|
.map((code) => this.normalizeMewpRequirementCodeWithSuffix(code))
|
|
@@ -1022,15 +1134,19 @@ export default class ResultDataProvider {
|
|
|
1022
1134
|
mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
|
|
1023
1135
|
if (validationStatus === 'Fail') diagnostics.failingRows += 1;
|
|
1024
1136
|
logger.debug(
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1137
|
+
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG, {
|
|
1138
|
+
testCaseId,
|
|
1139
|
+
parsedSteps: executableSteps.length,
|
|
1140
|
+
stepsWithMentions: mentionEntries.length,
|
|
1141
|
+
customerIdsFound: mentionedL2Only.size,
|
|
1142
|
+
linkedRequirements: linkedFullCodes.size,
|
|
1143
|
+
mentionedButNotLinked:
|
|
1029
1144
|
sortedMissingSpecificMentionedNoFamily.length +
|
|
1030
|
-
sortedMissingBaseWhenFamilyUncovered.length
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1145
|
+
sortedMissingBaseWhenFamilyUncovered.length,
|
|
1146
|
+
linkedButNotMentioned: sortedExtraLinked.length,
|
|
1147
|
+
status: validationStatus,
|
|
1148
|
+
customerIdSample: [...mentionedL2Only].slice(0, 5).join(', '),
|
|
1149
|
+
})
|
|
1034
1150
|
);
|
|
1035
1151
|
|
|
1036
1152
|
rows.push({
|
|
@@ -1042,11 +1158,14 @@ export default class ResultDataProvider {
|
|
|
1042
1158
|
});
|
|
1043
1159
|
}
|
|
1044
1160
|
logger.info(
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1161
|
+
this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_SUMMARY_TAG, {
|
|
1162
|
+
testCases: diagnostics.totalTestCases,
|
|
1163
|
+
parsedSteps: diagnostics.totalParsedSteps,
|
|
1164
|
+
stepsWithMentions: diagnostics.totalStepsWithMentions,
|
|
1165
|
+
totalCustomerIdsFound: diagnostics.totalMentionedCustomerIds,
|
|
1166
|
+
testCasesWithoutCustomerIds: diagnostics.testCasesWithoutMentionedCustomerIds,
|
|
1167
|
+
failingRows: diagnostics.failingRows,
|
|
1168
|
+
})
|
|
1050
1169
|
);
|
|
1051
1170
|
|
|
1052
1171
|
return {
|
|
@@ -1163,6 +1282,17 @@ export default class ResultDataProvider {
|
|
|
1163
1282
|
return `MEWP Internal Validation - ${suffix}`;
|
|
1164
1283
|
}
|
|
1165
1284
|
|
|
1285
|
+
private formatLogValue(value: any): string {
|
|
1286
|
+
if (value === null || value === undefined) return '<none>';
|
|
1287
|
+
const asText = String(value).trim();
|
|
1288
|
+
return asText !== '' ? asText : '<none>';
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
private buildTaggedLogMessage(tag: string, fields: Record<string, any>): string {
|
|
1292
|
+
const sections = Object.entries(fields).map(([key, value]) => `${key}=${this.formatLogValue(value)}`);
|
|
1293
|
+
return `${tag} ${sections.join(' | ')}`;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1166
1296
|
private createMewpCoverageRow(
|
|
1167
1297
|
requirement: Pick<
|
|
1168
1298
|
MewpL2RequirementFamily,
|
|
@@ -2698,7 +2828,7 @@ export default class ResultDataProvider {
|
|
|
2698
2828
|
// Direction B display is family-level when multiple members exist.
|
|
2699
2829
|
return baseKey;
|
|
2700
2830
|
})
|
|
2701
|
-
.join('
|
|
2831
|
+
.join('; ');
|
|
2702
2832
|
}
|
|
2703
2833
|
|
|
2704
2834
|
private toMewpComparableText(value: any): string {
|
|
@@ -2919,7 +3049,7 @@ export default class ResultDataProvider {
|
|
|
2919
3049
|
// Fetch detailed information for each test point and map to required format
|
|
2920
3050
|
const detailedPoints = await Promise.all(
|
|
2921
3051
|
latestPoints.map(async (point: any) => {
|
|
2922
|
-
const url = `${point.url}?witFields=Microsoft.VSTS.TCM.Steps&includePointDetails=true`;
|
|
3052
|
+
const url = `${point.url}?witFields=Microsoft.VSTS.TCM.Steps,System.Rev&includePointDetails=true`;
|
|
2923
3053
|
const detailedPoint = await TFSServices.getItemContent(url, this.token);
|
|
2924
3054
|
return this.mapTestPointForCrossPlans(detailedPoint, projectName);
|
|
2925
3055
|
// return this.mapTestPointForCrossPlans(detailedPoint, projectName);
|
|
@@ -3012,7 +3142,7 @@ export default class ResultDataProvider {
|
|
|
3012
3142
|
testPlanId: string,
|
|
3013
3143
|
suiteId: string
|
|
3014
3144
|
): Promise<any[]> {
|
|
3015
|
-
const url = `${this.orgUrl}${projectName}/_apis/testplan/Plans/${testPlanId}/Suites/${suiteId}/TestCase?witFields=Microsoft.VSTS.TCM.Steps`;
|
|
3145
|
+
const url = `${this.orgUrl}${projectName}/_apis/testplan/Plans/${testPlanId}/Suites/${suiteId}/TestCase?witFields=Microsoft.VSTS.TCM.Steps,System.Rev`;
|
|
3016
3146
|
|
|
3017
3147
|
const { value: testCases } = await TFSServices.getItemContent(url, this.token);
|
|
3018
3148
|
|
|
@@ -3056,8 +3186,28 @@ export default class ResultDataProvider {
|
|
|
3056
3186
|
return fields;
|
|
3057
3187
|
}
|
|
3058
3188
|
|
|
3189
|
+
private getFieldValueByName(fields: Record<string, any>, fieldName: string): any {
|
|
3190
|
+
if (!fields || typeof fields !== 'object') return undefined;
|
|
3191
|
+
if (Object.prototype.hasOwnProperty.call(fields, fieldName)) {
|
|
3192
|
+
return fields[fieldName];
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
const lookupName = String(fieldName || '').toLowerCase().trim();
|
|
3196
|
+
if (!lookupName) return undefined;
|
|
3197
|
+
const matchedKey = Object.keys(fields).find(
|
|
3198
|
+
(key) => String(key || '').toLowerCase().trim() === lookupName
|
|
3199
|
+
);
|
|
3200
|
+
return matchedKey ? fields[matchedKey] : undefined;
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3059
3203
|
private resolveSuiteTestCaseRevision(testCaseItem: any): number {
|
|
3204
|
+
const fieldsFromList = this.extractWorkItemFieldsMap(testCaseItem?.workItem?.workItemFields);
|
|
3205
|
+
const fieldsFromMap = testCaseItem?.workItem?.fields || {};
|
|
3206
|
+
const systemRevFromList = this.getFieldValueByName(fieldsFromList, 'System.Rev');
|
|
3207
|
+
const systemRevFromMap = this.getFieldValueByName(fieldsFromMap, 'System.Rev');
|
|
3060
3208
|
const revisionCandidates = [
|
|
3209
|
+
systemRevFromList,
|
|
3210
|
+
systemRevFromMap,
|
|
3061
3211
|
testCaseItem?.workItem?.rev,
|
|
3062
3212
|
testCaseItem?.workItem?.revision,
|
|
3063
3213
|
testCaseItem?.workItem?.version,
|
|
@@ -679,6 +679,34 @@ describe('ResultDataProvider', () => {
|
|
|
679
679
|
|
|
680
680
|
// Assert
|
|
681
681
|
expect(result).toEqual(mockTestCases.value);
|
|
682
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
683
|
+
expect.stringContaining(
|
|
684
|
+
`/_apis/testplan/Plans/${mockTestPlanId}/Suites/${mockSuiteId}/TestCase?witFields=Microsoft.VSTS.TCM.Steps,System.Rev`
|
|
685
|
+
),
|
|
686
|
+
mockToken
|
|
687
|
+
);
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
describe('resolveSuiteTestCaseRevision', () => {
|
|
692
|
+
it('should resolve System.Rev from workItemFields', () => {
|
|
693
|
+
const revision = (resultDataProvider as any).resolveSuiteTestCaseRevision({
|
|
694
|
+
workItem: {
|
|
695
|
+
workItemFields: [{ key: 'System.Rev', value: '12' }],
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
expect(revision).toBe(12);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('should resolve System.Rev case-insensitively from workItem fields map', () => {
|
|
703
|
+
const revision = (resultDataProvider as any).resolveSuiteTestCaseRevision({
|
|
704
|
+
workItem: {
|
|
705
|
+
fields: { 'system.rev': 14 },
|
|
706
|
+
},
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
expect(revision).toBe(14);
|
|
682
710
|
});
|
|
683
711
|
});
|
|
684
712
|
|
|
@@ -837,7 +865,7 @@ describe('ResultDataProvider', () => {
|
|
|
837
865
|
const result = await (resultDataProvider as any).fetchCrossTestPoints(mockProjectName, [1, 2]);
|
|
838
866
|
|
|
839
867
|
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
840
|
-
'https://example.com/points/2?witFields=Microsoft.VSTS.TCM.Steps&includePointDetails=true',
|
|
868
|
+
'https://example.com/points/2?witFields=Microsoft.VSTS.TCM.Steps,System.Rev&includePointDetails=true',
|
|
841
869
|
mockToken
|
|
842
870
|
);
|
|
843
871
|
expect(result).toHaveLength(2);
|
|
@@ -2993,7 +3021,7 @@ describe('ResultDataProvider', () => {
|
|
|
2993
3021
|
'Test Case ID': 42,
|
|
2994
3022
|
'Test Case Title': 'TC-0042',
|
|
2995
3023
|
'Mentioned but Not Linked': 'Step 3: SR0027',
|
|
2996
|
-
'Linked but Not Mentioned': 'SR0817
|
|
3024
|
+
'Linked but Not Mentioned': 'SR0817; SR0818; SR0859',
|
|
2997
3025
|
'Validation Status': 'Fail',
|
|
2998
3026
|
})
|
|
2999
3027
|
);
|
|
@@ -3874,6 +3902,53 @@ describe('ResultDataProvider', () => {
|
|
|
3874
3902
|
expect(res).toEqual(expect.objectContaining({ testCaseRevision: 9 }));
|
|
3875
3903
|
});
|
|
3876
3904
|
|
|
3905
|
+
it('should resolve no-run revision from System.Rev in suite test-case fields', async () => {
|
|
3906
|
+
const point = {
|
|
3907
|
+
testCaseId: '321',
|
|
3908
|
+
testCaseName: 'TC 321',
|
|
3909
|
+
outcome: 'Not Run',
|
|
3910
|
+
suiteTestCase: {
|
|
3911
|
+
workItem: {
|
|
3912
|
+
id: 321,
|
|
3913
|
+
workItemFields: [
|
|
3914
|
+
{ key: 'System.Rev', value: '13' },
|
|
3915
|
+
{ key: 'Microsoft.VSTS.TCM.Steps', value: '<steps></steps>' },
|
|
3916
|
+
],
|
|
3917
|
+
},
|
|
3918
|
+
},
|
|
3919
|
+
testSuite: { id: '1', name: 'Suite' },
|
|
3920
|
+
};
|
|
3921
|
+
|
|
3922
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({
|
|
3923
|
+
id: 321,
|
|
3924
|
+
rev: 13,
|
|
3925
|
+
fields: {
|
|
3926
|
+
'System.State': 'Design',
|
|
3927
|
+
'System.CreatedDate': '2024-05-01T00:00:00',
|
|
3928
|
+
'Microsoft.VSTS.TCM.Priority': 1,
|
|
3929
|
+
'System.Title': 'Title 321',
|
|
3930
|
+
'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
|
|
3931
|
+
},
|
|
3932
|
+
relations: [],
|
|
3933
|
+
});
|
|
3934
|
+
|
|
3935
|
+
const res = await (resultDataProvider as any).fetchResultDataBasedOnWiBase(
|
|
3936
|
+
mockProjectName,
|
|
3937
|
+
'0',
|
|
3938
|
+
'0',
|
|
3939
|
+
true,
|
|
3940
|
+
[],
|
|
3941
|
+
false,
|
|
3942
|
+
point
|
|
3943
|
+
);
|
|
3944
|
+
|
|
3945
|
+
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
3946
|
+
expect.stringContaining('/_apis/wit/workItems/321/revisions/13?$expand=all'),
|
|
3947
|
+
mockToken
|
|
3948
|
+
);
|
|
3949
|
+
expect(res).toEqual(expect.objectContaining({ testCaseRevision: 13 }));
|
|
3950
|
+
});
|
|
3951
|
+
|
|
3877
3952
|
it('should append linked relations and filter testCaseWorkItemField when isTestReporter=true and isQueryMode=false', async () => {
|
|
3878
3953
|
(TFSServices.getItemContent as jest.Mock).mockReset();
|
|
3879
3954
|
|
|
@@ -5045,6 +5120,35 @@ describe('ResultDataProvider', () => {
|
|
|
5045
5120
|
expect(fetchStrategy).not.toHaveBeenCalled();
|
|
5046
5121
|
expect(result).toEqual([]);
|
|
5047
5122
|
});
|
|
5123
|
+
|
|
5124
|
+
it('should keep points without run/result IDs when test reporter mode is enabled', async () => {
|
|
5125
|
+
const testData = [
|
|
5126
|
+
{
|
|
5127
|
+
testSuiteId: 1,
|
|
5128
|
+
testPointsItems: [{ testCaseId: 10, lastRunId: 101, lastResultId: 201 }, { testCaseId: 11 }],
|
|
5129
|
+
},
|
|
5130
|
+
];
|
|
5131
|
+
const fetchStrategy = jest
|
|
5132
|
+
.fn()
|
|
5133
|
+
.mockResolvedValueOnce({ testCaseId: 10 })
|
|
5134
|
+
.mockResolvedValueOnce({ testCaseId: 11 });
|
|
5135
|
+
|
|
5136
|
+
const result = await (resultDataProvider as any).fetchAllResultDataBase(
|
|
5137
|
+
testData,
|
|
5138
|
+
mockProjectName,
|
|
5139
|
+
true,
|
|
5140
|
+
fetchStrategy
|
|
5141
|
+
);
|
|
5142
|
+
|
|
5143
|
+
expect(fetchStrategy).toHaveBeenCalledTimes(2);
|
|
5144
|
+
expect(fetchStrategy).toHaveBeenNthCalledWith(
|
|
5145
|
+
2,
|
|
5146
|
+
mockProjectName,
|
|
5147
|
+
1,
|
|
5148
|
+
expect.objectContaining({ testCaseId: 11 })
|
|
5149
|
+
);
|
|
5150
|
+
expect(result).toEqual([{ testCaseId: 10 }, { testCaseId: 11 }]);
|
|
5151
|
+
});
|
|
5048
5152
|
});
|
|
5049
5153
|
|
|
5050
5154
|
describe('fetchResultDataBase', () => {
|
|
@@ -5071,6 +5175,26 @@ describe('ResultDataProvider', () => {
|
|
|
5071
5175
|
expect(fetchResultMethod).toHaveBeenCalled();
|
|
5072
5176
|
expect(result).toBeDefined();
|
|
5073
5177
|
});
|
|
5178
|
+
|
|
5179
|
+
it('should call fetch method with runId/resultId as 0 when point has no run history', async () => {
|
|
5180
|
+
const point = { testCaseId: 15, lastRunId: undefined, lastResultId: undefined };
|
|
5181
|
+
const fetchResultMethod = jest.fn().mockResolvedValue({
|
|
5182
|
+
testCase: { id: 15, name: 'TC 15' },
|
|
5183
|
+
testSuite: { name: 'S' },
|
|
5184
|
+
iterationDetails: [],
|
|
5185
|
+
});
|
|
5186
|
+
const createResponseObject = jest.fn().mockReturnValue({ id: 15 });
|
|
5187
|
+
|
|
5188
|
+
await (resultDataProvider as any).fetchResultDataBase(
|
|
5189
|
+
mockProjectName,
|
|
5190
|
+
'suite-no-runs',
|
|
5191
|
+
point,
|
|
5192
|
+
fetchResultMethod,
|
|
5193
|
+
createResponseObject
|
|
5194
|
+
);
|
|
5195
|
+
|
|
5196
|
+
expect(fetchResultMethod).toHaveBeenCalledWith(mockProjectName, '0', '0');
|
|
5197
|
+
});
|
|
5074
5198
|
});
|
|
5075
5199
|
|
|
5076
5200
|
describe('getCombinedResultsSummary', () => {
|
|
@@ -5460,6 +5584,86 @@ describe('ResultDataProvider', () => {
|
|
|
5460
5584
|
})
|
|
5461
5585
|
);
|
|
5462
5586
|
});
|
|
5587
|
+
|
|
5588
|
+
it('should return test-level row with empty step fields when suite has no run history', async () => {
|
|
5589
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan 12');
|
|
5590
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([
|
|
5591
|
+
{
|
|
5592
|
+
testSuiteId: 300,
|
|
5593
|
+
suiteId: 300,
|
|
5594
|
+
suiteName: 'suite no runs',
|
|
5595
|
+
parentSuiteId: 100,
|
|
5596
|
+
parentSuiteName: 'Rel3',
|
|
5597
|
+
suitePath: 'Root/Rel3/suite no runs',
|
|
5598
|
+
testGroupName: 'suite no runs',
|
|
5599
|
+
},
|
|
5600
|
+
]);
|
|
5601
|
+
|
|
5602
|
+
jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
|
|
5603
|
+
{
|
|
5604
|
+
testSuiteId: 300,
|
|
5605
|
+
suiteId: 300,
|
|
5606
|
+
suiteName: 'suite no runs',
|
|
5607
|
+
parentSuiteId: 100,
|
|
5608
|
+
parentSuiteName: 'Rel3',
|
|
5609
|
+
suitePath: 'Root/Rel3/suite no runs',
|
|
5610
|
+
testGroupName: 'suite no runs',
|
|
5611
|
+
testPointsItems: [
|
|
5612
|
+
{
|
|
5613
|
+
testCaseId: 55,
|
|
5614
|
+
testCaseName: 'TC 55',
|
|
5615
|
+
outcome: 'Not Run',
|
|
5616
|
+
testPointId: 9001,
|
|
5617
|
+
lastRunId: undefined,
|
|
5618
|
+
lastResultId: undefined,
|
|
5619
|
+
lastResultDetails: undefined,
|
|
5620
|
+
},
|
|
5621
|
+
],
|
|
5622
|
+
testCasesItems: [
|
|
5623
|
+
{
|
|
5624
|
+
workItem: {
|
|
5625
|
+
id: 55,
|
|
5626
|
+
workItemFields: [{ key: 'System.Rev', value: 4 }],
|
|
5627
|
+
},
|
|
5628
|
+
},
|
|
5629
|
+
],
|
|
5630
|
+
},
|
|
5631
|
+
]);
|
|
5632
|
+
|
|
5633
|
+
jest.spyOn(resultDataProvider as any, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([
|
|
5634
|
+
{
|
|
5635
|
+
testCaseId: 55,
|
|
5636
|
+
testCase: { id: 55, name: 'TC 55' },
|
|
5637
|
+
testSuite: { name: 'suite no runs' },
|
|
5638
|
+
executionDate: '',
|
|
5639
|
+
testCaseResult: { resultMessage: 'Not Run', url: '' },
|
|
5640
|
+
customFields: {},
|
|
5641
|
+
runBy: '',
|
|
5642
|
+
iteration: undefined,
|
|
5643
|
+
lastRunId: undefined,
|
|
5644
|
+
lastResultId: undefined,
|
|
5645
|
+
},
|
|
5646
|
+
]);
|
|
5647
|
+
|
|
5648
|
+
const result = await resultDataProvider.getTestReporterFlatResults(
|
|
5649
|
+
mockTestPlanId,
|
|
5650
|
+
mockProjectName,
|
|
5651
|
+
undefined,
|
|
5652
|
+
[],
|
|
5653
|
+
false
|
|
5654
|
+
);
|
|
5655
|
+
|
|
5656
|
+
expect(result.rows).toHaveLength(1);
|
|
5657
|
+
expect(result.rows[0]).toEqual(
|
|
5658
|
+
expect.objectContaining({
|
|
5659
|
+
testCaseId: 55,
|
|
5660
|
+
testRunId: undefined,
|
|
5661
|
+
testPointId: 9001,
|
|
5662
|
+
stepOutcome: undefined,
|
|
5663
|
+
stepStepIdentifier: '',
|
|
5664
|
+
})
|
|
5665
|
+
);
|
|
5666
|
+
});
|
|
5463
5667
|
});
|
|
5464
5668
|
|
|
5465
5669
|
describe('getCombinedResultsSummary - appendix branches', () => {
|