@elisra-devops/docgen-data-provider 1.84.0 → 1.85.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/modules/ResultDataProvider.d.ts +2 -0
- package/bin/modules/ResultDataProvider.js +146 -19
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +117 -0
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/ResultDataProvider.ts +162 -16
- package/src/tests/modules/ResultDataProvider.test.ts +147 -0
package/package.json
CHANGED
|
@@ -478,6 +478,10 @@ export default class ResultDataProvider {
|
|
|
478
478
|
testData
|
|
479
479
|
);
|
|
480
480
|
const requirementSapWbsByBaseKey = this.buildRequirementSapWbsByBaseKey(allRequirements);
|
|
481
|
+
const testCaseResponsibilityById = await this.buildMewpTestCaseResponsibilityMap(
|
|
482
|
+
testData,
|
|
483
|
+
projectName
|
|
484
|
+
);
|
|
481
485
|
const externalBugsByTestCase = await this.loadExternalBugsByTestCase(options?.externalBugsFile);
|
|
482
486
|
const externalL3L4ByBaseKey = await this.loadExternalL3L4ByBaseKey(
|
|
483
487
|
options?.externalL3L4File,
|
|
@@ -690,7 +694,8 @@ export default class ResultDataProvider {
|
|
|
690
694
|
linkedRequirementsByTestCase,
|
|
691
695
|
externalL3L4ByBaseKey,
|
|
692
696
|
externalBugsByTestCase,
|
|
693
|
-
externalJoinKeysByL2
|
|
697
|
+
externalJoinKeysByL2,
|
|
698
|
+
testCaseResponsibilityById
|
|
694
699
|
);
|
|
695
700
|
const coverageRowStats = rows.reduce(
|
|
696
701
|
(acc, row) => {
|
|
@@ -1158,7 +1163,8 @@ export default class ResultDataProvider {
|
|
|
1158
1163
|
linkedRequirementsByTestCase: MewpLinkedRequirementsByTestCase,
|
|
1159
1164
|
l3l4ByBaseKey: Map<string, MewpL3L4Pair[]>,
|
|
1160
1165
|
externalBugsByTestCase: Map<number, MewpBugLink[]>,
|
|
1161
|
-
externalJoinKeysByL2?: Map<string, Set<string
|
|
1166
|
+
externalJoinKeysByL2?: Map<string, Set<string>>,
|
|
1167
|
+
testCaseResponsibilityById?: Map<number, string>
|
|
1162
1168
|
): MewpCoverageRow[] {
|
|
1163
1169
|
const rows: MewpCoverageRow[] = [];
|
|
1164
1170
|
const linkedByRequirement = this.invertBaseRequirementLinks(linkedRequirementsByTestCase);
|
|
@@ -1202,7 +1208,8 @@ export default class ResultDataProvider {
|
|
|
1202
1208
|
...bug,
|
|
1203
1209
|
responsibility: this.resolveCoverageBugResponsibility(
|
|
1204
1210
|
String(bug?.responsibility || ''),
|
|
1205
|
-
requirement
|
|
1211
|
+
requirement,
|
|
1212
|
+
String(testCaseResponsibilityById?.get(testCaseId) || '')
|
|
1206
1213
|
),
|
|
1207
1214
|
});
|
|
1208
1215
|
}
|
|
@@ -1246,18 +1253,127 @@ export default class ResultDataProvider {
|
|
|
1246
1253
|
|
|
1247
1254
|
private resolveCoverageBugResponsibility(
|
|
1248
1255
|
rawResponsibility: string,
|
|
1249
|
-
requirement: Pick<MewpL2RequirementFamily, 'responsibility'
|
|
1256
|
+
requirement: Pick<MewpL2RequirementFamily, 'responsibility'>,
|
|
1257
|
+
testCaseResponsibility: string = ''
|
|
1250
1258
|
): string {
|
|
1251
|
-
const
|
|
1259
|
+
const normalizeDisplay = (value: string): string => {
|
|
1260
|
+
const direct = String(value || '').trim();
|
|
1261
|
+
if (!direct) return '';
|
|
1262
|
+
const resolved = this.resolveMewpResponsibility(this.toMewpComparableText(direct));
|
|
1263
|
+
if (resolved === 'ESUK') return 'ESUK';
|
|
1264
|
+
if (resolved === 'IL') return 'Elisra';
|
|
1265
|
+
return direct;
|
|
1266
|
+
};
|
|
1267
|
+
|
|
1268
|
+
const direct = normalizeDisplay(rawResponsibility);
|
|
1252
1269
|
if (direct && direct.toLowerCase() !== 'unknown') return direct;
|
|
1253
1270
|
|
|
1254
|
-
const
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
if (
|
|
1271
|
+
const fromTestCase = normalizeDisplay(testCaseResponsibility);
|
|
1272
|
+
if (fromTestCase && fromTestCase.toLowerCase() !== 'unknown') return fromTestCase;
|
|
1273
|
+
|
|
1274
|
+
const fromRequirement = normalizeDisplay(String(requirement?.responsibility || ''));
|
|
1275
|
+
if (fromRequirement && fromRequirement.toLowerCase() !== 'unknown') return fromRequirement;
|
|
1259
1276
|
|
|
1260
|
-
return direct || 'Unknown';
|
|
1277
|
+
return direct || fromTestCase || fromRequirement || 'Unknown';
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
private async buildMewpTestCaseResponsibilityMap(
|
|
1281
|
+
testData: any[],
|
|
1282
|
+
projectName: string
|
|
1283
|
+
): Promise<Map<number, string>> {
|
|
1284
|
+
const out = new Map<number, string>();
|
|
1285
|
+
const unresolved = new Set<number>();
|
|
1286
|
+
|
|
1287
|
+
const extractFromWorkItemFields = (workItemFields: any): Record<string, any> => {
|
|
1288
|
+
const fields: Record<string, any> = {};
|
|
1289
|
+
if (!Array.isArray(workItemFields)) return fields;
|
|
1290
|
+
for (const field of workItemFields) {
|
|
1291
|
+
const keyCandidates = [field?.key, field?.name, field?.referenceName]
|
|
1292
|
+
.map((item) => String(item || '').trim())
|
|
1293
|
+
.filter((item) => !!item);
|
|
1294
|
+
for (const key of keyCandidates) {
|
|
1295
|
+
fields[key] = field?.value;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return fields;
|
|
1299
|
+
};
|
|
1300
|
+
|
|
1301
|
+
for (const suite of testData || []) {
|
|
1302
|
+
const testCasesItems = Array.isArray(suite?.testCasesItems) ? suite.testCasesItems : [];
|
|
1303
|
+
for (const testCase of testCasesItems) {
|
|
1304
|
+
const testCaseId = Number(testCase?.workItem?.id || testCase?.testCaseId || testCase?.id || 0);
|
|
1305
|
+
if (!Number.isFinite(testCaseId) || testCaseId <= 0 || out.has(testCaseId)) continue;
|
|
1306
|
+
|
|
1307
|
+
const workItemFieldsMap = testCase?.workItem?.fields || {};
|
|
1308
|
+
const workItemFieldsList = extractFromWorkItemFields(testCase?.workItem?.workItemFields);
|
|
1309
|
+
const directFieldAliases = Object.fromEntries(
|
|
1310
|
+
Object.entries({
|
|
1311
|
+
'System.AreaPath':
|
|
1312
|
+
testCase?.workItem?.areaPath ||
|
|
1313
|
+
testCase?.workItem?.AreaPath ||
|
|
1314
|
+
testCase?.areaPath ||
|
|
1315
|
+
testCase?.AreaPath ||
|
|
1316
|
+
testCase?.testCase?.areaPath ||
|
|
1317
|
+
testCase?.testCase?.AreaPath,
|
|
1318
|
+
'Area Path':
|
|
1319
|
+
testCase?.workItem?.['Area Path'] ||
|
|
1320
|
+
testCase?.['Area Path'] ||
|
|
1321
|
+
testCase?.testCase?.['Area Path'],
|
|
1322
|
+
AreaPath:
|
|
1323
|
+
testCase?.workItem?.AreaPath ||
|
|
1324
|
+
testCase?.AreaPath ||
|
|
1325
|
+
testCase?.testCase?.AreaPath,
|
|
1326
|
+
}).filter(([, value]) => value !== undefined && value !== null && String(value).trim() !== '')
|
|
1327
|
+
);
|
|
1328
|
+
const fromWorkItem = this.deriveMewpTestCaseResponsibility({
|
|
1329
|
+
...directFieldAliases,
|
|
1330
|
+
...workItemFieldsList,
|
|
1331
|
+
...workItemFieldsMap,
|
|
1332
|
+
});
|
|
1333
|
+
|
|
1334
|
+
if (fromWorkItem) {
|
|
1335
|
+
out.set(testCaseId, fromWorkItem);
|
|
1336
|
+
} else {
|
|
1337
|
+
unresolved.add(testCaseId);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
const testPointsItems = Array.isArray(suite?.testPointsItems) ? suite.testPointsItems : [];
|
|
1342
|
+
for (const testPoint of testPointsItems) {
|
|
1343
|
+
const testCaseId = Number(testPoint?.testCaseId || testPoint?.testCase?.id || 0);
|
|
1344
|
+
if (!Number.isFinite(testCaseId) || testCaseId <= 0 || out.has(testCaseId)) continue;
|
|
1345
|
+
const fromPoint = this.deriveMewpTestCaseResponsibility({
|
|
1346
|
+
'System.AreaPath': testPoint?.areaPath || testPoint?.AreaPath,
|
|
1347
|
+
'Area Path': testPoint?.['Area Path'],
|
|
1348
|
+
AreaPath: testPoint?.AreaPath,
|
|
1349
|
+
});
|
|
1350
|
+
if (fromPoint) {
|
|
1351
|
+
out.set(testCaseId, fromPoint);
|
|
1352
|
+
unresolved.delete(testCaseId);
|
|
1353
|
+
} else {
|
|
1354
|
+
unresolved.add(testCaseId);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
if (unresolved.size > 0) {
|
|
1360
|
+
try {
|
|
1361
|
+
const workItems = await this.fetchWorkItemsByIds(projectName, [...unresolved], false);
|
|
1362
|
+
for (const workItem of workItems || []) {
|
|
1363
|
+
const testCaseId = Number(workItem?.id || 0);
|
|
1364
|
+
if (!Number.isFinite(testCaseId) || testCaseId <= 0 || out.has(testCaseId)) continue;
|
|
1365
|
+
const resolved = this.deriveMewpTestCaseResponsibility(workItem?.fields || {});
|
|
1366
|
+
if (!resolved) continue;
|
|
1367
|
+
out.set(testCaseId, resolved);
|
|
1368
|
+
}
|
|
1369
|
+
} catch (error: any) {
|
|
1370
|
+
logger.warn(
|
|
1371
|
+
`MEWP coverage: failed to enrich test-case responsibility fallback: ${error?.message || error}`
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
return out;
|
|
1261
1377
|
}
|
|
1262
1378
|
|
|
1263
1379
|
private resolveMewpL2RunStatus(input: {
|
|
@@ -2457,11 +2573,42 @@ export default class ResultDataProvider {
|
|
|
2457
2573
|
if (fromExplicitLabel) return fromExplicitLabel;
|
|
2458
2574
|
if (explicitSapWbsByLabel) return explicitSapWbsByLabel;
|
|
2459
2575
|
|
|
2460
|
-
const
|
|
2461
|
-
|
|
2462
|
-
|
|
2576
|
+
const areaPathCandidates = [
|
|
2577
|
+
fields?.['System.AreaPath'],
|
|
2578
|
+
fields?.['Area Path'],
|
|
2579
|
+
fields?.['AreaPath'],
|
|
2580
|
+
];
|
|
2581
|
+
for (const candidate of areaPathCandidates) {
|
|
2582
|
+
const normalized = this.toMewpComparableText(candidate);
|
|
2583
|
+
const resolved = this.resolveMewpResponsibility(normalized);
|
|
2584
|
+
if (resolved) return resolved;
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
const keyHints = ['sapwbs', 'responsibility', 'owner', 'areapath', 'area path'];
|
|
2588
|
+
for (const [key, value] of Object.entries(fields || {})) {
|
|
2589
|
+
const normalizedKey = String(key || '').toLowerCase();
|
|
2590
|
+
if (!keyHints.some((hint) => normalizedKey.includes(hint))) continue;
|
|
2591
|
+
const resolved = this.resolveMewpResponsibility(this.toMewpComparableText(value));
|
|
2592
|
+
if (resolved) return resolved;
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
return '';
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
// Test-case responsibility must come from test-case path context (not SAPWBS).
|
|
2599
|
+
private deriveMewpTestCaseResponsibility(fields: Record<string, any>): string {
|
|
2600
|
+
const areaPathCandidates = [
|
|
2601
|
+
fields?.['System.AreaPath'],
|
|
2602
|
+
fields?.['Area Path'],
|
|
2603
|
+
fields?.['AreaPath'],
|
|
2604
|
+
];
|
|
2605
|
+
for (const candidate of areaPathCandidates) {
|
|
2606
|
+
const normalized = this.toMewpComparableText(candidate);
|
|
2607
|
+
const resolved = this.resolveMewpResponsibility(normalized);
|
|
2608
|
+
if (resolved) return resolved;
|
|
2609
|
+
}
|
|
2463
2610
|
|
|
2464
|
-
const keyHints = ['
|
|
2611
|
+
const keyHints = ['areapath', 'area path', 'responsibility', 'owner'];
|
|
2465
2612
|
for (const [key, value] of Object.entries(fields || {})) {
|
|
2466
2613
|
const normalizedKey = String(key || '').toLowerCase();
|
|
2467
2614
|
if (!keyHints.some((hint) => normalizedKey.includes(hint))) continue;
|
|
@@ -2905,7 +3052,6 @@ export default class ResultDataProvider {
|
|
|
2905
3052
|
logger.warn(`Invalid run result ${runId} or result ${resultId}`);
|
|
2906
3053
|
return null;
|
|
2907
3054
|
}
|
|
2908
|
-
logger.warn(`Current Test point for Test case ${point.testCaseId} is in Active state`);
|
|
2909
3055
|
const url = `${this.orgUrl}${projectName}/_apis/wit/workItems/${point.testCaseId}?$expand=all`;
|
|
2910
3056
|
const testCaseData = await TFSServices.getItemContent(url, this.token);
|
|
2911
3057
|
const newResultData: PlainTestResult = {
|
|
@@ -1341,6 +1341,81 @@ describe('ResultDataProvider', () => {
|
|
|
1341
1341
|
expect(il).toBe('IL');
|
|
1342
1342
|
});
|
|
1343
1343
|
|
|
1344
|
+
it('should derive responsibility from Area Path alias when System.AreaPath is missing', () => {
|
|
1345
|
+
const esuk = (resultDataProvider as any).deriveMewpResponsibility({
|
|
1346
|
+
'Custom.SAPWBS': '',
|
|
1347
|
+
'Area Path': 'MEWP\\Customer Requirements\\Level 2\\ATP\\ESUK',
|
|
1348
|
+
});
|
|
1349
|
+
const il = (resultDataProvider as any).deriveMewpResponsibility({
|
|
1350
|
+
'Custom.SAPWBS': '',
|
|
1351
|
+
'Area Path': 'MEWP\\Customer Requirements\\Level 2\\ATP',
|
|
1352
|
+
});
|
|
1353
|
+
|
|
1354
|
+
expect(esuk).toBe('ESUK');
|
|
1355
|
+
expect(il).toBe('IL');
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
it('should derive test-case responsibility from testCasesItems area-path fields', async () => {
|
|
1359
|
+
const fetchByIdsSpy = jest
|
|
1360
|
+
.spyOn(resultDataProvider as any, 'fetchWorkItemsByIds')
|
|
1361
|
+
.mockResolvedValue([]);
|
|
1362
|
+
|
|
1363
|
+
const map = await (resultDataProvider as any).buildMewpTestCaseResponsibilityMap(
|
|
1364
|
+
[
|
|
1365
|
+
{
|
|
1366
|
+
testCasesItems: [
|
|
1367
|
+
{
|
|
1368
|
+
workItem: {
|
|
1369
|
+
id: 101,
|
|
1370
|
+
workItemFields: [{ name: 'Area Path', value: 'MEWP\\Customer Requirements\\Level 2\\ATP' }],
|
|
1371
|
+
},
|
|
1372
|
+
},
|
|
1373
|
+
{
|
|
1374
|
+
workItem: {
|
|
1375
|
+
id: 102,
|
|
1376
|
+
workItemFields: [
|
|
1377
|
+
{ referenceName: 'System.AreaPath', value: 'MEWP\\Customer Requirements\\Level 2\\ATP\\ESUK' },
|
|
1378
|
+
],
|
|
1379
|
+
},
|
|
1380
|
+
},
|
|
1381
|
+
],
|
|
1382
|
+
testPointsItems: [],
|
|
1383
|
+
},
|
|
1384
|
+
],
|
|
1385
|
+
mockProjectName
|
|
1386
|
+
);
|
|
1387
|
+
|
|
1388
|
+
expect(map.get(101)).toBe('IL');
|
|
1389
|
+
expect(map.get(102)).toBe('ESUK');
|
|
1390
|
+
expect(fetchByIdsSpy).not.toHaveBeenCalled();
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
it('should derive test-case responsibility from AreaPath even when SAPWBS exists on test-case payload', async () => {
|
|
1394
|
+
jest.spyOn(resultDataProvider as any, 'fetchWorkItemsByIds').mockResolvedValue([]);
|
|
1395
|
+
|
|
1396
|
+
const map = await (resultDataProvider as any).buildMewpTestCaseResponsibilityMap(
|
|
1397
|
+
[
|
|
1398
|
+
{
|
|
1399
|
+
testCasesItems: [
|
|
1400
|
+
{
|
|
1401
|
+
workItem: {
|
|
1402
|
+
id: 201,
|
|
1403
|
+
workItemFields: [
|
|
1404
|
+
{ referenceName: 'Custom.SAPWBS', value: 'ESUK' },
|
|
1405
|
+
{ referenceName: 'System.AreaPath', value: 'MEWP\\Customer Requirements\\Level 2\\ATP' },
|
|
1406
|
+
],
|
|
1407
|
+
},
|
|
1408
|
+
},
|
|
1409
|
+
],
|
|
1410
|
+
testPointsItems: [],
|
|
1411
|
+
},
|
|
1412
|
+
],
|
|
1413
|
+
mockProjectName
|
|
1414
|
+
);
|
|
1415
|
+
|
|
1416
|
+
expect(map.get(201)).toBe('IL');
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1344
1419
|
it('should zip bug rows with L3/L4 pairs and avoid cross-product duplication', () => {
|
|
1345
1420
|
const requirements = [
|
|
1346
1421
|
{
|
|
@@ -1628,6 +1703,78 @@ describe('ResultDataProvider', () => {
|
|
|
1628
1703
|
expect(rows[0]['Bug ID']).toBe(99001);
|
|
1629
1704
|
expect(rows[0]['Bug Responsibility']).toBe('Elisra');
|
|
1630
1705
|
});
|
|
1706
|
+
|
|
1707
|
+
it('should fallback bug responsibility from test case mapping when external bug row is unknown', () => {
|
|
1708
|
+
const requirements = [
|
|
1709
|
+
{
|
|
1710
|
+
requirementId: 'SR5310',
|
|
1711
|
+
baseKey: 'SR5310',
|
|
1712
|
+
title: 'Req 5310',
|
|
1713
|
+
subSystem: 'Power',
|
|
1714
|
+
responsibility: '',
|
|
1715
|
+
linkedTestCaseIds: [101],
|
|
1716
|
+
},
|
|
1717
|
+
];
|
|
1718
|
+
|
|
1719
|
+
const requirementIndex = new Map([
|
|
1720
|
+
[
|
|
1721
|
+
'SR5310',
|
|
1722
|
+
new Map([
|
|
1723
|
+
[
|
|
1724
|
+
101,
|
|
1725
|
+
{
|
|
1726
|
+
passed: 0,
|
|
1727
|
+
failed: 1,
|
|
1728
|
+
notRun: 0,
|
|
1729
|
+
},
|
|
1730
|
+
],
|
|
1731
|
+
]),
|
|
1732
|
+
],
|
|
1733
|
+
]);
|
|
1734
|
+
|
|
1735
|
+
const observedTestCaseIdsByRequirement = new Map<string, Set<number>>([
|
|
1736
|
+
['SR5310', new Set([101])],
|
|
1737
|
+
]);
|
|
1738
|
+
|
|
1739
|
+
const linkedRequirementsByTestCase = new Map([
|
|
1740
|
+
[
|
|
1741
|
+
101,
|
|
1742
|
+
{
|
|
1743
|
+
baseKeys: new Set(['SR5310']),
|
|
1744
|
+
fullCodes: new Set(['SR5310']),
|
|
1745
|
+
bugIds: new Set([10003]),
|
|
1746
|
+
},
|
|
1747
|
+
],
|
|
1748
|
+
]);
|
|
1749
|
+
|
|
1750
|
+
const externalBugsByTestCase = new Map([
|
|
1751
|
+
[
|
|
1752
|
+
101,
|
|
1753
|
+
[
|
|
1754
|
+
{
|
|
1755
|
+
id: 10003,
|
|
1756
|
+
title: 'Bug 10003',
|
|
1757
|
+
responsibility: 'Unknown',
|
|
1758
|
+
requirementBaseKey: 'SR5310',
|
|
1759
|
+
},
|
|
1760
|
+
],
|
|
1761
|
+
],
|
|
1762
|
+
]);
|
|
1763
|
+
|
|
1764
|
+
const rows = (resultDataProvider as any).buildMewpCoverageRows(
|
|
1765
|
+
requirements,
|
|
1766
|
+
requirementIndex,
|
|
1767
|
+
observedTestCaseIdsByRequirement,
|
|
1768
|
+
linkedRequirementsByTestCase,
|
|
1769
|
+
new Map(),
|
|
1770
|
+
externalBugsByTestCase,
|
|
1771
|
+
new Map(),
|
|
1772
|
+
new Map([[101, 'IL']])
|
|
1773
|
+
);
|
|
1774
|
+
|
|
1775
|
+
expect(rows).toHaveLength(1);
|
|
1776
|
+
expect(rows[0]['Bug Responsibility']).toBe('Elisra');
|
|
1777
|
+
});
|
|
1631
1778
|
});
|
|
1632
1779
|
|
|
1633
1780
|
describe('getMewpInternalValidationFlatResults', () => {
|