@elisra-devops/docgen-data-provider 1.71.0 → 1.73.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.
@@ -1074,6 +1074,192 @@ describe('ResultDataProvider', () => {
1074
1074
  });
1075
1075
  });
1076
1076
 
1077
+ describe('getMewpL2CoverageFlatResults', () => {
1078
+ it('should map SR ids from step action/expected and aggregate passed/failed/not-run by requirement', async () => {
1079
+ jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1080
+ jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
1081
+ jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
1082
+ {
1083
+ testPointsItems: [
1084
+ { testCaseId: 101, lastRunId: 11, lastResultId: 22, testCaseName: 'TC 101' },
1085
+ { testCaseId: 102, lastRunId: 0, lastResultId: 0, testCaseName: 'TC 102' },
1086
+ ],
1087
+ testCasesItems: [
1088
+ {
1089
+ workItem: {
1090
+ id: 102,
1091
+ workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
1092
+ },
1093
+ },
1094
+ ],
1095
+ },
1096
+ ]);
1097
+ jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
1098
+ {
1099
+ workItemId: 5001,
1100
+ requirementId: 'SR1001',
1101
+ title: 'Covered requirement',
1102
+ responsibility: 'ESUK',
1103
+ linkedTestCaseIds: [101],
1104
+ },
1105
+ {
1106
+ workItemId: 5002,
1107
+ requirementId: 'SR1002',
1108
+ title: 'Uncovered requirement',
1109
+ responsibility: 'IL',
1110
+ linkedTestCaseIds: [],
1111
+ },
1112
+ ]);
1113
+ jest.spyOn(resultDataProvider as any, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([
1114
+ {
1115
+ testCaseId: 101,
1116
+ iteration: {
1117
+ actionResults: [
1118
+ {
1119
+ action: 'Validate <b>S</b><b>R</b> 1 0 0 1 happy path',
1120
+ expected: '',
1121
+ outcome: 'Passed',
1122
+ },
1123
+ { action: 'Validate SR1001 failed flow', expected: '&nbsp;', outcome: 'Failed' },
1124
+ { action: '', expected: 'Pending S R 1 0 0 1 scenario', outcome: 'Unspecified' },
1125
+ ],
1126
+ },
1127
+ },
1128
+ {
1129
+ testCaseId: 102,
1130
+ iteration: undefined,
1131
+ },
1132
+ ]);
1133
+ jest
1134
+ .spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps')
1135
+ .mockResolvedValueOnce([
1136
+ {
1137
+ stepId: '1',
1138
+ stepPosition: '1',
1139
+ action: 'Definition contains SR1002',
1140
+ expected: '',
1141
+ isSharedStepTitle: false,
1142
+ },
1143
+ ]);
1144
+
1145
+ const result = await (resultDataProvider as any).getMewpL2CoverageFlatResults(
1146
+ '123',
1147
+ mockProjectName,
1148
+ [1]
1149
+ );
1150
+
1151
+ expect(result).toEqual(
1152
+ expect.objectContaining({
1153
+ sheetName: expect.stringContaining('MEWP L2 Coverage'),
1154
+ columnOrder: expect.arrayContaining(['Requirement ID', 'Number of steps not run']),
1155
+ })
1156
+ );
1157
+
1158
+ const covered = result.rows.find((row: any) => row['Requirement ID'] === 'SR1001');
1159
+ const uncovered = result.rows.find((row: any) => row['Requirement ID'] === 'SR1002');
1160
+
1161
+ expect(covered).toEqual(
1162
+ expect.objectContaining({
1163
+ 'Number of passed steps': 1,
1164
+ 'Number of failed steps': 1,
1165
+ 'Number of steps not run': 1,
1166
+ })
1167
+ );
1168
+ expect(uncovered).toEqual(
1169
+ expect.objectContaining({
1170
+ 'Number of passed steps': 0,
1171
+ 'Number of failed steps': 0,
1172
+ 'Number of steps not run': 1,
1173
+ })
1174
+ );
1175
+ });
1176
+
1177
+ it('should extract SR ids from HTML/spacing and return unique ids per step text', () => {
1178
+ const text =
1179
+ 'A: <b>S</b><b>R</b> 0 0 0 1; B: SR0002; C: S R 0 0 0 3; D: SR0002; E: &lt;b&gt;SR&lt;/b&gt;0004';
1180
+ const codes = (resultDataProvider as any).extractRequirementCodesFromText(text);
1181
+ expect([...codes].sort()).toEqual(['SR1', 'SR2', 'SR3', 'SR4']);
1182
+ });
1183
+
1184
+ it('should not backfill definition steps as not-run when a real run exists but has no action results', async () => {
1185
+ jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1186
+ jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
1187
+ jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
1188
+ {
1189
+ testPointsItems: [{ testCaseId: 101, lastRunId: 88, lastResultId: 99, testCaseName: 'TC 101' }],
1190
+ testCasesItems: [
1191
+ {
1192
+ workItem: {
1193
+ id: 101,
1194
+ workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
1195
+ },
1196
+ },
1197
+ ],
1198
+ },
1199
+ ]);
1200
+ jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
1201
+ {
1202
+ workItemId: 7001,
1203
+ requirementId: 'SR2001',
1204
+ title: 'Has run but no actions',
1205
+ responsibility: 'ESUK',
1206
+ linkedTestCaseIds: [101],
1207
+ },
1208
+ ]);
1209
+ jest.spyOn(resultDataProvider as any, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([
1210
+ {
1211
+ testCaseId: 101,
1212
+ lastRunId: 88,
1213
+ lastResultId: 99,
1214
+ iteration: {
1215
+ actionResults: [],
1216
+ },
1217
+ },
1218
+ ]);
1219
+
1220
+ const parseSpy = jest
1221
+ .spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps')
1222
+ .mockResolvedValueOnce([
1223
+ {
1224
+ stepId: '1',
1225
+ stepPosition: '1',
1226
+ action: 'SR2001 from definition',
1227
+ expected: '',
1228
+ isSharedStepTitle: false,
1229
+ },
1230
+ ]);
1231
+
1232
+ const result = await (resultDataProvider as any).getMewpL2CoverageFlatResults(
1233
+ '123',
1234
+ mockProjectName,
1235
+ [1]
1236
+ );
1237
+
1238
+ const row = result.rows.find((item: any) => item['Requirement ID'] === 'SR2001');
1239
+ expect(parseSpy).not.toHaveBeenCalled();
1240
+ expect(row).toEqual(
1241
+ expect.objectContaining({
1242
+ 'Number of passed steps': 0,
1243
+ 'Number of failed steps': 0,
1244
+ 'Number of steps not run': 0,
1245
+ })
1246
+ );
1247
+ });
1248
+
1249
+ it('should not infer requirement id from unrelated SR text in non-identifier fields', () => {
1250
+ const requirementId = (resultDataProvider as any).extractMewpRequirementIdentifier(
1251
+ {
1252
+ 'System.Description': 'random text with SR9999 that is unrelated',
1253
+ 'Custom.CustomerId': 'customer id unknown',
1254
+ 'System.Title': 'Requirement without explicit SR code',
1255
+ },
1256
+ 4321
1257
+ );
1258
+
1259
+ expect(requirementId).toBe('4321');
1260
+ });
1261
+ });
1262
+
1077
1263
  describe('fetchResultDataForTestReporter (runResultField switch)', () => {
1078
1264
  it('should populate requested runResultField values including testCaseResult URL branches', async () => {
1079
1265
  jest
@@ -2586,6 +2772,244 @@ describe('ResultDataProvider', () => {
2586
2772
  expect.objectContaining({ stepIdentifier: '2', action: 'A2', isSharedStepTitle: true })
2587
2773
  );
2588
2774
  });
2775
+
2776
+ it('should fall back to parsed test steps when actionResults are missing for latest run', async () => {
2777
+ const point = { testCaseId: 1, lastRunId: 10, lastResultId: 20 };
2778
+ const fetchResultMethod = jest.fn().mockResolvedValue({
2779
+ testCase: { id: 1, name: 'TC' },
2780
+ stepsResultXml: '<steps></steps>',
2781
+ iterationDetails: [{}],
2782
+ });
2783
+
2784
+ const createResponseObject = (resultData: any) => ({ iteration: resultData.iterationDetails[0] });
2785
+
2786
+ const helper = (resultDataProvider as any).testStepParserHelper;
2787
+ helper.parseTestSteps.mockResolvedValueOnce([
2788
+ { stepId: 172, stepPosition: '1', action: 'Step 1', expected: 'Expected 1', isSharedStepTitle: false },
2789
+ { stepId: 173, stepPosition: '2', action: 'Step 2', expected: 'Expected 2', isSharedStepTitle: false },
2790
+ ]);
2791
+
2792
+ const res = await (resultDataProvider as any).fetchResultDataBase(
2793
+ mockProjectName,
2794
+ 'suite1',
2795
+ point,
2796
+ fetchResultMethod,
2797
+ createResponseObject,
2798
+ []
2799
+ );
2800
+
2801
+ const actionResults = res.iteration.actionResults;
2802
+ expect(actionResults).toHaveLength(2);
2803
+ expect(actionResults[0]).toEqual(
2804
+ expect.objectContaining({
2805
+ stepIdentifier: '172',
2806
+ stepPosition: '1',
2807
+ action: 'Step 1',
2808
+ expected: 'Expected 1',
2809
+ outcome: 'Unspecified',
2810
+ })
2811
+ );
2812
+ expect(actionResults[1]).toEqual(
2813
+ expect.objectContaining({
2814
+ stepIdentifier: '173',
2815
+ stepPosition: '2',
2816
+ action: 'Step 2',
2817
+ expected: 'Expected 2',
2818
+ outcome: 'Unspecified',
2819
+ })
2820
+ );
2821
+ });
2822
+
2823
+ it('should create synthetic iteration and fall back to parsed test steps when iterationDetails are missing', async () => {
2824
+ const point = { testCaseId: 1, lastRunId: 10, lastResultId: 20 };
2825
+ const fetchResultMethod = jest.fn().mockResolvedValue({
2826
+ testCase: { id: 1, name: 'TC' },
2827
+ stepsResultXml: '<steps></steps>',
2828
+ iterationDetails: [],
2829
+ });
2830
+
2831
+ const createResponseObject = (resultData: any) => ({ iteration: resultData.iterationDetails[0] });
2832
+
2833
+ const helper = (resultDataProvider as any).testStepParserHelper;
2834
+ helper.parseTestSteps.mockResolvedValueOnce([
2835
+ { stepId: 301, stepPosition: '1', action: 'S1', expected: 'E1', isSharedStepTitle: false },
2836
+ ]);
2837
+
2838
+ const res = await (resultDataProvider as any).fetchResultDataBase(
2839
+ mockProjectName,
2840
+ 'suite1',
2841
+ point,
2842
+ fetchResultMethod,
2843
+ createResponseObject,
2844
+ []
2845
+ );
2846
+
2847
+ expect(res.iteration).toBeDefined();
2848
+ expect(res.iteration.actionResults).toHaveLength(1);
2849
+ expect(res.iteration.actionResults[0]).toEqual(
2850
+ expect.objectContaining({
2851
+ stepIdentifier: '301',
2852
+ stepPosition: '1',
2853
+ action: 'S1',
2854
+ expected: 'E1',
2855
+ outcome: 'Unspecified',
2856
+ })
2857
+ );
2858
+ });
2859
+
2860
+ it('should fall back to parsed test steps when actionResults is an empty array', async () => {
2861
+ const point = { testCaseId: 1, lastRunId: 10, lastResultId: 20 };
2862
+ const fetchResultMethod = jest.fn().mockResolvedValue({
2863
+ testCase: { id: 1, name: 'TC' },
2864
+ stepsResultXml: '<steps></steps>',
2865
+ iterationDetails: [{ actionResults: [] }],
2866
+ });
2867
+
2868
+ const createResponseObject = (resultData: any) => ({ iteration: resultData.iterationDetails[0] });
2869
+
2870
+ const helper = (resultDataProvider as any).testStepParserHelper;
2871
+ helper.parseTestSteps.mockResolvedValueOnce([
2872
+ { stepId: 11, stepPosition: '1', action: 'A1', expected: 'E1', isSharedStepTitle: false },
2873
+ { stepId: 22, stepPosition: '2', action: 'A2', expected: 'E2', isSharedStepTitle: false },
2874
+ ]);
2875
+
2876
+ const res = await (resultDataProvider as any).fetchResultDataBase(
2877
+ mockProjectName,
2878
+ 'suite1',
2879
+ point,
2880
+ fetchResultMethod,
2881
+ createResponseObject,
2882
+ []
2883
+ );
2884
+
2885
+ expect(helper.parseTestSteps).toHaveBeenCalledTimes(1);
2886
+ expect(res.iteration.actionResults).toHaveLength(2);
2887
+ expect(res.iteration.actionResults[0]).toEqual(
2888
+ expect.objectContaining({
2889
+ stepIdentifier: '11',
2890
+ stepPosition: '1',
2891
+ action: 'A1',
2892
+ expected: 'E1',
2893
+ outcome: 'Unspecified',
2894
+ })
2895
+ );
2896
+ expect(res.iteration.actionResults[1]).toEqual(
2897
+ expect.objectContaining({
2898
+ stepIdentifier: '22',
2899
+ stepPosition: '2',
2900
+ action: 'A2',
2901
+ expected: 'E2',
2902
+ outcome: 'Unspecified',
2903
+ })
2904
+ );
2905
+ });
2906
+ });
2907
+
2908
+ describe('getTestReporterFlatResults', () => {
2909
+ it('should return flat rows with logical step numbering for fallback-generated action results', async () => {
2910
+ jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan 12');
2911
+ jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([
2912
+ {
2913
+ testSuiteId: 200,
2914
+ suiteId: 200,
2915
+ suiteName: 'suite 2.1',
2916
+ parentSuiteId: 100,
2917
+ parentSuiteName: 'Rel2',
2918
+ suitePath: 'Root/Rel2/suite 2.1',
2919
+ testGroupName: 'suite 2.1',
2920
+ },
2921
+ ]);
2922
+
2923
+ const testData = [
2924
+ {
2925
+ testSuiteId: 200,
2926
+ suiteId: 200,
2927
+ suiteName: 'suite 2.1',
2928
+ parentSuiteId: 100,
2929
+ parentSuiteName: 'Rel2',
2930
+ suitePath: 'Root/Rel2/suite 2.1',
2931
+ testGroupName: 'suite 2.1',
2932
+ testPointsItems: [
2933
+ {
2934
+ testCaseId: 17,
2935
+ testCaseName: 'TC 17',
2936
+ outcome: 'Unspecified',
2937
+ lastRunId: 99,
2938
+ lastResultId: 88,
2939
+ testPointId: 501,
2940
+ lastResultDetails: {
2941
+ dateCompleted: '2026-02-01T10:00:00.000Z',
2942
+ outcome: 'Unspecified',
2943
+ runBy: { displayName: 'tester user' },
2944
+ },
2945
+ },
2946
+ ],
2947
+ testCasesItems: [
2948
+ {
2949
+ workItem: {
2950
+ id: 17,
2951
+ workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
2952
+ },
2953
+ },
2954
+ ],
2955
+ },
2956
+ ];
2957
+ jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce(testData);
2958
+ jest.spyOn(resultDataProvider as any, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([
2959
+ {
2960
+ testCaseId: 17,
2961
+ lastRunId: 99,
2962
+ lastResultId: 88,
2963
+ executionDate: '2026-02-01T10:00:00.000Z',
2964
+ testCaseResult: { resultMessage: '' },
2965
+ customFields: { 'Custom.SubSystem': 'SYS' },
2966
+ runBy: 'tester user',
2967
+ iteration: {
2968
+ actionResults: [
2969
+ {
2970
+ stepIdentifier: '172',
2971
+ stepPosition: '1',
2972
+ actionPath: '1',
2973
+ action: 'fallback action',
2974
+ expected: 'fallback expected',
2975
+ outcome: 'Unspecified',
2976
+ errorMessage: '',
2977
+ isSharedStepTitle: false,
2978
+ },
2979
+ ],
2980
+ },
2981
+ },
2982
+ ]);
2983
+
2984
+ const result = await resultDataProvider.getTestReporterFlatResults(
2985
+ mockTestPlanId,
2986
+ mockProjectName,
2987
+ undefined,
2988
+ [],
2989
+ false
2990
+ );
2991
+
2992
+ expect(result.planId).toBe(mockTestPlanId);
2993
+ expect(result.planName).toBe('Plan 12');
2994
+ expect(result.rows).toHaveLength(1);
2995
+ expect(result.rows[0]).toEqual(
2996
+ expect.objectContaining({
2997
+ planId: mockTestPlanId,
2998
+ planName: 'Plan 12',
2999
+ suiteId: 200,
3000
+ suiteName: 'suite 2.1',
3001
+ parentSuiteId: 100,
3002
+ parentSuiteName: 'Rel2',
3003
+ suitePath: 'Root/Rel2/suite 2.1',
3004
+ testCaseId: 17,
3005
+ testCaseName: 'TC 17',
3006
+ testRunId: 99,
3007
+ testPointId: 501,
3008
+ stepOutcome: 'Unspecified',
3009
+ stepStepIdentifier: '1',
3010
+ })
3011
+ );
3012
+ });
2589
3013
  });
2590
3014
 
2591
3015
  describe('getCombinedResultsSummary - appendix branches', () => {