@elisra-devops/docgen-data-provider 1.92.0 → 1.94.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.
@@ -23,6 +23,9 @@ import type { MewpCoverageFlatPayload, MewpExternalFilesValidationResponse, Mewp
23
23
  * Instantiate the class with the organization URL and token, and use the provided methods to fetch and process test data.
24
24
  */
25
25
  export default class ResultDataProvider {
26
+ private static readonly MEWP_INTERNAL_VALIDATION_TRACE_TAG;
27
+ private static readonly MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG;
28
+ private static readonly MEWP_INTERNAL_VALIDATION_SUMMARY_TAG;
26
29
  private static readonly MEWP_L2_COVERAGE_COLUMNS;
27
30
  private static readonly INTERNAL_VALIDATION_COLUMNS;
28
31
  orgUrl: string;
@@ -70,7 +73,9 @@ export default class ResultDataProvider {
70
73
  * Rows are one Requirement-TestCase pair; uncovered requirements are emitted with empty test-case columns.
71
74
  */
72
75
  getMewpL2CoverageFlatResults(testPlanId: string, projectName: string, selectedSuiteIds: number[] | undefined, linkedQueryRequest?: any, options?: MewpCoverageRequestOptions): Promise<MewpCoverageFlatPayload>;
73
- getMewpInternalValidationFlatResults(testPlanId: string, projectName: string, selectedSuiteIds: number[] | undefined, linkedQueryRequest?: any): Promise<MewpInternalValidationFlatPayload>;
76
+ getMewpInternalValidationFlatResults(testPlanId: string, projectName: string, selectedSuiteIds: number[] | undefined, linkedQueryRequest?: any, options?: {
77
+ debugMode?: boolean;
78
+ }): Promise<MewpInternalValidationFlatPayload>;
74
79
  validateMewpExternalFiles(options: {
75
80
  externalBugsFile?: MewpExternalFileRef | null;
76
81
  externalL3L4File?: MewpExternalFileRef | null;
@@ -82,6 +87,8 @@ export default class ResultDataProvider {
82
87
  mapAttachmentsUrl(runResults: any[], project: string): any[];
83
88
  private buildMewpCoverageSheetName;
84
89
  private buildInternalValidationSheetName;
90
+ private formatLogValue;
91
+ private buildTaggedLogMessage;
85
92
  private createMewpCoverageRow;
86
93
  private createEmptyMewpCoverageBugCell;
87
94
  private createEmptyMewpCoverageL3L4Cell;
@@ -194,6 +201,7 @@ export default class ResultDataProvider {
194
201
  private fetchTestCasesBySuiteId;
195
202
  private attachSuiteTestCaseContextToPoints;
196
203
  private extractWorkItemFieldsMap;
204
+ private getFieldValueByName;
197
205
  private resolveSuiteTestCaseRevision;
198
206
  private buildWorkItemSnapshotFromSuiteTestCase;
199
207
  private fetchWorkItemByRevision;
@@ -475,7 +475,7 @@ class ResultDataProvider {
475
475
  return defaultPayload;
476
476
  }
477
477
  }
478
- async getMewpInternalValidationFlatResults(testPlanId, projectName, selectedSuiteIds, linkedQueryRequest) {
478
+ async getMewpInternalValidationFlatResults(testPlanId, projectName, selectedSuiteIds, linkedQueryRequest, options) {
479
479
  var _a, _b;
480
480
  const defaultPayload = {
481
481
  sheetName: `MEWP Internal Validation - Plan ${testPlanId}`,
@@ -546,8 +546,16 @@ class ResultDataProvider {
546
546
  testCasesWithoutMentionedCustomerIds: 0,
547
547
  failingRows: 0,
548
548
  };
549
+ const traceInternalValidation = (options === null || options === void 0 ? void 0 : options.debugMode) === true;
550
+ if (traceInternalValidation) {
551
+ logger_1.default.info(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
552
+ mode: 'enabled',
553
+ source: 'ui-debug-mode',
554
+ }));
555
+ }
549
556
  for (const testCaseId of [...allTestCaseIds].sort((a, b) => a - b)) {
550
557
  diagnostics.totalTestCases += 1;
558
+ const traceCurrentTestCase = traceInternalValidation;
551
559
  const stepsXml = stepsXmlByTestCase.get(testCaseId) || '';
552
560
  const parsedSteps = stepsXml && String(stepsXml).trim() !== ''
553
561
  ? await this.testStepParserHelper.parseTestSteps(stepsXml, new Map())
@@ -593,6 +601,19 @@ class ResultDataProvider {
593
601
  mentionedCodesByBase.set(baseKey, new Set());
594
602
  mentionedCodesByBase.get(baseKey).add(code);
595
603
  }
604
+ if (traceCurrentTestCase) {
605
+ logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
606
+ event: 'test-case-start',
607
+ tc: testCaseId,
608
+ parsedSteps: executableSteps.length,
609
+ stepsWithMentions: mentionEntries.length,
610
+ mentionedCodes: [...mentionedL2Only].sort((a, b) => this.compareMewpRequirementCodes(a, b)).join('; ') ||
611
+ '<none>',
612
+ linkedCodesInTestCase: [...linkedFullCodes]
613
+ .sort((a, b) => this.compareMewpRequirementCodes(a, b))
614
+ .join('; ') || '<none>',
615
+ }));
616
+ }
596
617
  // Direction A logic:
597
618
  // 1) Base mention ("SR0054") is parent-level and considered covered only when
598
619
  // the whole family is covered across scoped test cases:
@@ -608,38 +629,88 @@ class ResultDataProvider {
608
629
  const mentionedSpecificMembers = mentionedCodesList.filter((code) => /-\d+$/.test(code));
609
630
  if (familyCodes === null || familyCodes === void 0 ? void 0 : familyCodes.size) {
610
631
  const familyLinkedCodes = linkedFamilyCodesAcrossTestCases.get(baseKey) || new Set();
632
+ const normalizedFamilyMembers = [...familyCodes]
633
+ .map((code) => this.normalizeMewpRequirementCodeWithSuffix(code))
634
+ .filter((code) => !!code);
635
+ const specificFamilyMembers = normalizedFamilyMembers.filter((code) => /-\d+$/.test(code));
636
+ const requiredFamilyMembers = specificFamilyMembers.length > 0 ? specificFamilyMembers : normalizedFamilyMembers;
611
637
  // Base mention ("SR0054") requires full family coverage across selected test cases.
612
638
  if (hasBaseMention) {
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;
639
+ const missingRequiredFamilyMembers = requiredFamilyMembers.filter((memberCode) => !familyLinkedCodes.has(memberCode));
618
640
  const isWholeFamilyCovered = requiredFamilyMembers.every((memberCode) => familyLinkedCodes.has(memberCode));
619
641
  if (!isWholeFamilyCovered) {
620
642
  missingBaseWhenFamilyUncovered.add(baseKey);
621
643
  }
644
+ if (traceCurrentTestCase) {
645
+ logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
646
+ event: 'base-family-coverage',
647
+ tc: testCaseId,
648
+ base: baseKey,
649
+ baseMention: true,
650
+ requiredFamily: requiredFamilyMembers
651
+ .sort((a, b) => this.compareMewpRequirementCodes(a, b))
652
+ .join('; ') || '<none>',
653
+ linkedAcrossScope: [...familyLinkedCodes]
654
+ .sort((a, b) => this.compareMewpRequirementCodes(a, b))
655
+ .join('; ') || '<none>',
656
+ missingRequired: missingRequiredFamilyMembers
657
+ .sort((a, b) => this.compareMewpRequirementCodes(a, b))
658
+ .join('; ') || '<none>',
659
+ covered: isWholeFamilyCovered,
660
+ }));
661
+ }
622
662
  }
623
663
  // Specific mention ("SR0054-1") validates as exact-match only across scoped test cases.
624
- for (const code of mentionedSpecificMembers) {
625
- if (!familyLinkedCodes.has(code)) {
626
- missingSpecificMentionedNoFamily.add(code);
627
- }
664
+ const missingSpecificMembers = mentionedSpecificMembers.filter((code) => !familyLinkedCodes.has(code));
665
+ for (const code of missingSpecificMembers) {
666
+ missingSpecificMentionedNoFamily.add(code);
667
+ }
668
+ if (traceCurrentTestCase && mentionedSpecificMembers.length > 0) {
669
+ logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
670
+ event: 'specific-members-check',
671
+ tc: testCaseId,
672
+ base: baseKey,
673
+ specificMentioned: mentionedSpecificMembers
674
+ .sort((a, b) => this.compareMewpRequirementCodes(a, b))
675
+ .join('; ') || '<none>',
676
+ specificMissing: missingSpecificMembers
677
+ .sort((a, b) => this.compareMewpRequirementCodes(a, b))
678
+ .join('; ') || '<none>',
679
+ }));
628
680
  }
629
681
  continue;
630
682
  }
631
683
  // Fallback path when family data is unavailable for this base key.
684
+ const fallbackMissingSpecific = [];
685
+ let fallbackMissingBase = false;
632
686
  for (const code of mentionedCodes) {
633
687
  const hasSpecificSuffix = /-\d+$/.test(code);
634
688
  if (hasSpecificSuffix) {
635
689
  if (!linkedFullCodesAcrossTestCases.has(code)) {
636
690
  missingSpecificMentionedNoFamily.add(code);
691
+ fallbackMissingSpecific.push(code);
637
692
  }
638
693
  }
639
694
  else if (!linkedBaseKeysAcrossTestCases.has(baseKey)) {
640
695
  missingBaseWhenFamilyUncovered.add(baseKey);
696
+ fallbackMissingBase = true;
641
697
  }
642
698
  }
699
+ if (traceCurrentTestCase) {
700
+ logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
701
+ event: 'fallback-path',
702
+ tc: testCaseId,
703
+ base: baseKey,
704
+ fallbackUsed: true,
705
+ mentioned: mentionedCodesList
706
+ .sort((a, b) => this.compareMewpRequirementCodes(a, b))
707
+ .join('; ') || '<none>',
708
+ missingSpecific: fallbackMissingSpecific
709
+ .sort((a, b) => this.compareMewpRequirementCodes(a, b))
710
+ .join('; ') || '<none>',
711
+ missingBase: fallbackMissingBase,
712
+ }));
713
+ }
643
714
  }
