@elisra-devops/docgen-data-provider 1.74.0 → 1.76.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.
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tfs_1 = require("../../helpers/tfs");
4
4
  const ResultDataProvider_1 = require("../../modules/ResultDataProvider");
5
5
  const logger_1 = require("../../utils/logger");
6
+ const axios_1 = require("axios");
7
+ const XLSX = require("xlsx");
6
8
  // Mock dependencies
7
9
  jest.mock('../../helpers/tfs');
8
10
  jest.mock('../../utils/logger');
@@ -22,6 +24,12 @@ describe('ResultDataProvider', () => {
22
24
  const mockToken = 'mock-token';
23
25
  const mockProjectName = 'test-project';
24
26
  const mockTestPlanId = '12345';
27
+ const buildWorkbookBuffer = (rows) => {
28
+ const worksheet = XLSX.utils.aoa_to_sheet(rows);
29
+ const workbook = XLSX.utils.book_new();
30
+ XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
31
+ return XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });
32
+ };
25
33
  beforeEach(() => {
26
34
  jest.clearAllMocks();
27
35
  resultDataProvider = new ResultDataProvider_1.default(mockOrgUrl, mockToken);
@@ -857,85 +865,6 @@ describe('ResultDataProvider', () => {
857
865
  });
858
866
  });
