@elisra-devops/docgen-data-provider 1.75.0 → 1.77.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/models/mewp-reporting.d.ts +120 -0
- package/bin/models/mewp-reporting.js +3 -0
- package/bin/models/mewp-reporting.js.map +1 -0
- package/bin/modules/ResultDataProvider.d.ts +46 -6
- package/bin/modules/ResultDataProvider.js +1156 -119
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +1285 -33
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/bin/utils/mewpExternalIngestionUtils.d.ts +17 -0
- package/bin/utils/mewpExternalIngestionUtils.js +269 -0
- package/bin/utils/mewpExternalIngestionUtils.js.map +1 -0
- package/bin/utils/mewpExternalTableUtils.d.ts +36 -0
- package/bin/utils/mewpExternalTableUtils.js +320 -0
- package/bin/utils/mewpExternalTableUtils.js.map +1 -0
- package/package.json +10 -1
- package/src/models/mewp-reporting.ts +138 -0
- package/src/modules/ResultDataProvider.ts +1399 -166
- package/src/tests/modules/ResultDataProvider.test.ts +1471 -42
- package/src/utils/mewpExternalIngestionUtils.ts +349 -0
- package/src/utils/mewpExternalTableUtils.ts +461 -0
|
@@ -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: '
|
|
1132
|
-
{ action: '', expected: '
|
|
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(['
|
|
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
|
-
|
|
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
|
-
'
|
|
1182
|
-
'
|
|
1183
|
-
'
|
|
1184
|
-
'
|
|
1185
|
-
'
|
|
1186
|
-
'
|
|
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
|
-
'
|
|
1192
|
-
'
|
|
1193
|
-
'
|
|
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
|
-
'
|
|
1202
|
-
'
|
|
1203
|
-
'
|
|
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
|
-
'
|
|
1213
|
+
'<b>S</b><b>R</b> 0 0 0 1; SR0002; S R 0 0 0 3; SR0002; <b>SR</b>0004';
|
|
1214
1214
|
const codes = (resultDataProvider as any).extractRequirementCodesFromText(text);
|
|
1215
|
-
expect([...codes].sort()).toEqual(['
|
|
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
|
-
'
|
|
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)', () => {
|