@elisra-devops/docgen-data-provider 1.75.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();
@@ -1098,6 +1106,8 @@ describe('ResultDataProvider', () => {
1098
1106
  {
1099
1107
  workItemId: 5001,
1100
1108
  requirementId: 'SR1001',
1109
+ baseKey: 'SR1001',
1110
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1101
1111
  title: 'Covered requirement',
1102
1112
  responsibility: 'ESUK',
1103
1113
  linkedTestCaseIds: [101],
@@ -1105,6 +1115,8 @@ describe('ResultDataProvider', () => {
1105
1115
  {
1106
1116
  workItemId: 5002,
1107
1117
  requirementId: 'SR1002',
1118
+ baseKey: 'SR1002',
1119
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1108
1120
  title: 'Referenced from non-linked step text',
1109
1121
  responsibility: 'IL',
1110
1122
  linkedTestCaseIds: [],
@@ -1112,6 +1124,8 @@ describe('ResultDataProvider', () => {
1112
1124
  {
1113
1125
  workItemId: 5003,
1114
1126
  requirementId: 'SR1003',
1127
+ baseKey: 'SR1003',
1128
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1115
1129
  title: 'Not covered by any test case',
1116
1130
  responsibility: 'IL',
1117
1131
  linkedTestCaseIds: [],
@@ -1125,11 +1139,11 @@ describe('ResultDataProvider', () => {
1125
1139
  actionResults: [
1126
1140
  {
1127
1141
  action: 'Validate <b>S</b><b>R</b> 1 0 0 1 happy path',
1128
- expected: '',
1142
+ expected: '<b>S</b><b>R</b> 1 0 0 1',
1129
1143
  outcome: 'Passed',
1130
1144
  },
1131
- { action: 'Validate SR1001 failed flow', expected: '&nbsp;', outcome: 'Failed' },
1132
- { 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' },
1133
1147
  ],
1134
1148
  },
1135
1149
  },
@@ -1146,7 +1160,7 @@ describe('ResultDataProvider', () => {
1146
1160
  stepId: '1',
1147
1161
  stepPosition: '1',
1148
1162
  action: 'Definition contains SR1002',
1149
- expected: '',
1163
+ expected: 'SR1002',
1150
1164
  isSharedStepTitle: false,
1151
1165
  },
1152
1166
  ]);
@@ -1160,59 +1174,71 @@ describe('ResultDataProvider', () => {
1160
1174
  expect(result).toEqual(
1161
1175
  expect.objectContaining({
1162
1176
  sheetName: expect.stringContaining('MEWP L2 Coverage'),
1163
- 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']),
1164
1178
  })
1165
1179
  );
1166
1180
 
1167
- const covered = result.rows.find(
1168
- (row: any) => row['Customer ID'] === 'SR1001' && row['Test case id'] === 101
1169
- );
1170
- const inferredByStepText = result.rows.find(
1171
- (row: any) => row['Customer ID'] === 'SR1002' && row['Test case id'] === 102
1172
- );
1173
- const uncovered = result.rows.find(
1174
- (row: any) =>
1175
- row['Customer ID'] === 'SR1003' &&
1176
- (row['Test case id'] === '' || row['Test case id'] === undefined || row['Test case id'] === null)
1177
- );
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');
1178
1184
 
1179
1185
  expect(covered).toEqual(
1180
1186
  expect.objectContaining({
1181
- 'Title (Customer name)': 'Covered requirement',
1182
- 'Responsibility - SAPWBS (ESUK/IL)': 'ESUK',
1183
- 'Test case title': 'TC 101',
1184
- 'Number of passed steps': 1,
1185
- 'Number of failed steps': 1,
1186
- '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': '',
1187
1193
  })
1188
1194
  );
1189
1195
  expect(inferredByStepText).toEqual(
1190
1196
  expect.objectContaining({
1191
- 'Title (Customer name)': 'Referenced from non-linked step text',
1192
- 'Responsibility - SAPWBS (ESUK/IL)': 'IL',
1193
- 'Test case title': 'TC 102',
1194
- 'Number of passed steps': 0,
1195
- 'Number of failed steps': 0,
1196
- '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',
1197
1200
  })
1198
1201
  );
1199
1202
  expect(uncovered).toEqual(
1200
1203
  expect.objectContaining({
1201
- 'Title (Customer name)': 'Not covered by any test case',
1202
- 'Responsibility - SAPWBS (ESUK/IL)': 'IL',
1203
- 'Test case title': '',
1204
- 'Number of passed steps': 0,
1205
- 'Number of failed steps': 0,
1206
- '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',
1207
1207
  })
1208
1208
  );
1209
1209
  });
1210
1210
 
1211
1211
  it('should extract SR ids from HTML/spacing and return unique ids per step text', () => {
1212
1212
  const text =
1213
- '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';
1214
1214
  const codes = (resultDataProvider as any).extractRequirementCodesFromText(text);
1215
- 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
+ ]);
1216
1242
  });
1217
1243
 
1218
1244
  it('should not backfill definition steps as not-run when a real run exists but has no action results', async () => {
@@ -1235,6 +1261,8 @@ describe('ResultDataProvider', () => {
1235
1261
  {
1236
1262
  workItemId: 7001,
1237
1263
  requirementId: 'SR2001',
1264
+ baseKey: 'SR2001',
1265
+ areaPath: 'MEWP\\Customer Requirements\\Level 2',
1238
1266
  title: 'Has run but no actions',
1239
1267
  responsibility: 'ESUK',
1240
1268
  linkedTestCaseIds: [101],
@@ -1269,15 +1297,11 @@ describe('ResultDataProvider', () => {
1269
1297
  [1]
1270
1298
  );
1271
1299
 
1272
- const row = result.rows.find(
1273
- (item: any) => item['Customer ID'] === 'SR2001' && item['Test case id'] === 101
1274
- );
1300
+ const row = result.rows.find((item: any) => item['L2 REQ ID'] === 'SR2001');
1275
1301
  expect(parseSpy).not.toHaveBeenCalled();
1276
1302
  expect(row).toEqual(
1277
1303
  expect.objectContaining({
1278
- 'Number of passed steps': 0,
1279
- 'Number of failed steps': 0,
1280
- 'Number of not run tests': 0,
1304
+ 'L2 Run Status': 'Not Run',
1281
1305
  })
1282
1306
  );
1283
1307
  });
@@ -1303,6 +1327,1411 @@ describe('ResultDataProvider', () => {
1303
1327
 
1304
1328
  expect(responsibility).toBe('IL');
1305
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);
2734
+ });
1306
2735
  });
1307
2736
 
1308
2737
  describe('fetchResultDataForTestReporter (runResultField switch)', () => {