859
867
  describe('getMewpL2CoverageFlatResults', () => {
860
- it('should support query-mode requirement scope for MEWP coverage', async () => {
861
- jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
862
- jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
863
- jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
864
- {
865
- testPointsItems: [{ testCaseId: 101, lastRunId: 10, lastResultId: 20, testCaseName: 'TC 101' }],
866
- testCasesItems: [
867
- {
868
- workItem: {
869
- id: 101,
870
- workItemFields: [{ key: 'System.Title', value: 'TC 101' }],
871
- },
872
- },
873
- ],
874
- },
875
- ]);
876
- jest.spyOn(resultDataProvider, 'fetchMewpRequirementTypeNames').mockResolvedValueOnce([
877
- 'Requirement',
878
- ]);
879
- jest.spyOn(resultDataProvider, 'fetchWorkItemsByIds').mockResolvedValueOnce([
880
- {
881
- id: 9001,
882
- fields: {
883
- 'System.WorkItemType': 'Requirement',
884
- 'System.Title': 'Requirement from query',
885
- 'Custom.CustomerId': 'SR3001',
886
- 'System.AreaPath': 'MEWP\\IL',
887
- },
888
- relations: [
889
- {
890
- rel: 'Microsoft.VSTS.Common.TestedBy-Forward',
891
- url: 'https://dev.azure.com/org/_apis/wit/workItems/101',
892
- },
893
- ],
894
- },
895
- ]);
896
- jest.spyOn(resultDataProvider, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([
897
- {
898
- testCaseId: 101,
899
- testCase: { id: 101, name: 'TC 101' },
900
- iteration: {
901
- actionResults: [{ action: 'Validate SR3001', expected: '', outcome: 'Passed' }],
902
- },
903
- },
904
- ]);
905
- const TicketsProviderMock = require('../../modules/TicketsDataProvider').default;
906
- TicketsProviderMock.mockImplementationOnce(() => ({
907
- GetQueryResultsFromWiql: jest.fn().mockResolvedValue({
908
- fetchedWorkItems: [
909
- {
910
- id: 9001,
911
- fields: {
912
- 'System.WorkItemType': 'Requirement',
913
- 'System.Title': 'Requirement from query',
914
- 'Custom.CustomerId': 'SR3001',
915
- 'System.AreaPath': 'MEWP\\IL',
916
- },
917
- },
918
- ],
919
- }),
920
- }));
921
- const result = await resultDataProvider.getMewpL2CoverageFlatResults('123', mockProjectName, [1], {
922
- linkedQueryMode: 'query',
923
- testAssociatedQuery: { wiql: { href: 'https://example.com/wiql' } },
924
- });
925
- const row = result.rows.find((item) => item['Customer ID'] === 'SR3001');
926
- expect(row).toEqual(expect.objectContaining({
927
- 'Title (Customer name)': 'Requirement from query',
928
- 'Responsibility - SAPWBS (ESUK/IL)': 'IL',
929
- 'Test case id': 101,
930
- 'Test case title': 'TC 101',
931
- 'Number of passed steps': 1,
932
- 'Number of failed steps': 0,
933
- 'Number of not run tests': 0,
934
- }));
935
- expect(TicketsProviderMock).toHaveBeenCalled();
936
- const instance = TicketsProviderMock.mock.results[0].value;
937
- expect(instance.GetQueryResultsFromWiql).toHaveBeenCalledWith('https://example.com/wiql', true, expect.any(Map));
938
- });
939
868
  it('should map SR ids from steps and output requirement-test-case coverage rows', async () => {
940
869
  jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
941
870
  jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
@@ -959,6 +888,8 @@ describe('ResultDataProvider', () => {
959
888
  {
960
889
  workItemId: 5001,
961
890
  requirementId: 'SR1001',
891
+ baseKey: 'SR1001',
892
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
962
893
  title: 'Covered requirement',
963
894
  responsibility: 'ESUK',
964
895
  linkedTestCaseIds: [101],
@@ -966,6 +897,8 @@ describe('ResultDataProvider', () => {
966
897
  {
967
898
  workItemId: 5002,
968
899
  requirementId: 'SR1002',
900
+ baseKey: 'SR1002',
901
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
969
902
  title: 'Referenced from non-linked step text',
970
903
  responsibility: 'IL',
971
904
  linkedTestCaseIds: [],
@@ -973,6 +906,8 @@ describe('ResultDataProvider', () => {
973
906
  {
974
907
  workItemId: 5003,
975
908
  requirementId: 'SR1003',
909
+ baseKey: 'SR1003',
910
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
976
911
  title: 'Not covered by any test case',
977
912
  responsibility: 'IL',
978
913
  linkedTestCaseIds: [],
@@ -986,11 +921,11 @@ describe('ResultDataProvider', () => {
986
921
  actionResults: [
987
922
  {
988
923
  action: 'Validate <b>S</b><b>R</b> 1 0 0 1 happy path',
989
- expected: '',
924
+ expected: '<b>S</b><b>R</b> 1 0 0 1',
990
925
  outcome: 'Passed',
991
926
  },
992
- { action: 'Validate SR1001 failed flow', expected: '&nbsp;', outcome: 'Failed' },
993
- { action: '', expected: 'Pending S R 1 0 0 1 scenario', outcome: 'Unspecified' },
927
+ { action: 'Validate SR1001 failed flow', expected: 'SR1001', outcome: 'Failed' },
928
+ { action: '', expected: 'S R 1 0 0 1', outcome: 'Unspecified' },
994
929
  ],
995
930
  },
996
931
  },
@@ -1007,48 +942,59 @@ describe('ResultDataProvider', () => {
1007
942
  stepId: '1',
1008
943
  stepPosition: '1',
1009
944
  action: 'Definition contains SR1002',
1010
- expected: '',
945
+ expected: 'SR1002',
1011
946
  isSharedStepTitle: false,
1012
947
  },
1013
948
  ]);
1014
949
  const result = await resultDataProvider.getMewpL2CoverageFlatResults('123', mockProjectName, [1]);
1015
950
  expect(result).toEqual(expect.objectContaining({
1016
951
  sheetName: expect.stringContaining('MEWP L2 Coverage'),
1017
- columnOrder: expect.arrayContaining(['Customer ID', 'Test case id', 'Number of not run tests']),
952
+ columnOrder: expect.arrayContaining(['L2 REQ ID', 'L2 REQ Title', 'L2 Run Status']),
1018
953
  }));
1019
- const covered = result.rows.find((row) => row['Customer ID'] === 'SR1001' && row['Test case id'] === 101);
1020
- const inferredByStepText = result.rows.find((row) => row['Customer ID'] === 'SR1002' && row['Test case id'] === 102);
1021
- const uncovered = result.rows.find((row) => row['Customer ID'] === 'SR1003' &&
1022
- (row['Test case id'] === '' || row['Test case id'] === undefined || row['Test case id'] === null));
954
+ const covered = result.rows.find((row) => row['L2 REQ ID'] === 'SR1001');
955
+ const inferredByStepText = result.rows.find((row) => row['L2 REQ ID'] === 'SR1002');
956
+ const uncovered = result.rows.find((row) => row['L2 REQ ID'] === 'SR1003');
1023
957
  expect(covered).toEqual(expect.objectContaining({
1024
- 'Title (Customer name)': 'Covered requirement',
1025
- 'Responsibility - SAPWBS (ESUK/IL)': 'ESUK',
1026
- 'Test case title': 'TC 101',
1027
- 'Number of passed steps': 1,
1028
- 'Number of failed steps': 1,
1029
- 'Number of not run tests': 1,
958
+ 'L2 REQ Title': 'Covered requirement',
959
+ 'L2 SubSystem': '',
960
+ 'L2 Run Status': 'Fail',
961
+ 'Bug ID': '',
962
+ 'L3 REQ ID': '',
963
+ 'L4 REQ ID': '',
1030
964
  }));
1031
965
  expect(inferredByStepText).toEqual(expect.objectContaining({
1032
- 'Title (Customer name)': 'Referenced from non-linked step text',
1033
- 'Responsibility - SAPWBS (ESUK/IL)': 'IL',
1034
- 'Test case title': 'TC 102',
1035
- 'Number of passed steps': 0,
1036
- 'Number of failed steps': 0,
1037
- 'Number of not run tests': 1,
966
+ 'L2 REQ Title': 'Referenced from non-linked step text',
967
+ 'L2 SubSystem': '',
968
+ 'L2 Run Status': 'Not Run',
1038
969
  }));
1039
970
  expect(uncovered).toEqual(expect.objectContaining({
1040
- 'Title (Customer name)': 'Not covered by any test case',
1041
- 'Responsibility - SAPWBS (ESUK/IL)': 'IL',
1042
- 'Test case title': '',
1043
- 'Number of passed steps': 0,
1044
- 'Number of failed steps': 0,
1045
- 'Number of not run tests': 0,
971
+ 'L2 REQ Title': 'Not covered by any test case',
972
+ 'L2 SubSystem': '',
973
+ 'L2 Run Status': 'Not Run',
1046
974
  }));
1047
975
  });
1048
976
  it('should extract SR ids from HTML/spacing and return unique ids per step text', () => {
1049
- const text = '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';
977
+ const text = '<b>S</b><b>R</b> 0 0 0 1; SR0002; S R 0 0 0 3; SR0002; &lt;b&gt;SR&lt;/b&gt;0004';
1050
978
  const codes = resultDataProvider.extractRequirementCodesFromText(text);
1051
- expect([...codes].sort()).toEqual(['SR1', 'SR2', 'SR3', 'SR4']);
979
+ expect([...codes].sort()).toEqual(['SR0001', 'SR0002', 'SR0003', 'SR0004']);
980
+ });
981
+ it('should keep only clean SR tokens and ignore noisy version/VVRM fragments', () => {
982
+ const extract = (text) => [...resultDataProvider.extractRequirementCodesFromText(text)].sort();
983
+ expect(extract('SR12413; SR24513; SR25135 VVRM2425')).toEqual(['SR12413', 'SR24513']);
984
+ expect(extract('SR12413; SR12412; SR12413-V3.24')).toEqual(['SR12412', 'SR12413']);
985
+ expect(extract('SR12413; SR12412; SR12413 V3.24')).toEqual(['SR12412', 'SR12413']);
986
+ expect(extract('SR12413, SR12412, SR12413-V3.24')).toEqual(['SR12412', 'SR12413']);
987
+ const extractWithSuffix = (text) => [
988
+ ...resultDataProvider.extractRequirementCodesFromExpectedText(text, true),
989
+ ].sort();
990
+ expect(extractWithSuffix('SR0095-2,3; SR0100-1,2,3,4')).toEqual([
991
+ 'SR0095-2',
992
+ 'SR0095-3',
993
+ 'SR0100-1',
994
+ 'SR0100-2',
995
+ 'SR0100-3',
996
+ 'SR0100-4',
997
+ ]);
1052
998
  });
1053
999
  it('should not backfill definition steps as not-run when a real run exists but has no action results', async () => {
1054
1000
  jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
@@ -1070,6 +1016,8 @@ describe('ResultDataProvider', () => {
1070
1016
  {
1071
1017
  workItemId: 7001,
1072
1018
  requirementId: 'SR2001',
1019
+ baseKey: 'SR2001',
1020
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1073
1021
  title: 'Has run but no actions',
1074
1022
  responsibility: 'ESUK',
1075
1023
  linkedTestCaseIds: [101],
@@ -1097,12 +1045,10 @@ describe('ResultDataProvider', () => {
1097
1045
  },
1098
1046
  ]);
1099
1047
  const result = await resultDataProvider.getMewpL2CoverageFlatResults('123', mockProjectName, [1]);
1100
- const row = result.rows.find((item) => item['Customer ID'] === 'SR2001' && item['Test case id'] === 101);
1048
+ const row = result.rows.find((item) => item['L2 REQ ID'] === 'SR2001');
1101
1049
  expect(parseSpy).not.toHaveBeenCalled();
1102
1050
  expect(row).toEqual(expect.objectContaining({
1103
- 'Number of passed steps': 0,
1104
- 'Number of failed steps': 0,
1105
- 'Number of not run tests': 0,
1051
+ 'L2 Run Status': 'Not Run',
1106
1052
  }));
1107
1053
  });
1108
1054
  it('should not infer requirement id from unrelated SR text in non-identifier fields', () => {
@@ -1111,7 +1057,1241 @@ describe('ResultDataProvider', () => {
1111
1057
  'Custom.CustomerId': 'customer id unknown',
1112
1058
  'System.Title': 'Requirement without explicit SR code',
1113
1059
  }, 4321);
1114
- expect(requirementId).toBe('4321');
1060
+ expect(requirementId).toBe('SR4321');
1061
+ });
1062
+ it('should derive responsibility from Custom.SAPWBS when present', () => {
1063
+ const responsibility = resultDataProvider.deriveMewpResponsibility({
1064
+ 'Custom.SAPWBS': 'IL',
1065
+ 'System.AreaPath': 'MEWP\\ESUK',
1066
+ });
1067
+ expect(responsibility).toBe('IL');
1068
+ });
1069
+ it('should derive responsibility from AreaPath suffix ATP/ATP\\\\ESUK when SAPWBS is empty', () => {
1070
+ const esuk = resultDataProvider.deriveMewpResponsibility({
1071
+ 'Custom.SAPWBS': '',
1072
+ 'System.AreaPath': 'MEWP\\Customer Requirements\\Level 2\\ATP\\ESUK',
1073
+ });
1074
+ const il = resultDataProvider.deriveMewpResponsibility({
1075
+ 'Custom.SAPWBS': '',
1076
+ 'System.AreaPath': 'MEWP\\Customer Requirements\\Level 2\\ATP',
1077
+ });
1078
+ expect(esuk).toBe('ESUK');
1079
+ expect(il).toBe('IL');
1080
+ });
1081
+ it('should emit additive rows for bugs and L3/L4 links without bug duplication', () => {
1082
+ const requirements = [
1083
+ {
1084
+ requirementId: 'SR5303',
1085
+ baseKey: 'SR5303',
1086
+ title: 'Req 5303',
1087
+ subSystem: 'Power',
1088
+ responsibility: 'ESUK',
1089
+ linkedTestCaseIds: [101],
1090
+ },
1091
+ ];
1092
+ const requirementIndex = new Map([
1093
+ [
1094
+ 'SR5303',
1095
+ new Map([
1096
+ [
1097
+ 101,
1098
+ {
1099
+ passed: 0,
1100
+ failed: 1,
1101
+ notRun: 0,
1102
+ },
1103
+ ],
1104
+ ]),
1105
+ ],
1106
+ ]);
1107
+ const observedTestCaseIdsByRequirement = new Map([
1108
+ ['SR5303', new Set([101])],
1109
+ ]);
1110
+ const linkedRequirementsByTestCase = new Map([
1111
+ [
1112
+ 101,
1113
+ {
1114
+ baseKeys: new Set(['SR5303']),
1115
+ fullCodes: new Set(['SR5303']),
1116
+ bugIds: new Set([10003, 20003]),
1117
+ },
1118
+ ],
1119
+ ]);
1120
+ const externalBugsByTestCase = new Map([
1121
+ [
1122
+ 101,
1123
+ [
1124
+ { id: 10003, title: 'Bug 10003', responsibility: 'Elisra', requirementBaseKey: 'SR5303' },
1125
+ { id: 20003, title: 'Bug 20003', responsibility: 'ESUK', requirementBaseKey: 'SR5303' },
1126
+ ],
1127
+ ],
1128
+ ]);
1129
+ const l3l4ByBaseKey = new Map([
1130
+ [
1131
+ 'SR5303',
1132
+ [
1133
+ { id: '9003', title: 'L3 9003', level: 'L3' },
1134
+ { id: '9103', title: 'L4 9103', level: 'L4' },
1135
+ ],
1136
+ ],
1137
+ ]);
1138
+ const rows = resultDataProvider.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, l3l4ByBaseKey, externalBugsByTestCase);
1139
+ expect(rows).toHaveLength(4);
1140
+ expect(rows.map((row) => row['Bug ID'])).toEqual([10003, 20003, '', '']);
1141
+ expect(rows[2]).toEqual(expect.objectContaining({
1142
+ 'L3 REQ ID': '9003',
1143
+ 'L4 REQ ID': '',
1144
+ }));
1145
+ expect(rows[3]).toEqual(expect.objectContaining({
1146
+ 'L3 REQ ID': '',
1147
+ 'L4 REQ ID': '9103',
1148
+ }));
1149
+ });
1150
+ it('should not emit bug rows from ADO-linked bug ids when external bugs source is empty', () => {
1151
+ const requirements = [
1152
+ {
1153
+ requirementId: 'SR5304',
1154
+ baseKey: 'SR5304',
1155
+ title: 'Req 5304',
1156
+ subSystem: 'Power',
1157
+ responsibility: 'ESUK',
1158
+ linkedTestCaseIds: [101],
1159
+ },
1160
+ ];
1161
+ const requirementIndex = new Map([
1162
+ [
1163
+ 'SR5304',
1164
+ new Map([
1165
+ [
1166
+ 101,
1167
+ {
1168
+ passed: 0,
1169
+ failed: 1,
1170
+ notRun: 0,
1171
+ },
1172
+ ],
1173
+ ]),
1174
+ ],
1175
+ ]);
1176
+ const observedTestCaseIdsByRequirement = new Map([
1177
+ ['SR5304', new Set([101])],
1178
+ ]);
1179
+ const linkedRequirementsByTestCase = new Map([
1180
+ [
1181
+ 101,
1182
+ {
1183
+ baseKeys: new Set(['SR5304']),
1184
+ fullCodes: new Set(['SR5304']),
1185
+ bugIds: new Set([55555]), // must be ignored in MEWP coverage mode
1186
+ },
1187
+ ],
1188
+ ]);
1189
+ const rows = resultDataProvider.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, new Map(), new Map());
1190
+ expect(rows).toHaveLength(1);
1191
+ expect(rows[0]['L2 Run Status']).toBe('Fail');
1192
+ expect(rows[0]['Bug ID']).toBe('');
1193
+ expect(rows[0]['Bug Title']).toBe('');
1194
+ expect(rows[0]['Bug Responsibility']).toBe('');
1195
+ });
1196
+ it('should fallback bug responsibility from requirement when external bug row has unknown responsibility', () => {
1197
+ const requirements = [
1198
+ {
1199
+ requirementId: 'SR5305',
1200
+ baseKey: 'SR5305',
1201
+ title: 'Req 5305',
1202
+ subSystem: 'Auth',
1203
+ responsibility: 'IL',
1204
+ linkedTestCaseIds: [202],
1205
+ },
1206
+ ];
1207
+ const requirementIndex = new Map([
1208
+ [
1209
+ 'SR5305',
1210
+ new Map([
1211
+ [
1212
+ 202,
1213
+ {
1214
+ passed: 0,
1215
+ failed: 1,
1216
+ notRun: 0,
1217
+ },
1218
+ ],
1219
+ ]),
1220
+ ],
1221
+ ]);
1222
+ const observedTestCaseIdsByRequirement = new Map([
1223
+ ['SR5305', new Set([202])],
1224
+ ]);
1225
+ const linkedRequirementsByTestCase = new Map([
1226
+ [
1227
+ 202,
1228
+ {
1229
+ baseKeys: new Set(['SR5305']),
1230
+ fullCodes: new Set(['SR5305']),
1231
+ bugIds: new Set(),
1232
+ },
1233
+ ],
1234
+ ]);
1235
+ const externalBugsByTestCase = new Map([
1236
+ [
1237
+ 202,
1238
+ [
1239
+ {
1240
+ id: 99001,
1241
+ title: 'External bug without SAPWBS',
1242
+ responsibility: 'Unknown',
1243
+ requirementBaseKey: 'SR5305',
1244
+ },
1245
+ ],
1246
+ ],
1247
+ ]);
1248
+ const rows = resultDataProvider.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, new Map(), externalBugsByTestCase);
1249
+ expect(rows).toHaveLength(1);
1250
+ expect(rows[0]['Bug ID']).toBe(99001);
1251
+ expect(rows[0]['Bug Responsibility']).toBe('Elisra');
1252
+ });
1253
+ });
1254
+ describe('getMewpInternalValidationFlatResults', () => {
1255
+ it('should skip test cases with no in-scope expected requirements and no linked requirements', async () => {
1256
+ jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1257
+ jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
1258
+ jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
1259
+ {
1260
+ testPointsItems: [
1261
+ { testCaseId: 101, testCaseName: 'TC 101' },
1262
+ { testCaseId: 102, testCaseName: 'TC 102' },
1263
+ ],
1264
+ testCasesItems: [
1265
+ {
1266
+ workItem: {
1267
+ id: 101,
1268
+ workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
1269
+ },
1270
+ },
1271
+ {
1272
+ workItem: {
1273
+ id: 102,
1274
+ workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
1275
+ },
1276
+ },
1277
+ ],
1278
+ },
1279
+ ]);
1280
+ jest.spyOn(resultDataProvider, 'fetchMewpL2Requirements').mockResolvedValueOnce([
1281
+ {
1282
+ workItemId: 5001,
1283
+ requirementId: 'SR0001',
1284
+ baseKey: 'SR0001',
1285
+ title: 'Req 1',
1286
+ responsibility: 'ESUK',
1287
+ linkedTestCaseIds: [101],
1288
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1289
+ },
1290
+ ]);
1291
+ jest.spyOn(resultDataProvider, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(new Map([[101, { baseKeys: new Set(['SR0001']), fullCodes: new Set(['SR0001']) }]]));
1292
+ jest
1293
+ .spyOn(resultDataProvider.testStepParserHelper, 'parseTestSteps')
1294
+ .mockResolvedValueOnce([
1295
+ {
1296
+ stepId: '1',
1297
+ stepPosition: '1',
1298
+ action: '',
1299
+ expected: 'SR0001',
1300
+ isSharedStepTitle: false,
1301
+ },
1302
+ ])
1303
+ .mockResolvedValueOnce([
1304
+ {
1305
+ stepId: '1',
1306
+ stepPosition: '1',
1307
+ action: '',
1308
+ expected: '',
1309
+ isSharedStepTitle: false,
1310
+ },
1311
+ ]);
1312
+ const result = await resultDataProvider.getMewpInternalValidationFlatResults('123', mockProjectName, [1]);
1313
+ expect(result.rows).toHaveLength(2);
1314
+ expect(result.rows).toEqual(expect.arrayContaining([
1315
+ expect.objectContaining({
1316
+ 'Test Case ID': 101,
1317
+ 'Validation Status': 'Pass',
1318
+ 'Mentioned but Not Linked': '',
1319
+ 'Linked but Not Mentioned': '',
1320
+ }),
1321
+ expect.objectContaining({
1322
+ 'Test Case ID': 102,
1323
+ 'Validation Status': 'Pass',
1324
+ 'Mentioned but Not Linked': '',
1325
+ 'Linked but Not Mentioned': '',
1326
+ }),
1327
+ ]));
1328
+ });
1329
+ it('should ignore non-L2 SR mentions and still report only real L2 discrepancies', async () => {
1330
+ jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1331
+ jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
1332
+ jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
1333
+ {
1334
+ testPointsItems: [{ testCaseId: 101, testCaseName: 'TC 101' }],
1335
+ testCasesItems: [
1336
+ {
1337
+ workItem: {
1338
+ id: 101,
1339
+ workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
1340
+ },
1341
+ },
1342
+ ],
1343
+ },
1344
+ ]);
1345
+ jest.spyOn(resultDataProvider, 'fetchMewpL2Requirements').mockResolvedValueOnce([
1346
+ {
1347
+ workItemId: 5001,
1348
+ requirementId: 'SR0001',
1349
+ baseKey: 'SR0001',
1350
+ title: 'Req 1',
1351
+ responsibility: 'ESUK',
1352
+ linkedTestCaseIds: [101],
1353
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1354
+ },
1355
+ ]);
1356
+ jest.spyOn(resultDataProvider, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(new Map());
1357
+ jest.spyOn(resultDataProvider.testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
1358
+ {
1359
+ stepId: '1',
1360
+ stepPosition: '1',
1361
+ action: '',
1362
+ expected: 'SR0001; SR9999',
1363
+ isSharedStepTitle: false,
1364
+ },
1365
+ ]);
1366
+ const result = await resultDataProvider.getMewpInternalValidationFlatResults('123', mockProjectName, [1]);
1367
+ expect(result.rows).toHaveLength(1);
1368
+ expect(result.rows[0]).toEqual(expect.objectContaining({
1369
+ 'Test Case ID': 101,
1370
+ 'Mentioned but Not Linked': 'Step 1: SR0001',
1371
+ 'Linked but Not Mentioned': '',
1372
+ 'Validation Status': 'Fail',
1373
+ }));
1374
+ });
1375
+ it('should emit Direction A rows with step context for missing sibling links', async () => {
1376
+ jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1377
+ jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
1378
+ jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
1379
+ {
1380
+ testPointsItems: [{ testCaseId: 101, testCaseName: 'TC 101' }],
1381
+ testCasesItems: [
1382
+ {
1383
+ workItem: {
1384
+ id: 101,
1385
+ workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
1386
+ },
1387
+ },
1388
+ ],
1389
+ },
1390
+ ]);
1391
+ jest.spyOn(resultDataProvider, 'fetchMewpL2Requirements').mockResolvedValueOnce([
1392
+ {
1393
+ workItemId: 5001,
1394
+ requirementId: 'SR0001',
1395
+ baseKey: 'SR0001',
1396
+ title: 'Req parent',
1397
+ responsibility: 'ESUK',
1398
+ linkedTestCaseIds: [101],
1399
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1400
+ },
1401
+ {
1402
+ workItemId: 5002,
1403
+ requirementId: 'SR0001-1',
1404
+ baseKey: 'SR0001',
1405
+ title: 'Req child',
1406
+ responsibility: 'ESUK',
1407
+ linkedTestCaseIds: [],
1408
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
1409
+ },
1410
+ ]);
1411
+ jest.spyOn(resultDataProvider, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(new Map([[101, { baseKeys: new Set(['SR0001']), fullCodes: new Set(['SR0001']) }]]));
1412
+ jest.spyOn(resultDataProvider.testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
1413
+ {
1414
+ stepId: '2',
1415
+ stepPosition: '2',
1416
+ action: '',
1417
+ expected: 'SR0001; SR0002',
1418
+ isSharedStepTitle: false,
1419
+ },
1420
+ ]);
1421
+ const result = await resultDataProvider.getMewpInternalValidationFlatResults('123', mockProjectName, [1]);
1422
+ expect(result.rows).toEqual(expect.arrayContaining([
1423
+ expect.objectContaining({
1424
+ 'Test Case ID': 101,
1425
+ 'Mentioned but Not Linked': expect.stringContaining('Step 2: SR0001-1'),
1426
+ 'Validation Status': 'Fail',
1427
+ }),
1428
+ ]));
1429
+ });
1430
+ it('should not duplicate Direction A discrepancy when same requirement is repeated in multiple steps', async () => {
1431
+ jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1432
+ jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
1433
+ jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
1434
+ {
1435
+ testPointsItems: [{ testCaseId: 101, testCaseName: 'TC 101' }],
1436
+ testCasesItems: [
1437
+ {
1438
+ workItem: {
1439
+ id: 101,
1440
+ workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
1441
+ },
1442
+ },
1443
+ ],
1444
+ },
1445
+ ]);
1446
+ jest.spyOn(resultDataProvider, 'fetchMewpL2Requirements').mockResolvedValueOnce([
1447
+ {
1448
+ workItemId: 5001,
1449
+ requirementId: 'SR0001',
1450
+ baseKey: 'SR0001',
1451
+ title: 'Req 1',
1452
+ responsibility: 'ESUK',
1453
+ linkedTestCaseIds: [],
1454
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1455
+ },
1456
+ ]);
1457
+ jest
1458
+ .spyOn(resultDataProvider, 'buildLinkedRequirementsByTestCase')
1459
+ .mockResolvedValueOnce(new Map());
1460
+ jest.spyOn(resultDataProvider.testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
1461
+ {
1462
+ stepId: '1',
1463
+ stepPosition: '1',
1464
+ action: '',
1465
+ expected: 'SR0001',
1466
+ isSharedStepTitle: false,
1467
+ },
1468
+ {
1469
+ stepId: '2',
1470
+ stepPosition: '2',
1471
+ action: '',
1472
+ expected: 'SR0001',
1473
+ isSharedStepTitle: false,
1474
+ },
1475
+ ]);
1476
+ const result = await resultDataProvider.getMewpInternalValidationFlatResults('123', mockProjectName, [1]);
1477
+ expect(result.rows).toHaveLength(1);
1478
+ expect(result.rows[0]).toEqual(expect.objectContaining({
1479
+ 'Test Case ID': 101,
1480
+ 'Mentioned but Not Linked': 'Step 1: SR0001',
1481
+ 'Linked but Not Mentioned': '',
1482
+ 'Validation Status': 'Fail',
1483
+ }));
1484
+ });
1485
+ it('should produce one detailed row per test case with correct bidirectional discrepancies', async () => {
1486
+ const mockDetailedStepsByTestCase = new Map([
1487
+ [
1488
+ 201,
1489
+ [
1490
+ {
1491
+ stepId: '1',
1492
+ stepPosition: '1',
1493
+ action: 'Validate parent SR0511 and SR0095 siblings',
1494
+ expected: '<b>sr0511</b>; SR0095-2,3; VVRM-05',
1495
+ isSharedStepTitle: false,
1496
+ },
1497
+ {
1498
+ stepId: '2',
1499
+ stepPosition: '2',
1500
+ action: 'Noisy requirement-like token should be ignored',
1501
+ expected: 'SR0511-V3.24',
1502
+ isSharedStepTitle: false,
1503
+ },
1504
+ {
1505
+ stepId: '3',
1506
+ stepPosition: '3',
1507
+ action: 'Regression note',
1508
+ expected: 'Verification note only',
1509
+ isSharedStepTitle: false,
1510
+ },
1511
+ ],
1512
+ ],
1513
+ [
1514
+ 202,
1515
+ [
1516
+ {
1517
+ stepId: '1',
1518
+ stepPosition: '1',
1519
+ action: 'Linked SR0200 exists but is not cleanly mentioned in expected result',
1520
+ expected: 'VVRM-22; SR0200 V3.1',
1521
+ isSharedStepTitle: false,
1522
+ },
1523
+ {
1524
+ stepId: '2',
1525
+ stepPosition: '2',
1526
+ action: 'Execution notes',
1527
+ expected: 'Notes without SR requirement token',
1528
+ isSharedStepTitle: false,
1529
+ },
1530
+ ],
1531
+ ],
1532
+ [
1533
+ 203,
1534
+ [
1535
+ {
1536
+ stepId: '1',
1537
+ stepPosition: '1',
1538
+ action: 'Primary requirement validation for SR0100-1',
1539
+ expected: '<i>SR0100-1</i>',
1540
+ isSharedStepTitle: false,
1541
+ },
1542
+ {
1543
+ stepId: '3',
1544
+ stepPosition: '3',
1545
+ action: 'Repeated mention should not create duplicate mismatch',
1546
+ expected: 'SR0100-1; SR0100-1',
1547
+ isSharedStepTitle: false,
1548
+ },
1549
+ ],
1550
+ ],
1551
+ ]);
1552
+ const mockLinkedRequirementsByTestCase = new Map([
1553
+ [
1554
+ 201,
1555
+ {
1556
+ baseKeys: new Set(['SR0511', 'SR0095', 'SR8888']),
1557
+ fullCodes: new Set(['SR0511', 'SR0095-2', 'SR8888']),
1558
+ },
1559
+ ],
1560
+ [
1561
+ 202,
1562
+ {
1563
+ baseKeys: new Set(['SR0200']),
1564
+ fullCodes: new Set(['SR0200']),
1565
+ },
1566
+ ],
1567
+ [
1568
+ 203,
1569
+ {
1570
+ baseKeys: new Set(['SR0100']),
1571
+ fullCodes: new Set(['SR0100-1']),
1572
+ },
1573
+ ],
1574
+ ]);
1575
+ jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1576
+ jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
1577
+ jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
1578
+ {
1579
+ testPointsItems: [
1580
+ { testCaseId: 201, testCaseName: 'TC 201 - Mixed discrepancies' },
1581
+ { testCaseId: 202, testCaseName: 'TC 202 - Link only' },
1582
+ { testCaseId: 203, testCaseName: 'TC 203 - Fully valid' },
1583
+ ],
1584
+ testCasesItems: [
1585
+ {
1586
+ workItem: {
1587
+ id: 201,
1588
+ workItemFields: [{ key: 'Steps', value: '<steps id="mock-steps-tc-201"></steps>' }],
1589
+ },
1590
+ },
1591
+ {
1592
+ workItem: {
1593
+ id: 202,
1594
+ workItemFields: [{ key: 'Steps', value: '<steps id="mock-steps-tc-202"></steps>' }],
1595
+ },
1596
+ },
1597
+ {
1598
+ workItem: {
1599
+ id: 203,
1600
+ workItemFields: [{ key: 'Steps', value: '<steps id="mock-steps-tc-203"></steps>' }],
1601
+ },
1602
+ },
1603
+ ],
1604
+ },
1605
+ ]);
1606
+ jest.spyOn(resultDataProvider, 'fetchMewpL2Requirements').mockResolvedValueOnce([
1607
+ {
1608
+ workItemId: 6001,
1609
+ requirementId: 'SR0511',
1610
+ baseKey: 'SR0511',
1611
+ title: 'Parent 0511',
1612
+ responsibility: 'ESUK',
1613
+ linkedTestCaseIds: [201],
1614
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1615
+ },
1616
+ {
1617
+ workItemId: 6002,
1618
+ requirementId: 'SR0511-1',
1619
+ baseKey: 'SR0511',
1620
+ title: 'Child 0511-1',
1621
+ responsibility: 'ESUK',
1622
+ linkedTestCaseIds: [],
1623
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
1624
+ },
1625
+ {
1626
+ workItemId: 6003,
1627
+ requirementId: 'SR0511-2',
1628
+ baseKey: 'SR0511',
1629
+ title: 'Child 0511-2',
1630
+ responsibility: 'ESUK',
1631
+ linkedTestCaseIds: [],
1632
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
1633
+ },
1634
+ {
1635
+ workItemId: 6004,
1636
+ requirementId: 'SR0095-2',
1637
+ baseKey: 'SR0095',
1638
+ title: 'SR0095 child 2',
1639
+ responsibility: 'ESUK',
1640
+ linkedTestCaseIds: [],
1641
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
1642
+ },
1643
+ {
1644
+ workItemId: 6005,
1645
+ requirementId: 'SR0095-3',
1646
+ baseKey: 'SR0095',
1647
+ title: 'SR0095 child 3',
1648
+ responsibility: 'ESUK',
1649
+ linkedTestCaseIds: [],
1650
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
1651
+ },
1652
+ {
1653
+ workItemId: 6006,
1654
+ requirementId: 'SR0200',
1655
+ baseKey: 'SR0200',
1656
+ title: 'SR0200 standalone',
1657
+ responsibility: 'ESUK',
1658
+ linkedTestCaseIds: [202],
1659
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1660
+ },
1661
+ {
1662
+ workItemId: 6007,
1663
+ requirementId: 'SR0100-1',
1664
+ baseKey: 'SR0100',
1665
+ title: 'SR0100 child 1',
1666
+ responsibility: 'ESUK',
1667
+ linkedTestCaseIds: [203],
1668
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
1669
+ },
1670
+ ]);
1671
+ jest
1672
+ .spyOn(resultDataProvider, 'buildLinkedRequirementsByTestCase')
1673
+ .mockResolvedValueOnce(mockLinkedRequirementsByTestCase);
1674
+ jest
1675
+ .spyOn(resultDataProvider.testStepParserHelper, 'parseTestSteps')
1676
+ .mockImplementation(async (...args) => {
1677
+ const stepsXml = String((args === null || args === void 0 ? void 0 : args[0]) || '');
1678
+ const testCaseMatch = /mock-steps-tc-(\d+)/i.exec(stepsXml);
1679
+ const testCaseId = Number((testCaseMatch === null || testCaseMatch === void 0 ? void 0 : testCaseMatch[1]) || 0);
1680
+ return mockDetailedStepsByTestCase.get(testCaseId) || [];
1681
+ });
1682
+ const result = await resultDataProvider.getMewpInternalValidationFlatResults('123', mockProjectName, [1]);
1683
+ expect(result.rows).toHaveLength(3);
1684
+ const byTestCase = new Map(result.rows.map((row) => [row['Test Case ID'], row]));
1685
+ expect(new Set(result.rows.map((row) => row['Test Case ID']))).toEqual(new Set([201, 202, 203]));
1686
+ expect(byTestCase.get(201)).toEqual(expect.objectContaining({
1687
+ 'Test Case Title': 'TC 201 - Mixed discrepancies',
1688
+ 'Mentioned but Not Linked': 'Step 1: SR0095-3, SR0511-1, SR0511-2',
1689
+ 'Linked but Not Mentioned': 'SR8888',
1690
+ 'Validation Status': 'Fail',
1691
+ }));
1692
+ expect(String(byTestCase.get(201)['Mentioned but Not Linked'] || '')).not.toContain('VVRM');
1693
+ expect(byTestCase.get(202)).toEqual(expect.objectContaining({
1694
+ 'Test Case Title': 'TC 202 - Link only',
1695
+ 'Mentioned but Not Linked': '',
1696
+ 'Linked but Not Mentioned': 'SR0200',
1697
+ 'Validation Status': 'Fail',
1698
+ }));
1699
+ expect(byTestCase.get(203)).toEqual(expect.objectContaining({
1700
+ 'Test Case Title': 'TC 203 - Fully valid',
1701
+ 'Mentioned but Not Linked': '',
1702
+ 'Linked but Not Mentioned': '',
1703
+ 'Validation Status': 'Pass',
1704
+ }));
1705
+ expect(resultDataProvider.testStepParserHelper.parseTestSteps).toHaveBeenCalledTimes(3);
1706
+ const parseCalls = resultDataProvider.testStepParserHelper.parseTestSteps.mock.calls;
1707
+ const parsedCaseIds = parseCalls
1708
+ .map(([xml]) => {
1709
+ const match = /mock-steps-tc-(\d+)/i.exec(String(xml || ''));
1710
+ return Number((match === null || match === void 0 ? void 0 : match[1]) || 0);
1711
+ })
1712
+ .filter((id) => Number.isFinite(id) && id > 0);
1713
+ expect(new Set(parsedCaseIds)).toEqual(new Set([201, 202, 203]));
1714
+ });
1715
+ it('should parse TC-0042 mixed expected text and keep only valid SR requirement codes', async () => {
1716
+ jest.spyOn(resultDataProvider, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1717
+ jest.spyOn(resultDataProvider, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
1718
+ jest.spyOn(resultDataProvider, 'fetchTestData').mockResolvedValueOnce([
1719
+ {
1720
+ testPointsItems: [{ testCaseId: 42, testCaseName: 'TC-0042' }],
1721
+ testCasesItems: [
1722
+ {
1723
+ workItem: {
1724
+ id: 42,
1725
+ workItemFields: [{ key: 'Steps', value: '<steps id="mock-steps-tc-0042"></steps>' }],
1726
+ },
1727
+ },
1728
+ ],
1729
+ },
1730
+ ]);
1731
+ jest.spyOn(resultDataProvider, 'fetchMewpL2Requirements').mockResolvedValueOnce([
1732
+ {
1733
+ workItemId: 7001,
1734
+ requirementId: 'SR0036',
1735
+ baseKey: 'SR0036',
1736
+ title: 'SR0036',
1737
+ responsibility: 'ESUK',
1738
+ linkedTestCaseIds: [],
1739
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1740
+ },
1741
+ {
1742
+ workItemId: 7002,
1743
+ requirementId: 'SR0215',
1744
+ baseKey: 'SR0215',
1745
+ title: 'SR0215',
1746
+ responsibility: 'ESUK',
1747
+ linkedTestCaseIds: [],
1748
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1749
+ },
1750
+ {
1751
+ workItemId: 7003,
1752
+ requirementId: 'SR0539',
1753
+ baseKey: 'SR0539',
1754
+ title: 'SR0539',
1755
+ responsibility: 'ESUK',
1756
+ linkedTestCaseIds: [],
1757
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1758
+ },
1759
+ {
1760
+ workItemId: 7004,
1761
+ requirementId: 'SR0348',
1762
+ baseKey: 'SR0348',
1763
+ title: 'SR0348',
1764
+ responsibility: 'ESUK',
1765
+ linkedTestCaseIds: [],
1766
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1767
+ },
1768
+ {
1769
+ workItemId: 7005,
1770
+ requirementId: 'SR0027',
1771
+ baseKey: 'SR0027',
1772
+ title: 'SR0027',
1773
+ responsibility: 'ESUK',
1774
+ linkedTestCaseIds: [],
1775
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1776
+ },
1777
+ {
1778
+ workItemId: 7006,
1779
+ requirementId: 'SR0041',
1780
+ baseKey: 'SR0041',
1781
+ title: 'SR0041',
1782
+ responsibility: 'ESUK',
1783
+ linkedTestCaseIds: [],
1784
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1785
+ },
1786
+ {
1787
+ workItemId: 7007,
1788
+ requirementId: 'SR0550',
1789
+ baseKey: 'SR0550',
1790
+ title: 'SR0550',
1791
+ responsibility: 'ESUK',
1792
+ linkedTestCaseIds: [],
1793
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1794
+ },
1795
+ {
1796
+ workItemId: 7008,
1797
+ requirementId: 'SR0550-2',
1798
+ baseKey: 'SR0550',
1799
+ title: 'SR0550-2',
1800
+ responsibility: 'ESUK',
1801
+ linkedTestCaseIds: [],
1802
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
1803
+ },
1804
+ {
1805
+ workItemId: 7009,
1806
+ requirementId: 'SR0817',
1807
+ baseKey: 'SR0817',
1808
+ title: 'SR0817',
1809
+ responsibility: 'ESUK',
1810
+ linkedTestCaseIds: [],
1811
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1812
+ },
1813
+ {
1814
+ workItemId: 7010,
1815
+ requirementId: 'SR0818',
1816
+ baseKey: 'SR0818',
1817
+ title: 'SR0818',
1818
+ responsibility: 'ESUK',
1819
+ linkedTestCaseIds: [],
1820
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1821
+ },
1822
+ {
1823
+ workItemId: 7011,
1824
+ requirementId: 'SR0859',
1825
+ baseKey: 'SR0859',
1826
+ title: 'SR0859',
1827
+ responsibility: 'ESUK',
1828
+ linkedTestCaseIds: [],
1829
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1830
+ },
1831
+ ]);
1832
+ jest
1833
+ .spyOn(resultDataProvider, 'buildLinkedRequirementsByTestCase')
1834
+ .mockResolvedValueOnce(new Map([
1835
+ [
1836
+ 42,
1837
+ {
1838
+ baseKeys: new Set([
1839
+ 'SR0036',
1840
+ 'SR0215',
1841
+ 'SR0539',
1842
+ 'SR0348',
1843
+ 'SR0041',
1844
+ 'SR0550',
1845
+ 'SR0817',
1846
+ 'SR0818',
1847
+ 'SR0859',
1848
+ ]),
1849
+ fullCodes: new Set([
1850
+ 'SR0036',
1851
+ 'SR0215',
1852
+ 'SR0539',
1853
+ 'SR0348',
1854
+ 'SR0041',
1855
+ 'SR0550',
1856
+ 'SR0550-2',
1857
+ 'SR0817',
1858
+ 'SR0818',
1859
+ 'SR0859',
1860
+ ]),
1861
+ },
1862
+ ],
1863
+ ]));
1864
+ jest.spyOn(resultDataProvider.testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
1865
+ {
1866
+ stepId: '1',
1867
+ stepPosition: '1',
1868
+ action: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
1869
+ expected: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. (SR0036; SR0215; VVRM-1; SR0817-V3.2; SR0818-V3.3; SR0859-V3.4)',
1870
+ isSharedStepTitle: false,
1871
+ },
1872
+ {
1873
+ stepId: '2',
1874
+ stepPosition: '2',
1875
+ action: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
1876
+ expected: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. (SR0539; SR0348; VVRM-1)',
1877
+ isSharedStepTitle: false,
1878
+ },
1879
+ {
1880
+ stepId: '3',
1881
+ stepPosition: '3',
1882
+ action: 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
1883
+ expected: 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. (SR0027, SR0036; SR0041; VVRM-2)',
1884
+ isSharedStepTitle: false,
1885
+ },
1886
+ {
1887
+ stepId: '4',
1888
+ stepPosition: '4',
1889
+ action: 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
1890
+ expected: 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. (SR0550-2)',
1891
+ isSharedStepTitle: false,
1892
+ },
1893
+ {
1894
+ stepId: '5',
1895
+ stepPosition: '5',
1896
+ action: 'Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit.',
1897
+ expected: 'Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit. (VVRM-1)',
1898
+ isSharedStepTitle: false,
1899
+ },
1900
+ {
1901
+ stepId: '6',
1902
+ stepPosition: '6',
1903
+ action: 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet consectetur adipisci velit.',
1904
+ expected: 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet consectetur adipisci velit. (SR0041; SR0550; VVRM-3)',
1905
+ isSharedStepTitle: false,
1906
+ },
1907
+ ]);
1908
+ const result = await resultDataProvider.getMewpInternalValidationFlatResults('123', mockProjectName, [1]);
1909
+ expect(result.rows).toHaveLength(1);
1910
+ expect(result.rows[0]).toEqual(expect.objectContaining({
1911
+ 'Test Case ID': 42,
1912
+ 'Test Case Title': 'TC-0042',
1913
+ 'Mentioned but Not Linked': 'Step 3: SR0027',
1914
+ 'Linked but Not Mentioned': 'SR0817; SR0818; SR0859',
1915
+ 'Validation Status': 'Fail',
1916
+ }));
1917
+ expect(String(result.rows[0]['Mentioned but Not Linked'] || '')).not.toContain('VVRM');
1918
+ expect(String(result.rows[0]['Linked but Not Mentioned'] || '')).not.toContain('VVRM');
1919
+ });
1920
+ });
1921
+ describe('MEWP rel fallback scoping', () => {
1922
+ it('should fallback to previous Rel run when latest selected Rel has no run for a test case', async () => {
1923
+ const suites = [
1924
+ { testSuiteId: 10, suiteName: 'Rel10 / Validation' },
1925
+ { testSuiteId: 11, suiteName: 'Rel11 / Validation' },
1926
+ ];
1927
+ const allSuites = [
1928
+ { testSuiteId: 10, suiteName: 'Rel10 / Validation' },
1929
+ { testSuiteId: 11, suiteName: 'Rel11 / Validation' },
1930
+ ];
1931
+ const rawTestData = [
1932
+ {
1933
+ suiteName: 'Rel10 / Validation',
1934
+ testPointsItems: [
1935
+ { testCaseId: 501, lastRunId: 100, lastResultId: 200 },
1936
+ { testCaseId: 502, lastRunId: 300, lastResultId: 400 },
1937
+ ],
1938
+ testCasesItems: [{ workItem: { id: 501 } }, { workItem: { id: 502 } }],
1939
+ },
1940
+ {
1941
+ suiteName: 'Rel11 / Validation',
1942
+ testPointsItems: [
1943
+ { testCaseId: 501, lastRunId: 0, lastResultId: 0 },
1944
+ { testCaseId: 502, lastRunId: 500, lastResultId: 600 },
1945
+ ],
1946
+ testCasesItems: [{ workItem: { id: 501 } }, { workItem: { id: 502 } }],
1947
+ },
1948
+ ];
1949
+ const fetchSuitesSpy = jest
1950
+ .spyOn(resultDataProvider, 'fetchTestSuites')
1951
+ .mockResolvedValueOnce(suites)
1952
+ .mockResolvedValueOnce(allSuites);
1953
+ const fetchDataSpy = jest
1954
+ .spyOn(resultDataProvider, 'fetchTestData')
1955
+ .mockResolvedValueOnce(rawTestData);
1956
+ const scoped = await resultDataProvider.fetchMewpScopedTestData('123', mockProjectName, [11], true);
1957
+ expect(fetchSuitesSpy).toHaveBeenCalledTimes(2);
1958
+ expect(fetchDataSpy).toHaveBeenCalledTimes(1);
1959
+ expect(scoped).toHaveLength(1);
1960
+ const selectedPoints = scoped[0].testPointsItems;
1961
+ const tc501 = selectedPoints.find((item) => item.testCaseId === 501);
1962
+ const tc502 = selectedPoints.find((item) => item.testCaseId === 502);
1963
+ expect(tc501).toEqual(expect.objectContaining({ lastRunId: 100, lastResultId: 200 }));
1964
+ expect(tc502).toEqual(expect.objectContaining({ lastRunId: 500, lastResultId: 600 }));
1965
+ });
1966
+ });
1967
+ describe('MEWP external ingestion validation/parsing', () => {
1968
+ const validBugsSource = {
1969
+ name: 'bugs.xlsx',
1970
+ url: 'https://minio.local/mewp-external-ingestion/MEWP/mewp-external-ingestion/bugs/bugs.xlsx',
1971
+ sourceType: 'mewpExternalIngestion',
1972
+ };
1973
+ const validL3L4Source = {
1974
+ name: 'l3l4.xlsx',
1975
+ url: 'https://minio.local/mewp-external-ingestion/MEWP/mewp-external-ingestion/l3l4/l3l4.xlsx',
1976
+ sourceType: 'mewpExternalIngestion',
1977
+ };
1978
+ it('should validate external files when required columns exist in A3 header row', async () => {
1979
+ const bugsBuffer = buildWorkbookBuffer([
1980
+ ['', '', '', '', ''],
1981
+ ['', '', '', '', ''],
1982
+ ['Elisra_SortIndex', 'SR', 'TargetWorkItemId', 'Title', 'TargetState'],
1983
+ ['101', 'SR0001', '9001', 'Bug one', 'Active'],
1984
+ ]);
1985
+ const l3l4Buffer = buildWorkbookBuffer([
1986
+ ['', '', '', '', '', '', '', ''],
1987
+ ['', '', '', '', '', '', '', ''],
1988
+ [
1989
+ 'SR',
1990
+ 'AREA 34',
1991
+ 'TargetWorkItemId Level 3',
1992
+ 'TargetTitleLevel3',
1993
+ 'TargetStateLevel 3',
1994
+ 'TargetWorkItemIdLevel 4',
1995
+ 'TargetTitleLevel4',
1996
+ 'TargetStateLevel 4',
1997
+ ],
1998
+ ['SR0001', 'Level 3', '7001', 'Req L3', 'Active', '', '', ''],
1999
+ ]);
2000
+ const axiosSpy = jest
2001
+ .spyOn(axios_1.default, 'get')
2002
+ .mockResolvedValueOnce({ data: bugsBuffer, headers: {} })
2003
+ .mockResolvedValueOnce({ data: l3l4Buffer, headers: {} });
2004
+ const result = await resultDataProvider.validateMewpExternalFiles({
2005
+ externalBugsFile: validBugsSource,
2006
+ externalL3L4File: validL3L4Source,
2007
+ });
2008
+ expect(axiosSpy).toHaveBeenCalledTimes(2);
2009
+ expect(result.valid).toBe(true);
2010
+ expect(result.bugs).toEqual(expect.objectContaining({
2011
+ valid: true,
2012
+ headerRow: 'A3',
2013
+ matchedRequiredColumns: 5,
2014
+ }));
2015
+ expect(result.l3l4).toEqual(expect.objectContaining({
2016
+ valid: true,
2017
+ headerRow: 'A3',
2018
+ matchedRequiredColumns: 8,
2019
+ }));
2020
+ });
2021
+ it('should accept A1 fallback header row for backward compatibility', async () => {
2022
+ const bugsBuffer = buildWorkbookBuffer([
2023
+ ['Elisra_SortIndex', 'SR', 'TargetWorkItemId', 'Title', 'TargetState'],
2024
+ ['101', 'SR0001', '9001', 'Bug one', 'Active'],
2025
+ ]);
2026
+ jest.spyOn(axios_1.default, 'get').mockResolvedValueOnce({ data: bugsBuffer, headers: {} });
2027
+ const result = await resultDataProvider.validateMewpExternalFiles({
2028
+ externalBugsFile: validBugsSource,
2029
+ });
2030
+ expect(result.valid).toBe(true);
2031
+ expect(result.bugs).toEqual(expect.objectContaining({
2032
+ valid: true,
2033
+ headerRow: 'A1',
2034
+ }));
2035
+ });
2036
+ it('should fail validation when required columns are missing', async () => {
2037
+ const invalidBuffer = buildWorkbookBuffer([
2038
+ ['', '', ''],
2039
+ ['', '', ''],
2040
+ ['Elisra_SortIndex', 'TargetWorkItemId', 'TargetState'],
2041
+ ['101', '9001', 'Active'],
2042
+ ]);
2043
+ jest.spyOn(axios_1.default, 'get').mockResolvedValueOnce({ data: invalidBuffer, headers: {} });
2044
+ const result = await resultDataProvider.validateMewpExternalFiles({
2045
+ externalBugsFile: validBugsSource,
2046
+ });
2047
+ expect(result.valid).toBe(false);
2048
+ expect(result.bugs).toEqual(expect.objectContaining({
2049
+ valid: false,
2050
+ matchedRequiredColumns: 3,
2051
+ missingRequiredColumns: expect.arrayContaining(['SR', 'Title']),
2052
+ }));
2053
+ });
2054
+ it('should reject files from non-dedicated bucket/object path', async () => {
2055
+ var _a;
2056
+ const result = await resultDataProvider.validateMewpExternalFiles({
2057
+ externalBugsFile: {
2058
+ name: 'bugs.xlsx',
2059
+ url: 'https://minio.local/mewp-external-ingestion/MEWP/other-prefix/bugs.xlsx',
2060
+ sourceType: 'mewpExternalIngestion',
2061
+ },
2062
+ });
2063
+ expect(result.valid).toBe(false);
2064
+ expect(((_a = result.bugs) === null || _a === void 0 ? void 0 : _a.message) || '').toContain('Invalid object path');
2065
+ });
2066
+ it('should filter external bugs by SR/state and dedupe by bug+requirement', async () => {
2067
+ const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
2068
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
2069
+ {
2070
+ Elisra_SortIndex: '101',
2071
+ SR: 'SR0001',
2072
+ TargetWorkItemId: '9001',
2073
+ Title: 'Bug one',
2074
+ TargetState: 'Active',
2075
+ SAPWBS: 'ESUK',
2076
+ },
2077
+ {
2078
+ Elisra_SortIndex: '101',
2079
+ SR: 'SR0001',
2080
+ TargetWorkItemId: '9001',
2081
+ Title: 'Bug one duplicate',
2082
+ TargetState: 'Active',
2083
+ },
2084
+ {
2085
+ Elisra_SortIndex: '101',
2086
+ SR: '',
2087
+ TargetWorkItemId: '9002',
2088
+ Title: 'Missing SR',
2089
+ TargetState: 'Active',
2090
+ },
2091
+ {
2092
+ Elisra_SortIndex: '101',
2093
+ SR: 'SR0002',
2094
+ TargetWorkItemId: '9003',
2095
+ Title: 'Closed bug',
2096
+ TargetState: 'Closed',
2097
+ },
2098
+ ]);
2099
+ const map = await resultDataProvider.loadExternalBugsByTestCase(validBugsSource);
2100
+ const bugs = map.get(101) || [];
2101
+ expect(bugs).toHaveLength(1);
2102
+ expect(bugs[0]).toEqual(expect.objectContaining({
2103
+ id: 9001,
2104
+ requirementBaseKey: 'SR0001',
2105
+ responsibility: 'ESUK',
2106
+ }));
2107
+ });
2108
+ it('should require Elisra_SortIndex and ignore rows that only provide WorkItemId', async () => {
2109
+ const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
2110
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
2111
+ {
2112
+ WorkItemId: '101',
2113
+ SR: 'SR0001',
2114
+ TargetWorkItemId: '9010',
2115
+ Title: 'Bug without Elisra_SortIndex',
2116
+ TargetState: 'Active',
2117
+ },
2118
+ ]);
2119
+ const map = await resultDataProvider.loadExternalBugsByTestCase(validBugsSource);
2120
+ expect(map.size).toBe(0);
2121
+ });
2122
+ it('should parse external L3/L4 file using AREA 34 semantics and terminal-state filtering', async () => {
2123
+ const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
2124
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
2125
+ {
2126
+ SR: 'SR0001',
2127
+ 'AREA 34': 'Level 4',
2128
+ 'TargetWorkItemId Level 3': '7001',
2129
+ TargetTitleLevel3: 'L4 From Level3 Column',
2130
+ 'TargetStateLevel 3': 'Active',
2131
+ },
2132
+ {
2133
+ SR: 'SR0001',
2134
+ 'AREA 34': 'Level 3',
2135
+ 'TargetWorkItemId Level 3': '7002',
2136
+ TargetTitleLevel3: 'L3 Requirement',
2137
+ 'TargetStateLevel 3': 'Active',
2138
+ 'TargetWorkItemIdLevel 4': '7003',
2139
+ TargetTitleLevel4: 'L4 Requirement',
2140
+ 'TargetStateLevel 4': 'Closed',
2141
+ },
2142
+ ]);
2143
+ const map = await resultDataProvider.loadExternalL3L4ByBaseKey(validL3L4Source);
2144
+ expect(map.get('SR0001')).toEqual([
2145
+ { id: '7002', title: 'L3 Requirement', level: 'L3' },
2146
+ { id: '7001', title: 'L4 From Level3 Column', level: 'L4' },
2147
+ ]);
2148
+ });
2149
+ it('should exclude external open L3/L4 rows when SAPWBS resolves to ESUK', async () => {
2150
+ const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
2151
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
2152
+ {
2153
+ SR: 'SR0001',
2154
+ 'AREA 34': 'Level 3',
2155
+ 'TargetWorkItemId Level 3': '7101',
2156
+ TargetTitleLevel3: 'L3 ESUK',
2157
+ 'TargetStateLevel 3': 'Active',
2158
+ 'TargetSapWbsLevel 3': 'ESUK',
2159
+ 'TargetWorkItemIdLevel 4': '7201',
2160
+ TargetTitleLevel4: 'L4 IL',
2161
+ 'TargetStateLevel 4': 'Active',
2162
+ 'TargetSapWbsLevel 4': 'IL',
2163
+ },
2164
+ {
2165
+ SR: 'SR0001',
2166
+ 'AREA 34': 'Level 3',
2167
+ 'TargetWorkItemId Level 3': '7102',
2168
+ TargetTitleLevel3: 'L3 IL',
2169
+ 'TargetStateLevel 3': 'Active',
2170
+ 'TargetSapWbsLevel 3': 'IL',
2171
+ },
2172
+ ]);
2173
+ const map = await resultDataProvider.loadExternalL3L4ByBaseKey(validL3L4Source);
2174
+ expect(map.get('SR0001')).toEqual([
2175
+ { id: '7102', title: 'L3 IL', level: 'L3' },
2176
+ { id: '7201', title: 'L4 IL', level: 'L4' },
2177
+ ]);
2178
+ });
2179
+ it('should fallback L3/L4 SAPWBS exclusion from SR-mapped requirement when row SAPWBS is empty', async () => {
2180
+ const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
2181
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
2182
+ {
2183
+ SR: 'SR0001',
2184
+ 'AREA 34': 'Level 3',
2185
+ 'TargetWorkItemId Level 3': '7301',
2186
+ TargetTitleLevel3: 'L3 From ESUK Requirement',
2187
+ 'TargetStateLevel 3': 'Active',
2188
+ 'TargetSapWbsLevel 3': '',
2189
+ },
2190
+ {
2191
+ SR: 'SR0002',
2192
+ 'AREA 34': 'Level 3',
2193
+ 'TargetWorkItemId Level 3': '7302',
2194
+ TargetTitleLevel3: 'L3 From IL Requirement',
2195
+ 'TargetStateLevel 3': 'Active',
2196
+ 'TargetSapWbsLevel 3': '',
2197
+ },
2198
+ ]);
2199
+ const map = await resultDataProvider.loadExternalL3L4ByBaseKey(validL3L4Source, new Map([
2200
+ ['SR0001', 'ESUK'],
2201
+ ['SR0002', 'IL'],
2202
+ ]));
2203
+ expect(map.has('SR0001')).toBe(false);
2204
+ expect(map.get('SR0002')).toEqual([{ id: '7302', title: 'L3 From IL Requirement', level: 'L3' }]);
2205
+ });
2206
+ it('should resolve bug responsibility from AreaPath when SAPWBS is empty', () => {
2207
+ const fromEsukAreaPath = resultDataProvider.resolveBugResponsibility({
2208
+ 'Custom.SAPWBS': '',
2209
+ 'System.AreaPath': 'MEWP\\Customer Requirements\\Level 2\\ATP\\ESUK',
2210
+ });
2211
+ const fromIlAreaPath = resultDataProvider.resolveBugResponsibility({
2212
+ 'Custom.SAPWBS': '',
2213
+ 'System.AreaPath': 'MEWP\\Customer Requirements\\Level 2\\ATP',
2214
+ });
2215
+ const unknown = resultDataProvider.resolveBugResponsibility({
2216
+ 'Custom.SAPWBS': '',
2217
+ 'System.AreaPath': 'MEWP\\Other\\Area',
2218
+ });
2219
+ expect(fromEsukAreaPath).toBe('ESUK');
2220
+ expect(fromIlAreaPath).toBe('Elisra');
2221
+ expect(unknown).toBe('Unknown');
2222
+ });
2223
+ it('should handle 1000 external bug rows and keep only in-scope parsed items', async () => {
2224
+ const rows = [];
2225
+ for (let i = 1; i <= 1000; i++) {
2226
+ rows.push({
2227
+ Elisra_SortIndex: String(4000 + (i % 20)),
2228
+ SR: `SR${String(6000 + (i % 50)).padStart(4, '0')}`,
2229
+ TargetWorkItemId: String(900000 + i),
2230
+ Title: `Bug ${i}`,
2231
+ TargetState: i % 10 === 0 ? 'Closed' : 'Active',
2232
+ SAPWBS: i % 2 === 0 ? 'ESUK' : 'IL',
2233
+ });
2234
+ }
2235
+ const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
2236
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce(rows);
2237
+ const startedAt = Date.now();
2238
+ const map = await resultDataProvider.loadExternalBugsByTestCase({
2239
+ name: 'bulk-bugs.xlsx',
2240
+ url: 'https://minio.local/mewp-external-ingestion/MEWP/mewp-external-ingestion/bugs/bulk-bugs.xlsx',
2241
+ sourceType: 'mewpExternalIngestion',
2242
+ });
2243
+ const elapsedMs = Date.now() - startedAt;
2244
+ const totalParsed = [...map.values()].reduce((sum, items) => sum + ((items === null || items === void 0 ? void 0 : items.length) || 0), 0);
2245
+ expect(totalParsed).toBe(900); // every 10th row is closed and filtered out
2246
+ expect(elapsedMs).toBeLessThan(5000);
2247
+ });
2248
+ it('should handle 1000 external L3/L4 rows and map all active links', async () => {
2249
+ const rows = [];
2250
+ for (let i = 1; i <= 1000; i++) {
2251
+ rows.push({
2252
+ SR: `SR${String(7000 + (i % 25)).padStart(4, '0')}`,
2253
+ 'AREA 34': i % 3 === 0 ? 'Level 4' : 'Level 3',
2254
+ 'TargetWorkItemId Level 3': String(800000 + i),
2255
+ TargetTitleLevel3: `L3/L4 Title ${i}`,
2256
+ 'TargetStateLevel 3': i % 11 === 0 ? 'Resolved' : 'Active',
2257
+ 'TargetWorkItemIdLevel 4': String(810000 + i),
2258
+ TargetTitleLevel4: `L4 Title ${i}`,
2259
+ 'TargetStateLevel 4': i % 13 === 0 ? 'Closed' : 'Active',
2260
+ });
2261
+ }
2262
+ const mewpExternalTableUtils = resultDataProvider.mewpExternalTableUtils;
2263
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce(rows);
2264
+ const startedAt = Date.now();
2265
+ const map = await resultDataProvider.loadExternalL3L4ByBaseKey({
2266
+ name: 'bulk-l3l4.xlsx',
2267
+ url: 'https://minio.local/mewp-external-ingestion/MEWP/mewp-external-ingestion/l3l4/bulk-l3l4.xlsx',
2268
+ sourceType: 'mewpExternalIngestion',
2269
+ });
2270
+ const elapsedMs = Date.now() - startedAt;
2271
+ const totalLinks = [...map.values()].reduce((sum, items) => sum + ((items === null || items === void 0 ? void 0 : items.length) || 0), 0);
2272
+ expect(totalLinks).toBeGreaterThan(700);
2273
+ expect(elapsedMs).toBeLessThan(5000);
2274
+ });
2275
+ });
2276
+ describe('MEWP high-volume requirement token parsing', () => {
2277
+ it('should parse 1000 expected-result requirement tokens with noisy fragments', () => {
2278
+ const tokens = [];
2279
+ for (let i = 1; i <= 1000; i++) {
2280
+ const code = `SR${String(10000 + i)}`;
2281
+ tokens.push(code);
2282
+ if (i % 100 === 0)
2283
+ tokens.push(`${code} V3.24`);
2284
+ if (i % 125 === 0)
2285
+ tokens.push(`${code} VVRM2425`);
2286
+ }
2287
+ const sourceText = tokens.join('; ');
2288
+ const startedAt = Date.now();
2289
+ const codes = resultDataProvider.extractRequirementCodesFromText(sourceText);
2290
+ const elapsedMs = Date.now() - startedAt;
2291
+ expect(codes.size).toBe(1000);
2292
+ expect(codes.has('SR10001')).toBe(true);
2293
+ expect(codes.has('SR11000')).toBe(true);
2294
+ expect(elapsedMs).toBeLessThan(3000);
1115
2295
  });
1116
2296
  });
1117
2297
  describe('fetchResultDataForTestReporter (runResultField switch)', () => {