@elisra-devops/docgen-data-provider 1.101.0 → 1.102.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.
@@ -26,6 +26,8 @@ export default class ResultDataProvider {
26
26
  private static readonly MEWP_INTERNAL_VALIDATION_TRACE_TAG;
27
27
  private static readonly MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG;
28
28
  private static readonly MEWP_INTERNAL_VALIDATION_SUMMARY_TAG;
29
+ private static readonly MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_REF;
30
+ private static readonly MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_HEADER_PATTERN;
29
31
  private static readonly MEWP_L2_COVERAGE_COLUMNS;
30
32
  private static readonly INTERNAL_VALIDATION_COLUMNS;
31
33
  orgUrl: string;
@@ -104,6 +106,15 @@ export default class ResultDataProvider {
104
106
  private isExternalStateInScope;
105
107
  private invertBaseRequirementLinks;
106
108
  private buildMewpTestCaseTitleMap;
109
+ /**
110
+ * Builds a lookup of test case id -> test case description using suite payload data.
111
+ *
112
+ * Resolution order:
113
+ * 1) direct description fields on test-case payload (`testCase.description` / `workItem.description`)
114
+ * 2) `System.Description` / `Description` in work-item fields map
115
+ * 3) `System.Description` / `Description` from work-item field list (`workItemFields`)
116
+ */
117
+ private buildMewpTestCaseDescriptionMap;
107
118
  private extractMewpTestCaseId;
108
119
  private buildTestCaseStepsXmlMap;
109
120
  private extractStepsXmlFromTestCaseItem;
@@ -115,6 +126,27 @@ export default class ResultDataProvider {
115
126
  private resolveRequirementStatusForWindow;
116
127
  private extractRequirementCodesFromText;
117
128
  private extractRequirementMentionsFromExpectedSteps;
129
+ /**
130
+ * Extracts SR requirement mentions from the "assumptions" section inside a test-case description.
131
+ *
132
+ * Notes:
133
+ * - Heading detection is case-insensitive and keyed by "assumptions".
134
+ * - Parsing is scoped to that section only and stops when a likely next section heading is reached
135
+ * after at least one requirement-bearing line was collected.
136
+ * - Returned stepRef is a synthetic marker ("Assumptions") so downstream discrepancy output can
137
+ * clearly attribute source to description-level assumptions.
138
+ */
139
+ private extractRequirementMentionsFromAssumptionsDescription;
140
+ /**
141
+ * Normalizes raw HTML/plain description content into comparable text lines.
142
+ * This makes section/title heuristics resilient to formatting differences.
143
+ */
144
+ private normalizeMewpDescriptionLines;
145
+ /**
146
+ * Heuristic detector for description section headings used to stop assumptions parsing.
147
+ * A line is considered a heading when it is short/title-like and does not itself contain SR codes.
148
+ */
149
+ private isLikelyMewpDescriptionSectionHeading;
118
150
  private extractRequirementCodesFromExpectedText;
119
151
  private extractRequirementCandidatesFromToken;
120
152
  private expandRequirementTokenByComma;
@@ -517,6 +517,7 @@ class ResultDataProvider {
517
517
  const rows = [];
518
518
  const stepsXmlByTestCase = this.buildTestCaseStepsXmlMap(testData);
519
519
  const testCaseTitleMap = this.buildMewpTestCaseTitleMap(testData);
520
+ const testCaseDescriptionMap = this.buildMewpTestCaseDescriptionMap(testData);
520
521
  const allTestCaseIds = new Set();
521
522
  for (const suite of testData || []) {
522
523
  const testCasesItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testCasesItems) ? suite.testCasesItems : [];
@@ -562,7 +563,13 @@ class ResultDataProvider {
562
563
  : [];
563
564
  const executableSteps = parsedSteps.filter((step) => !(step === null || step === void 0 ? void 0 : step.isSharedStepTitle));
564
565
  diagnostics.totalParsedSteps += executableSteps.length;
565
- const mentionEntries = this.extractRequirementMentionsFromExpectedSteps(parsedSteps, true);
566
+ // Direction A/B mentions can come from two sources:
567
+ // 1) Expected Result in executable steps.
568
+ // 2) "Assumptions" section in the test-case description.
569
+ const stepMentionEntries = this.extractRequirementMentionsFromExpectedSteps(parsedSteps, true);
570
+ const descriptionText = testCaseDescriptionMap.get(testCaseId) || '';
571
+ const assumptionsMentionEntries = this.extractRequirementMentionsFromAssumptionsDescription(descriptionText, true);
572
+ const mentionEntries = [...stepMentionEntries, ...assumptionsMentionEntries];
566
573
  diagnostics.totalStepsWithMentions += mentionEntries.length;
567
574
  const mentionedL2Only = new Set();
568
575
  const mentionedCodeFirstStep = new Map();
@@ -1289,6 +1296,48 @@ class ResultDataProvider {
1289
1296
  }
1290
1297
  return map;
1291
1298
  }
1299
+ /**
1300
+ * Builds a lookup of test case id -> test case description using suite payload data.
1301
+ *
1302
+ * Resolution order:
1303
+ * 1) direct description fields on test-case payload (`testCase.description` / `workItem.description`)
1304
+ * 2) `System.Description` / `Description` in work-item fields map
1305
+ * 3) `System.Description` / `Description` from work-item field list (`workItemFields`)
1306
+ */
1307
+ buildMewpTestCaseDescriptionMap(testData) {
1308
+ var _a, _b, _c, _d;
1309
+ const map = new Map();
1310
+ const readDescriptionFromFields = (fields) => {
1311
+ var _a;
1312
+ const value = (_a = this.getFieldValueByName(fields, 'System.Description')) !== null && _a !== void 0 ? _a : this.getFieldValueByName(fields, 'Description');
1313
+ return this.toMewpComparableText(value);
1314
+ };
1315
+ for (const suite of testData || []) {
1316
+ const testCasesItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testCasesItems) ? suite.testCasesItems : [];
1317
+ for (const testCase of testCasesItems) {
1318
+ const id = Number(((_a = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _a === void 0 ? void 0 : _a.id) || (testCase === null || testCase === void 0 ? void 0 : testCase.testCaseId) || (testCase === null || testCase === void 0 ? void 0 : testCase.id));
1319
+ if (!Number.isFinite(id) || id <= 0 || map.has(id))
1320
+ continue;
1321
+ const fromDirect = this.toMewpComparableText((testCase === null || testCase === void 0 ? void 0 : testCase.description) || ((_b = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _b === void 0 ? void 0 : _b.description));
1322
+ if (fromDirect) {
1323
+ map.set(id, fromDirect);
1324
+ continue;
1325
+ }
1326
+ const fieldsFromMap = ((_c = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _c === void 0 ? void 0 : _c.fields) || {};
1327
+ const fromFieldsMap = readDescriptionFromFields(fieldsFromMap);
1328
+ if (fromFieldsMap) {
1329
+ map.set(id, fromFieldsMap);
1330
+ continue;
1331
+ }
1332
+ const fieldsFromList = this.extractWorkItemFieldsMap((_d = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _d === void 0 ? void 0 : _d.workItemFields);
1333
+ const fromFieldsList = readDescriptionFromFields(fieldsFromList);
1334
+ if (fromFieldsList) {
1335
+ map.set(id, fromFieldsList);
1336
+ }
1337
+ }
1338
+ }
1339
+ return map;
1340
+ }
1292
1341
  extractMewpTestCaseId(runResult) {
1293
1342
  var _a;
1294
1343
  const testCaseId = Number((runResult === null || runResult === void 0 ? void 0 : runResult.testCaseId) || ((_a = runResult === null || runResult === void 0 ? void 0 : runResult.testCase) === null || _a === void 0 ? void 0 : _a.id) || 0);
@@ -1479,6 +1528,102 @@ class ResultDataProvider {
1479
1528
  }
1480
1529
  return out;
1481
1530
  }
1531
+ /**
1532
+ * Extracts SR requirement mentions from the "assumptions" section inside a test-case description.
1533
+ *
1534
+ * Notes:
1535
+ * - Heading detection is case-insensitive and keyed by "assumptions".
1536
+ * - Parsing is scoped to that section only and stops when a likely next section heading is reached
1537
+ * after at least one requirement-bearing line was collected.
1538
+ * - Returned stepRef is a synthetic marker ("Assumptions") so downstream discrepancy output can
1539
+ * clearly attribute source to description-level assumptions.
1540
+ */
1541
+ extractRequirementMentionsFromAssumptionsDescription(description, includeSuffix) {
1542
+ const lines = this.normalizeMewpDescriptionLines(description);
1543
+ if (lines.length === 0)
1544
+ return [];
1545
+ const assumptionsHeaderIndex = lines.findIndex((line) => ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_HEADER_PATTERN.test(line));
1546
+ if (assumptionsHeaderIndex < 0)
1547
+ return [];
1548
+ const assumptionCodes = new Set();
1549
+ let hasCollectedRequirementCodes = false;
1550
+ for (let index = assumptionsHeaderIndex + 1; index < lines.length; index += 1) {
1551
+ const rawLine = String(lines[index] || '').trim();
1552
+ if (!rawLine)
1553
+ continue;
1554
+ const line = rawLine.replace(/^[-*•]+\s*/, '').trim();
1555
+ if (!line)
1556
+ continue;
1557
+ const lineCodes = this.extractRequirementCodesFromExpectedText(line, includeSuffix);
1558
+ if (lineCodes.size > 0) {
1559
+ for (const code of lineCodes)
1560
+ assumptionCodes.add(code);
1561
+ hasCollectedRequirementCodes = true;
1562
+ continue;
1563
+ }
1564
+ if (hasCollectedRequirementCodes && this.isLikelyMewpDescriptionSectionHeading(line)) {
1565
+ break;
1566
+ }
1567
+ }
1568
+ if (assumptionCodes.size === 0)
1569
+ return [];
1570
+ return [{ stepRef: ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_REF, codes: assumptionCodes }];
1571
+ }
1572
+ /**
1573
+ * Normalizes raw HTML/plain description content into comparable text lines.
1574
+ * This makes section/title heuristics resilient to formatting differences.
1575
+ */
1576
+ normalizeMewpDescriptionLines(description) {
1577
+ const raw = String(description || '');
1578
+ if (!raw)
1579
+ return [];
1580
+ const normalized = raw
1581
+ .replace(/\r\n?/g, '\n')
1582
+ .replace(/<\s*br\s*\/?>/gi, '\n')
1583
+ // Underlined sections are commonly used as inline HTML titles in MEWP test-case descriptions.
1584
+ // Treat underline tags as boundaries so compact forms like <u><b>Title</b></u><p>... preserve section split.
1585
+ .replace(/<\s*u\b[^>]*>/gi, '\n')
1586
+ .replace(/<\/\s*u\s*>/gi, '\n')
1587
+ .replace(/<\s*li\b[^>]*>/gi, '\n- ')
1588
+ .replace(/<\/\s*(p|div|li|ul|ol|tr|td|h[1-6])\s*>/gi, '\n')
1589
+ .replace(/&nbsp;|&#160;|&#xA0;/gi, ' ')
1590
+ .replace(/&lt;/gi, '<')
1591
+ .replace(/&gt;/gi, '>')
1592
+ .replace(/&amp;/gi, '&')
1593
+ .replace(/&quot;/gi, '"')
1594
+ .replace(/&#39;|&apos;/gi, "'")
1595
+ .replace(/<[^>]*>/g, ' ')
1596
+ .replace(/[\u200B-\u200D\uFEFF]/g, '')
1597
+ .replace(/[ \t]+/g, ' ');
1598
+ return normalized
1599
+ .split('\n')
1600
+ .map((line) => String(line || '').trim())
1601
+ .filter((line) => !!line);
1602
+ }
1603
+ /**
1604
+ * Heuristic detector for description section headings used to stop assumptions parsing.
1605
+ * A line is considered a heading when it is short/title-like and does not itself contain SR codes.
1606
+ */
1607
+ isLikelyMewpDescriptionSectionHeading(line) {
1608
+ const normalized = String(line || '')
1609
+ .replace(/^[-*•]+\s*/, '')
1610
+ .trim();
1611
+ if (!normalized)
1612
+ return false;
1613
+ if (ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_HEADER_PATTERN.test(normalized))
1614
+ return false;
1615
+ if (this.extractRequirementCodesFromExpectedText(normalized, true).size > 0)
1616
+ return false;
1617
+ const compact = normalized.replace(/[.:;,\-–—]+$/, '').trim();
1618
+ if (!compact)
1619
+ return false;
1620
+ const wordCount = compact.split(/\s+/).filter((item) => !!item).length;
1621
+ if (wordCount === 0 || wordCount > 12)
1622
+ return false;
1623
+ if (/[.;!?]/.test(compact))
1624
+ return false;
1625
+ return /^[a-z0-9\s()&/+_'-]+$/i.test(compact);
1626
+ }
1482
1627
  extractRequirementCodesFromExpectedText(text, includeSuffix) {
1483
1628
  const out = new Set();
1484
1629
  const source = this.normalizeRequirementStepText(text);
@@ -4511,6 +4656,8 @@ class ResultDataProvider {
4511
4656
  ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG = '[MEWP][InternalValidation][Trace]';
4512
4657
  ResultDataProvider.MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG = '[MEWP][InternalValidation][Diagnostics]';
4513
4658
  ResultDataProvider.MEWP_INTERNAL_VALIDATION_SUMMARY_TAG = '[MEWP][InternalValidation][Summary]';
4659
+ ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_REF = 'Assumptions';
4660
+ ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_HEADER_PATTERN = /assumptions/i;
4514
4661
  ResultDataProvider.MEWP_L2_COVERAGE_COLUMNS = [
4515
4662
  'L2 REQ ID',
4516
4663
  'L2 REQ Title',