644
715
  // Direction B is family-based: if any member of a family is mentioned in Expected Result,
645
716
  // linked members of that same family are not considered "linked but not mentioned".
@@ -679,6 +750,14 @@ class ResultDataProvider {
679
750
  const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
680
751
  appendMentionedButNotLinked(baseKey, stepRef);
681
752
  }
753
+ if (traceCurrentTestCase) {
754
+ logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
755
+ event: 'direction-a-summary',
756
+ tc: testCaseId,
757
+ missingSpecific: sortedMissingSpecificMentionedNoFamily.join('; ') || '<none>',
758
+ missingBase: sortedMissingBaseWhenFamilyUncovered.join('; ') || '<none>',
759
+ }));
760
+ }
682
761
  const sortedExtraLinked = [...new Set(extraLinked)]
683
762
  .map((code) => this.normalizeMewpRequirementCodeWithSuffix(code))
684
763
  .filter((code) => !!code)
@@ -713,13 +792,18 @@ class ResultDataProvider {
713
792
  const validationStatus = mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
714
793
  if (validationStatus === 'Fail')
715
794
  diagnostics.failingRows += 1;
716
- logger_1.default.debug(`MEWP internal validation parse diagnostics: ` +
717
- `testCaseId=${testCaseId} parsedSteps=${executableSteps.length} ` +
718
- `stepsWithMentions=${mentionEntries.length} customerIdsFound=${mentionedL2Only.size} ` +
719
- `linkedRequirements=${linkedFullCodes.size} mentionedButNotLinked=${sortedMissingSpecificMentionedNoFamily.length +
720
- sortedMissingBaseWhenFamilyUncovered.length} ` +
721
- `linkedButNotMentioned=${sortedExtraLinked.length} status=${validationStatus} ` +
722
- `customerIdSample='${[...mentionedL2Only].slice(0, 5).join(', ')}'`);
795
+ logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG, {
796
+ testCaseId,
797
+ parsedSteps: executableSteps.length,
798
+ stepsWithMentions: mentionEntries.length,
799
+ customerIdsFound: mentionedL2Only.size,
800
+ linkedRequirements: linkedFullCodes.size,
801
+ mentionedButNotLinked: sortedMissingSpecificMentionedNoFamily.length +
802
+ sortedMissingBaseWhenFamilyUncovered.length,
803
+ linkedButNotMentioned: sortedExtraLinked.length,
804
+ status: validationStatus,
805
+ customerIdSample: [...mentionedL2Only].slice(0, 5).join(', '),
806
+ }));
723
807
  rows.push({
724
808
  'Test Case ID': testCaseId,
725
809
  'Test Case Title': String(testCaseTitleMap.get(testCaseId) || '').trim(),
@@ -728,11 +812,14 @@ class ResultDataProvider {
728
812
  'Validation Status': validationStatus,
729
813
  });
730
814
  }
