@elisra-devops/docgen-data-provider 1.90.0 → 1.92.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.
@@ -138,6 +138,7 @@ export default class ResultDataProvider {
138
138
  private isExcludedL3L4BySapWbs;
139
139
  private normalizeMewpRequirementCode;
140
140
  private normalizeMewpRequirementCodeWithSuffix;
141
+ private compareMewpRequirementCodes;
141
142
  private formatRequirementCodesGroupedByFamily;
142
143
  private toMewpComparableText;
143
144
  private fetchTestPlanName;
@@ -476,7 +476,7 @@ class ResultDataProvider {
476
476
  }
477
477
  }
478
478
  async getMewpInternalValidationFlatResults(testPlanId, projectName, selectedSuiteIds, linkedQueryRequest) {
479
- var _a, _b, _c;
479
+ var _a, _b;
480
480
  const defaultPayload = {
481
481
  sheetName: `MEWP Internal Validation - Plan ${testPlanId}`,
482
482
  columnOrder: [...ResultDataProvider.INTERNAL_VALIDATION_COLUMNS],
@@ -489,6 +489,31 @@ class ResultDataProvider {
489
489
  const linkedRequirementsByTestCase = await this.buildLinkedRequirementsByTestCase(allRequirements, testData, projectName);
490
490
  const scopedRequirementKeys = await this.resolveMewpRequirementScopeKeysFromQuery(linkedQueryRequest, allRequirements, linkedRequirementsByTestCase);
491
491
  const requirementFamilies = this.buildRequirementFamilyMap(allRequirements, (scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) ? scopedRequirementKeys : undefined);
492
+ const linkedFullCodesByTestCase = new Map();
493
+ const linkedFamilyCodesAcrossTestCases = new Map();
494
+ const linkedFullCodesAcrossTestCases = new Set();
495
+ const linkedBaseKeysAcrossTestCases = new Set();
496
+ for (const [linkedTestCaseId, links] of linkedRequirementsByTestCase.entries()) {
497
+ const rawFullCodes = (links === null || links === void 0 ? void 0 : links.fullCodes) || new Set();
498
+ const filteredFullCodes = (scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) && rawFullCodes.size > 0
499
+ ? new Set([...rawFullCodes].filter((code) => scopedRequirementKeys.has(this.toRequirementKey(code))))
500
+ : rawFullCodes;
501
+ linkedFullCodesByTestCase.set(linkedTestCaseId, filteredFullCodes);
502
+ for (const code of filteredFullCodes) {
503
+ const normalizedCode = this.normalizeMewpRequirementCodeWithSuffix(code);
504
+ if (!normalizedCode)
505
+ continue;
506
+ linkedFullCodesAcrossTestCases.add(normalizedCode);
507
+ const baseKey = this.toRequirementKey(normalizedCode);
508
+ if (!baseKey)
509
+ continue;
510
+ linkedBaseKeysAcrossTestCases.add(baseKey);
511
+ if (!linkedFamilyCodesAcrossTestCases.has(baseKey)) {
512
+ linkedFamilyCodesAcrossTestCases.set(baseKey, new Set());
513
+ }
514
+ linkedFamilyCodesAcrossTestCases.get(baseKey).add(normalizedCode);
515
+ }
516
+ }
492
517
  const rows = [];
493
518
  const stepsXmlByTestCase = this.buildTestCaseStepsXmlMap(testData);
494
519
  const testCaseTitleMap = this.buildMewpTestCaseTitleMap(testData);
@@ -558,11 +583,7 @@ class ResultDataProvider {
558
583
  diagnostics.testCasesWithoutMentionedCustomerIds += 1;
559
584
  }
560
585
  const mentionedBaseKeys = new Set([...mentionedL2Only].map((code) => this.toRequirementKey(code)).filter((code) => !!code));
561
- const linkedFullCodesRaw = ((_c = linkedRequirementsByTestCase.get(testCaseId)) === null || _c === void 0 ? void 0 : _c.fullCodes) || new Set();
562
- const linkedFullCodes = (scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) && linkedFullCodesRaw.size > 0
563
- ? new Set([...linkedFullCodesRaw].filter((code) => scopedRequirementKeys.has(this.toRequirementKey(code))))
564
- : linkedFullCodesRaw;
565
- const linkedBaseKeys = new Set([...linkedFullCodes].map((code) => this.toRequirementKey(code)).filter((code) => !!code));
586
+ const linkedFullCodes = linkedFullCodesByTestCase.get(testCaseId) || new Set();
566
587
  const mentionedCodesByBase = new Map();
567
588
  for (const code of mentionedL2Only) {
568
589
  const baseKey = this.toRequirementKey(code);
@@ -572,42 +593,36 @@ class ResultDataProvider {
572
593
  mentionedCodesByBase.set(baseKey, new Set());
573
594
  mentionedCodesByBase.get(baseKey).add(code);
574
595
  }
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.
596
+ // Direction A logic:
597
+ // 1) Base mention ("SR0054") is parent-level and considered covered only when
598
+ // the whole family is covered across scoped test cases:
599
+ // - if family has children, all children must be linked;
600
+ // - if family has no children, the base item itself must be linked.
601
+ // 2) Child mention ("SR0054-1") is exact-match and checked across scoped test cases.
579
602
  const missingBaseWhenFamilyUncovered = new Set();
580
603
  const missingSpecificMentionedNoFamily = new Set();
581
- const missingFamilyMembers = new Set();
582
604
  for (const [baseKey, mentionedCodes] of mentionedCodesByBase.entries()) {
583
605
  const familyCodes = requirementFamilies.get(baseKey);
584
606
  const mentionedCodesList = [...mentionedCodes];
585
607
  const hasBaseMention = mentionedCodesList.some((code) => !/-\d+$/.test(code));
586
608
  const mentionedSpecificMembers = mentionedCodesList.filter((code) => /-\d+$/.test(code));
587
609
  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.
610
+ const familyLinkedCodes = linkedFamilyCodesAcrossTestCases.get(baseKey) || new Set();
611
+ // Base mention ("SR0054") requires full family coverage across selected test cases.
590
612
  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
- }
613
+ const normalizedFamilyMembers = [...familyCodes]
614
+ .map((code) => this.normalizeMewpRequirementCodeWithSuffix(code))
615
+ .filter((code) => !!code);
616
+ const specificFamilyMembers = normalizedFamilyMembers.filter((code) => /-\d+$/.test(code));
617
+ const requiredFamilyMembers = specificFamilyMembers.length > 0 ? specificFamilyMembers : normalizedFamilyMembers;
618
+ const isWholeFamilyCovered = requiredFamilyMembers.every((memberCode) => familyLinkedCodes.has(memberCode));
619
+ if (!isWholeFamilyCovered) {
620
+ missingBaseWhenFamilyUncovered.add(baseKey);
605
621
  }
606
- continue;
607
622
  }
608
- // Specific mention ("SR0054-1") validates as exact-match only.
623
+ // Specific mention ("SR0054-1") validates as exact-match only across scoped test cases.
609
624
  for (const code of mentionedSpecificMembers) {
610
- if (!linkedFullCodes.has(code)) {
625
+ if (!familyLinkedCodes.has(code)) {
611
626
  missingSpecificMentionedNoFamily.add(code);
612
627
  }
613
628
  }
@@ -617,11 +632,11 @@ class ResultDataProvider {
617
632
  for (const code of mentionedCodes) {
618
633
  const hasSpecificSuffix = /-\d+$/.test(code);
619
634
  if (hasSpecificSuffix) {
620
- if (!linkedFullCodes.has(code)) {
635
+ if (!linkedFullCodesAcrossTestCases.has(code)) {
621
636
  missingSpecificMentionedNoFamily.add(code);
622
637
  }
623
638
  }
624
- else if (!linkedBaseKeys.has(baseKey)) {
639
+ else if (!linkedBaseKeysAcrossTestCases.has(baseKey)) {
625
640
  missingBaseWhenFamilyUncovered.add(baseKey);
626
641
  }
627
642
  }
@@ -641,13 +656,21 @@ class ResultDataProvider {
641
656
  return;
642
657
  const normalizedStepRef = String(stepRef || 'Step ?').trim() || 'Step ?';
643
658
  if (!mentionedButNotLinkedByStep.has(normalizedStepRef)) {
644
- mentionedButNotLinkedByStep.set(normalizedStepRef, new Set());
659
+ mentionedButNotLinkedByStep.set(normalizedStepRef, {
660
+ baseIds: new Set(),
661
+ specificIds: new Set(),
662
+ });
663
+ }
664
+ const entry = mentionedButNotLinkedByStep.get(normalizedStepRef);
665
+ if (/-\d+$/.test(normalizedRequirementId)) {
666
+ entry.specificIds.add(normalizedRequirementId);
667
+ }
668
+ else {
669
+ entry.baseIds.add(normalizedRequirementId);
645
670
  }
646
- mentionedButNotLinkedByStep.get(normalizedStepRef).add(normalizedRequirementId);
647
671
  };
648
672
  const sortedMissingSpecificMentionedNoFamily = [...missingSpecificMentionedNoFamily].sort((a, b) => a.localeCompare(b));
649
673
  const sortedMissingBaseWhenFamilyUncovered = [...missingBaseWhenFamilyUncovered].sort((a, b) => a.localeCompare(b));
650
- const sortedMissingFamilyMembers = [...missingFamilyMembers].sort((a, b) => a.localeCompare(b));
651
674
  for (const code of sortedMissingSpecificMentionedNoFamily) {
652
675
  const stepRef = mentionedCodeFirstStep.get(code) || 'Step ?';
653
676
  appendMentionedButNotLinked(code, stepRef);
@@ -656,11 +679,6 @@ class ResultDataProvider {
656
679
  const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
657
680
  appendMentionedButNotLinked(baseKey, stepRef);
658
681
  }
659
- for (const code of sortedMissingFamilyMembers) {
660
- const baseKey = this.toRequirementKey(code);
661
- const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
662
- appendMentionedButNotLinked(code, stepRef);
663
- }
664
682
  const sortedExtraLinked = [...new Set(extraLinked)]
665
683
  .map((code) => this.normalizeMewpRequirementCodeWithSuffix(code))
666
684
  .filter((code) => !!code)
@@ -678,12 +696,20 @@ class ResultDataProvider {
678
696
  return stepOrderA - stepOrderB;
679
697
  return String(a[0]).localeCompare(String(b[0]));
680
698
  })
681
- .map(([stepRef, requirementIds]) => {
682
- const groupedRequirementList = this.formatRequirementCodesGroupedByFamily(requirementIds);
683
- return `${stepRef}: ${groupedRequirementList}`;
699
+ .map(([stepRef, requirementIdsByType]) => {
700
+ const specificCodes = [...requirementIdsByType.specificIds].sort((a, b) => this.compareMewpRequirementCodes(a, b));
701
+ const specificFamilies = new Set(specificCodes.map((code) => this.toRequirementKey(code)).filter((code) => !!code));
702
+ const baseCodes = [...requirementIdsByType.baseIds]
703
+ .filter((baseCode) => !specificFamilies.has(baseCode))
704
+ .sort((a, b) => this.compareMewpRequirementCodes(a, b));
705
+ const displayCodes = [...specificCodes, ...baseCodes];
706
+ if (displayCodes.length === 0)
707
+ return `${stepRef}:`;
708
+ return `${stepRef}: ${displayCodes.join('; ')}`.trim();
684
709
  })
685
- .join('\n');
686
- const linkedButNotMentioned = this.formatRequirementCodesGroupedByFamily(sortedExtraLinked);
710
+ .join('\n')
711
+ .trim();
712
+ const linkedButNotMentioned = this.formatRequirementCodesGroupedByFamily(sortedExtraLinked).trim();
687
713
  const validationStatus = mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
688
714
  if (validationStatus === 'Fail')
689
715
  diagnostics.failingRows += 1;
@@ -691,8 +717,7 @@ class ResultDataProvider {
691
717
  `testCaseId=${testCaseId} parsedSteps=${executableSteps.length} ` +
692
718
  `stepsWithMentions=${mentionEntries.length} customerIdsFound=${mentionedL2Only.size} ` +
693
719
  `linkedRequirements=${linkedFullCodes.size} mentionedButNotLinked=${sortedMissingSpecificMentionedNoFamily.length +
694
- sortedMissingBaseWhenFamilyUncovered.length +
695
- sortedMissingFamilyMembers.length} ` +
720
+ sortedMissingBaseWhenFamilyUncovered.length} ` +
696
721
  `linkedButNotMentioned=${sortedExtraLinked.length} status=${validationStatus} ` +
697
722
  `customerIdSample='${[...mentionedL2Only].slice(0, 5).join(', ')}'`);
698
723
  rows.push({
@@ -2145,6 +2170,35 @@ class ResultDataProvider {
2145
2170
  return `SR${match[1]}-${match[2]}`;
2146
2171
  return `SR${match[1]}`;
2147
2172
  }
2173
+ compareMewpRequirementCodes(a, b) {
2174
+ const normalizeComparableCode = (value) => {
2175
+ const normalizedCode = this.normalizeMewpRequirementCodeWithSuffix(value);
2176
+ const match = /^SR(\d+)(?:-(\d+))?$/i.exec(normalizedCode);
2177
+ if (!match) {
2178
+ return {
2179
+ base: Number.POSITIVE_INFINITY,
2180
+ hasSuffix: 1,
2181
+ suffix: Number.POSITIVE_INFINITY,
2182
+ raw: String(value || ''),
2183
+ };
2184
+ }
2185
+ return {
2186
+ base: Number(match[1]),
2187
+ hasSuffix: match[2] ? 1 : 0,
2188
+ suffix: match[2] ? Number(match[2]) : -1,
2189
+ raw: normalizedCode,
2190
+ };
2191
+ };
2192
+ const left = normalizeComparableCode(a);
2193
+ const right = normalizeComparableCode(b);
2194
+ if (left.base !== right.base)
2195
+ return left.base - right.base;
2196
+ if (left.hasSuffix !== right.hasSuffix)
2197
+ return left.hasSuffix - right.hasSuffix;
2198
+ if (left.suffix !== right.suffix)
2199
+ return left.suffix - right.suffix;
2200
+ return left.raw.localeCompare(right.raw);
2201
+ }
2148
2202
  formatRequirementCodesGroupedByFamily(codes) {
2149
2203
  const byBaseKey = new Map();
2150
2204
  for (const rawCode of codes || []) {
@@ -2159,12 +2213,13 @@ class ResultDataProvider {
2159
2213
  if (byBaseKey.size === 0)
2160
2214
  return '';
2161
2215
  return [...byBaseKey.entries()]
2162
- .sort((a, b) => a[0].localeCompare(b[0]))
2216
+ .sort((a, b) => this.compareMewpRequirementCodes(a[0], b[0]))
2163
2217
  .map(([baseKey, members]) => {
2164
- const sortedMembers = [...members].sort((a, b) => a.localeCompare(b));
2218
+ const sortedMembers = [...members].sort((a, b) => this.compareMewpRequirementCodes(a, b));
2165
2219
  if (sortedMembers.length <= 1)
2166
2220
  return sortedMembers[0];
2167
- return `${baseKey}: ${sortedMembers.join(', ')}`;
2221
+ // Direction B display is family-level when multiple members exist.
2222
+ return baseKey;
2168
2223
  })
2169
2224
  .join('\n');
2170
2225
  }