@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elisra-devops/docgen-data-provider",
3
- "version": "1.84.0",
3
+ "version": "1.85.0",
4
4
  "description": "A document generator data provider, aimed to retrive data from azure devops",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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 direct = String(rawResponsibility || '').trim();
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 requirementResponsibility = String(requirement?.responsibility || '')
1255
- .trim()
1256
- .toUpperCase();
1257
- if (requirementResponsibility === 'ESUK') return 'ESUK';
1258
- if (requirementResponsibility === 'IL' || requirementResponsibility === 'ELISRA') return 'Elisra';
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 areaPath = this.toMewpComparableText(fields?.['System.AreaPath']);
2461
- const fromAreaPath = this.resolveMewpResponsibility(areaPath);
2462
- if (fromAreaPath) return fromAreaPath;
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 = ['sapwbs', 'responsibility', 'owner'];
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', () => {