731
- logger_1.default.info(`MEWP internal validation summary: testCases=${diagnostics.totalTestCases} ` +
732
- `parsedSteps=${diagnostics.totalParsedSteps} stepsWithMentions=${diagnostics.totalStepsWithMentions} ` +
733
- `totalCustomerIdsFound=${diagnostics.totalMentionedCustomerIds} ` +
734
- `testCasesWithoutCustomerIds=${diagnostics.testCasesWithoutMentionedCustomerIds} ` +
735
- `failingRows=${diagnostics.failingRows}`);
815
+ logger_1.default.info(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_SUMMARY_TAG, {
816
+ testCases: diagnostics.totalTestCases,
817
+ parsedSteps: diagnostics.totalParsedSteps,
818
+ stepsWithMentions: diagnostics.totalStepsWithMentions,
819
+ totalCustomerIdsFound: diagnostics.totalMentionedCustomerIds,
820
+ testCasesWithoutCustomerIds: diagnostics.testCasesWithoutMentionedCustomerIds,
821
+ failingRows: diagnostics.failingRows,
822
+ }));
736
823
  return {
737
824
  sheetName: this.buildInternalValidationSheetName(planName, testPlanId),
738
825
  columnOrder: [...ResultDataProvider.INTERNAL_VALIDATION_COLUMNS],
@@ -821,6 +908,16 @@ class ResultDataProvider {
821
908
  const suffix = String(planName || '').trim() || `Plan ${testPlanId}`;
822
909
  return `MEWP Internal Validation - ${suffix}`;
823
910
  }
911
+ formatLogValue(value) {
912
+ if (value === null || value === undefined)
913
+ return '<none>';
914
+ const asText = String(value).trim();
915
+ return asText !== '' ? asText : '<none>';
916
+ }
917
+ buildTaggedLogMessage(tag, fields) {
918
+ const sections = Object.entries(fields).map(([key, value]) => `${key}=${this.formatLogValue(value)}`);
919
+ return `${tag} ${sections.join(' | ')}`;
920
+ }
824
921
  createMewpCoverageRow(requirement, runStatus, bug, linkedL3L4) {
825
922
  const l2ReqIdNumeric = Number((requirement === null || requirement === void 0 ? void 0 : requirement.workItemId) || 0);
826
923
  const l2ReqId = l2ReqIdNumeric > 0 ? String(l2ReqIdNumeric) : '';
@@ -2221,7 +2318,7 @@ class ResultDataProvider {
2221
2318
  // Direction B display is family-level when multiple members exist.
2222
2319
  return baseKey;
2223
2320
  })
2224
- .join('\n');
2321
+ .join('; ');
2225
2322
  }
