@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.
@@ -2,6 +2,8 @@ import { TFSServices } from '../../helpers/tfs';
2
2
  import ResultDataProvider from '../../modules/ResultDataProvider';
3
3
  import logger from '../../utils/logger';
4
4
  import Utils from '../../utils/testStepParserHelper';
5
+ import axios from 'axios';
6
+ import * as XLSX from 'xlsx';
5
7
 
6
8
  // Mock dependencies
7
9
  jest.mock('../../helpers/tfs');
@@ -23,6 +25,12 @@ describe('ResultDataProvider', () => {
23
25
  const mockToken = 'mock-token';
24
26
  const mockProjectName = 'test-project';
25
27
  const mockTestPlanId = '12345';
28
+ const buildWorkbookBuffer = (rows: any[][]): Buffer => {
29
+ const worksheet = XLSX.utils.aoa_to_sheet(rows);
30
+ const workbook = XLSX.utils.book_new();
31
+ XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
32
+ return XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }) as Buffer;
33
+ };
26
34
 
27
35
  beforeEach(() => {
28
36
  jest.clearAllMocks();
@@ -1075,101 +1083,6 @@ describe('ResultDataProvider', () => {
1075
1083
  });
1076
1084
 
1077
1085
  describe('getMewpL2CoverageFlatResults', () => {
1078
- it('should support query-mode requirement scope for MEWP coverage', 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: [{ testCaseId: 101, lastRunId: 10, lastResultId: 20, testCaseName: 'TC 101' }],
1084
- testCasesItems: [
1085
- {
1086
- workItem: {
1087
- id: 101,
1088
- workItemFields: [{ key: 'System.Title', value: 'TC 101' }],
1089
- },
1090
- },
1091
- ],
1092
- },
1093
- ]);
1094
- jest.spyOn(resultDataProvider as any, 'fetchMewpRequirementTypeNames').mockResolvedValueOnce([
1095
- 'Requirement',
1096
- ]);
1097
- jest.spyOn(resultDataProvider as any, 'fetchWorkItemsByIds').mockResolvedValueOnce([
1098
- {
1099
- id: 9001,
1100
- fields: {
1101
- 'System.WorkItemType': 'Requirement',
1102
- 'System.Title': 'Requirement from query',
1103
- 'Custom.CustomerId': 'SR3001',
1104
- 'System.AreaPath': 'MEWP\\IL',
1105
- },
1106
- relations: [
1107
- {
1108
- rel: 'Microsoft.VSTS.Common.TestedBy-Forward',
1109
- url: 'https://dev.azure.com/org/_apis/wit/workItems/101',
1110
- },
1111
- ],
1112
- },
1113
- ]);
1114
- jest.spyOn(resultDataProvider as any, 'fetchAllResultDataTestReporter').mockResolvedValueOnce([
1115
- {
1116
- testCaseId: 101,
1117
- testCase: { id: 101, name: 'TC 101' },
1118
- iteration: {
1119
- actionResults: [{ action: 'Validate SR3001', expected: '', outcome: 'Passed' }],
1120
- },
1121
- },
1122
- ]);
1123
-
1124
- const TicketsProviderMock: any = require('../../modules/TicketsDataProvider').default;
1125
- TicketsProviderMock.mockImplementationOnce(() => ({
1126
- GetQueryResultsFromWiql: jest.fn().mockResolvedValue({
1127
- fetchedWorkItems: [
1128
- {
1129
- id: 9001,
1130
- fields: {
1131
- 'System.WorkItemType': 'Requirement',
1132
- 'System.Title': 'Requirement from query',
1133
- 'Custom.CustomerId': 'SR3001',
1134
- 'System.AreaPath': 'MEWP\\IL',
1135
- },
1136
- },
1137
- ],
1138
- }),
1139
- }));
1140
-
1141
- const result = await (resultDataProvider as any).getMewpL2CoverageFlatResults(
1142
- '123',
1143
- mockProjectName,
1144
- [1],
1145
- {
1146
- linkedQueryMode: 'query',
1147
- testAssociatedQuery: { wiql: { href: 'https://example.com/wiql' } },
1148
- }
1149
- );
1150
-
1151
- const row = result.rows.find((item: any) => item['Customer ID'] === 'SR3001');
1152
- expect(row).toEqual(
1153
- expect.objectContaining({
1154
- 'Title (Customer name)': 'Requirement from query',
1155
- 'Responsibility - SAPWBS (ESUK/IL)': 'IL',
1156
- 'Test case id': 101,
1157
- 'Test case title': 'TC 101',
1158
- 'Number of passed steps': 1,
1159
- 'Number of failed steps': 0,
1160
- 'Number of not run tests': 0,
1161
- })
1162
- );
1163
-
1164
- expect(TicketsProviderMock).toHaveBeenCalled();
1165
- const instance = TicketsProviderMock.mock.results[0].value;
1166
- expect(instance.GetQueryResultsFromWiql).toHaveBeenCalledWith(
1167
- 'https://example.com/wiql',
1168
- true,
1169
- expect.any(Map)
1170
- );
1171
- });
1172
-
1173
1086
  it('should map SR ids from steps and output requirement-test-case coverage rows', async () => {
1174
1087
  jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1175
1088
  jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
@@ -1193,6 +1106,8 @@ describe('ResultDataProvider', () => {
1193
1106
  {
1194
1107
  workItemId: 5001,
1195
1108
  requirementId: 'SR1001',
1109
+ baseKey: 'SR1001',
1110
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1196
1111
  title: 'Covered requirement',
1197
1112
  responsibility: 'ESUK',
1198
1113
  linkedTestCaseIds: [101],
@@ -1200,6 +1115,8 @@ describe('ResultDataProvider', () => {
1200
1115
  {
1201
1116
  workItemId: 5002,
1202
1117
  requirementId: 'SR1002',
1118
+ baseKey: 'SR1002',
1119
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1203
1120
  title: 'Referenced from non-linked step text',
1204
1121
  responsibility: 'IL',
1205
1122
  linkedTestCaseIds: [],
@@ -1207,6 +1124,8 @@ describe('ResultDataProvider', () => {
1207
1124
  {
1208
1125
  workItemId: 5003,
1209
1126
  requirementId: 'SR1003',
1127
+ baseKey: 'SR1003',
1128
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1210
1129
  title: 'Not covered by any test case',
1211
1130
  responsibility: 'IL',
1212
1131
  linkedTestCaseIds: [],
@@ -1220,11 +1139,11 @@ describe('ResultDataProvider', () => {
1220
1139
  actionResults: [
1221
1140
  {
1222
1141
  action: 'Validate <b>S</b><b>R</b> 1 0 0 1 happy path',
1223
- expected: '',
1142
+ expected: '<b>S</b><b>R</b> 1 0 0 1',
1224
1143
  outcome: 'Passed',
1225
1144
  },
1226
- { action: 'Validate SR1001 failed flow', expected: '&nbsp;', outcome: 'Failed' },
1227
- { action: '', expected: 'Pending S R 1 0 0 1 scenario', outcome: 'Unspecified' },
1145
+ { action: 'Validate SR1001 failed flow', expected: 'SR1001', outcome: 'Failed' },
1146
+ { action: '', expected: 'S R 1 0 0 1', outcome: 'Unspecified' },
1228
1147
  ],
1229
1148
  },
1230
1149
  },
@@ -1241,7 +1160,7 @@ describe('ResultDataProvider', () => {
1241
1160
  stepId: '1',
1242
1161
  stepPosition: '1',
1243
1162
  action: 'Definition contains SR1002',
1244
- expected: '',
1163
+ expected: 'SR1002',
1245
1164
  isSharedStepTitle: false,
1246
1165
  },
1247
1166
  ]);
@@ -1255,59 +1174,71 @@ describe('ResultDataProvider', () => {
1255
1174
  expect(result).toEqual(
1256
1175
  expect.objectContaining({
1257
1176
  sheetName: expect.stringContaining('MEWP L2 Coverage'),
1258
- columnOrder: expect.arrayContaining(['Customer ID', 'Test case id', 'Number of not run tests']),
1177
+ columnOrder: expect.arrayContaining(['L2 REQ ID', 'L2 REQ Title', 'L2 Run Status']),
1259
1178
  })
1260
1179
  );
1261
1180
 
1262
- const covered = result.rows.find(
1263
- (row: any) => row['Customer ID'] === 'SR1001' && row['Test case id'] === 101
1264
- );
1265
- const inferredByStepText = result.rows.find(
1266
- (row: any) => row['Customer ID'] === 'SR1002' && row['Test case id'] === 102
1267
- );
1268
- const uncovered = result.rows.find(
1269
- (row: any) =>
1270
- row['Customer ID'] === 'SR1003' &&
1271
- (row['Test case id'] === '' || row['Test case id'] === undefined || row['Test case id'] === null)
1272
- );
1181
+ const covered = result.rows.find((row: any) => row['L2 REQ ID'] === 'SR1001');
1182
+ const inferredByStepText = result.rows.find((row: any) => row['L2 REQ ID'] === 'SR1002');
1183
+ const uncovered = result.rows.find((row: any) => row['L2 REQ ID'] === 'SR1003');
1273
1184
 
1274
1185
  expect(covered).toEqual(
1275
1186
  expect.objectContaining({
1276
- 'Title (Customer name)': 'Covered requirement',
1277
- 'Responsibility - SAPWBS (ESUK/IL)': 'ESUK',
1278
- 'Test case title': 'TC 101',
1279
- 'Number of passed steps': 1,
1280
- 'Number of failed steps': 1,
1281
- 'Number of not run tests': 1,
1187
+ 'L2 REQ Title': 'Covered requirement',
1188
+ 'L2 SubSystem': '',
1189
+ 'L2 Run Status': 'Fail',
1190
+ 'Bug ID': '',
1191
+ 'L3 REQ ID': '',
1192
+ 'L4 REQ ID': '',
1282
1193
  })
1283
1194
  );
1284
1195
  expect(inferredByStepText).toEqual(
1285
1196
  expect.objectContaining({
1286
- 'Title (Customer name)': 'Referenced from non-linked step text',
1287
- 'Responsibility - SAPWBS (ESUK/IL)': 'IL',
1288
- 'Test case title': 'TC 102',
1289
- 'Number of passed steps': 0,
1290
- 'Number of failed steps': 0,
1291
- 'Number of not run tests': 1,
1197
+ 'L2 REQ Title': 'Referenced from non-linked step text',
1198
+ 'L2 SubSystem': '',
1199
+ 'L2 Run Status': 'Not Run',
1292
1200
  })
1293
1201
  );
1294
1202
  expect(uncovered).toEqual(
1295
1203
  expect.objectContaining({
1296
- 'Title (Customer name)': 'Not covered by any test case',
1297
- 'Responsibility - SAPWBS (ESUK/IL)': 'IL',
1298
- 'Test case title': '',
1299
- 'Number of passed steps': 0,
1300
- 'Number of failed steps': 0,
1301
- 'Number of not run tests': 0,
1204
+ 'L2 REQ Title': 'Not covered by any test case',
1205
+ 'L2 SubSystem': '',
1206
+ 'L2 Run Status': 'Not Run',
1302
1207
  })
1303
1208
  );
1304
1209
  });
1305
1210
 
1306
1211
  it('should extract SR ids from HTML/spacing and return unique ids per step text', () => {
1307
1212
  const text =
1308
- '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';
1213
+ '<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';
1309
1214
  const codes = (resultDataProvider as any).extractRequirementCodesFromText(text);
1310
- expect([...codes].sort()).toEqual(['SR1', 'SR2', 'SR3', 'SR4']);
1215
+ expect([...codes].sort()).toEqual(['SR0001', 'SR0002', 'SR0003', 'SR0004']);
1216
+ });
1217
+
1218
+ it('should keep only clean SR tokens and ignore noisy version/VVRM fragments', () => {
1219
+ const extract = (text: string) =>
1220
+ [...((resultDataProvider as any).extractRequirementCodesFromText(text) as Set<string>)].sort();
1221
+
1222
+ expect(extract('SR12413; SR24513; SR25135 VVRM2425')).toEqual(['SR12413', 'SR24513']);
1223
+ expect(extract('SR12413; SR12412; SR12413-V3.24')).toEqual(['SR12412', 'SR12413']);
1224
+ expect(extract('SR12413; SR12412; SR12413 V3.24')).toEqual(['SR12412', 'SR12413']);
1225
+ expect(extract('SR12413, SR12412, SR12413-V3.24')).toEqual(['SR12412', 'SR12413']);
1226
+
1227
+ const extractWithSuffix = (text: string) =>
1228
+ [
1229
+ ...((resultDataProvider as any).extractRequirementCodesFromExpectedText(
1230
+ text,
1231
+ true
1232
+ ) as Set<string>),
1233
+ ].sort();
1234
+ expect(extractWithSuffix('SR0095-2,3; SR0100-1,2,3,4')).toEqual([
1235
+ 'SR0095-2',
1236
+ 'SR0095-3',
1237
+ 'SR0100-1',
1238
+ 'SR0100-2',
1239
+ 'SR0100-3',
1240
+ 'SR0100-4',
1241
+ ]);
1311
1242
  });
1312
1243
 
1313
1244
  it('should not backfill definition steps as not-run when a real run exists but has no action results', async () => {
@@ -1330,6 +1261,8 @@ describe('ResultDataProvider', () => {
1330
1261
  {
1331
1262
  workItemId: 7001,
1332
1263
  requirementId: 'SR2001',
1264
+ baseKey: 'SR2001',
1265
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1333
1266
  title: 'Has run but no actions',
1334
1267
  responsibility: 'ESUK',
1335
1268
  linkedTestCaseIds: [101],
@@ -1364,15 +1297,11 @@ describe('ResultDataProvider', () => {
1364
1297
  [1]
1365
1298
  );
1366
1299
 
1367
- const row = result.rows.find(
1368
- (item: any) => item['Customer ID'] === 'SR2001' && item['Test case id'] === 101
1369
- );
1300
+ const row = result.rows.find((item: any) => item['L2 REQ ID'] === 'SR2001');
1370
1301
  expect(parseSpy).not.toHaveBeenCalled();
1371
1302
  expect(row).toEqual(
1372
1303
  expect.objectContaining({
1373
- 'Number of passed steps': 0,
1374
- 'Number of failed steps': 0,
1375
- 'Number of not run tests': 0,
1304
+ 'L2 Run Status': 'Not Run',
1376
1305
  })
1377
1306
  );
1378
1307
  });
@@ -1387,7 +1316,1421 @@ describe('ResultDataProvider', () => {
1387
1316
  4321
1388
1317
  );
1389
1318
 
1390
- expect(requirementId).toBe('4321');
1319
+ expect(requirementId).toBe('SR4321');
1320
+ });
1321
+
1322
+ it('should derive responsibility from Custom.SAPWBS when present', () => {
1323
+ const responsibility = (resultDataProvider as any).deriveMewpResponsibility({
1324
+ 'Custom.SAPWBS': 'IL',
1325
+ 'System.AreaPath': 'MEWP\\ESUK',
1326
+ });
1327
+
1328
+ expect(responsibility).toBe('IL');
1329
+ });
1330
+
1331
+ it('should derive responsibility from AreaPath suffix ATP/ATP\\\\ESUK when SAPWBS is empty', () => {
1332
+ const esuk = (resultDataProvider as any).deriveMewpResponsibility({
1333
+ 'Custom.SAPWBS': '',
1334
+ 'System.AreaPath': 'MEWP\\Customer Requirements\\Level 2\\ATP\\ESUK',
1335
+ });
1336
+ const il = (resultDataProvider as any).deriveMewpResponsibility({
1337
+ 'Custom.SAPWBS': '',
1338
+ 'System.AreaPath': 'MEWP\\Customer Requirements\\Level 2\\ATP',
1339
+ });
1340
+
1341
+ expect(esuk).toBe('ESUK');
1342
+ expect(il).toBe('IL');
1343
+ });
1344
+
1345
+ it('should emit additive rows for bugs and L3/L4 links without bug duplication', () => {
1346
+ const requirements = [
1347
+ {
1348
+ requirementId: 'SR5303',
1349
+ baseKey: 'SR5303',
1350
+ title: 'Req 5303',
1351
+ subSystem: 'Power',
1352
+ responsibility: 'ESUK',
1353
+ linkedTestCaseIds: [101],
1354
+ },
1355
+ ];
1356
+
1357
+ const requirementIndex = new Map([
1358
+ [
1359
+ 'SR5303',
1360
+ new Map([
1361
+ [
1362
+ 101,
1363
+ {
1364
+ passed: 0,
1365
+ failed: 1,
1366
+ notRun: 0,
1367
+ },
1368
+ ],
1369
+ ]),
1370
+ ],
1371
+ ]);
1372
+
1373
+ const observedTestCaseIdsByRequirement = new Map<string, Set<number>>([
1374
+ ['SR5303', new Set([101])],
1375
+ ]);
1376
+
1377
+ const linkedRequirementsByTestCase = new Map([
1378
+ [
1379
+ 101,
1380
+ {
1381
+ baseKeys: new Set(['SR5303']),
1382
+ fullCodes: new Set(['SR5303']),
1383
+ bugIds: new Set([10003, 20003]),
1384
+ },
1385
+ ],
1386
+ ]);
1387
+
1388
+ const externalBugsByTestCase = new Map([
1389
+ [
1390
+ 101,
1391
+ [
1392
+ { id: 10003, title: 'Bug 10003', responsibility: 'Elisra', requirementBaseKey: 'SR5303' },
1393
+ { id: 20003, title: 'Bug 20003', responsibility: 'ESUK', requirementBaseKey: 'SR5303' },
1394
+ ],
1395
+ ],
1396
+ ]);
1397
+
1398
+ const l3l4ByBaseKey = new Map([
1399
+ [
1400
+ 'SR5303',
1401
+ [
1402
+ { id: '9003', title: 'L3 9003', level: 'L3' as const },
1403
+ { id: '9103', title: 'L4 9103', level: 'L4' as const },
1404
+ ],
1405
+ ],
1406
+ ]);
1407
+
1408
+ const rows = (resultDataProvider as any).buildMewpCoverageRows(
1409
+ requirements,
1410
+ requirementIndex,
1411
+ observedTestCaseIdsByRequirement,
1412
+ linkedRequirementsByTestCase,
1413
+ l3l4ByBaseKey,
1414
+ externalBugsByTestCase
1415
+ );
1416
+
1417
+ expect(rows).toHaveLength(4);
1418
+ expect(rows.map((row: any) => row['Bug ID'])).toEqual([10003, 20003, '', '']);
1419
+ expect(rows[2]).toEqual(
1420
+ expect.objectContaining({
1421
+ 'L3 REQ ID': '9003',
1422
+ 'L4 REQ ID': '',
1423
+ })
1424
+ );
1425
+ expect(rows[3]).toEqual(
1426
+ expect.objectContaining({
1427
+ 'L3 REQ ID': '',
1428
+ 'L4 REQ ID': '9103',
1429
+ })
1430
+ );
1431
+ });
1432
+
1433
+ it('should not emit bug rows from ADO-linked bug ids when external bugs source is empty', () => {
1434
+ const requirements = [
1435
+ {
1436
+ requirementId: 'SR5304',
1437
+ baseKey: 'SR5304',
1438
+ title: 'Req 5304',
1439
+ subSystem: 'Power',
1440
+ responsibility: 'ESUK',
1441
+ linkedTestCaseIds: [101],
1442
+ },
1443
+ ];
1444
+ const requirementIndex = new Map([
1445
+ [
1446
+ 'SR5304',
1447
+ new Map([
1448
+ [
1449
+ 101,
1450
+ {
1451
+ passed: 0,
1452
+ failed: 1,
1453
+ notRun: 0,
1454
+ },
1455
+ ],
1456
+ ]),
1457
+ ],
1458
+ ]);
1459
+ const observedTestCaseIdsByRequirement = new Map<string, Set<number>>([
1460
+ ['SR5304', new Set([101])],
1461
+ ]);
1462
+ const linkedRequirementsByTestCase = new Map([
1463
+ [
1464
+ 101,
1465
+ {
1466
+ baseKeys: new Set(['SR5304']),
1467
+ fullCodes: new Set(['SR5304']),
1468
+ bugIds: new Set([55555]), // must be ignored in MEWP coverage mode
1469
+ },
1470
+ ],
1471
+ ]);
1472
+
1473
+ const rows = (resultDataProvider as any).buildMewpCoverageRows(
1474
+ requirements,
1475
+ requirementIndex,
1476
+ observedTestCaseIdsByRequirement,
1477
+ linkedRequirementsByTestCase,
1478
+ new Map(),
1479
+ new Map()
1480
+ );
1481
+
1482
+ expect(rows).toHaveLength(1);
1483
+ expect(rows[0]['L2 Run Status']).toBe('Fail');
1484
+ expect(rows[0]['Bug ID']).toBe('');
1485
+ expect(rows[0]['Bug Title']).toBe('');
1486
+ expect(rows[0]['Bug Responsibility']).toBe('');
1487
+ });
1488
+
1489
+ it('should fallback bug responsibility from requirement when external bug row has unknown responsibility', () => {
1490
+ const requirements = [
1491
+ {
1492
+ requirementId: 'SR5305',
1493
+ baseKey: 'SR5305',
1494
+ title: 'Req 5305',
1495
+ subSystem: 'Auth',
1496
+ responsibility: 'IL',
1497
+ linkedTestCaseIds: [202],
1498
+ },
1499
+ ];
1500
+ const requirementIndex = new Map([
1501
+ [
1502
+ 'SR5305',
1503
+ new Map([
1504
+ [
1505
+ 202,
1506
+ {
1507
+ passed: 0,
1508
+ failed: 1,
1509
+ notRun: 0,
1510
+ },
1511
+ ],
1512
+ ]),
1513
+ ],
1514
+ ]);
1515
+ const observedTestCaseIdsByRequirement = new Map<string, Set<number>>([
1516
+ ['SR5305', new Set([202])],
1517
+ ]);
1518
+ const linkedRequirementsByTestCase = new Map([
1519
+ [
1520
+ 202,
1521
+ {
1522
+ baseKeys: new Set(['SR5305']),
1523
+ fullCodes: new Set(['SR5305']),
1524
+ bugIds: new Set(),
1525
+ },
1526
+ ],
1527
+ ]);
1528
+ const externalBugsByTestCase = new Map([
1529
+ [
1530
+ 202,
1531
+ [
1532
+ {
1533
+ id: 99001,
1534
+ title: 'External bug without SAPWBS',
1535
+ responsibility: 'Unknown',
1536
+ requirementBaseKey: 'SR5305',
1537
+ },
1538
+ ],
1539
+ ],
1540
+ ]);
1541
+
1542
+ const rows = (resultDataProvider as any).buildMewpCoverageRows(
1543
+ requirements,
1544
+ requirementIndex,
1545
+ observedTestCaseIdsByRequirement,
1546
+ linkedRequirementsByTestCase,
1547
+ new Map(),
1548
+ externalBugsByTestCase
1549
+ );
1550
+
1551
+ expect(rows).toHaveLength(1);
1552
+ expect(rows[0]['Bug ID']).toBe(99001);
1553
+ expect(rows[0]['Bug Responsibility']).toBe('Elisra');
1554
+ });
1555
+ });
1556
+
1557
+ describe('getMewpInternalValidationFlatResults', () => {
1558
+ it('should skip test cases with no in-scope expected requirements and no linked requirements', async () => {
1559
+ jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1560
+ jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
1561
+ jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
1562
+ {
1563
+ testPointsItems: [
1564
+ { testCaseId: 101, testCaseName: 'TC 101' },
1565
+ { testCaseId: 102, testCaseName: 'TC 102' },
1566
+ ],
1567
+ testCasesItems: [
1568
+ {
1569
+ workItem: {
1570
+ id: 101,
1571
+ workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
1572
+ },
1573
+ },
1574
+ {
1575
+ workItem: {
1576
+ id: 102,
1577
+ workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
1578
+ },
1579
+ },
1580
+ ],
1581
+ },
1582
+ ]);
1583
+ jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
1584
+ {
1585
+ workItemId: 5001,
1586
+ requirementId: 'SR0001',
1587
+ baseKey: 'SR0001',
1588
+ title: 'Req 1',
1589
+ responsibility: 'ESUK',
1590
+ linkedTestCaseIds: [101],
1591
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1592
+ },
1593
+ ]);
1594
+ jest.spyOn(resultDataProvider as any, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(
1595
+ new Map([[101, { baseKeys: new Set(['SR0001']), fullCodes: new Set(['SR0001']) }]])
1596
+ );
1597
+ jest
1598
+ .spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps')
1599
+ .mockResolvedValueOnce([
1600
+ {
1601
+ stepId: '1',
1602
+ stepPosition: '1',
1603
+ action: '',
1604
+ expected: 'SR0001',
1605
+ isSharedStepTitle: false,
1606
+ },
1607
+ ])
1608
+ .mockResolvedValueOnce([
1609
+ {
1610
+ stepId: '1',
1611
+ stepPosition: '1',
1612
+ action: '',
1613
+ expected: '',
1614
+ isSharedStepTitle: false,
1615
+ },
1616
+ ]);
1617
+
1618
+ const result = await (resultDataProvider as any).getMewpInternalValidationFlatResults(
1619
+ '123',
1620
+ mockProjectName,
1621
+ [1]
1622
+ );
1623
+
1624
+ expect(result.rows).toHaveLength(2);
1625
+ expect(result.rows).toEqual(
1626
+ expect.arrayContaining([
1627
+ expect.objectContaining({
1628
+ 'Test Case ID': 101,
1629
+ 'Validation Status': 'Pass',
1630
+ 'Mentioned but Not Linked': '',
1631
+ 'Linked but Not Mentioned': '',
1632
+ }),
1633
+ expect.objectContaining({
1634
+ 'Test Case ID': 102,
1635
+ 'Validation Status': 'Pass',
1636
+ 'Mentioned but Not Linked': '',
1637
+ 'Linked but Not Mentioned': '',
1638
+ }),
1639
+ ])
1640
+ );
1641
+ });
1642
+
1643
+ it('should ignore non-L2 SR mentions and still report only real L2 discrepancies', async () => {
1644
+ jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1645
+ jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
1646
+ jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
1647
+ {
1648
+ testPointsItems: [{ testCaseId: 101, testCaseName: 'TC 101' }],
1649
+ testCasesItems: [
1650
+ {
1651
+ workItem: {
1652
+ id: 101,
1653
+ workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
1654
+ },
1655
+ },
1656
+ ],
1657
+ },
1658
+ ]);
1659
+ jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
1660
+ {
1661
+ workItemId: 5001,
1662
+ requirementId: 'SR0001',
1663
+ baseKey: 'SR0001',
1664
+ title: 'Req 1',
1665
+ responsibility: 'ESUK',
1666
+ linkedTestCaseIds: [101],
1667
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1668
+ },
1669
+ ]);
1670
+ jest.spyOn(resultDataProvider as any, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(
1671
+ new Map()
1672
+ );
1673
+ jest.spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
1674
+ {
1675
+ stepId: '1',
1676
+ stepPosition: '1',
1677
+ action: '',
1678
+ expected: 'SR0001; SR9999',
1679
+ isSharedStepTitle: false,
1680
+ },
1681
+ ]);
1682
+
1683
+ const result = await (resultDataProvider as any).getMewpInternalValidationFlatResults(
1684
+ '123',
1685
+ mockProjectName,
1686
+ [1]
1687
+ );
1688
+
1689
+ expect(result.rows).toHaveLength(1);
1690
+ expect(result.rows[0]).toEqual(
1691
+ expect.objectContaining({
1692
+ 'Test Case ID': 101,
1693
+ 'Mentioned but Not Linked': 'Step 1: SR0001',
1694
+ 'Linked but Not Mentioned': '',
1695
+ 'Validation Status': 'Fail',
1696
+ })
1697
+ );
1698
+ });
1699
+
1700
+ it('should emit Direction A rows with step context for missing sibling links', async () => {
1701
+ jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1702
+ jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
1703
+ jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
1704
+ {
1705
+ testPointsItems: [{ testCaseId: 101, testCaseName: 'TC 101' }],
1706
+ testCasesItems: [
1707
+ {
1708
+ workItem: {
1709
+ id: 101,
1710
+ workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
1711
+ },
1712
+ },
1713
+ ],
1714
+ },
1715
+ ]);
1716
+ jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
1717
+ {
1718
+ workItemId: 5001,
1719
+ requirementId: 'SR0001',
1720
+ baseKey: 'SR0001',
1721
+ title: 'Req parent',
1722
+ responsibility: 'ESUK',
1723
+ linkedTestCaseIds: [101],
1724
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1725
+ },
1726
+ {
1727
+ workItemId: 5002,
1728
+ requirementId: 'SR0001-1',
1729
+ baseKey: 'SR0001',
1730
+ title: 'Req child',
1731
+ responsibility: 'ESUK',
1732
+ linkedTestCaseIds: [],
1733
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
1734
+ },
1735
+ ]);
1736
+ jest.spyOn(resultDataProvider as any, 'buildLinkedRequirementsByTestCase').mockResolvedValueOnce(
1737
+ new Map([[101, { baseKeys: new Set(['SR0001']), fullCodes: new Set(['SR0001']) }]])
1738
+ );
1739
+ jest.spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
1740
+ {
1741
+ stepId: '2',
1742
+ stepPosition: '2',
1743
+ action: '',
1744
+ expected: 'SR0001; SR0002',
1745
+ isSharedStepTitle: false,
1746
+ },
1747
+ ]);
1748
+
1749
+ const result = await (resultDataProvider as any).getMewpInternalValidationFlatResults(
1750
+ '123',
1751
+ mockProjectName,
1752
+ [1]
1753
+ );
1754
+
1755
+ expect(result.rows).toEqual(
1756
+ expect.arrayContaining([
1757
+ expect.objectContaining({
1758
+ 'Test Case ID': 101,
1759
+ 'Mentioned but Not Linked': expect.stringContaining('Step 2: SR0001-1'),
1760
+ 'Validation Status': 'Fail',
1761
+ }),
1762
+ ])
1763
+ );
1764
+ });
1765
+
1766
+ it('should not duplicate Direction A discrepancy when same requirement is repeated in multiple steps', async () => {
1767
+ jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1768
+ jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
1769
+ jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
1770
+ {
1771
+ testPointsItems: [{ testCaseId: 101, testCaseName: 'TC 101' }],
1772
+ testCasesItems: [
1773
+ {
1774
+ workItem: {
1775
+ id: 101,
1776
+ workItemFields: [{ key: 'Steps', value: '<steps></steps>' }],
1777
+ },
1778
+ },
1779
+ ],
1780
+ },
1781
+ ]);
1782
+ jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
1783
+ {
1784
+ workItemId: 5001,
1785
+ requirementId: 'SR0001',
1786
+ baseKey: 'SR0001',
1787
+ title: 'Req 1',
1788
+ responsibility: 'ESUK',
1789
+ linkedTestCaseIds: [],
1790
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1791
+ },
1792
+ ]);
1793
+ jest
1794
+ .spyOn(resultDataProvider as any, 'buildLinkedRequirementsByTestCase')
1795
+ .mockResolvedValueOnce(new Map());
1796
+ jest.spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
1797
+ {
1798
+ stepId: '1',
1799
+ stepPosition: '1',
1800
+ action: '',
1801
+ expected: 'SR0001',
1802
+ isSharedStepTitle: false,
1803
+ },
1804
+ {
1805
+ stepId: '2',
1806
+ stepPosition: '2',
1807
+ action: '',
1808
+ expected: 'SR0001',
1809
+ isSharedStepTitle: false,
1810
+ },
1811
+ ]);
1812
+
1813
+ const result = await (resultDataProvider as any).getMewpInternalValidationFlatResults(
1814
+ '123',
1815
+ mockProjectName,
1816
+ [1]
1817
+ );
1818
+
1819
+ expect(result.rows).toHaveLength(1);
1820
+ expect(result.rows[0]).toEqual(
1821
+ expect.objectContaining({
1822
+ 'Test Case ID': 101,
1823
+ 'Mentioned but Not Linked': 'Step 1: SR0001',
1824
+ 'Linked but Not Mentioned': '',
1825
+ 'Validation Status': 'Fail',
1826
+ })
1827
+ );
1828
+ });
1829
+
1830
+ it('should produce one detailed row per test case with correct bidirectional discrepancies', async () => {
1831
+ const mockDetailedStepsByTestCase = new Map<number, any[]>([
1832
+ [
1833
+ 201,
1834
+ [
1835
+ {
1836
+ stepId: '1',
1837
+ stepPosition: '1',
1838
+ action: 'Validate parent SR0511 and SR0095 siblings',
1839
+ expected: '<b>sr0511</b>; SR0095-2,3; VVRM-05',
1840
+ isSharedStepTitle: false,
1841
+ },
1842
+ {
1843
+ stepId: '2',
1844
+ stepPosition: '2',
1845
+ action: 'Noisy requirement-like token should be ignored',
1846
+ expected: 'SR0511-V3.24',
1847
+ isSharedStepTitle: false,
1848
+ },
1849
+ {
1850
+ stepId: '3',
1851
+ stepPosition: '3',
1852
+ action: 'Regression note',
1853
+ expected: 'Verification note only',
1854
+ isSharedStepTitle: false,
1855
+ },
1856
+ ],
1857
+ ],
1858
+ [
1859
+ 202,
1860
+ [
1861
+ {
1862
+ stepId: '1',
1863
+ stepPosition: '1',
1864
+ action: 'Linked SR0200 exists but is not cleanly mentioned in expected result',
1865
+ expected: 'VVRM-22; SR0200 V3.1',
1866
+ isSharedStepTitle: false,
1867
+ },
1868
+ {
1869
+ stepId: '2',
1870
+ stepPosition: '2',
1871
+ action: 'Execution notes',
1872
+ expected: 'Notes without SR requirement token',
1873
+ isSharedStepTitle: false,
1874
+ },
1875
+ ],
1876
+ ],
1877
+ [
1878
+ 203,
1879
+ [
1880
+ {
1881
+ stepId: '1',
1882
+ stepPosition: '1',
1883
+ action: 'Primary requirement validation for SR0100-1',
1884
+ expected: '<i>SR0100-1</i>',
1885
+ isSharedStepTitle: false,
1886
+ },
1887
+ {
1888
+ stepId: '3',
1889
+ stepPosition: '3',
1890
+ action: 'Repeated mention should not create duplicate mismatch',
1891
+ expected: 'SR0100-1; SR0100-1',
1892
+ isSharedStepTitle: false,
1893
+ },
1894
+ ],
1895
+ ],
1896
+ ]);
1897
+ const mockLinkedRequirementsByTestCase = new Map<number, any>([
1898
+ [
1899
+ 201,
1900
+ {
1901
+ baseKeys: new Set(['SR0511', 'SR0095', 'SR8888']),
1902
+ fullCodes: new Set(['SR0511', 'SR0095-2', 'SR8888']),
1903
+ },
1904
+ ],
1905
+ [
1906
+ 202,
1907
+ {
1908
+ baseKeys: new Set(['SR0200']),
1909
+ fullCodes: new Set(['SR0200']),
1910
+ },
1911
+ ],
1912
+ [
1913
+ 203,
1914
+ {
1915
+ baseKeys: new Set(['SR0100']),
1916
+ fullCodes: new Set(['SR0100-1']),
1917
+ },
1918
+ ],
1919
+ ]);
1920
+
1921
+ jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
1922
+ jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
1923
+ jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
1924
+ {
1925
+ testPointsItems: [
1926
+ { testCaseId: 201, testCaseName: 'TC 201 - Mixed discrepancies' },
1927
+ { testCaseId: 202, testCaseName: 'TC 202 - Link only' },
1928
+ { testCaseId: 203, testCaseName: 'TC 203 - Fully valid' },
1929
+ ],
1930
+ testCasesItems: [
1931
+ {
1932
+ workItem: {
1933
+ id: 201,
1934
+ workItemFields: [{ key: 'Steps', value: '<steps id="mock-steps-tc-201"></steps>' }],
1935
+ },
1936
+ },
1937
+ {
1938
+ workItem: {
1939
+ id: 202,
1940
+ workItemFields: [{ key: 'Steps', value: '<steps id="mock-steps-tc-202"></steps>' }],
1941
+ },
1942
+ },
1943
+ {
1944
+ workItem: {
1945
+ id: 203,
1946
+ workItemFields: [{ key: 'Steps', value: '<steps id="mock-steps-tc-203"></steps>' }],
1947
+ },
1948
+ },
1949
+ ],
1950
+ },
1951
+ ]);
1952
+ jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
1953
+ {
1954
+ workItemId: 6001,
1955
+ requirementId: 'SR0511',
1956
+ baseKey: 'SR0511',
1957
+ title: 'Parent 0511',
1958
+ responsibility: 'ESUK',
1959
+ linkedTestCaseIds: [201],
1960
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1961
+ },
1962
+ {
1963
+ workItemId: 6002,
1964
+ requirementId: 'SR0511-1',
1965
+ baseKey: 'SR0511',
1966
+ title: 'Child 0511-1',
1967
+ responsibility: 'ESUK',
1968
+ linkedTestCaseIds: [],
1969
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
1970
+ },
1971
+ {
1972
+ workItemId: 6003,
1973
+ requirementId: 'SR0511-2',
1974
+ baseKey: 'SR0511',
1975
+ title: 'Child 0511-2',
1976
+ responsibility: 'ESUK',
1977
+ linkedTestCaseIds: [],
1978
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
1979
+ },
1980
+ {
1981
+ workItemId: 6004,
1982
+ requirementId: 'SR0095-2',
1983
+ baseKey: 'SR0095',
1984
+ title: 'SR0095 child 2',
1985
+ responsibility: 'ESUK',
1986
+ linkedTestCaseIds: [],
1987
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
1988
+ },
1989
+ {
1990
+ workItemId: 6005,
1991
+ requirementId: 'SR0095-3',
1992
+ baseKey: 'SR0095',
1993
+ title: 'SR0095 child 3',
1994
+ responsibility: 'ESUK',
1995
+ linkedTestCaseIds: [],
1996
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
1997
+ },
1998
+ {
1999
+ workItemId: 6006,
2000
+ requirementId: 'SR0200',
2001
+ baseKey: 'SR0200',
2002
+ title: 'SR0200 standalone',
2003
+ responsibility: 'ESUK',
2004
+ linkedTestCaseIds: [202],
2005
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
2006
+ },
2007
+ {
2008
+ workItemId: 6007,
2009
+ requirementId: 'SR0100-1',
2010
+ baseKey: 'SR0100',
2011
+ title: 'SR0100 child 1',
2012
+ responsibility: 'ESUK',
2013
+ linkedTestCaseIds: [203],
2014
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
2015
+ },
2016
+ ]);
2017
+ jest
2018
+ .spyOn(resultDataProvider as any, 'buildLinkedRequirementsByTestCase')
2019
+ .mockResolvedValueOnce(mockLinkedRequirementsByTestCase);
2020
+ jest
2021
+ .spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps')
2022
+ .mockImplementation(async (...args: unknown[]) => {
2023
+ const stepsXml = String(args?.[0] || '');
2024
+ const testCaseMatch = /mock-steps-tc-(\d+)/i.exec(stepsXml);
2025
+ const testCaseId = Number(testCaseMatch?.[1] || 0);
2026
+ return mockDetailedStepsByTestCase.get(testCaseId) || [];
2027
+ });
2028
+
2029
+ const result = await (resultDataProvider as any).getMewpInternalValidationFlatResults(
2030
+ '123',
2031
+ mockProjectName,
2032
+ [1]
2033
+ );
2034
+
2035
+ expect(result.rows).toHaveLength(3);
2036
+ const byTestCase = new Map<number, any>(result.rows.map((row: any) => [row['Test Case ID'], row]));
2037
+ expect(new Set(result.rows.map((row: any) => row['Test Case ID']))).toEqual(new Set([201, 202, 203]));
2038
+
2039
+ expect(byTestCase.get(201)).toEqual(
2040
+ expect.objectContaining({
2041
+ 'Test Case Title': 'TC 201 - Mixed discrepancies',
2042
+ 'Mentioned but Not Linked': 'Step 1: SR0095-3, SR0511-1, SR0511-2',
2043
+ 'Linked but Not Mentioned': 'SR8888',
2044
+ 'Validation Status': 'Fail',
2045
+ })
2046
+ );
2047
+ expect(String(byTestCase.get(201)['Mentioned but Not Linked'] || '')).not.toContain('VVRM');
2048
+
2049
+ expect(byTestCase.get(202)).toEqual(
2050
+ expect.objectContaining({
2051
+ 'Test Case Title': 'TC 202 - Link only',
2052
+ 'Mentioned but Not Linked': '',
2053
+ 'Linked but Not Mentioned': 'SR0200',
2054
+ 'Validation Status': 'Fail',
2055
+ })
2056
+ );
2057
+
2058
+ expect(byTestCase.get(203)).toEqual(
2059
+ expect.objectContaining({
2060
+ 'Test Case Title': 'TC 203 - Fully valid',
2061
+ 'Mentioned but Not Linked': '',
2062
+ 'Linked but Not Mentioned': '',
2063
+ 'Validation Status': 'Pass',
2064
+ })
2065
+ );
2066
+
2067
+ expect((resultDataProvider as any).testStepParserHelper.parseTestSteps).toHaveBeenCalledTimes(3);
2068
+ const parseCalls = ((resultDataProvider as any).testStepParserHelper.parseTestSteps as jest.Mock).mock.calls;
2069
+ const parsedCaseIds = parseCalls
2070
+ .map(([xml]: [string]) => {
2071
+ const match = /mock-steps-tc-(\d+)/i.exec(String(xml || ''));
2072
+ return Number(match?.[1] || 0);
2073
+ })
2074
+ .filter((id: number) => Number.isFinite(id) && id > 0);
2075
+ expect(new Set(parsedCaseIds)).toEqual(new Set([201, 202, 203]));
2076
+ });
2077
+
2078
+ it('should parse TC-0042 mixed expected text and keep only valid SR requirement codes', async () => {
2079
+ jest.spyOn(resultDataProvider as any, 'fetchTestPlanName').mockResolvedValueOnce('Plan A');
2080
+ jest.spyOn(resultDataProvider as any, 'fetchTestSuites').mockResolvedValueOnce([{ testSuiteId: 1 }]);
2081
+ jest.spyOn(resultDataProvider as any, 'fetchTestData').mockResolvedValueOnce([
2082
+ {
2083
+ testPointsItems: [{ testCaseId: 42, testCaseName: 'TC-0042' }],
2084
+ testCasesItems: [
2085
+ {
2086
+ workItem: {
2087
+ id: 42,
2088
+ workItemFields: [{ key: 'Steps', value: '<steps id="mock-steps-tc-0042"></steps>' }],
2089
+ },
2090
+ },
2091
+ ],
2092
+ },
2093
+ ]);
2094
+ jest.spyOn(resultDataProvider as any, 'fetchMewpL2Requirements').mockResolvedValueOnce([
2095
+ {
2096
+ workItemId: 7001,
2097
+ requirementId: 'SR0036',
2098
+ baseKey: 'SR0036',
2099
+ title: 'SR0036',
2100
+ responsibility: 'ESUK',
2101
+ linkedTestCaseIds: [],
2102
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
2103
+ },
2104
+ {
2105
+ workItemId: 7002,
2106
+ requirementId: 'SR0215',
2107
+ baseKey: 'SR0215',
2108
+ title: 'SR0215',
2109
+ responsibility: 'ESUK',
2110
+ linkedTestCaseIds: [],
2111
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
2112
+ },
2113
+ {
2114
+ workItemId: 7003,
2115
+ requirementId: 'SR0539',
2116
+ baseKey: 'SR0539',
2117
+ title: 'SR0539',
2118
+ responsibility: 'ESUK',
2119
+ linkedTestCaseIds: [],
2120
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
2121
+ },
2122
+ {
2123
+ workItemId: 7004,
2124
+ requirementId: 'SR0348',
2125
+ baseKey: 'SR0348',
2126
+ title: 'SR0348',
2127
+ responsibility: 'ESUK',
2128
+ linkedTestCaseIds: [],
2129
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
2130
+ },
2131
+ {
2132
+ workItemId: 7005,
2133
+ requirementId: 'SR0027',
2134
+ baseKey: 'SR0027',
2135
+ title: 'SR0027',
2136
+ responsibility: 'ESUK',
2137
+ linkedTestCaseIds: [],
2138
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
2139
+ },
2140
+ {
2141
+ workItemId: 7006,
2142
+ requirementId: 'SR0041',
2143
+ baseKey: 'SR0041',
2144
+ title: 'SR0041',
2145
+ responsibility: 'ESUK',
2146
+ linkedTestCaseIds: [],
2147
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
2148
+ },
2149
+ {
2150
+ workItemId: 7007,
2151
+ requirementId: 'SR0550',
2152
+ baseKey: 'SR0550',
2153
+ title: 'SR0550',
2154
+ responsibility: 'ESUK',
2155
+ linkedTestCaseIds: [],
2156
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
2157
+ },
2158
+ {
2159
+ workItemId: 7008,
2160
+ requirementId: 'SR0550-2',
2161
+ baseKey: 'SR0550',
2162
+ title: 'SR0550-2',
2163
+ responsibility: 'ESUK',
2164
+ linkedTestCaseIds: [],
2165
+ areaPath: 'MEWP\\Customer Requirements\\Level 2\\MOP',
2166
+ },
2167
+ {
2168
+ workItemId: 7009,
2169
+ requirementId: 'SR0817',
2170
+ baseKey: 'SR0817',
2171
+ title: 'SR0817',
2172
+ responsibility: 'ESUK',
2173
+ linkedTestCaseIds: [],
2174
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
2175
+ },
2176
+ {
2177
+ workItemId: 7010,
2178
+ requirementId: 'SR0818',
2179
+ baseKey: 'SR0818',
2180
+ title: 'SR0818',
2181
+ responsibility: 'ESUK',
2182
+ linkedTestCaseIds: [],
2183
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
2184
+ },
2185
+ {
2186
+ workItemId: 7011,
2187
+ requirementId: 'SR0859',
2188
+ baseKey: 'SR0859',
2189
+ title: 'SR0859',
2190
+ responsibility: 'ESUK',
2191
+ linkedTestCaseIds: [],
2192
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
2193
+ },
2194
+ ]);
2195
+ jest
2196
+ .spyOn(resultDataProvider as any, 'buildLinkedRequirementsByTestCase')
2197
+ .mockResolvedValueOnce(
2198
+ new Map([
2199
+ [
2200
+ 42,
2201
+ {
2202
+ baseKeys: new Set([
2203
+ 'SR0036',
2204
+ 'SR0215',
2205
+ 'SR0539',
2206
+ 'SR0348',
2207
+ 'SR0041',
2208
+ 'SR0550',
2209
+ 'SR0817',
2210
+ 'SR0818',
2211
+ 'SR0859',
2212
+ ]),
2213
+ fullCodes: new Set([
2214
+ 'SR0036',
2215
+ 'SR0215',
2216
+ 'SR0539',
2217
+ 'SR0348',
2218
+ 'SR0041',
2219
+ 'SR0550',
2220
+ 'SR0550-2',
2221
+ 'SR0817',
2222
+ 'SR0818',
2223
+ 'SR0859',
2224
+ ]),
2225
+ },
2226
+ ],
2227
+ ])
2228
+ );
2229
+ jest.spyOn((resultDataProvider as any).testStepParserHelper, 'parseTestSteps').mockResolvedValueOnce([
2230
+ {
2231
+ stepId: '1',
2232
+ stepPosition: '1',
2233
+ action:
2234
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
2235
+ expected:
2236
+ '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)',
2237
+ isSharedStepTitle: false,
2238
+ },
2239
+ {
2240
+ stepId: '2',
2241
+ stepPosition: '2',
2242
+ action:
2243
+ 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
2244
+ expected:
2245
+ 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. (SR0539; SR0348; VVRM-1)',
2246
+ isSharedStepTitle: false,
2247
+ },
2248
+ {
2249
+ stepId: '3',
2250
+ stepPosition: '3',
2251
+ action:
2252
+ 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
2253
+ expected:
2254
+ 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. (SR0027, SR0036; SR0041; VVRM-2)',
2255
+ isSharedStepTitle: false,
2256
+ },
2257
+ {
2258
+ stepId: '4',
2259
+ stepPosition: '4',
2260
+ action:
2261
+ 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
2262
+ expected:
2263
+ 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. (SR0550-2)',
2264
+ isSharedStepTitle: false,
2265
+ },
2266
+ {
2267
+ stepId: '5',
2268
+ stepPosition: '5',
2269
+ action: 'Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit.',
2270
+ expected: 'Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit. (VVRM-1)',
2271
+ isSharedStepTitle: false,
2272
+ },
2273
+ {
2274
+ stepId: '6',
2275
+ stepPosition: '6',
2276
+ action:
2277
+ 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet consectetur adipisci velit.',
2278
+ expected:
2279
+ 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet consectetur adipisci velit. (SR0041; SR0550; VVRM-3)',
2280
+ isSharedStepTitle: false,
2281
+ },
2282
+ ]);
2283
+
2284
+ const result = await (resultDataProvider as any).getMewpInternalValidationFlatResults(
2285
+ '123',
2286
+ mockProjectName,
2287
+ [1]
2288
+ );
2289
+
2290
+ expect(result.rows).toHaveLength(1);
2291
+ expect(result.rows[0]).toEqual(
2292
+ expect.objectContaining({
2293
+ 'Test Case ID': 42,
2294
+ 'Test Case Title': 'TC-0042',
2295
+ 'Mentioned but Not Linked': 'Step 3: SR0027',
2296
+ 'Linked but Not Mentioned': 'SR0817; SR0818; SR0859',
2297
+ 'Validation Status': 'Fail',
2298
+ })
2299
+ );
2300
+ expect(String(result.rows[0]['Mentioned but Not Linked'] || '')).not.toContain('VVRM');
2301
+ expect(String(result.rows[0]['Linked but Not Mentioned'] || '')).not.toContain('VVRM');
2302
+ });
2303
+ });
2304
+
2305
+ describe('MEWP rel fallback scoping', () => {
2306
+ it('should fallback to previous Rel run when latest selected Rel has no run for a test case', async () => {
2307
+ const suites = [
2308
+ { testSuiteId: 10, suiteName: 'Rel10 / Validation' },
2309
+ { testSuiteId: 11, suiteName: 'Rel11 / Validation' },
2310
+ ];
2311
+ const allSuites = [
2312
+ { testSuiteId: 10, suiteName: 'Rel10 / Validation' },
2313
+ { testSuiteId: 11, suiteName: 'Rel11 / Validation' },
2314
+ ];
2315
+ const rawTestData = [
2316
+ {
2317
+ suiteName: 'Rel10 / Validation',
2318
+ testPointsItems: [
2319
+ { testCaseId: 501, lastRunId: 100, lastResultId: 200 },
2320
+ { testCaseId: 502, lastRunId: 300, lastResultId: 400 },
2321
+ ],
2322
+ testCasesItems: [{ workItem: { id: 501 } }, { workItem: { id: 502 } }],
2323
+ },
2324
+ {
2325
+ suiteName: 'Rel11 / Validation',
2326
+ testPointsItems: [
2327
+ { testCaseId: 501, lastRunId: 0, lastResultId: 0 },
2328
+ { testCaseId: 502, lastRunId: 500, lastResultId: 600 },
2329
+ ],
2330
+ testCasesItems: [{ workItem: { id: 501 } }, { workItem: { id: 502 } }],
2331
+ },
2332
+ ];
2333
+
2334
+ const fetchSuitesSpy = jest
2335
+ .spyOn(resultDataProvider as any, 'fetchTestSuites')
2336
+ .mockResolvedValueOnce(suites)
2337
+ .mockResolvedValueOnce(allSuites);
2338
+ const fetchDataSpy = jest
2339
+ .spyOn(resultDataProvider as any, 'fetchTestData')
2340
+ .mockResolvedValueOnce(rawTestData);
2341
+
2342
+ const scoped = await (resultDataProvider as any).fetchMewpScopedTestData(
2343
+ '123',
2344
+ mockProjectName,
2345
+ [11],
2346
+ true
2347
+ );
2348
+
2349
+ expect(fetchSuitesSpy).toHaveBeenCalledTimes(2);
2350
+ expect(fetchDataSpy).toHaveBeenCalledTimes(1);
2351
+ expect(scoped).toHaveLength(1);
2352
+ const selectedPoints = scoped[0].testPointsItems;
2353
+ const tc501 = selectedPoints.find((item: any) => item.testCaseId === 501);
2354
+ const tc502 = selectedPoints.find((item: any) => item.testCaseId === 502);
2355
+ expect(tc501).toEqual(expect.objectContaining({ lastRunId: 100, lastResultId: 200 }));
2356
+ expect(tc502).toEqual(expect.objectContaining({ lastRunId: 500, lastResultId: 600 }));
2357
+ });
2358
+ });
2359
+
2360
+ describe('MEWP external ingestion validation/parsing', () => {
2361
+ const validBugsSource = {
2362
+ name: 'bugs.xlsx',
2363
+ url: 'https://minio.local/mewp-external-ingestion/MEWP/mewp-external-ingestion/bugs/bugs.xlsx',
2364
+ sourceType: 'mewpExternalIngestion',
2365
+ };
2366
+ const validL3L4Source = {
2367
+ name: 'l3l4.xlsx',
2368
+ url: 'https://minio.local/mewp-external-ingestion/MEWP/mewp-external-ingestion/l3l4/l3l4.xlsx',
2369
+ sourceType: 'mewpExternalIngestion',
2370
+ };
2371
+
2372
+ it('should validate external files when required columns exist in A3 header row', async () => {
2373
+ const bugsBuffer = buildWorkbookBuffer([
2374
+ ['', '', '', '', ''],
2375
+ ['', '', '', '', ''],
2376
+ ['Elisra_SortIndex', 'SR', 'TargetWorkItemId', 'Title', 'TargetState'],
2377
+ ['101', 'SR0001', '9001', 'Bug one', 'Active'],
2378
+ ]);
2379
+ const l3l4Buffer = buildWorkbookBuffer([
2380
+ ['', '', '', '', '', '', '', ''],
2381
+ ['', '', '', '', '', '', '', ''],
2382
+ [
2383
+ 'SR',
2384
+ 'AREA 34',
2385
+ 'TargetWorkItemId Level 3',
2386
+ 'TargetTitleLevel3',
2387
+ 'TargetStateLevel 3',
2388
+ 'TargetWorkItemIdLevel 4',
2389
+ 'TargetTitleLevel4',
2390
+ 'TargetStateLevel 4',
2391
+ ],
2392
+ ['SR0001', 'Level 3', '7001', 'Req L3', 'Active', '', '', ''],
2393
+ ]);
2394
+ const axiosSpy = jest
2395
+ .spyOn(axios, 'get')
2396
+ .mockResolvedValueOnce({ data: bugsBuffer, headers: {} } as any)
2397
+ .mockResolvedValueOnce({ data: l3l4Buffer, headers: {} } as any);
2398
+
2399
+ const result = await (resultDataProvider as any).validateMewpExternalFiles({
2400
+ externalBugsFile: validBugsSource,
2401
+ externalL3L4File: validL3L4Source,
2402
+ });
2403
+
2404
+ expect(axiosSpy).toHaveBeenCalledTimes(2);
2405
+ expect(result.valid).toBe(true);
2406
+ expect(result.bugs).toEqual(
2407
+ expect.objectContaining({
2408
+ valid: true,
2409
+ headerRow: 'A3',
2410
+ matchedRequiredColumns: 5,
2411
+ })
2412
+ );
2413
+ expect(result.l3l4).toEqual(
2414
+ expect.objectContaining({
2415
+ valid: true,
2416
+ headerRow: 'A3',
2417
+ matchedRequiredColumns: 8,
2418
+ })
2419
+ );
2420
+ });
2421
+
2422
+ it('should accept A1 fallback header row for backward compatibility', async () => {
2423
+ const bugsBuffer = buildWorkbookBuffer([
2424
+ ['Elisra_SortIndex', 'SR', 'TargetWorkItemId', 'Title', 'TargetState'],
2425
+ ['101', 'SR0001', '9001', 'Bug one', 'Active'],
2426
+ ]);
2427
+ jest.spyOn(axios, 'get').mockResolvedValueOnce({ data: bugsBuffer, headers: {} } as any);
2428
+
2429
+ const result = await (resultDataProvider as any).validateMewpExternalFiles({
2430
+ externalBugsFile: validBugsSource,
2431
+ });
2432
+
2433
+ expect(result.valid).toBe(true);
2434
+ expect(result.bugs).toEqual(
2435
+ expect.objectContaining({
2436
+ valid: true,
2437
+ headerRow: 'A1',
2438
+ })
2439
+ );
2440
+ });
2441
+
2442
+ it('should fail validation when required columns are missing', async () => {
2443
+ const invalidBuffer = buildWorkbookBuffer([
2444
+ ['', '', ''],
2445
+ ['', '', ''],
2446
+ ['Elisra_SortIndex', 'TargetWorkItemId', 'TargetState'],
2447
+ ['101', '9001', 'Active'],
2448
+ ]);
2449
+ jest.spyOn(axios, 'get').mockResolvedValueOnce({ data: invalidBuffer, headers: {} } as any);
2450
+
2451
+ const result = await (resultDataProvider as any).validateMewpExternalFiles({
2452
+ externalBugsFile: validBugsSource,
2453
+ });
2454
+
2455
+ expect(result.valid).toBe(false);
2456
+ expect(result.bugs).toEqual(
2457
+ expect.objectContaining({
2458
+ valid: false,
2459
+ matchedRequiredColumns: 3,
2460
+ missingRequiredColumns: expect.arrayContaining(['SR', 'Title']),
2461
+ })
2462
+ );
2463
+ });
2464
+
2465
+ it('should reject files from non-dedicated bucket/object path', async () => {
2466
+ const result = await (resultDataProvider as any).validateMewpExternalFiles({
2467
+ externalBugsFile: {
2468
+ name: 'bugs.xlsx',
2469
+ url: 'https://minio.local/mewp-external-ingestion/MEWP/other-prefix/bugs.xlsx',
2470
+ sourceType: 'mewpExternalIngestion',
2471
+ },
2472
+ });
2473
+
2474
+ expect(result.valid).toBe(false);
2475
+ expect(result.bugs?.message || '').toContain('Invalid object path');
2476
+ });
2477
+
2478
+ it('should filter external bugs by SR/state and dedupe by bug+requirement', async () => {
2479
+ const mewpExternalTableUtils = (resultDataProvider as any).mewpExternalTableUtils;
2480
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
2481
+ {
2482
+ Elisra_SortIndex: '101',
2483
+ SR: 'SR0001',
2484
+ TargetWorkItemId: '9001',
2485
+ Title: 'Bug one',
2486
+ TargetState: 'Active',
2487
+ SAPWBS: 'ESUK',
2488
+ },
2489
+ {
2490
+ Elisra_SortIndex: '101',
2491
+ SR: 'SR0001',
2492
+ TargetWorkItemId: '9001',
2493
+ Title: 'Bug one duplicate',
2494
+ TargetState: 'Active',
2495
+ },
2496
+ {
2497
+ Elisra_SortIndex: '101',
2498
+ SR: '',
2499
+ TargetWorkItemId: '9002',
2500
+ Title: 'Missing SR',
2501
+ TargetState: 'Active',
2502
+ },
2503
+ {
2504
+ Elisra_SortIndex: '101',
2505
+ SR: 'SR0002',
2506
+ TargetWorkItemId: '9003',
2507
+ Title: 'Closed bug',
2508
+ TargetState: 'Closed',
2509
+ },
2510
+ ]);
2511
+
2512
+ const map = await (resultDataProvider as any).loadExternalBugsByTestCase(validBugsSource);
2513
+ const bugs = map.get(101) || [];
2514
+
2515
+ expect(bugs).toHaveLength(1);
2516
+ expect(bugs[0]).toEqual(
2517
+ expect.objectContaining({
2518
+ id: 9001,
2519
+ requirementBaseKey: 'SR0001',
2520
+ responsibility: 'ESUK',
2521
+ })
2522
+ );
2523
+ });
2524
+
2525
+ it('should require Elisra_SortIndex and ignore rows that only provide WorkItemId', async () => {
2526
+ const mewpExternalTableUtils = (resultDataProvider as any).mewpExternalTableUtils;
2527
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
2528
+ {
2529
+ WorkItemId: '101',
2530
+ SR: 'SR0001',
2531
+ TargetWorkItemId: '9010',
2532
+ Title: 'Bug without Elisra_SortIndex',
2533
+ TargetState: 'Active',
2534
+ },
2535
+ ]);
2536
+
2537
+ const map = await (resultDataProvider as any).loadExternalBugsByTestCase(validBugsSource);
2538
+ expect(map.size).toBe(0);
2539
+ });
2540
+
2541
+ it('should parse external L3/L4 file using AREA 34 semantics and terminal-state filtering', async () => {
2542
+ const mewpExternalTableUtils = (resultDataProvider as any).mewpExternalTableUtils;
2543
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
2544
+ {
2545
+ SR: 'SR0001',
2546
+ 'AREA 34': 'Level 4',
2547
+ 'TargetWorkItemId Level 3': '7001',
2548
+ TargetTitleLevel3: 'L4 From Level3 Column',
2549
+ 'TargetStateLevel 3': 'Active',
2550
+ },
2551
+ {
2552
+ SR: 'SR0001',
2553
+ 'AREA 34': 'Level 3',
2554
+ 'TargetWorkItemId Level 3': '7002',
2555
+ TargetTitleLevel3: 'L3 Requirement',
2556
+ 'TargetStateLevel 3': 'Active',
2557
+ 'TargetWorkItemIdLevel 4': '7003',
2558
+ TargetTitleLevel4: 'L4 Requirement',
2559
+ 'TargetStateLevel 4': 'Closed',
2560
+ },
2561
+ ]);
2562
+
2563
+ const map = await (resultDataProvider as any).loadExternalL3L4ByBaseKey(validL3L4Source);
2564
+ expect(map.get('SR0001')).toEqual([
2565
+ { id: '7002', title: 'L3 Requirement', level: 'L3' },
2566
+ { id: '7001', title: 'L4 From Level3 Column', level: 'L4' },
2567
+ ]);
2568
+ });
2569
+
2570
+ it('should exclude external open L3/L4 rows when SAPWBS resolves to ESUK', async () => {
2571
+ const mewpExternalTableUtils = (resultDataProvider as any).mewpExternalTableUtils;
2572
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
2573
+ {
2574
+ SR: 'SR0001',
2575
+ 'AREA 34': 'Level 3',
2576
+ 'TargetWorkItemId Level 3': '7101',
2577
+ TargetTitleLevel3: 'L3 ESUK',
2578
+ 'TargetStateLevel 3': 'Active',
2579
+ 'TargetSapWbsLevel 3': 'ESUK',
2580
+ 'TargetWorkItemIdLevel 4': '7201',
2581
+ TargetTitleLevel4: 'L4 IL',
2582
+ 'TargetStateLevel 4': 'Active',
2583
+ 'TargetSapWbsLevel 4': 'IL',
2584
+ },
2585
+ {
2586
+ SR: 'SR0001',
2587
+ 'AREA 34': 'Level 3',
2588
+ 'TargetWorkItemId Level 3': '7102',
2589
+ TargetTitleLevel3: 'L3 IL',
2590
+ 'TargetStateLevel 3': 'Active',
2591
+ 'TargetSapWbsLevel 3': 'IL',
2592
+ },
2593
+ ]);
2594
+
2595
+ const map = await (resultDataProvider as any).loadExternalL3L4ByBaseKey(validL3L4Source);
2596
+ expect(map.get('SR0001')).toEqual([
2597
+ { id: '7102', title: 'L3 IL', level: 'L3' },
2598
+ { id: '7201', title: 'L4 IL', level: 'L4' },
2599
+ ]);
2600
+ });
2601
+
2602
+ it('should fallback L3/L4 SAPWBS exclusion from SR-mapped requirement when row SAPWBS is empty', async () => {
2603
+ const mewpExternalTableUtils = (resultDataProvider as any).mewpExternalTableUtils;
2604
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce([
2605
+ {
2606
+ SR: 'SR0001',
2607
+ 'AREA 34': 'Level 3',
2608
+ 'TargetWorkItemId Level 3': '7301',
2609
+ TargetTitleLevel3: 'L3 From ESUK Requirement',
2610
+ 'TargetStateLevel 3': 'Active',
2611
+ 'TargetSapWbsLevel 3': '',
2612
+ },
2613
+ {
2614
+ SR: 'SR0002',
2615
+ 'AREA 34': 'Level 3',
2616
+ 'TargetWorkItemId Level 3': '7302',
2617
+ TargetTitleLevel3: 'L3 From IL Requirement',
2618
+ 'TargetStateLevel 3': 'Active',
2619
+ 'TargetSapWbsLevel 3': '',
2620
+ },
2621
+ ]);
2622
+
2623
+ const map = await (resultDataProvider as any).loadExternalL3L4ByBaseKey(
2624
+ validL3L4Source,
2625
+ new Map([
2626
+ ['SR0001', 'ESUK'],
2627
+ ['SR0002', 'IL'],
2628
+ ])
2629
+ );
2630
+
2631
+ expect(map.has('SR0001')).toBe(false);
2632
+ expect(map.get('SR0002')).toEqual([{ id: '7302', title: 'L3 From IL Requirement', level: 'L3' }]);
2633
+ });
2634
+
2635
+ it('should resolve bug responsibility from AreaPath when SAPWBS is empty', () => {
2636
+ const fromEsukAreaPath = (resultDataProvider as any).resolveBugResponsibility({
2637
+ 'Custom.SAPWBS': '',
2638
+ 'System.AreaPath': 'MEWP\\Customer Requirements\\Level 2\\ATP\\ESUK',
2639
+ });
2640
+ const fromIlAreaPath = (resultDataProvider as any).resolveBugResponsibility({
2641
+ 'Custom.SAPWBS': '',
2642
+ 'System.AreaPath': 'MEWP\\Customer Requirements\\Level 2\\ATP',
2643
+ });
2644
+ const unknown = (resultDataProvider as any).resolveBugResponsibility({
2645
+ 'Custom.SAPWBS': '',
2646
+ 'System.AreaPath': 'MEWP\\Other\\Area',
2647
+ });
2648
+
2649
+ expect(fromEsukAreaPath).toBe('ESUK');
2650
+ expect(fromIlAreaPath).toBe('Elisra');
2651
+ expect(unknown).toBe('Unknown');
2652
+ });
2653
+
2654
+ it('should handle 1000 external bug rows and keep only in-scope parsed items', async () => {
2655
+ const rows: any[] = [];
2656
+ for (let i = 1; i <= 1000; i++) {
2657
+ rows.push({
2658
+ Elisra_SortIndex: String(4000 + (i % 20)),
2659
+ SR: `SR${String(6000 + (i % 50)).padStart(4, '0')}`,
2660
+ TargetWorkItemId: String(900000 + i),
2661
+ Title: `Bug ${i}`,
2662
+ TargetState: i % 10 === 0 ? 'Closed' : 'Active',
2663
+ SAPWBS: i % 2 === 0 ? 'ESUK' : 'IL',
2664
+ });
2665
+ }
2666
+
2667
+ const mewpExternalTableUtils = (resultDataProvider as any).mewpExternalTableUtils;
2668
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce(rows);
2669
+
2670
+ const startedAt = Date.now();
2671
+ const map = await (resultDataProvider as any).loadExternalBugsByTestCase({
2672
+ name: 'bulk-bugs.xlsx',
2673
+ url: 'https://minio.local/mewp-external-ingestion/MEWP/mewp-external-ingestion/bugs/bulk-bugs.xlsx',
2674
+ sourceType: 'mewpExternalIngestion',
2675
+ });
2676
+ const elapsedMs = Date.now() - startedAt;
2677
+
2678
+ const totalParsed = [...map.values()].reduce((sum, items) => sum + (items?.length || 0), 0);
2679
+ expect(totalParsed).toBe(900); // every 10th row is closed and filtered out
2680
+ expect(elapsedMs).toBeLessThan(5000);
2681
+ });
2682
+
2683
+ it('should handle 1000 external L3/L4 rows and map all active links', async () => {
2684
+ const rows: any[] = [];
2685
+ for (let i = 1; i <= 1000; i++) {
2686
+ rows.push({
2687
+ SR: `SR${String(7000 + (i % 25)).padStart(4, '0')}`,
2688
+ 'AREA 34': i % 3 === 0 ? 'Level 4' : 'Level 3',
2689
+ 'TargetWorkItemId Level 3': String(800000 + i),
2690
+ TargetTitleLevel3: `L3/L4 Title ${i}`,
2691
+ 'TargetStateLevel 3': i % 11 === 0 ? 'Resolved' : 'Active',
2692
+ 'TargetWorkItemIdLevel 4': String(810000 + i),
2693
+ TargetTitleLevel4: `L4 Title ${i}`,
2694
+ 'TargetStateLevel 4': i % 13 === 0 ? 'Closed' : 'Active',
2695
+ });
2696
+ }
2697
+
2698
+ const mewpExternalTableUtils = (resultDataProvider as any).mewpExternalTableUtils;
2699
+ jest.spyOn(mewpExternalTableUtils, 'loadExternalTableRows').mockResolvedValueOnce(rows);
2700
+
2701
+ const startedAt = Date.now();
2702
+ const map = await (resultDataProvider as any).loadExternalL3L4ByBaseKey({
2703
+ name: 'bulk-l3l4.xlsx',
2704
+ url: 'https://minio.local/mewp-external-ingestion/MEWP/mewp-external-ingestion/l3l4/bulk-l3l4.xlsx',
2705
+ sourceType: 'mewpExternalIngestion',
2706
+ });
2707
+ const elapsedMs = Date.now() - startedAt;
2708
+
2709
+ const totalLinks = [...map.values()].reduce((sum, items) => sum + (items?.length || 0), 0);
2710
+ expect(totalLinks).toBeGreaterThan(700);
2711
+ expect(elapsedMs).toBeLessThan(5000);
2712
+ });
2713
+ });
2714
+
2715
+ describe('MEWP high-volume requirement token parsing', () => {
2716
+ it('should parse 1000 expected-result requirement tokens with noisy fragments', () => {
2717
+ const tokens: string[] = [];
2718
+ for (let i = 1; i <= 1000; i++) {
2719
+ const code = `SR${String(10000 + i)}`;
2720
+ tokens.push(code);
2721
+ if (i % 100 === 0) tokens.push(`${code} V3.24`);
2722
+ if (i % 125 === 0) tokens.push(`${code} VVRM2425`);
2723
+ }
2724
+ const sourceText = tokens.join('; ');
2725
+
2726
+ const startedAt = Date.now();
2727
+ const codes = (resultDataProvider as any).extractRequirementCodesFromText(sourceText) as Set<string>;
2728
+ const elapsedMs = Date.now() - startedAt;
2729
+
2730
+ expect(codes.size).toBe(1000);
2731
+ expect(codes.has('SR10001')).toBe(true);
2732
+ expect(codes.has('SR11000')).toBe(true);
2733
+ expect(elapsedMs).toBeLessThan(3000);
1391
2734
  });
1392
2735
  });
1393
2736