@elisra-devops/docgen-data-provider 1.85.0 → 1.87.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.
@@ -142,6 +142,7 @@ export default class ResultDataProvider {
142
142
  private isExcludedL3L4BySapWbs;
143
143
  private normalizeMewpRequirementCode;
144
144
  private normalizeMewpRequirementCodeWithSuffix;
145
+ private formatRequirementCodesGroupedByFamily;
145
146
  private toMewpComparableText;
146
147
  private fetchTestPlanName;
147
148
  /**
@@ -558,34 +558,73 @@ class ResultDataProvider {
558
558
  diagnostics.testCasesWithoutMentionedCustomerIds += 1;
559
559
  }
560
560
  const mentionedBaseKeys = new Set([...mentionedL2Only].map((code) => this.toRequirementKey(code)).filter((code) => !!code));
561
- const expectedFamilyCodes = new Set();
562
- for (const baseKey of mentionedBaseKeys) {
563
- const familyCodes = requirementFamilies.get(baseKey);
564
- if (familyCodes === null || familyCodes === void 0 ? void 0 : familyCodes.size) {
565
- familyCodes.forEach((code) => expectedFamilyCodes.add(code));
566
- }
567
- else {
568
- for (const code of mentionedL2Only) {
569
- if (this.toRequirementKey(code) === baseKey)
570
- expectedFamilyCodes.add(code);
571
- }
572
- }
573
- }
574
561
  const linkedFullCodesRaw = ((_c = linkedRequirementsByTestCase.get(testCaseId)) === null || _c === void 0 ? void 0 : _c.fullCodes) || new Set();
575
562
  const linkedFullCodes = (scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) && linkedFullCodesRaw.size > 0
576
563
  ? new Set([...linkedFullCodesRaw].filter((code) => scopedRequirementKeys.has(this.toRequirementKey(code))))
577
564
  : linkedFullCodesRaw;
578
565
  const linkedBaseKeys = new Set([...linkedFullCodes].map((code) => this.toRequirementKey(code)).filter((code) => !!code));
579
- const missingMentioned = [...mentionedL2Only].filter((code) => {
566
+ const mentionedCodesByBase = new Map();
567
+ for (const code of mentionedL2Only) {
580
568
  const baseKey = this.toRequirementKey(code);
581
569
  if (!baseKey)
582
- return false;
583
- const hasSpecificSuffix = /-\d+$/.test(code);
584
- if (hasSpecificSuffix)
585
- return !linkedFullCodes.has(code);
586
- return !linkedBaseKeys.has(baseKey);
587
- });
588
- const missingFamily = [...expectedFamilyCodes].filter((code) => !linkedFullCodes.has(code));
570
+ continue;
571
+ if (!mentionedCodesByBase.has(baseKey))
572
+ mentionedCodesByBase.set(baseKey, new Set());
573
+ mentionedCodesByBase.get(baseKey).add(code);
574
+ }
575
+ // Context-based direction A logic:
576
+ // 1) If no member of family is linked -> report only base SR (Step X: SR0054).
577
+ // 2) If family is partially linked -> report only specific missing members.
578
+ // 3) If family fully linked -> report nothing for that family.
579
+ const missingBaseWhenFamilyUncovered = new Set();
580
+ const missingSpecificMentionedNoFamily = new Set();
581
+ const missingFamilyMembers = new Set();
582
+ for (const [baseKey, mentionedCodes] of mentionedCodesByBase.entries()) {
583
+ const familyCodes = requirementFamilies.get(baseKey);
584
+ const mentionedCodesList = [...mentionedCodes];
585
+ const hasBaseMention = mentionedCodesList.some((code) => !/-\d+$/.test(code));
586
+ const mentionedSpecificMembers = mentionedCodesList.filter((code) => /-\d+$/.test(code));
587
+ if (familyCodes === null || familyCodes === void 0 ? void 0 : familyCodes.size) {
588
+ // Base mention ("SR0054") validates against child coverage when children exist.
589
+ // If no child variants exist, fallback to the single standalone requirement code.
590
+ if (hasBaseMention) {
591
+ const familyCodesList = [...familyCodes];
592
+ const childFamilyCodes = familyCodesList.filter((code) => /-\d+$/.test(code));
593
+ const targetFamilyCodes = childFamilyCodes.length > 0 ? childFamilyCodes : familyCodesList;
594
+ const missingInTargetFamily = targetFamilyCodes.filter((code) => !linkedFullCodes.has(code));
595
+ if (missingInTargetFamily.length > 0) {
596
+ const hasAnyLinkedInFamily = familyCodesList.some((code) => linkedFullCodes.has(code));
597
+ if (!hasAnyLinkedInFamily) {
598
+ missingBaseWhenFamilyUncovered.add(baseKey);
599
+ }
600
+ else {
601
+ for (const code of missingInTargetFamily) {
602
+ missingFamilyMembers.add(code);
603
+ }
604
+ }
605
+ }
606
+ continue;
607
+ }
608
+ // Specific mention ("SR0054-1") validates as exact-match only.
609
+ for (const code of mentionedSpecificMembers) {
610
+ if (!linkedFullCodes.has(code)) {
611
+ missingSpecificMentionedNoFamily.add(code);
612
+ }
613
+ }
614
+ continue;
615
+ }
616
+ // Fallback path when family data is unavailable for this base key.
617
+ for (const code of mentionedCodes) {
618
+ const hasSpecificSuffix = /-\d+$/.test(code);
619
+ if (hasSpecificSuffix) {
620
+ if (!linkedFullCodes.has(code))
621
+ missingSpecificMentionedNoFamily.add(code);
622
+ }
623
+ else if (!linkedBaseKeys.has(baseKey)) {
624
+ missingBaseWhenFamilyUncovered.add(baseKey);
625
+ }
626
+ }
627
+ }
589
628
  // Direction B is family-based: if any member of a family is mentioned in Expected Result,
590
629
  // linked members of that same family are not considered "linked but not mentioned".
591
630
  const extraLinked = [...linkedFullCodes].filter((code) => {
@@ -605,13 +644,18 @@ class ResultDataProvider {
605
644
  }
606
645
  mentionedButNotLinkedByStep.get(normalizedStepRef).add(normalizedRequirementId);
607
646
  };
608
- const sortedMissingMentioned = [...new Set(missingMentioned)].sort((a, b) => a.localeCompare(b));
609
- const sortedMissingFamily = [...new Set(missingFamily)].sort((a, b) => a.localeCompare(b));
610
- for (const code of sortedMissingMentioned) {
647
+ const sortedMissingSpecificMentionedNoFamily = [...missingSpecificMentionedNoFamily].sort((a, b) => a.localeCompare(b));
648
+ const sortedMissingBaseWhenFamilyUncovered = [...missingBaseWhenFamilyUncovered].sort((a, b) => a.localeCompare(b));
649
+ const sortedMissingFamilyMembers = [...missingFamilyMembers].sort((a, b) => a.localeCompare(b));
650
+ for (const code of sortedMissingSpecificMentionedNoFamily) {
611
651
  const stepRef = mentionedCodeFirstStep.get(code) || 'Step ?';
612
652
  appendMentionedButNotLinked(code, stepRef);
613
653
  }
614
- for (const code of sortedMissingFamily) {
654
+ for (const baseKey of sortedMissingBaseWhenFamilyUncovered) {
655
+ const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
656
+ appendMentionedButNotLinked(baseKey, stepRef);
657
+ }
658
+ for (const code of sortedMissingFamilyMembers) {
615
659
  const baseKey = this.toRequirementKey(code);
616
660
  const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
617
661
  appendMentionedButNotLinked(code, stepRef);
@@ -634,18 +678,20 @@ class ResultDataProvider {
634
678
  return String(a[0]).localeCompare(String(b[0]));
635
679
  })
636
680
  .map(([stepRef, requirementIds]) => {
637
- const requirementList = [...requirementIds].sort((a, b) => a.localeCompare(b));
638
- return `${stepRef}: ${requirementList.join(', ')}`;
681
+ const groupedRequirementList = this.formatRequirementCodesGroupedByFamily(requirementIds);
682
+ return `${stepRef}: ${groupedRequirementList}`;
639
683
  })
640
684
  .join('; ');
641
- const linkedButNotMentioned = sortedExtraLinked.join('; ');
685
+ const linkedButNotMentioned = this.formatRequirementCodesGroupedByFamily(sortedExtraLinked);
642
686
  const validationStatus = mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
643
687
  if (validationStatus === 'Fail')
644
688
  diagnostics.failingRows += 1;
645
689
  logger_1.default.debug(`MEWP internal validation parse diagnostics: ` +
646
690
  `testCaseId=${testCaseId} parsedSteps=${executableSteps.length} ` +
647
691
  `stepsWithMentions=${mentionEntries.length} customerIdsFound=${mentionedL2Only.size} ` +
648
- `linkedRequirements=${linkedFullCodes.size} mentionedButNotLinked=${sortedMissingMentioned.length + sortedMissingFamily.length} ` +
692
+ `linkedRequirements=${linkedFullCodes.size} mentionedButNotLinked=${sortedMissingSpecificMentionedNoFamily.length +
693
+ sortedMissingBaseWhenFamilyUncovered.length +
694
+ sortedMissingFamilyMembers.length} ` +
649
695
  `linkedButNotMentioned=${sortedExtraLinked.length} status=${validationStatus} ` +
650
696
  `customerIdSample='${[...mentionedL2Only].slice(0, 5).join(', ')}'`);
651
697
  rows.push({
@@ -2225,6 +2271,29 @@ class ResultDataProvider {
2225
2271
  return `SR${match[1]}-${match[2]}`;
2226
2272
  return `SR${match[1]}`;
2227
2273
  }
2274
+ formatRequirementCodesGroupedByFamily(codes) {
2275
+ const byBaseKey = new Map();
2276
+ for (const rawCode of codes || []) {
2277
+ const normalizedCode = this.normalizeMewpRequirementCodeWithSuffix(String(rawCode || ''));
2278
+ if (!normalizedCode)
2279
+ continue;
2280
+ const baseKey = this.toRequirementKey(normalizedCode) || normalizedCode;
2281
+ if (!byBaseKey.has(baseKey))
2282
+ byBaseKey.set(baseKey, new Set());
2283
+ byBaseKey.get(baseKey).add(normalizedCode);
2284
+ }
2285
+ if (byBaseKey.size === 0)
2286
+ return '';
2287
+ return [...byBaseKey.entries()]
2288
+ .sort((a, b) => a[0].localeCompare(b[0]))
2289
+ .map(([baseKey, members]) => {
2290
+ const sortedMembers = [...members].sort((a, b) => a.localeCompare(b));
2291
+ if (sortedMembers.length <= 1)
2292
+ return sortedMembers[0];
2293
+ return `${baseKey}: ${sortedMembers.join(', ')}`;
2294
+ })
2295
+ .join('; ');
2296
+ }
2228
2297
  toMewpComparableText(value) {
2229
2298
  if (value === null || value === undefined)
2230
2299
  return '';