2226
2323
  toMewpComparableText(value) {
2227
2324
  if (value === null || value === undefined)
@@ -2419,7 +2516,7 @@ class ResultDataProvider {
2419
2516
  }
2420
2517
  // Fetch detailed information for each test point and map to required format
2421
2518
  const detailedPoints = await Promise.all(latestPoints.map(async (point) => {
2422
- const url = `${point.url}?witFields=Microsoft.VSTS.TCM.Steps&includePointDetails=true`;
2519
+ const url = `${point.url}?witFields=Microsoft.VSTS.TCM.Steps,System.Rev&includePointDetails=true`;
2423
2520
  const detailedPoint = await tfs_1.TFSServices.getItemContent(url, this.token);
2424
2521
  return this.mapTestPointForCrossPlans(detailedPoint, projectName);
2425
2522
  // return this.mapTestPointForCrossPlans(detailedPoint, projectName);
@@ -2500,7 +2597,7 @@ class ResultDataProvider {
2500
2597
  * Fetches test cases by suite ID.
2501
2598
  */
2502
2599
  async fetchTestCasesBySuiteId(projectName, testPlanId, suiteId) {
2503
- const url = `${this.orgUrl}${projectName}/_apis/testplan/Plans/${testPlanId}/Suites/${suiteId}/TestCase?witFields=Microsoft.VSTS.TCM.Steps`;
2600
+ const url = `${this.orgUrl}${projectName}/_apis/testplan/Plans/${testPlanId}/Suites/${suiteId}/TestCase?witFields=Microsoft.VSTS.TCM.Steps,System.Rev`;
2504
2601
  const { value: testCases } = await tfs_1.TFSServices.getItemContent(url, this.token);
2505
2602
  return testCases;
2506
2603
  }
@@ -2542,14 +2639,32 @@ class ResultDataProvider {
2542
2639
  }
2543
2640
  return fields;
2544
2641
  }
2642
+ getFieldValueByName(fields, fieldName) {
2643
+ if (!fields || typeof fields !== 'object')
2644
+ return undefined;
2645
+ if (Object.prototype.hasOwnProperty.call(fields, fieldName)) {
2646
+ return fields[fieldName];
2647
+ }
2648
+ const lookupName = String(fieldName || '').toLowerCase().trim();
2649
+ if (!lookupName)
2650
+ return undefined;
2651
+ const matchedKey = Object.keys(fields).find((key) => String(key || '').toLowerCase().trim() === lookupName);
2652
+ return matchedKey ? fields[matchedKey] : undefined;
2653
+ }
2545
2654
  resolveSuiteTestCaseRevision(testCaseItem) {
2546
- var _a, _b, _c, _d, _e;
2655
+ var _a, _b, _c, _d, _e, _f, _g;
2656
+ const fieldsFromList = this.extractWorkItemFieldsMap((_a = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _a === void 0 ? void 0 : _a.workItemFields);
2657
+ const fieldsFromMap = ((_b = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _b === void 0 ? void 0 : _b.fields) || {};
2658
+ const systemRevFromList = this.getFieldValueByName(fieldsFromList, 'System.Rev');
2659
+ const systemRevFromMap = this.getFieldValueByName(fieldsFromMap, 'System.Rev');
2547
2660
  const revisionCandidates = [
2548
- (_a = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _a === void 0 ? void 0 : _a.rev,
2549
- (_b = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _b === void 0 ? void 0 : _b.revision,
2550
- (_c = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _c === void 0 ? void 0 : _c.version,
2551
- (_d = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _d === void 0 ? void 0 : _d.workItemRevision,
2552
- (_e = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _e === void 0 ? void 0 : _e.workItemVersion,
2661
+ systemRevFromList,
2662
+ systemRevFromMap,
2663
+ (_c = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _c === void 0 ? void 0 : _c.rev,
2664
+ (_d = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _d === void 0 ? void 0 : _d.revision,
2665
+ (_e = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _e === void 0 ? void 0 : _e.version,
2666
+ (_f = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _f === void 0 ? void 0 : _f.workItemRevision,
2667
+ (_g = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _g === void 0 ? void 0 : _g.workItemVersion,
2553
2668
  testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.revision,
2554
2669
  testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItemRevision,
2555
2670
  testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItemVersion,
@@ -4200,6 +4315,9 @@ class ResultDataProvider {
4200
4315
  return customFields;
4201
4316
  }
4202
4317
  }
4318
+ ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG = '[MEWP][InternalValidation][Trace]';
4319
+ ResultDataProvider.MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG = '[MEWP][InternalValidation][Diagnostics]';
4320
+ ResultDataProvider.MEWP_INTERNAL_VALIDATION_SUMMARY_TAG = '[MEWP][InternalValidation][Summary]';
4203
4321
  ResultDataProvider.MEWP_L2_COVERAGE_COLUMNS = [
4204
4322
  'L2 REQ ID',
4205
4323
  'L2 REQ Title',