@elisra-devops/docgen-data-provider 1.86.0 → 1.88.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
  /**
@@ -523,6 +523,35 @@ class ResultDataProvider {
523
523
  };
524
524
  for (const testCaseId of [...allTestCaseIds].sort((a, b) => a - b)) {
525
525
  diagnostics.totalTestCases += 1;
526
+ const logList = (items, max = 12) => {
527
+ const values = [...items].map((item) => String(item || '').trim()).filter((item) => !!item);
528
+ const shown = values.slice(0, max);
529
+ const suffix = values.length > max ? ` ...(+${values.length - max} more)` : '';
530
+ return shown.join(', ') + suffix;
531
+ };
532
+ const logByFamily = (items, maxFamilies = 8, maxMembers = 10) => {
533
+ const map = new Map();
534
+ for (const item of items || []) {
535
+ const normalized = this.normalizeMewpRequirementCodeWithSuffix(String(item || ''));
536
+ if (!normalized)
537
+ continue;
538
+ const base = this.toRequirementKey(normalized) || normalized;
539
+ if (!map.has(base))
540
+ map.set(base, new Set());
541
+ map.get(base).add(normalized);
542
+ }
543
+ const entries = [...map.entries()]
544
+ .sort((a, b) => a[0].localeCompare(b[0]))
545
+ .slice(0, maxFamilies)
546
+ .map(([base, members]) => {
547
+ const sortedMembers = [...members].sort((a, b) => a.localeCompare(b));
548
+ const shownMembers = sortedMembers.slice(0, maxMembers);
549
+ const membersSuffix = sortedMembers.length > maxMembers ? ` ...(+${sortedMembers.length - maxMembers} more)` : '';
550
+ return `${base}=[${shownMembers.join(', ')}${membersSuffix}]`;
551
+ });
552
+ const suffix = map.size > maxFamilies ? ` ...(+${map.size - maxFamilies} families)` : '';
553
+ return entries.join(' | ') + suffix;
554
+ };
526
555
  const stepsXml = stepsXmlByTestCase.get(testCaseId) || '';
527
556
  const parsedSteps = stepsXml && String(stepsXml).trim() !== ''
528
557
  ? await this.testStepParserHelper.parseTestSteps(stepsXml, new Map())
@@ -563,6 +592,13 @@ class ResultDataProvider {
563
592
  ? new Set([...linkedFullCodesRaw].filter((code) => scopedRequirementKeys.has(this.toRequirementKey(code))))
564
593
  : linkedFullCodesRaw;
565
594
  const linkedBaseKeys = new Set([...linkedFullCodes].map((code) => this.toRequirementKey(code)).filter((code) => !!code));
595
+ const mentionStepSample = mentionEntries
596
+ .slice(0, 8)
597
+ .map((entry) => `${entry.stepRef}=[${logList(entry.codes, 6)}]`)
598
+ .join(' | ');
599
+ logger_1.default.debug(`MEWP internal validation trace: testCaseId=${testCaseId} ` +
600
+ `mentionSteps=${mentionEntries.length} mentionStepSample='${mentionStepSample}' ` +
601
+ `mentionedL2ByFamily='${logByFamily(mentionedL2Only)}' linkedByFamily='${logByFamily(linkedFullCodes)}'`);
566
602
  const mentionedCodesByBase = new Map();
567
603
  for (const code of mentionedL2Only) {
568
604
  const baseKey = this.toRequirementKey(code);
@@ -581,32 +617,85 @@ class ResultDataProvider {
581
617
  const missingFamilyMembers = new Set();
582
618
  for (const [baseKey, mentionedCodes] of mentionedCodesByBase.entries()) {
583
619
  const familyCodes = requirementFamilies.get(baseKey);
620
+ const mentionedCodesList = [...mentionedCodes];
621
+ const hasBaseMention = mentionedCodesList.some((code) => !/-\d+$/.test(code));
622
+ const mentionedSpecificMembers = mentionedCodesList.filter((code) => /-\d+$/.test(code));
623
+ let familyDecision = 'no-action';
624
+ let familyTargetCodes = [];
625
+ let familyMissingCodes = [];
626
+ let familyLinkedCodes = [];
627
+ let familyAllCodes = [];
584
628
  if (familyCodes === null || familyCodes === void 0 ? void 0 : familyCodes.size) {
585
- const missingInFamily = [...familyCodes].filter((code) => !linkedFullCodes.has(code));
586
- if (missingInFamily.length === 0)
629
+ familyAllCodes = [...familyCodes].sort((a, b) => a.localeCompare(b));
630
+ familyLinkedCodes = familyAllCodes.filter((code) => linkedFullCodes.has(code));
631
+ // Base mention ("SR0054") validates against child coverage when children exist.
632
+ // If no child variants exist, fallback to the single standalone requirement code.
633
+ if (hasBaseMention) {
634
+ const familyCodesList = [...familyCodes];
635
+ const childFamilyCodes = familyCodesList.filter((code) => /-\d+$/.test(code));
636
+ const targetFamilyCodes = childFamilyCodes.length > 0 ? childFamilyCodes : familyCodesList;
637
+ familyTargetCodes = [...targetFamilyCodes].sort((a, b) => a.localeCompare(b));
638
+ const missingInTargetFamily = targetFamilyCodes.filter((code) => !linkedFullCodes.has(code));
639
+ familyMissingCodes = [...missingInTargetFamily].sort((a, b) => a.localeCompare(b));
640
+ if (missingInTargetFamily.length > 0) {
641
+ const hasAnyLinkedInFamily = familyCodesList.some((code) => linkedFullCodes.has(code));
642
+ if (!hasAnyLinkedInFamily) {
643
+ missingBaseWhenFamilyUncovered.add(baseKey);
644
+ familyDecision = 'base-mentioned-family-uncovered';
645
+ }
646
+ else {
647
+ for (const code of missingInTargetFamily) {
648
+ missingFamilyMembers.add(code);
649
+ }
650
+ familyDecision = 'base-mentioned-family-partial-missing-children';
651
+ }
652
+ }
653
+ else {
654
+ familyDecision = 'base-mentioned-family-fully-covered';
655
+ }
656
+ logger_1.default.debug(`MEWP internal validation family decision: testCaseId=${testCaseId} base=${baseKey} ` +
657
+ `mode=baseMention mentioned='${logList(mentionedCodesList)}' familyAll='${logList(familyAllCodes)}' ` +
658
+ `target='${logList(familyTargetCodes)}' linked='${logList(familyLinkedCodes)}' ` +
659
+ `missing='${logList(familyMissingCodes)}' decision=${familyDecision}`);
587
660
  continue;
588
- const linkedInFamilyCount = familyCodes.size - missingInFamily.length;
589
- if (linkedInFamilyCount === 0) {
590
- missingBaseWhenFamilyUncovered.add(baseKey);
591
661
  }
592
- else {
593
- for (const code of missingInFamily) {
594
- missingFamilyMembers.add(code);
662
+ // Specific mention ("SR0054-1") validates as exact-match only.
663
+ const missingSpecificMembers = [];
664
+ for (const code of mentionedSpecificMembers) {
665
+ if (!linkedFullCodes.has(code)) {
666
+ missingSpecificMentionedNoFamily.add(code);
667
+ missingSpecificMembers.push(code);
595
668
  }
596
669
  }
670
+ familyDecision =
671
+ missingSpecificMembers.length > 0
672
+ ? 'specific-mentioned-exact-missing'
673
+ : 'specific-mentioned-exact-covered';
674
+ logger_1.default.debug(`MEWP internal validation family decision: testCaseId=${testCaseId} base=${baseKey} ` +
675
+ `mode=specificMention mentioned='${logList(mentionedCodesList)}' familyAll='${logList(familyAllCodes)}' ` +
676
+ `linked='${logList(familyLinkedCodes)}' missingSpecific='${logList(missingSpecificMembers)}' ` +
677
+ `decision=${familyDecision}`);
597
678
  continue;
598
679
  }
599
680
  // Fallback path when family data is unavailable for this base key.
681
+ const fallbackMissingSpecific = [];
682
+ let fallbackBaseMissing = false;
600
683
  for (const code of mentionedCodes) {
601
684
  const hasSpecificSuffix = /-\d+$/.test(code);
602
685
  if (hasSpecificSuffix) {
603
- if (!linkedFullCodes.has(code))
686
+ if (!linkedFullCodes.has(code)) {
604
687
  missingSpecificMentionedNoFamily.add(code);
688
+ fallbackMissingSpecific.push(code);
689
+ }
605
690
  }
606
691
  else if (!linkedBaseKeys.has(baseKey)) {
607
692
  missingBaseWhenFamilyUncovered.add(baseKey);
693
+ fallbackBaseMissing = true;
608
694
  }
609
695
  }
696
+ logger_1.default.debug(`MEWP internal validation family decision: testCaseId=${testCaseId} base=${baseKey} ` +
697
+ `mode=noFamilyData mentioned='${logList(mentionedCodesList)}' linkedBasePresent=${linkedBaseKeys.has(baseKey)} ` +
698
+ `missingSpecific='${logList(fallbackMissingSpecific)}' missingBase=${fallbackBaseMissing}`);
610
699
  }
611
700
  // Direction B is family-based: if any member of a family is mentioned in Expected Result,
612
701
  // linked members of that same family are not considered "linked but not mentioned".
@@ -661,11 +750,17 @@ class ResultDataProvider {
661
750
  return String(a[0]).localeCompare(String(b[0]));
662
751
  })
663
752
  .map(([stepRef, requirementIds]) => {
664
- const requirementList = [...requirementIds].sort((a, b) => a.localeCompare(b));
665
- return `${stepRef}: ${requirementList.join(', ')}`;
753
+ const groupedRequirementList = this.formatRequirementCodesGroupedByFamily(requirementIds);
754
+ return `${stepRef}: ${groupedRequirementList}`;
666
755
  })
667
756
  .join('; ');
668
- const linkedButNotMentioned = sortedExtraLinked.join('; ');
757
+ const linkedButNotMentioned = this.formatRequirementCodesGroupedByFamily(sortedExtraLinked);
758
+ const rawMentionedByStepForLog = [...mentionedButNotLinkedByStep.entries()]
759
+ .map(([stepRef, requirementIds]) => `${stepRef}=[${logList(requirementIds, 8)}]`)
760
+ .join(' | ');
761
+ logger_1.default.debug(`MEWP internal validation grouped diagnostics: testCaseId=${testCaseId} ` +
762
+ `rawMentionedByStep='${rawMentionedByStepForLog}' groupedMentioned='${mentionedButNotLinked}' ` +
763
+ `rawLinkedOnlyByFamily='${logByFamily(sortedExtraLinked)}' groupedLinkedOnly='${linkedButNotMentioned}'`);
669
764
  const validationStatus = mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
670
765
  if (validationStatus === 'Fail')
671
766
  diagnostics.failingRows += 1;
@@ -2254,6 +2349,29 @@ class ResultDataProvider {
2254
2349
  return `SR${match[1]}-${match[2]}`;
2255
2350
  return `SR${match[1]}`;
2256
2351
  }
2352
+ formatRequirementCodesGroupedByFamily(codes) {
2353
+ const byBaseKey = new Map();
2354
+ for (const rawCode of codes || []) {
2355
+ const normalizedCode = this.normalizeMewpRequirementCodeWithSuffix(String(rawCode || ''));
2356
+ if (!normalizedCode)
2357
+ continue;
2358
+ const baseKey = this.toRequirementKey(normalizedCode) || normalizedCode;
2359
+ if (!byBaseKey.has(baseKey))
2360
+ byBaseKey.set(baseKey, new Set());
2361
+ byBaseKey.get(baseKey).add(normalizedCode);
2362
+ }
2363
+ if (byBaseKey.size === 0)
2364
+ return '';
2365
+ return [...byBaseKey.entries()]
2366
+ .sort((a, b) => a[0].localeCompare(b[0]))
2367
+ .map(([baseKey, members]) => {
2368
+ const sortedMembers = [...members].sort((a, b) => a.localeCompare(b));
2369
+ if (sortedMembers.length <= 1)
2370
+ return sortedMembers[0];
2371
+ return `${baseKey}: ${sortedMembers.join(', ')}`;
2372
+ })
2373
+ .join('; ');
2374
+ }
2257
2375
  toMewpComparableText(value) {
2258
2376
  if (value === null || value === undefined)
2259
2377
  return '';