@elisra-devops/docgen-data-provider 1.75.0 → 1.76.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.
@@ -14,6 +14,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
14
14
  const DataProviderUtils_1 = require("../utils/DataProviderUtils");
15
15
  const tfs_1 = require("../helpers/tfs");
16
16
  const logger_1 = require("../utils/logger");
17
+ const mewpExternalIngestionUtils_1 = require("../utils/mewpExternalIngestionUtils");
18
+ const mewpExternalTableUtils_1 = require("../utils/mewpExternalTableUtils");
17
19
  const testStepParserHelper_1 = require("../utils/testStepParserHelper");
18
20
  const TicketsDataProvider_1 = require("./TicketsDataProvider");
19
21
  const pLimit = require('p-limit');
@@ -69,6 +71,8 @@ class ResultDataProvider {
69
71
  this.testToAssociatedItemMap = new Map();
70
72
  this.querySelectedColumns = [];
71
73
  this.workItemDiscussionCache = new Map();
74
+ this.mewpExternalTableUtils = new mewpExternalTableUtils_1.default();
75
+ this.mewpExternalIngestionUtils = new mewpExternalIngestionUtils_1.default(this.mewpExternalTableUtils);
72
76
  }
73
77
  /**
74
78
  * Combines the results of test group result summary, test results summary, and detailed results summary into a single key-value pair array.
@@ -283,8 +287,8 @@ class ResultDataProvider {
283
287
  * Builds MEWP L2 requirement coverage rows for audit reporting.
284
288
  * Rows are one Requirement-TestCase pair; uncovered requirements are emitted with empty test-case columns.
285
289
  */
286
- async getMewpL2CoverageFlatResults(testPlanId, projectName, selectedSuiteIds) {
287
- var _a, _b;
290
+ async getMewpL2CoverageFlatResults(testPlanId, projectName, selectedSuiteIds, linkedQueryRequest, options) {
291
+ var _a;
288
292
  const defaultPayload = {
289
293
  sheetName: `MEWP L2 Coverage - Plan ${testPlanId}`,
290
294
  columnOrder: [...ResultDataProvider.MEWP_L2_COVERAGE_COLUMNS],
@@ -292,9 +296,17 @@ class ResultDataProvider {
292
296
  };
293
297
  try {
294
298
  const planName = await this.fetchTestPlanName(testPlanId, projectName);
295
- const suites = await this.fetchTestSuites(testPlanId, projectName, selectedSuiteIds, true);
296
- const testData = await this.fetchTestData(suites, projectName, testPlanId, false);
297
- const requirements = await this.fetchMewpL2Requirements(projectName);
299
+ const testData = await this.fetchMewpScopedTestData(testPlanId, projectName, selectedSuiteIds, !!(options === null || options === void 0 ? void 0 : options.useRelFallback));
300
+ const allRequirements = await this.fetchMewpL2Requirements(projectName);
301
+ if (allRequirements.length === 0) {
302
+ return Object.assign(Object.assign({}, defaultPayload), { sheetName: this.buildMewpCoverageSheetName(planName, testPlanId) });
303
+ }
304
+ const linkedRequirementsByTestCase = await this.buildLinkedRequirementsByTestCase(allRequirements, testData, projectName);
305
+ const scopedRequirementKeys = await this.resolveMewpRequirementScopeKeysFromQuery(linkedQueryRequest, allRequirements, linkedRequirementsByTestCase);
306
+ const requirements = this.collapseMewpRequirementFamilies(allRequirements, (scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) ? scopedRequirementKeys : undefined);
307
+ const requirementSapWbsByBaseKey = this.buildRequirementSapWbsByBaseKey(allRequirements);
308
+ const externalBugsByTestCase = await this.loadExternalBugsByTestCase(options === null || options === void 0 ? void 0 : options.externalBugsFile);
309
+ const externalL3L4ByBaseKey = await this.loadExternalL3L4ByBaseKey(options === null || options === void 0 ? void 0 : options.externalL3L4File, requirementSapWbsByBaseKey);
298
310
  if (requirements.length === 0) {
299
311
  return Object.assign(Object.assign({}, defaultPayload), { sheetName: this.buildMewpCoverageSheetName(planName, testPlanId) });
300
312
  }
@@ -302,32 +314,23 @@ class ResultDataProvider {
302
314
  const observedTestCaseIdsByRequirement = new Map();
303
315
  const requirementKeys = new Set();
304
316
  requirements.forEach((requirement) => {
305
- const key = this.toRequirementKey(requirement.requirementId);
317
+ const key = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
306
318
  if (!key)
307
319
  return;
308
320
  requirementKeys.add(key);
309
321
  });
310
322
  const parsedDefinitionStepsByTestCase = new Map();
311
323
  const testCaseStepsXmlMap = this.buildTestCaseStepsXmlMap(testData);
312
- const testCaseTitleMap = this.buildMewpTestCaseTitleMap(testData);
313
324
  const runResults = await this.fetchAllResultDataTestReporter(testData, projectName, [], false, false);
314
325
  for (const runResult of runResults) {
315
326
  const testCaseId = this.extractMewpTestCaseId(runResult);
316
- const runTestCaseTitle = this.toMewpComparableText(((_a = runResult === null || runResult === void 0 ? void 0 : runResult.testCase) === null || _a === void 0 ? void 0 : _a.name) || (runResult === null || runResult === void 0 ? void 0 : runResult.testCaseName) || (runResult === null || runResult === void 0 ? void 0 : runResult.testCaseTitle));
317
- if (Number.isFinite(testCaseId) && testCaseId > 0 && runTestCaseTitle && !testCaseTitleMap.has(testCaseId)) {
318
- testCaseTitleMap.set(testCaseId, runTestCaseTitle);
319
- }
320
- const actionResults = Array.isArray((_b = runResult === null || runResult === void 0 ? void 0 : runResult.iteration) === null || _b === void 0 ? void 0 : _b.actionResults)
321
- ? runResult.iteration.actionResults
327
+ const rawActionResults = Array.isArray((_a = runResult === null || runResult === void 0 ? void 0 : runResult.iteration) === null || _a === void 0 ? void 0 : _a.actionResults)
328
+ ? runResult.iteration.actionResults.filter((item) => !(item === null || item === void 0 ? void 0 : item.isSharedStepTitle))
322
329
  : [];
330
+ const actionResults = rawActionResults.sort((a, b) => this.compareActionResults(String((a === null || a === void 0 ? void 0 : a.stepPosition) || (a === null || a === void 0 ? void 0 : a.stepIdentifier) || ''), String((b === null || b === void 0 ? void 0 : b.stepPosition) || (b === null || b === void 0 ? void 0 : b.stepIdentifier) || '')));
323
331
  const hasExecutedRun = Number((runResult === null || runResult === void 0 ? void 0 : runResult.lastRunId) || 0) > 0 && Number((runResult === null || runResult === void 0 ? void 0 : runResult.lastResultId) || 0) > 0;
324
332
  if (actionResults.length > 0) {
325
- for (const actionResult of actionResults) {
326
- if (actionResult === null || actionResult === void 0 ? void 0 : actionResult.isSharedStepTitle)
327
- continue;
328
- const stepStatus = this.classifyRequirementStepOutcome(actionResult === null || actionResult === void 0 ? void 0 : actionResult.outcome);
329
- this.accumulateRequirementCountsFromStepText(`${String((actionResult === null || actionResult === void 0 ? void 0 : actionResult.action) || '')} ${String((actionResult === null || actionResult === void 0 ? void 0 : actionResult.expected) || '')}`, stepStatus, testCaseId, requirementKeys, requirementIndex, observedTestCaseIdsByRequirement);
330
- }
333
+ this.accumulateRequirementCountsFromActionResults(actionResults, testCaseId, requirementKeys, requirementIndex, observedTestCaseIdsByRequirement);
331
334
  continue;
332
335
  }
333
336
  // Do not force "not run" from definition steps when a run exists:
@@ -345,13 +348,17 @@ class ResultDataProvider {
345
348
  parsedDefinitionStepsByTestCase.set(testCaseId, parsed);
346
349
  }
347
350
  const definitionSteps = parsedDefinitionStepsByTestCase.get(testCaseId) || [];
348
- for (const step of definitionSteps) {
349
- if (step === null || step === void 0 ? void 0 : step.isSharedStepTitle)
350
- continue;
351
- this.accumulateRequirementCountsFromStepText(`${String((step === null || step === void 0 ? void 0 : step.action) || '')} ${String((step === null || step === void 0 ? void 0 : step.expected) || '')}`, 'notRun', testCaseId, requirementKeys, requirementIndex, observedTestCaseIdsByRequirement);
352
- }
351
+ const fallbackActionResults = definitionSteps
352
+ .filter((step) => !(step === null || step === void 0 ? void 0 : step.isSharedStepTitle))
353
+ .sort((a, b) => this.compareActionResults(String((a === null || a === void 0 ? void 0 : a.stepPosition) || ''), String((b === null || b === void 0 ? void 0 : b.stepPosition) || '')))
354
+ .map((step) => ({
355
+ stepPosition: step === null || step === void 0 ? void 0 : step.stepPosition,
356
+ expected: step === null || step === void 0 ? void 0 : step.expected,
357
+ outcome: 'Unspecified',
358
+ }));
359
+ this.accumulateRequirementCountsFromActionResults(fallbackActionResults, testCaseId, requirementKeys, requirementIndex, observedTestCaseIdsByRequirement);
353
360
  }
354
- const rows = this.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, testCaseTitleMap);
361
+ const rows = this.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, externalL3L4ByBaseKey, externalBugsByTestCase);
355
362
  return {
356
363
  sheetName: this.buildMewpCoverageSheetName(planName, testPlanId),
357
364
  columnOrder: [...ResultDataProvider.MEWP_L2_COVERAGE_COLUMNS],
@@ -360,9 +367,210 @@ class ResultDataProvider {
360
367
  }
361
368
  catch (error) {
362
369
  logger_1.default.error(`Error during getMewpL2CoverageFlatResults: ${error.message}`);
370
+ if (error instanceof mewpExternalTableUtils_1.MewpExternalFileValidationError) {
371
+ throw error;
372
+ }
363
373
  return defaultPayload;
364
374
  }
365
375
  }
376
+ async getMewpInternalValidationFlatResults(testPlanId, projectName, selectedSuiteIds, linkedQueryRequest, options) {
377
+ var _a, _b, _c;
378
+ const defaultPayload = {
379
+ sheetName: `MEWP Internal Validation - Plan ${testPlanId}`,
380
+ columnOrder: [...ResultDataProvider.INTERNAL_VALIDATION_COLUMNS],
381
+ rows: [],
382
+ };
383
+ try {
384
+ const planName = await this.fetchTestPlanName(testPlanId, projectName);
385
+ const testData = await this.fetchMewpScopedTestData(testPlanId, projectName, selectedSuiteIds, !!(options === null || options === void 0 ? void 0 : options.useRelFallback));
386
+ const allRequirements = await this.fetchMewpL2Requirements(projectName);
387
+ const linkedRequirementsByTestCase = await this.buildLinkedRequirementsByTestCase(allRequirements, testData, projectName);
388
+ const scopedRequirementKeys = await this.resolveMewpRequirementScopeKeysFromQuery(linkedQueryRequest, allRequirements, linkedRequirementsByTestCase);
389
+ const requirementFamilies = this.buildRequirementFamilyMap(allRequirements, (scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) ? scopedRequirementKeys : undefined);
390
+ const rows = [];
391
+ const stepsXmlByTestCase = this.buildTestCaseStepsXmlMap(testData);
392
+ const testCaseTitleMap = this.buildMewpTestCaseTitleMap(testData);
393
+ const allTestCaseIds = new Set();
394
+ for (const suite of testData || []) {
395
+ const testCasesItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testCasesItems) ? suite.testCasesItems : [];
396
+ for (const testCase of testCasesItems) {
397
+ 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) || 0);
398
+ if (Number.isFinite(id) && id > 0)
399
+ allTestCaseIds.add(id);
400
+ }
401
+ const testPointsItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testPointsItems) ? suite.testPointsItems : [];
402
+ for (const testPoint of testPointsItems) {
403
+ const id = Number((testPoint === null || testPoint === void 0 ? void 0 : testPoint.testCaseId) || ((_b = testPoint === null || testPoint === void 0 ? void 0 : testPoint.testCase) === null || _b === void 0 ? void 0 : _b.id) || 0);
404
+ if (Number.isFinite(id) && id > 0)
405
+ allTestCaseIds.add(id);
406
+ }
407
+ }
408
+ const validL2BaseKeys = new Set([...requirementFamilies.keys()]);
409
+ for (const testCaseId of [...allTestCaseIds].sort((a, b) => a - b)) {
410
+ const stepsXml = stepsXmlByTestCase.get(testCaseId) || '';
411
+ const parsedSteps = stepsXml && String(stepsXml).trim() !== ''
412
+ ? await this.testStepParserHelper.parseTestSteps(stepsXml, new Map())
413
+ : [];
414
+ const mentionEntries = this.extractRequirementMentionsFromExpectedSteps(parsedSteps, true);
415
+ const mentionedL2Only = new Set();
416
+ const mentionedCodeFirstStep = new Map();
417
+ const mentionedBaseFirstStep = new Map();
418
+ for (const mentionEntry of mentionEntries) {
419
+ const scopeFilteredCodes = (scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) && mentionEntry.codes.size > 0
420
+ ? [...mentionEntry.codes].filter((code) => scopedRequirementKeys.has(this.toRequirementKey(code)))
421
+ : [...mentionEntry.codes];
422
+ for (const code of scopeFilteredCodes) {
423
+ const baseKey = this.toRequirementKey(code);
424
+ if (!baseKey)
425
+ continue;
426
+ if (validL2BaseKeys.has(baseKey)) {
427
+ mentionedL2Only.add(code);
428
+ if (!mentionedCodeFirstStep.has(code)) {
429
+ mentionedCodeFirstStep.set(code, mentionEntry.stepRef);
430
+ }
431
+ if (!mentionedBaseFirstStep.has(baseKey)) {
432
+ mentionedBaseFirstStep.set(baseKey, mentionEntry.stepRef);
433
+ }
434
+ }
435
+ }
436
+ }
437
+ const mentionedBaseKeys = new Set([...mentionedL2Only].map((code) => this.toRequirementKey(code)).filter((code) => !!code));
438
+ const expectedFamilyCodes = new Set();
439
+ for (const baseKey of mentionedBaseKeys) {
440
+ const familyCodes = requirementFamilies.get(baseKey);
441
+ if (familyCodes === null || familyCodes === void 0 ? void 0 : familyCodes.size) {
442
+ familyCodes.forEach((code) => expectedFamilyCodes.add(code));
443
+ }
444
+ else {
445
+ for (const code of mentionedL2Only) {
446
+ if (this.toRequirementKey(code) === baseKey)
447
+ expectedFamilyCodes.add(code);
448
+ }
449
+ }
450
+ }
451
+ const linkedFullCodesRaw = ((_c = linkedRequirementsByTestCase.get(testCaseId)) === null || _c === void 0 ? void 0 : _c.fullCodes) || new Set();
452
+ const linkedFullCodes = (scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) && linkedFullCodesRaw.size > 0
453
+ ? new Set([...linkedFullCodesRaw].filter((code) => scopedRequirementKeys.has(this.toRequirementKey(code))))
454
+ : linkedFullCodesRaw;
455
+ const linkedBaseKeys = new Set([...linkedFullCodes].map((code) => this.toRequirementKey(code)).filter((code) => !!code));
456
+ const missingMentioned = [...mentionedL2Only].filter((code) => {
457
+ const baseKey = this.toRequirementKey(code);
458
+ if (!baseKey)
459
+ return false;
460
+ const hasSpecificSuffix = /-\d+$/.test(code);
461
+ if (hasSpecificSuffix)
462
+ return !linkedFullCodes.has(code);
463
+ return !linkedBaseKeys.has(baseKey);
464
+ });
465
+ const missingFamily = [...expectedFamilyCodes].filter((code) => !linkedFullCodes.has(code));
466
+ const extraLinked = [...linkedFullCodes].filter((code) => !expectedFamilyCodes.has(code));
467
+ const mentionedButNotLinkedByStep = new Map();
468
+ const appendMentionedButNotLinked = (requirementId, stepRef) => {
469
+ const normalizedRequirementId = this.normalizeMewpRequirementCodeWithSuffix(requirementId);
470
+ if (!normalizedRequirementId)
471
+ return;
472
+ const normalizedStepRef = String(stepRef || 'Step ?').trim() || 'Step ?';
473
+ if (!mentionedButNotLinkedByStep.has(normalizedStepRef)) {
474
+ mentionedButNotLinkedByStep.set(normalizedStepRef, new Set());
475
+ }
476
+ mentionedButNotLinkedByStep.get(normalizedStepRef).add(normalizedRequirementId);
477
+ };
478
+ const sortedMissingMentioned = [...new Set(missingMentioned)].sort((a, b) => a.localeCompare(b));
479
+ const sortedMissingFamily = [...new Set(missingFamily)].sort((a, b) => a.localeCompare(b));
480
+ for (const code of sortedMissingMentioned) {
481
+ const stepRef = mentionedCodeFirstStep.get(code) || 'Step ?';
482
+ appendMentionedButNotLinked(code, stepRef);
483
+ }
484
+ for (const code of sortedMissingFamily) {
485
+ const baseKey = this.toRequirementKey(code);
486
+ const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
487
+ appendMentionedButNotLinked(code, stepRef);
488
+ }
489
+ const sortedExtraLinked = [...new Set(extraLinked)]
490
+ .map((code) => this.normalizeMewpRequirementCodeWithSuffix(code))
491
+ .filter((code) => !!code)
492
+ .sort((a, b) => a.localeCompare(b));
493
+ const parseStepOrder = (stepRef) => {
494
+ const match = /step\s+(\d+)/i.exec(String(stepRef || ''));
495
+ const parsed = Number((match === null || match === void 0 ? void 0 : match[1]) || Number.POSITIVE_INFINITY);
496
+ return Number.isFinite(parsed) ? parsed : Number.POSITIVE_INFINITY;
497
+ };
498
+ const mentionedButNotLinked = [...mentionedButNotLinkedByStep.entries()]
499
+ .sort((a, b) => {
500
+ const stepOrderA = parseStepOrder(a[0]);
501
+ const stepOrderB = parseStepOrder(b[0]);
502
+ if (stepOrderA !== stepOrderB)
503
+ return stepOrderA - stepOrderB;
504
+ return String(a[0]).localeCompare(String(b[0]));
505
+ })
506
+ .map(([stepRef, requirementIds]) => {
507
+ const requirementList = [...requirementIds].sort((a, b) => a.localeCompare(b));
508
+ return `${stepRef}: ${requirementList.join(', ')}`;
509
+ })
510
+ .join('; ');
511
+ const linkedButNotMentioned = sortedExtraLinked.join('; ');
512
+ const validationStatus = mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
513
+ rows.push({
514
+ 'Test Case ID': testCaseId,
515
+ 'Test Case Title': String(testCaseTitleMap.get(testCaseId) || '').trim(),
516
+ 'Mentioned but Not Linked': mentionedButNotLinked,
517
+ 'Linked but Not Mentioned': linkedButNotMentioned,
518
+ 'Validation Status': validationStatus,
519
+ });
520
+ }
521
+ return {
522
+ sheetName: this.buildInternalValidationSheetName(planName, testPlanId),
523
+ columnOrder: [...ResultDataProvider.INTERNAL_VALIDATION_COLUMNS],
524
+ rows,
525
+ };
526
+ }
527
+ catch (error) {
528
+ logger_1.default.error(`Error during getMewpInternalValidationFlatResults: ${error.message}`);
529
+ return defaultPayload;
530
+ }
531
+ }
532
+ async validateMewpExternalFiles(options) {
533
+ const response = { valid: true };
534
+ const validateOne = async (file, tableType) => {
535
+ const sourceName = String((file === null || file === void 0 ? void 0 : file.name) || (file === null || file === void 0 ? void 0 : file.objectName) || (file === null || file === void 0 ? void 0 : file.text) || (file === null || file === void 0 ? void 0 : file.url) || '').trim();
536
+ if (!sourceName)
537
+ return undefined;
538
+ try {
539
+ const { rows, meta } = await this.mewpExternalTableUtils.loadExternalTableRowsWithMeta(file, tableType);
540
+ return {
541
+ tableType,
542
+ sourceName: meta.sourceName,
543
+ valid: true,
544
+ headerRow: meta.headerRow,
545
+ matchedRequiredColumns: meta.matchedRequiredColumns,
546
+ totalRequiredColumns: meta.totalRequiredColumns,
547
+ missingRequiredColumns: [],
548
+ rowCount: rows.length,
549
+ message: 'File schema is valid',
550
+ };
551
+ }
552
+ catch (error) {
553
+ if (error instanceof mewpExternalTableUtils_1.MewpExternalFileValidationError) {
554
+ return Object.assign(Object.assign({}, error.details), { valid: false });
555
+ }
556
+ return {
557
+ tableType,
558
+ sourceName: sourceName || tableType,
559
+ valid: false,
560
+ headerRow: '',
561
+ matchedRequiredColumns: 0,
562
+ totalRequiredColumns: this.mewpExternalTableUtils.getRequiredColumnCount(tableType),
563
+ missingRequiredColumns: [],
564
+ rowCount: 0,
565
+ message: String((error === null || error === void 0 ? void 0 : error.message) || error || 'Unknown validation error'),
566
+ };
567
+ }
568
+ };
569
+ response.bugs = await validateOne(options === null || options === void 0 ? void 0 : options.externalBugsFile, 'bugs');
570
+ response.l3l4 = await validateOne(options === null || options === void 0 ? void 0 : options.externalL3L4File, 'l3l4');
571
+ response.valid = [response.bugs, response.l3l4].filter(Boolean).every((item) => !!(item === null || item === void 0 ? void 0 : item.valid));
572
+ return response;
573
+ }
366
574
  /**
367
575
  * Mapping each attachment to a proper URL for downloading it
368
576
  * @param runResults Array of run results
@@ -394,22 +602,52 @@ class ResultDataProvider {
394
602
  const suffix = String(planName || '').trim() || `Plan ${testPlanId}`;
395
603
  return `MEWP L2 Coverage - ${suffix}`;
396
604
  }
397
- createMewpCoverageRow(requirement, testCaseId, testCaseTitle, stepSummary) {
398
- const customerId = this.formatMewpCustomerId(requirement.requirementId);
399
- const customerTitle = this.toMewpComparableText(requirement.title);
400
- const responsibility = this.toMewpComparableText(requirement.responsibility);
401
- const safeTestCaseId = Number.isFinite(testCaseId) && Number(testCaseId) > 0 ? Number(testCaseId) : '';
605
+ buildInternalValidationSheetName(planName, testPlanId) {
606
+ const suffix = String(planName || '').trim() || `Plan ${testPlanId}`;
607
+ return `MEWP Internal Validation - ${suffix}`;
608
+ }
609
+ createMewpCoverageRow(requirement, runStatus, bug, linkedL3L4) {
610
+ const l2ReqId = this.formatMewpCustomerId(requirement.requirementId);
611
+ const l2ReqTitle = this.toMewpComparableText(requirement.title);
612
+ const l2SubSystem = this.toMewpComparableText(requirement.subSystem);
402
613
  return {
403
- 'Customer ID': customerId,
404
- 'Title (Customer name)': customerTitle,
405
- 'Responsibility - SAPWBS (ESUK/IL)': responsibility,
406
- 'Test case id': safeTestCaseId,
407
- 'Test case title': String(testCaseTitle || '').trim(),
408
- 'Number of passed steps': Number.isFinite(stepSummary === null || stepSummary === void 0 ? void 0 : stepSummary.passed) ? stepSummary.passed : 0,
409
- 'Number of failed steps': Number.isFinite(stepSummary === null || stepSummary === void 0 ? void 0 : stepSummary.failed) ? stepSummary.failed : 0,
410
- 'Number of not run tests': Number.isFinite(stepSummary === null || stepSummary === void 0 ? void 0 : stepSummary.notRun) ? stepSummary.notRun : 0,
614
+ 'L2 REQ ID': l2ReqId,
615
+ 'L2 REQ Title': l2ReqTitle,
616
+ 'L2 SubSystem': l2SubSystem,
617
+ 'L2 Run Status': runStatus,
618
+ 'Bug ID': Number.isFinite(Number(bug === null || bug === void 0 ? void 0 : bug.id)) && Number(bug === null || bug === void 0 ? void 0 : bug.id) > 0 ? Number(bug === null || bug === void 0 ? void 0 : bug.id) : '',
619
+ 'Bug Title': String((bug === null || bug === void 0 ? void 0 : bug.title) || '').trim(),
620
+ 'Bug Responsibility': String((bug === null || bug === void 0 ? void 0 : bug.responsibility) || '').trim(),
621
+ 'L3 REQ ID': String((linkedL3L4 === null || linkedL3L4 === void 0 ? void 0 : linkedL3L4.l3Id) || '').trim(),
622
+ 'L3 REQ Title': String((linkedL3L4 === null || linkedL3L4 === void 0 ? void 0 : linkedL3L4.l3Title) || '').trim(),
623
+ 'L4 REQ ID': String((linkedL3L4 === null || linkedL3L4 === void 0 ? void 0 : linkedL3L4.l4Id) || '').trim(),
624
+ 'L4 REQ Title': String((linkedL3L4 === null || linkedL3L4 === void 0 ? void 0 : linkedL3L4.l4Title) || '').trim(),
411
625
  };
412
626
  }
627
+ createEmptyMewpCoverageBugCell() {
628
+ return { id: '', title: '', responsibility: '' };
629
+ }
630
+ createEmptyMewpCoverageL3L4Cell() {
631
+ return { l3Id: '', l3Title: '', l4Id: '', l4Title: '' };
632
+ }
633
+ buildMewpCoverageL3L4Rows(links) {
634
+ const sorted = [...(links || [])].sort((a, b) => {
635
+ if (a.level !== b.level)
636
+ return a.level === 'L3' ? -1 : 1;
637
+ return String(a.id || '').localeCompare(String(b.id || ''));
638
+ });
639
+ const rows = [];
640
+ for (const item of sorted) {
641
+ const isL3 = item.level === 'L3';
642
+ rows.push({
643
+ l3Id: isL3 ? String((item === null || item === void 0 ? void 0 : item.id) || '').trim() : '',
644
+ l3Title: isL3 ? String((item === null || item === void 0 ? void 0 : item.title) || '').trim() : '',
645
+ l4Id: isL3 ? '' : String((item === null || item === void 0 ? void 0 : item.id) || '').trim(),
646
+ l4Title: isL3 ? '' : String((item === null || item === void 0 ? void 0 : item.title) || '').trim(),
647
+ });
648
+ }
649
+ return rows;
650
+ }
413
651
  formatMewpCustomerId(rawValue) {
414
652
  const normalized = this.normalizeMewpRequirementCode(this.toMewpComparableText(rawValue));
415
653
  if (normalized)
@@ -419,33 +657,289 @@ class ResultDataProvider {
419
657
  return `SR${onlyDigits}`;
420
658
  return '';
421
659
  }
422
- buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, testCaseTitleMap) {
660
+ buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, l3l4ByBaseKey, externalBugsByTestCase) {
423
661
  var _a;
424
662
  const rows = [];
663
+ const linkedByRequirement = this.invertBaseRequirementLinks(linkedRequirementsByTestCase);
425
664
  for (const requirement of requirements) {
426
- const key = this.toRequirementKey(requirement.requirementId);
665
+ const key = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || this.toRequirementKey(requirement.requirementId) || '').trim();
427
666
  const linkedTestCaseIds = ((requirement === null || requirement === void 0 ? void 0 : requirement.linkedTestCaseIds) || []).filter((id) => Number.isFinite(id) && Number(id) > 0);
667
+ const linkedByTestCase = key ? Array.from(linkedByRequirement.get(key) || []) : [];
428
668
  const observedTestCaseIds = key
429
669
  ? Array.from(observedTestCaseIdsByRequirement.get(key) || [])
430
670
  : [];
431
- const testCaseIds = Array.from(new Set([...linkedTestCaseIds, ...observedTestCaseIds])).sort((a, b) => a - b);
432
- if (testCaseIds.length === 0) {
433
- rows.push(this.createMewpCoverageRow(requirement, undefined, '', {
434
- passed: 0,
435
- failed: 0,
436
- notRun: 0,
437
- }));
438
- continue;
439
- }
671
+ const testCaseIds = Array.from(new Set([...linkedTestCaseIds, ...linkedByTestCase, ...observedTestCaseIds])).sort((a, b) => a - b);
672
+ let totalPassed = 0;
673
+ let totalFailed = 0;
674
+ let totalNotRun = 0;
675
+ const aggregatedBugs = new Map();
440
676
  for (const testCaseId of testCaseIds) {
441
677
  const summary = key
442
678
  ? ((_a = requirementIndex.get(key)) === null || _a === void 0 ? void 0 : _a.get(testCaseId)) || { passed: 0, failed: 0, notRun: 0 }
443
679
  : { passed: 0, failed: 0, notRun: 0 };
444
- rows.push(this.createMewpCoverageRow(requirement, testCaseId, String(testCaseTitleMap.get(testCaseId) || ''), summary));
680
+ totalPassed += summary.passed;
681
+ totalFailed += summary.failed;
682
+ totalNotRun += summary.notRun;
683
+ if (summary.failed > 0) {
684
+ const externalBugs = externalBugsByTestCase.get(testCaseId) || [];
685
+ for (const bug of externalBugs) {
686
+ const bugBaseKey = String((bug === null || bug === void 0 ? void 0 : bug.requirementBaseKey) || '').trim();
687
+ if (bugBaseKey && bugBaseKey !== key)
688
+ continue;
689
+ const bugId = Number((bug === null || bug === void 0 ? void 0 : bug.id) || 0);
690
+ if (!Number.isFinite(bugId) || bugId <= 0)
691
+ continue;
692
+ aggregatedBugs.set(bugId, Object.assign(Object.assign({}, bug), { responsibility: this.resolveCoverageBugResponsibility(String((bug === null || bug === void 0 ? void 0 : bug.responsibility) || ''), requirement) }));
693
+ }
694
+ }
695
+ }
696
+ const runStatus = this.resolveMewpL2RunStatus({
697
+ passed: totalPassed,
698
+ failed: totalFailed,
699
+ notRun: totalNotRun,
700
+ hasAnyTestCase: testCaseIds.length > 0,
701
+ });
702
+ const bugsForRows = runStatus === 'Fail'
703
+ ? Array.from(aggregatedBugs.values()).sort((a, b) => a.id - b.id)
704
+ : [];
705
+ const l3l4ForRows = [...(l3l4ByBaseKey.get(key) || [])];
706
+ const bugRows = bugsForRows.length > 0
707
+ ? bugsForRows
708
+ : [];
709
+ const l3l4Rows = this.buildMewpCoverageL3L4Rows(l3l4ForRows);
710
+ if (bugRows.length === 0 && l3l4Rows.length === 0) {
711
+ rows.push(this.createMewpCoverageRow(requirement, runStatus, this.createEmptyMewpCoverageBugCell(), this.createEmptyMewpCoverageL3L4Cell()));
712
+ continue;
713
+ }
714
+ for (const bug of bugRows) {
715
+ rows.push(this.createMewpCoverageRow(requirement, runStatus, bug, this.createEmptyMewpCoverageL3L4Cell()));
716
+ }
717
+ for (const linkedL3L4 of l3l4Rows) {
718
+ rows.push(this.createMewpCoverageRow(requirement, runStatus, this.createEmptyMewpCoverageBugCell(), linkedL3L4));
445
719
  }
446
720
  }
447
721
  return rows;
448
722
  }
723
+ resolveCoverageBugResponsibility(rawResponsibility, requirement) {
724
+ const direct = String(rawResponsibility || '').trim();
725
+ if (direct && direct.toLowerCase() !== 'unknown')
726
+ return direct;
727
+ const requirementResponsibility = String((requirement === null || requirement === void 0 ? void 0 : requirement.responsibility) || '')
728
+ .trim()
729
+ .toUpperCase();
730
+ if (requirementResponsibility === 'ESUK')
731
+ return 'ESUK';
732
+ if (requirementResponsibility === 'IL' || requirementResponsibility === 'ELISRA')
733
+ return 'Elisra';
734
+ return direct || 'Unknown';
735
+ }
736
+ resolveMewpL2RunStatus(input) {
737
+ if (((input === null || input === void 0 ? void 0 : input.failed) || 0) > 0)
738
+ return 'Fail';
739
+ if (((input === null || input === void 0 ? void 0 : input.notRun) || 0) > 0)
740
+ return 'Not Run';
741
+ if (((input === null || input === void 0 ? void 0 : input.passed) || 0) > 0)
742
+ return 'Pass';
743
+ return (input === null || input === void 0 ? void 0 : input.hasAnyTestCase) ? 'Not Run' : 'Not Run';
744
+ }
745
+ async fetchMewpScopedTestData(testPlanId, projectName, selectedSuiteIds, useRelFallback) {
746
+ if (!useRelFallback) {
747
+ const suites = await this.fetchTestSuites(testPlanId, projectName, selectedSuiteIds, true);
748
+ return this.fetchTestData(suites, projectName, testPlanId, false);
749
+ }
750
+ const selectedSuites = await this.fetchTestSuites(testPlanId, projectName, selectedSuiteIds, true);
751
+ const selectedRel = this.resolveMaxRelNumberFromSuites(selectedSuites);
752
+ if (selectedRel <= 0) {
753
+ return this.fetchTestData(selectedSuites, projectName, testPlanId, false);
754
+ }
755
+ const allSuites = await this.fetchTestSuites(testPlanId, projectName, undefined, true);
756
+ const relScopedSuites = allSuites.filter((suite) => {
757
+ const rel = this.extractRelNumberFromSuite(suite);
758
+ return rel > 0 && rel <= selectedRel;
759
+ });
760
+ const suitesForFetch = relScopedSuites.length > 0 ? relScopedSuites : selectedSuites;
761
+ const rawTestData = await this.fetchTestData(suitesForFetch, projectName, testPlanId, false);
762
+ return this.reduceToLatestRelRunPerTestCase(rawTestData);
763
+ }
764
+ extractRelNumberFromSuite(suite) {
765
+ const candidates = [
766
+ suite === null || suite === void 0 ? void 0 : suite.suiteName,
767
+ suite === null || suite === void 0 ? void 0 : suite.parentSuiteName,
768
+ suite === null || suite === void 0 ? void 0 : suite.suitePath,
769
+ suite === null || suite === void 0 ? void 0 : suite.testGroupName,
770
+ ];
771
+ const pattern = /(?:^|[^a-z0-9])rel\s*([0-9]+)/i;
772
+ for (const item of candidates) {
773
+ const match = pattern.exec(String(item || ''));
774
+ if (!match)
775
+ continue;
776
+ const parsed = Number(match[1]);
777
+ if (Number.isFinite(parsed) && parsed > 0) {
778
+ return parsed;
779
+ }
780
+ }
781
+ return 0;
782
+ }
783
+ resolveMaxRelNumberFromSuites(suites) {
784
+ let maxRel = 0;
785
+ for (const suite of suites || []) {
786
+ const rel = this.extractRelNumberFromSuite(suite);
787
+ if (rel > maxRel)
788
+ maxRel = rel;
789
+ }
790
+ return maxRel;
791
+ }
792
+ reduceToLatestRelRunPerTestCase(testData) {
793
+ var _a, _b;
794
+ const candidatesByTestCase = new Map();
795
+ const testCaseDefinitionById = new Map();
796
+ for (const suite of testData || []) {
797
+ const rel = this.extractRelNumberFromSuite(suite);
798
+ const testPointsItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testPointsItems) ? suite.testPointsItems : [];
799
+ const testCasesItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testCasesItems) ? suite.testCasesItems : [];
800
+ for (const testCase of testCasesItems) {
801
+ const testCaseId = 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) || 0);
802
+ if (!Number.isFinite(testCaseId) || testCaseId <= 0)
803
+ continue;
804
+ if (!testCaseDefinitionById.has(testCaseId)) {
805
+ testCaseDefinitionById.set(testCaseId, testCase);
806
+ }
807
+ }
808
+ for (const point of testPointsItems) {
809
+ const testCaseId = Number((point === null || point === void 0 ? void 0 : point.testCaseId) || ((_b = point === null || point === void 0 ? void 0 : point.testCase) === null || _b === void 0 ? void 0 : _b.id) || 0);
810
+ if (!Number.isFinite(testCaseId) || testCaseId <= 0)
811
+ continue;
812
+ const runId = Number((point === null || point === void 0 ? void 0 : point.lastRunId) || 0);
813
+ const resultId = Number((point === null || point === void 0 ? void 0 : point.lastResultId) || 0);
814
+ const hasRun = runId > 0 && resultId > 0;
815
+ if (!candidatesByTestCase.has(testCaseId)) {
816
+ candidatesByTestCase.set(testCaseId, []);
817
+ }
818
+ candidatesByTestCase.get(testCaseId).push({
819
+ point,
820
+ rel,
821
+ runId,
822
+ resultId,
823
+ hasRun,
824
+ });
825
+ }
826
+ }
827
+ const selectedPoints = [];
828
+ const selectedTestCaseIds = new Set();
829
+ for (const [testCaseId, candidates] of candidatesByTestCase.entries()) {
830
+ const sorted = [...candidates].sort((a, b) => {
831
+ if (a.hasRun !== b.hasRun)
832
+ return a.hasRun ? -1 : 1;
833
+ if (a.rel !== b.rel)
834
+ return b.rel - a.rel;
835
+ if (a.runId !== b.runId)
836
+ return b.runId - a.runId;
837
+ return b.resultId - a.resultId;
838
+ });
839
+ const chosen = sorted[0];
840
+ if (!(chosen === null || chosen === void 0 ? void 0 : chosen.point))
841
+ continue;
842
+ selectedPoints.push(chosen.point);
843
+ selectedTestCaseIds.add(testCaseId);
844
+ }
845
+ const selectedTestCases = [];
846
+ for (const testCaseId of selectedTestCaseIds) {
847
+ const definition = testCaseDefinitionById.get(testCaseId);
848
+ if (definition) {
849
+ selectedTestCases.push(definition);
850
+ }
851
+ }
852
+ return [
853
+ {
854
+ testSuiteId: 'MEWP_REL_SCOPED',
855
+ suiteId: 'MEWP_REL_SCOPED',
856
+ suiteName: 'MEWP Rel Scoped',
857
+ parentSuiteId: '',
858
+ parentSuiteName: '',
859
+ suitePath: 'MEWP Rel Scoped',
860
+ testGroupName: 'MEWP Rel Scoped',
861
+ testPointsItems: selectedPoints,
862
+ testCasesItems: selectedTestCases,
863
+ },
864
+ ];
865
+ }
866
+ async loadExternalBugsByTestCase(externalBugsFile) {
867
+ return this.mewpExternalIngestionUtils.loadExternalBugsByTestCase(externalBugsFile, {
868
+ toComparableText: (value) => this.toMewpComparableText(value),
869
+ toRequirementKey: (value) => this.toRequirementKey(value),
870
+ resolveBugResponsibility: (fields) => this.resolveBugResponsibility(fields),
871
+ isExternalStateInScope: (value, itemType) => this.isExternalStateInScope(value, itemType),
872
+ isExcludedL3L4BySapWbs: (value) => this.isExcludedL3L4BySapWbs(value),
873
+ resolveRequirementSapWbsByBaseKey: () => '',
874
+ });
875
+ }
876
+ async loadExternalL3L4ByBaseKey(externalL3L4File, requirementSapWbsByBaseKey = new Map()) {
877
+ return this.mewpExternalIngestionUtils.loadExternalL3L4ByBaseKey(externalL3L4File, {
878
+ toComparableText: (value) => this.toMewpComparableText(value),
879
+ toRequirementKey: (value) => this.toRequirementKey(value),
880
+ resolveBugResponsibility: (fields) => this.resolveBugResponsibility(fields),
881
+ isExternalStateInScope: (value, itemType) => this.isExternalStateInScope(value, itemType),
882
+ isExcludedL3L4BySapWbs: (value) => this.isExcludedL3L4BySapWbs(value),
883
+ resolveRequirementSapWbsByBaseKey: (baseKey) => String(requirementSapWbsByBaseKey.get(baseKey) || ''),
884
+ });
885
+ }
886
+ buildRequirementSapWbsByBaseKey(requirements) {
887
+ const out = new Map();
888
+ for (const requirement of requirements || []) {
889
+ const baseKey = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
890
+ if (!baseKey)
891
+ continue;
892
+ const normalized = this.resolveMewpResponsibility(this.toMewpComparableText(requirement === null || requirement === void 0 ? void 0 : requirement.responsibility));
893
+ if (!normalized)
894
+ continue;
895
+ const existing = out.get(baseKey) || '';
896
+ // Keep ESUK as dominant if conflicting values are ever present across family items.
897
+ if (existing === 'ESUK')
898
+ continue;
899
+ if (normalized === 'ESUK' || !existing) {
900
+ out.set(baseKey, normalized);
901
+ }
902
+ }
903
+ return out;
904
+ }
905
+ isExternalStateInScope(value, itemType) {
906
+ const normalized = String(value || '').trim().toLowerCase();
907
+ if (!normalized)
908
+ return true;
909
+ // TFS/ADO processes usually don't expose a literal "Open" state.
910
+ // Keep non-terminal states, exclude terminal states.
911
+ const terminalStates = new Set([
912
+ 'resolved',
913
+ 'closed',
914
+ 'done',
915
+ 'completed',
916
+ 'complete',
917
+ 'removed',
918
+ 'rejected',
919
+ 'cancelled',
920
+ 'canceled',
921
+ 'obsolete',
922
+ ]);
923
+ if (terminalStates.has(normalized))
924
+ return false;
925
+ // Bug-specific terminal variants often used in custom processes.
926
+ if (itemType === 'bug') {
927
+ if (normalized === 'fixed')
928
+ return false;
929
+ }
930
+ return true;
931
+ }
932
+ invertBaseRequirementLinks(linkedRequirementsByTestCase) {
933
+ const out = new Map();
934
+ for (const [testCaseId, links] of linkedRequirementsByTestCase.entries()) {
935
+ for (const baseKey of (links === null || links === void 0 ? void 0 : links.baseKeys) || []) {
936
+ if (!out.has(baseKey))
937
+ out.set(baseKey, new Set());
938
+ out.get(baseKey).add(testCaseId);
939
+ }
940
+ }
941
+ return out;
942
+ }
449
943
  buildMewpTestCaseTitleMap(testData) {
450
944
  var _a, _b, _c, _d, _e;
451
945
  const map = new Map();
@@ -545,49 +1039,181 @@ class ResultDataProvider {
545
1039
  return 'failed';
546
1040
  return 'notRun';
547
1041
  }
548
- accumulateRequirementCountsFromStepText(stepText, status, testCaseId, requirementKeys, counters, observedTestCaseIdsByRequirement) {
1042
+ accumulateRequirementCountsFromActionResults(actionResults, testCaseId, requirementKeys, counters, observedTestCaseIdsByRequirement) {
549
1043
  if (!Number.isFinite(testCaseId) || testCaseId <= 0)
550
1044
  return;
551
- const codes = this.extractRequirementCodesFromText(stepText);
552
- for (const code of codes) {
553
- if (requirementKeys.size > 0 && !requirementKeys.has(code))
1045
+ const sortedResults = Array.isArray(actionResults) ? actionResults : [];
1046
+ let previousRequirementStepIndex = -1;
1047
+ for (let i = 0; i < sortedResults.length; i++) {
1048
+ const actionResult = sortedResults[i];
1049
+ if (actionResult === null || actionResult === void 0 ? void 0 : actionResult.isSharedStepTitle)
554
1050
  continue;
555
- if (!counters.has(code)) {
556
- counters.set(code, new Map());
557
- }
558
- const perTestCaseCounters = counters.get(code);
559
- if (!perTestCaseCounters.has(testCaseId)) {
560
- perTestCaseCounters.set(testCaseId, { passed: 0, failed: 0, notRun: 0 });
561
- }
562
- if (!observedTestCaseIdsByRequirement.has(code)) {
563
- observedTestCaseIdsByRequirement.set(code, new Set());
1051
+ const requirementCodes = this.extractRequirementCodesFromText((actionResult === null || actionResult === void 0 ? void 0 : actionResult.expected) || '');
1052
+ if (requirementCodes.size === 0)
1053
+ continue;
1054
+ const startIndex = previousRequirementStepIndex + 1;
1055
+ const status = this.resolveRequirementStatusForWindow(sortedResults, startIndex, i);
1056
+ previousRequirementStepIndex = i;
1057
+ for (const code of requirementCodes) {
1058
+ if (requirementKeys.size > 0 && !requirementKeys.has(code))
1059
+ continue;
1060
+ if (!counters.has(code)) {
1061
+ counters.set(code, new Map());
1062
+ }
1063
+ const perTestCaseCounters = counters.get(code);
1064
+ if (!perTestCaseCounters.has(testCaseId)) {
1065
+ perTestCaseCounters.set(testCaseId, { passed: 0, failed: 0, notRun: 0 });
1066
+ }
1067
+ if (!observedTestCaseIdsByRequirement.has(code)) {
1068
+ observedTestCaseIdsByRequirement.set(code, new Set());
1069
+ }
1070
+ observedTestCaseIdsByRequirement.get(code).add(testCaseId);
1071
+ const counter = perTestCaseCounters.get(testCaseId);
1072
+ if (status === 'passed')
1073
+ counter.passed += 1;
1074
+ else if (status === 'failed')
1075
+ counter.failed += 1;
1076
+ else
1077
+ counter.notRun += 1;
564
1078
  }
565
- observedTestCaseIdsByRequirement.get(code).add(testCaseId);
566
- const counter = perTestCaseCounters.get(testCaseId);
567
- if (status === 'passed')
568
- counter.passed += 1;
569
- else if (status === 'failed')
570
- counter.failed += 1;
571
- else
572
- counter.notRun += 1;
573
1079
  }
574
1080
  }
1081
+ resolveRequirementStatusForWindow(actionResults, startIndex, endIndex) {
1082
+ var _a;
1083
+ let hasNotRun = false;
1084
+ for (let index = startIndex; index <= endIndex; index++) {
1085
+ const status = this.classifyRequirementStepOutcome((_a = actionResults[index]) === null || _a === void 0 ? void 0 : _a.outcome);
1086
+ if (status === 'failed')
1087
+ return 'failed';
1088
+ if (status === 'notRun')
1089
+ hasNotRun = true;
1090
+ }
1091
+ return hasNotRun ? 'notRun' : 'passed';
1092
+ }
575
1093
  extractRequirementCodesFromText(text) {
1094
+ return this.extractRequirementCodesFromExpectedText(text, false);
1095
+ }
1096
+ extractRequirementMentionsFromExpectedSteps(steps, includeSuffix) {
1097
+ const out = [];
1098
+ const allSteps = Array.isArray(steps) ? steps : [];
1099
+ for (let index = 0; index < allSteps.length; index += 1) {
1100
+ const step = allSteps[index];
1101
+ if (step === null || step === void 0 ? void 0 : step.isSharedStepTitle)
1102
+ continue;
1103
+ const codes = this.extractRequirementCodesFromExpectedText((step === null || step === void 0 ? void 0 : step.expected) || '', includeSuffix);
1104
+ if (codes.size === 0)
1105
+ continue;
1106
+ out.push({
1107
+ stepRef: this.resolveValidationStepReference(step, index),
1108
+ codes,
1109
+ });
1110
+ }
1111
+ return out;
1112
+ }
1113
+ extractRequirementCodesFromExpectedSteps(steps, includeSuffix) {
1114
+ const out = new Set();
1115
+ for (const step of Array.isArray(steps) ? steps : []) {
1116
+ if (step === null || step === void 0 ? void 0 : step.isSharedStepTitle)
1117
+ continue;
1118
+ const codes = this.extractRequirementCodesFromExpectedText((step === null || step === void 0 ? void 0 : step.expected) || '', includeSuffix);
1119
+ codes.forEach((code) => out.add(code));
1120
+ }
1121
+ return out;
1122
+ }
1123
+ extractRequirementCodesFromExpectedText(text, includeSuffix) {
576
1124
  const out = new Set();
577
1125
  const source = this.normalizeRequirementStepText(text);
578
- // Supports SR<ID> patterns even when HTML formatting breaks the token,
579
- // e.g. "S<b>R</b> 0 0 1" or "S R 0 0 1".
580
- const regex = /S[\s\u00A0]*R(?:[\s\u00A0\-_]*\d){1,12}/gi;
581
- let match = null;
582
- while ((match = regex.exec(source)) !== null) {
583
- const digitsOnly = String(match[0] || '').replace(/\D/g, '');
584
- const digits = Number.parseInt(digitsOnly, 10);
585
- if (Number.isFinite(digits)) {
586
- out.add(`SR${digits}`);
1126
+ if (!source)
1127
+ return out;
1128
+ const tokens = source
1129
+ .split(';')
1130
+ .map((token) => String(token || '').trim())
1131
+ .filter((token) => token !== '');
1132
+ for (const token of tokens) {
1133
+ const candidates = this.extractRequirementCandidatesFromToken(token);
1134
+ for (const candidate of candidates) {
1135
+ const expandedTokens = this.expandRequirementTokenByComma(candidate);
1136
+ for (const expandedToken of expandedTokens) {
1137
+ if (!expandedToken || /vvrm/i.test(expandedToken))
1138
+ continue;
1139
+ const normalized = this.normalizeRequirementCodeToken(expandedToken, includeSuffix);
1140
+ if (normalized) {
1141
+ out.add(normalized);
1142
+ }
1143
+ }
587
1144
  }
588
1145
  }
589
1146
  return out;
590
1147
  }
1148
+ extractRequirementCandidatesFromToken(token) {
1149
+ const source = String(token || '');
1150
+ if (!source)
1151
+ return [];
1152
+ const out = new Set();
1153
+ const collectCandidates = (input, rejectTailPattern) => {
1154
+ for (const match of input.matchAll(/SR\d{4,}(?:-\d+(?:,\d+)*)?/gi)) {
1155
+ const matchedValue = String((match === null || match === void 0 ? void 0 : match[0]) || '')
1156
+ .trim()
1157
+ .toUpperCase();
1158
+ if (!matchedValue)
1159
+ continue;
1160
+ const endIndex = Number((match === null || match === void 0 ? void 0 : match.index) || 0) + matchedValue.length;
1161
+ const tail = String(input.slice(endIndex) || '');
1162
+ if (rejectTailPattern.test(tail))
1163
+ continue;
1164
+ out.add(matchedValue);
1165
+ }
1166
+ };
1167
+ // Normal scan keeps punctuation context (" SR0817-V3.2 " -> reject via tail).
1168
+ collectCandidates(source, /^\s*(?:V\d|VVRM|-V\d)/i);
1169
+ // Compact scan preserves legacy support for spaced SR letters/digits
1170
+ // such as "S R 0 0 0 1" and HTML-fragmented tokens.
1171
+ const compactSource = source.replace(/\s+/g, '');
1172
+ if (compactSource && compactSource !== source) {
1173
+ collectCandidates(compactSource, /^(?:V\d|VVRM|-V\d)/i);
1174
+ }
1175
+ return [...out];
1176
+ }
1177
+ expandRequirementTokenByComma(token) {
1178
+ const compact = String(token || '').trim().toUpperCase();
1179
+ if (!compact)
1180
+ return [];
1181
+ const suffixBatchMatch = /^SR(\d{4,})-(\d+(?:,\d+)+)$/.exec(compact);
1182
+ if (suffixBatchMatch) {
1183
+ const base = String(suffixBatchMatch[1] || '').trim();
1184
+ const suffixes = String(suffixBatchMatch[2] || '')
1185
+ .split(',')
1186
+ .map((item) => String(item || '').trim())
1187
+ .filter((item) => /^\d+$/.test(item));
1188
+ return suffixes.map((suffix) => `SR${base}-${suffix}`);
1189
+ }
1190
+ return compact
1191
+ .split(',')
1192
+ .map((part) => String(part || '').trim())
1193
+ .filter((part) => !!part);
1194
+ }
1195
+ normalizeRequirementCodeToken(token, includeSuffix) {
1196
+ const compact = String(token || '')
1197
+ .trim()
1198
+ .replace(/[\u200B-\u200D\uFEFF]/g, '')
1199
+ .toUpperCase();
1200
+ if (!compact)
1201
+ return '';
1202
+ const pattern = includeSuffix ? /^SR(\d{4,})(?:-(\d+))?$/ : /^SR(\d{4,})(?:-\d+)?$/;
1203
+ const match = pattern.exec(compact);
1204
+ if (!match)
1205
+ return '';
1206
+ const baseDigits = String(match[1] || '').trim();
1207
+ if (!baseDigits)
1208
+ return '';
1209
+ if (includeSuffix && match[2]) {
1210
+ const suffixDigits = String(match[2] || '').trim();
1211
+ if (!suffixDigits)
1212
+ return '';
1213
+ return `SR${baseDigits}-${suffixDigits}`;
1214
+ }
1215
+ return `SR${baseDigits}`;
1216
+ }
591
1217
  normalizeRequirementStepText(text) {
592
1218
  const raw = String(text || '');
593
1219
  if (!raw)
@@ -603,17 +1229,19 @@ class ResultDataProvider {
603
1229
  .replace(/[\u200B-\u200D\uFEFF]/g, '')
604
1230
  .replace(/\s+/g, ' ');
605
1231
  }
1232
+ resolveValidationStepReference(step, index) {
1233
+ const fromPosition = String((step === null || step === void 0 ? void 0 : step.stepPosition) || '').trim();
1234
+ if (fromPosition)
1235
+ return `Step ${fromPosition}`;
1236
+ const fromId = String((step === null || step === void 0 ? void 0 : step.stepId) || '').trim();
1237
+ if (fromId)
1238
+ return `Step ${fromId}`;
1239
+ return `Step ${index + 1}`;
1240
+ }
606
1241
  toRequirementKey(requirementId) {
607
- const normalized = this.normalizeMewpRequirementCode(requirementId);
608
- if (!normalized)
609
- return '';
610
- const digits = Number.parseInt(normalized.replace(/^SR/i, ''), 10);
611
- if (!Number.isFinite(digits))
612
- return '';
613
- return `SR${digits}`;
1242
+ return this.normalizeMewpRequirementCode(requirementId);
614
1243
  }
615
1244
  async fetchMewpL2Requirements(projectName) {
616
- var _a;
617
1245
  const workItemTypeNames = await this.fetchMewpRequirementTypeNames(projectName);
618
1246
  if (workItemTypeNames.length === 0) {
619
1247
  return [];
@@ -621,32 +1249,315 @@ class ResultDataProvider {
621
1249
  const quotedTypeNames = workItemTypeNames
622
1250
  .map((name) => `'${String(name).replace(/'/g, "''")}'`)
623
1251
  .join(', ');
624
- const wiql = `SELECT [System.Id]
625
- FROM WorkItems
626
- WHERE [System.TeamProject] = @project
627
- AND [System.WorkItemType] IN (${quotedTypeNames})
628
- ORDER BY [System.Id]`;
629
- const wiqlUrl = `${this.orgUrl}${projectName}/_apis/wit/wiql?api-version=7.1-preview.2`;
630
- const wiqlResponse = await tfs_1.TFSServices.postRequest(wiqlUrl, this.token, 'Post', { query: wiql }, null);
631
- const workItemRefs = Array.isArray((_a = wiqlResponse === null || wiqlResponse === void 0 ? void 0 : wiqlResponse.data) === null || _a === void 0 ? void 0 : _a.workItems) ? wiqlResponse.data.workItems : [];
632
- const requirementIds = workItemRefs
633
- .map((item) => Number(item === null || item === void 0 ? void 0 : item.id))
634
- .filter((id) => Number.isFinite(id));
1252
+ const queryRequirementIds = async (l2AreaPath) => {
1253
+ var _a;
1254
+ const escapedAreaPath = l2AreaPath ? String(l2AreaPath).replace(/'/g, "''") : '';
1255
+ const areaFilter = escapedAreaPath ? `\n AND [System.AreaPath] UNDER '${escapedAreaPath}'` : '';
1256
+ const wiql = `SELECT [System.Id]
1257
+ FROM WorkItems
1258
+ WHERE [System.TeamProject] = @project
1259
+ AND [System.WorkItemType] IN (${quotedTypeNames})${areaFilter}
1260
+ ORDER BY [System.Id]`;
1261
+ const wiqlUrl = `${this.orgUrl}${projectName}/_apis/wit/wiql?api-version=7.1-preview.2`;
1262
+ const wiqlResponse = await tfs_1.TFSServices.postRequest(wiqlUrl, this.token, 'Post', { query: wiql }, null);
1263
+ const workItemRefs = Array.isArray((_a = wiqlResponse === null || wiqlResponse === void 0 ? void 0 : wiqlResponse.data) === null || _a === void 0 ? void 0 : _a.workItems) ? wiqlResponse.data.workItems : [];
1264
+ return workItemRefs
1265
+ .map((item) => Number(item === null || item === void 0 ? void 0 : item.id))
1266
+ .filter((id) => Number.isFinite(id));
1267
+ };
1268
+ const defaultL2AreaPath = `${String(projectName || '').trim()}\\Customer Requirements\\Level 2`;
1269
+ let requirementIds = [];
1270
+ try {
1271
+ requirementIds = await queryRequirementIds(defaultL2AreaPath);
1272
+ }
1273
+ catch (error) {
1274
+ logger_1.default.warn(`Could not apply MEWP L2 WIQL area-path optimization. Falling back to full requirement scope: ${(error === null || error === void 0 ? void 0 : error.message) || error}`);
1275
+ }
1276
+ if (requirementIds.length === 0) {
1277
+ requirementIds = await queryRequirementIds(null);
1278
+ }
635
1279
  if (requirementIds.length === 0) {
636
1280
  return [];
637
1281
  }
638
1282
  const workItems = await this.fetchWorkItemsByIds(projectName, requirementIds, true);
639
1283
  const requirements = workItems.map((wi) => {
640
1284
  const fields = (wi === null || wi === void 0 ? void 0 : wi.fields) || {};
1285
+ const requirementId = this.extractMewpRequirementIdentifier(fields, Number((wi === null || wi === void 0 ? void 0 : wi.id) || 0));
1286
+ const areaPath = this.toMewpComparableText(fields === null || fields === void 0 ? void 0 : fields['System.AreaPath']);
641
1287
  return {
642
1288
  workItemId: Number((wi === null || wi === void 0 ? void 0 : wi.id) || 0),
643
- requirementId: this.extractMewpRequirementIdentifier(fields, Number((wi === null || wi === void 0 ? void 0 : wi.id) || 0)),
1289
+ requirementId,
1290
+ baseKey: this.toRequirementKey(requirementId),
644
1291
  title: this.toMewpComparableText((fields === null || fields === void 0 ? void 0 : fields['System.Title']) || (wi === null || wi === void 0 ? void 0 : wi.title)),
1292
+ subSystem: this.deriveMewpSubSystem(fields),
645
1293
  responsibility: this.deriveMewpResponsibility(fields),
646
1294
  linkedTestCaseIds: this.extractLinkedTestCaseIdsFromRequirement((wi === null || wi === void 0 ? void 0 : wi.relations) || []),
1295
+ relatedWorkItemIds: this.extractLinkedWorkItemIdsFromRelations((wi === null || wi === void 0 ? void 0 : wi.relations) || []),
1296
+ areaPath,
647
1297
  };
648
1298
  });
649
- return requirements.sort((a, b) => String(a.requirementId).localeCompare(String(b.requirementId)));
1299
+ return requirements
1300
+ .filter((item) => {
1301
+ if (!item.baseKey)
1302
+ return false;
1303
+ if (!item.areaPath)
1304
+ return true;
1305
+ return this.isMewpL2AreaPath(item.areaPath);
1306
+ })
1307
+ .sort((a, b) => String(a.requirementId).localeCompare(String(b.requirementId)));
1308
+ }
1309
+ isMewpL2AreaPath(areaPath) {
1310
+ const normalized = String(areaPath || '')
1311
+ .trim()
1312
+ .toLowerCase()
1313
+ .replace(/\//g, '\\');
1314
+ if (!normalized)
1315
+ return false;
1316
+ return normalized.includes('\\customer requirements\\level 2');
1317
+ }
1318
+ collapseMewpRequirementFamilies(requirements, scopedRequirementKeys) {
1319
+ const families = new Map();
1320
+ const calcScore = (item) => {
1321
+ const requirementId = String((item === null || item === void 0 ? void 0 : item.requirementId) || '').trim();
1322
+ const areaPath = String((item === null || item === void 0 ? void 0 : item.areaPath) || '')
1323
+ .trim()
1324
+ .toLowerCase();
1325
+ let score = 0;
1326
+ if (/^SR\d+$/i.test(requirementId))
1327
+ score += 6;
1328
+ if (areaPath.includes('\\customer requirements\\level 2'))
1329
+ score += 3;
1330
+ if (!areaPath.includes('\\mop'))
1331
+ score += 2;
1332
+ if (String((item === null || item === void 0 ? void 0 : item.title) || '').trim())
1333
+ score += 1;
1334
+ if (String((item === null || item === void 0 ? void 0 : item.subSystem) || '').trim())
1335
+ score += 1;
1336
+ if (String((item === null || item === void 0 ? void 0 : item.responsibility) || '').trim())
1337
+ score += 1;
1338
+ return score;
1339
+ };
1340
+ for (const requirement of requirements || []) {
1341
+ const baseKey = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
1342
+ if (!baseKey)
1343
+ continue;
1344
+ if ((scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) && !scopedRequirementKeys.has(baseKey))
1345
+ continue;
1346
+ if (!families.has(baseKey)) {
1347
+ families.set(baseKey, {
1348
+ representative: requirement,
1349
+ score: calcScore(requirement),
1350
+ linkedTestCaseIds: new Set(),
1351
+ });
1352
+ }
1353
+ const family = families.get(baseKey);
1354
+ const score = calcScore(requirement);
1355
+ if (score > family.score) {
1356
+ family.representative = requirement;
1357
+ family.score = score;
1358
+ }
1359
+ for (const testCaseId of (requirement === null || requirement === void 0 ? void 0 : requirement.linkedTestCaseIds) || []) {
1360
+ if (Number.isFinite(testCaseId) && Number(testCaseId) > 0) {
1361
+ family.linkedTestCaseIds.add(Number(testCaseId));
1362
+ }
1363
+ }
1364
+ }
1365
+ return [...families.entries()]
1366
+ .map(([baseKey, family]) => {
1367
+ var _a, _b, _c, _d;
1368
+ return ({
1369
+ requirementId: String(((_a = family === null || family === void 0 ? void 0 : family.representative) === null || _a === void 0 ? void 0 : _a.requirementId) || baseKey),
1370
+ baseKey,
1371
+ title: String(((_b = family === null || family === void 0 ? void 0 : family.representative) === null || _b === void 0 ? void 0 : _b.title) || ''),
1372
+ subSystem: String(((_c = family === null || family === void 0 ? void 0 : family.representative) === null || _c === void 0 ? void 0 : _c.subSystem) || ''),
1373
+ responsibility: String(((_d = family === null || family === void 0 ? void 0 : family.representative) === null || _d === void 0 ? void 0 : _d.responsibility) || ''),
1374
+ linkedTestCaseIds: [...family.linkedTestCaseIds].sort((a, b) => a - b),
1375
+ });
1376
+ })
1377
+ .sort((a, b) => String(a.requirementId).localeCompare(String(b.requirementId)));
1378
+ }
1379
+ buildRequirementFamilyMap(requirements, scopedRequirementKeys) {
1380
+ const familyMap = new Map();
1381
+ for (const requirement of requirements || []) {
1382
+ const baseKey = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
1383
+ if (!baseKey)
1384
+ continue;
1385
+ if ((scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) && !scopedRequirementKeys.has(baseKey))
1386
+ continue;
1387
+ const fullCode = this.normalizeMewpRequirementCodeWithSuffix((requirement === null || requirement === void 0 ? void 0 : requirement.requirementId) || '');
1388
+ if (!fullCode)
1389
+ continue;
1390
+ if (!familyMap.has(baseKey))
1391
+ familyMap.set(baseKey, new Set());
1392
+ familyMap.get(baseKey).add(fullCode);
1393
+ }
1394
+ return familyMap;
1395
+ }
1396
+ async buildLinkedRequirementsByTestCase(requirements, testData, projectName) {
1397
+ var _a, _b, _c;
1398
+ const map = new Map();
1399
+ const ensure = (testCaseId) => {
1400
+ if (!map.has(testCaseId)) {
1401
+ map.set(testCaseId, {
1402
+ baseKeys: new Set(),
1403
+ fullCodes: new Set(),
1404
+ bugIds: new Set(),
1405
+ });
1406
+ }
1407
+ return map.get(testCaseId);
1408
+ };
1409
+ const requirementById = new Map();
1410
+ for (const requirement of requirements || []) {
1411
+ const workItemId = Number((requirement === null || requirement === void 0 ? void 0 : requirement.workItemId) || 0);
1412
+ const baseKey = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
1413
+ const fullCode = this.normalizeMewpRequirementCodeWithSuffix((requirement === null || requirement === void 0 ? void 0 : requirement.requirementId) || '');
1414
+ if (workItemId > 0 && baseKey && fullCode) {
1415
+ requirementById.set(workItemId, { baseKey, fullCode });
1416
+ }
1417
+ for (const testCaseIdRaw of (requirement === null || requirement === void 0 ? void 0 : requirement.linkedTestCaseIds) || []) {
1418
+ const testCaseId = Number(testCaseIdRaw);
1419
+ if (!Number.isFinite(testCaseId) || testCaseId <= 0 || !baseKey || !fullCode)
1420
+ continue;
1421
+ const entry = ensure(testCaseId);
1422
+ entry.baseKeys.add(baseKey);
1423
+ entry.fullCodes.add(fullCode);
1424
+ }
1425
+ }
1426
+ const testCaseIds = new Set();
1427
+ for (const suite of testData || []) {
1428
+ const testCasesItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testCasesItems) ? suite.testCasesItems : [];
1429
+ for (const testCase of testCasesItems) {
1430
+ 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) || 0);
1431
+ if (Number.isFinite(id) && id > 0)
1432
+ testCaseIds.add(id);
1433
+ }
1434
+ const testPointsItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testPointsItems) ? suite.testPointsItems : [];
1435
+ for (const testPoint of testPointsItems) {
1436
+ const id = Number((testPoint === null || testPoint === void 0 ? void 0 : testPoint.testCaseId) || ((_b = testPoint === null || testPoint === void 0 ? void 0 : testPoint.testCase) === null || _b === void 0 ? void 0 : _b.id) || 0);
1437
+ if (Number.isFinite(id) && id > 0)
1438
+ testCaseIds.add(id);
1439
+ }
1440
+ }
1441
+ const relatedIdsByTestCase = new Map();
1442
+ const allRelatedIds = new Set();
1443
+ if (testCaseIds.size > 0) {
1444
+ const testCaseWorkItems = await this.fetchWorkItemsByIds(projectName, [...testCaseIds], true);
1445
+ for (const workItem of testCaseWorkItems || []) {
1446
+ const testCaseId = Number((workItem === null || workItem === void 0 ? void 0 : workItem.id) || 0);
1447
+ if (!Number.isFinite(testCaseId) || testCaseId <= 0)
1448
+ continue;
1449
+ const relations = Array.isArray(workItem === null || workItem === void 0 ? void 0 : workItem.relations) ? workItem.relations : [];
1450
+ if (!relatedIdsByTestCase.has(testCaseId))
1451
+ relatedIdsByTestCase.set(testCaseId, new Set());
1452
+ for (const relation of relations) {
1453
+ const linkedWorkItemId = this.extractLinkedWorkItemIdFromRelation(relation);
1454
+ if (!linkedWorkItemId)
1455
+ continue;
1456
+ relatedIdsByTestCase.get(testCaseId).add(linkedWorkItemId);
1457
+ allRelatedIds.add(linkedWorkItemId);
1458
+ if (this.isTestCaseToRequirementRelation(relation) && requirementById.has(linkedWorkItemId)) {
1459
+ const linkedRequirement = requirementById.get(linkedWorkItemId);
1460
+ const entry = ensure(testCaseId);
1461
+ entry.baseKeys.add(linkedRequirement.baseKey);
1462
+ entry.fullCodes.add(linkedRequirement.fullCode);
1463
+ }
1464
+ }
1465
+ }
1466
+ }
1467
+ if (allRelatedIds.size > 0) {
1468
+ const relatedWorkItems = await this.fetchWorkItemsByIds(projectName, [...allRelatedIds], false);
1469
+ const typeById = new Map();
1470
+ for (const workItem of relatedWorkItems || []) {
1471
+ const id = Number((workItem === null || workItem === void 0 ? void 0 : workItem.id) || 0);
1472
+ if (!Number.isFinite(id) || id <= 0)
1473
+ continue;
1474
+ const type = String(((_c = workItem === null || workItem === void 0 ? void 0 : workItem.fields) === null || _c === void 0 ? void 0 : _c['System.WorkItemType']) || '')
1475
+ .trim()
1476
+ .toLowerCase();
1477
+ typeById.set(id, type);
1478
+ }
1479
+ for (const [testCaseId, ids] of relatedIdsByTestCase.entries()) {
1480
+ const entry = ensure(testCaseId);
1481
+ for (const linkedId of ids) {
1482
+ const linkedType = typeById.get(linkedId) || '';
1483
+ if (linkedType === 'bug') {
1484
+ entry.bugIds.add(linkedId);
1485
+ }
1486
+ }
1487
+ }
1488
+ }
1489
+ return map;
1490
+ }
1491
+ async resolveMewpRequirementScopeKeysFromQuery(linkedQueryRequest, requirements, linkedRequirementsByTestCase) {
1492
+ var _a, _b, _c, _d, _e;
1493
+ const mode = String((linkedQueryRequest === null || linkedQueryRequest === void 0 ? void 0 : linkedQueryRequest.linkedQueryMode) || '')
1494
+ .trim()
1495
+ .toLowerCase();
1496
+ const wiqlHref = String(((_b = (_a = linkedQueryRequest === null || linkedQueryRequest === void 0 ? void 0 : linkedQueryRequest.testAssociatedQuery) === null || _a === void 0 ? void 0 : _a.wiql) === null || _b === void 0 ? void 0 : _b.href) || '').trim();
1497
+ if (mode !== 'query' || !wiqlHref)
1498
+ return undefined;
1499
+ try {
1500
+ const queryResult = await tfs_1.TFSServices.getItemContent(wiqlHref, this.token);
1501
+ const queryIds = new Set();
1502
+ if (Array.isArray(queryResult === null || queryResult === void 0 ? void 0 : queryResult.workItems)) {
1503
+ for (const workItem of queryResult.workItems) {
1504
+ const id = Number((workItem === null || workItem === void 0 ? void 0 : workItem.id) || 0);
1505
+ if (Number.isFinite(id) && id > 0)
1506
+ queryIds.add(id);
1507
+ }
1508
+ }
1509
+ if (Array.isArray(queryResult === null || queryResult === void 0 ? void 0 : queryResult.workItemRelations)) {
1510
+ for (const relation of queryResult.workItemRelations) {
1511
+ const sourceId = Number(((_c = relation === null || relation === void 0 ? void 0 : relation.source) === null || _c === void 0 ? void 0 : _c.id) || 0);
1512
+ const targetId = Number(((_d = relation === null || relation === void 0 ? void 0 : relation.target) === null || _d === void 0 ? void 0 : _d.id) || 0);
1513
+ if (Number.isFinite(sourceId) && sourceId > 0)
1514
+ queryIds.add(sourceId);
1515
+ if (Number.isFinite(targetId) && targetId > 0)
1516
+ queryIds.add(targetId);
1517
+ }
1518
+ }
1519
+ if (queryIds.size === 0)
1520
+ return undefined;
1521
+ const reqIdToBaseKey = new Map();
1522
+ for (const requirement of requirements || []) {
1523
+ const id = Number((requirement === null || requirement === void 0 ? void 0 : requirement.workItemId) || 0);
1524
+ const baseKey = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
1525
+ if (id > 0 && baseKey)
1526
+ reqIdToBaseKey.set(id, baseKey);
1527
+ }
1528
+ const scopedKeys = new Set();
1529
+ for (const queryId of queryIds) {
1530
+ if (reqIdToBaseKey.has(queryId)) {
1531
+ scopedKeys.add(reqIdToBaseKey.get(queryId));
1532
+ continue;
1533
+ }
1534
+ const linked = linkedRequirementsByTestCase.get(queryId);
1535
+ if (!((_e = linked === null || linked === void 0 ? void 0 : linked.baseKeys) === null || _e === void 0 ? void 0 : _e.size))
1536
+ continue;
1537
+ linked.baseKeys.forEach((baseKey) => scopedKeys.add(baseKey));
1538
+ }
1539
+ return scopedKeys.size > 0 ? scopedKeys : undefined;
1540
+ }
1541
+ catch (error) {
1542
+ logger_1.default.warn(`Could not resolve MEWP query scope: ${(error === null || error === void 0 ? void 0 : error.message) || error}`);
1543
+ return undefined;
1544
+ }
1545
+ }
1546
+ isTestCaseToRequirementRelation(relation) {
1547
+ const rel = String((relation === null || relation === void 0 ? void 0 : relation.rel) || '')
1548
+ .trim()
1549
+ .toLowerCase();
1550
+ if (!rel)
1551
+ return false;
1552
+ return rel.includes('testedby-reverse') || (rel.includes('tests') && rel.includes('reverse'));
1553
+ }
1554
+ extractLinkedWorkItemIdFromRelation(relation) {
1555
+ const url = String((relation === null || relation === void 0 ? void 0 : relation.url) || '');
1556
+ const match = /\/workItems\/(\d+)/i.exec(url);
1557
+ if (!match)
1558
+ return 0;
1559
+ const parsed = Number(match[1]);
1560
+ return Number.isFinite(parsed) ? parsed : 0;
650
1561
  }
651
1562
  async fetchMewpRequirementTypeNames(projectName) {
652
1563
  try {
@@ -702,6 +1613,19 @@ ORDER BY [System.Id]`;
702
1613
  }
703
1614
  return [...out].sort((a, b) => a - b);
704
1615
  }
1616
+ extractLinkedWorkItemIdsFromRelations(relations) {
1617
+ const out = new Set();
1618
+ for (const relation of Array.isArray(relations) ? relations : []) {
1619
+ const url = String((relation === null || relation === void 0 ? void 0 : relation.url) || '');
1620
+ const match = /\/workItems\/(\d+)/i.exec(url);
1621
+ if (!match)
1622
+ continue;
1623
+ const id = Number(match[1]);
1624
+ if (Number.isFinite(id) && id > 0)
1625
+ out.add(id);
1626
+ }
1627
+ return [...out].sort((a, b) => a - b);
1628
+ }
705
1629
  extractMewpRequirementIdentifier(fields, fallbackWorkItemId) {
706
1630
  const entries = Object.entries(fields || {});
707
1631
  // First pass: only trusted identifier-like fields.
@@ -721,7 +1645,7 @@ ORDER BY [System.Id]`;
721
1645
  const valueAsString = this.toMewpComparableText(value);
722
1646
  if (!valueAsString)
723
1647
  continue;
724
- const normalized = this.normalizeMewpRequirementCode(valueAsString);
1648
+ const normalized = this.normalizeMewpRequirementCodeWithSuffix(valueAsString);
725
1649
  if (normalized)
726
1650
  return normalized;
727
1651
  }
@@ -734,13 +1658,13 @@ ORDER BY [System.Id]`;
734
1658
  const valueAsString = this.toMewpComparableText(value);
735
1659
  if (!valueAsString)
736
1660
  continue;
737
- const normalized = this.normalizeMewpRequirementCode(valueAsString);
1661
+ const normalized = this.normalizeMewpRequirementCodeWithSuffix(valueAsString);
738
1662
  if (normalized)
739
1663
  return normalized;
740
1664
  }
741
1665
  // Optional fallback from title only (avoid scanning all fields and accidental SR matches).
742
1666
  const title = this.toMewpComparableText(fields === null || fields === void 0 ? void 0 : fields['System.Title']);
743
- const titleCode = this.normalizeMewpRequirementCode(title);
1667
+ const titleCode = this.normalizeMewpRequirementCodeWithSuffix(title);
744
1668
  if (titleCode)
745
1669
  return titleCode;
746
1670
  return fallbackWorkItemId ? `SR${fallbackWorkItemId}` : '';
@@ -773,18 +1697,69 @@ ORDER BY [System.Id]`;
773
1697
  }
774
1698
  return '';
775
1699
  }
1700
+ deriveMewpSubSystem(fields) {
1701
+ const directCandidates = [
1702
+ fields === null || fields === void 0 ? void 0 : fields['Custom.SubSystem'],
1703
+ fields === null || fields === void 0 ? void 0 : fields['Custom.Subsystem'],
1704
+ fields === null || fields === void 0 ? void 0 : fields['SubSystem'],
1705
+ fields === null || fields === void 0 ? void 0 : fields['Subsystem'],
1706
+ fields === null || fields === void 0 ? void 0 : fields['subSystem'],
1707
+ ];
1708
+ for (const candidate of directCandidates) {
1709
+ const value = this.toMewpComparableText(candidate);
1710
+ if (value)
1711
+ return value;
1712
+ }
1713
+ const keyHints = ['subsystem', 'sub system', 'sub_system'];
1714
+ for (const [key, value] of Object.entries(fields || {})) {
1715
+ const normalizedKey = String(key || '').toLowerCase();
1716
+ if (!keyHints.some((hint) => normalizedKey.includes(hint)))
1717
+ continue;
1718
+ const resolved = this.toMewpComparableText(value);
1719
+ if (resolved)
1720
+ return resolved;
1721
+ }
1722
+ return '';
1723
+ }
1724
+ resolveBugResponsibility(fields) {
1725
+ const sapWbsRaw = this.toMewpComparableText((fields === null || fields === void 0 ? void 0 : fields['Custom.SAPWBS']) || (fields === null || fields === void 0 ? void 0 : fields['SAPWBS']));
1726
+ const fromSapWbs = this.resolveMewpResponsibility(sapWbsRaw);
1727
+ if (fromSapWbs === 'ESUK')
1728
+ return 'ESUK';
1729
+ if (fromSapWbs === 'IL')
1730
+ return 'Elisra';
1731
+ const areaPathRaw = this.toMewpComparableText(fields === null || fields === void 0 ? void 0 : fields['System.AreaPath']);
1732
+ const fromAreaPath = this.resolveMewpResponsibility(areaPathRaw);
1733
+ if (fromAreaPath === 'ESUK')
1734
+ return 'ESUK';
1735
+ if (fromAreaPath === 'IL')
1736
+ return 'Elisra';
1737
+ return 'Unknown';
1738
+ }
776
1739
  resolveMewpResponsibility(value) {
777
- const text = String(value || '')
778
- .trim()
779
- .toLowerCase();
780
- if (!text)
1740
+ const raw = String(value || '').trim();
1741
+ if (!raw)
781
1742
  return '';
782
- if (/(^|[^a-z0-9])esuk([^a-z0-9]|$)/i.test(text))
1743
+ const rawUpper = raw.toUpperCase();
1744
+ if (rawUpper === 'ESUK')
1745
+ return 'ESUK';
1746
+ if (rawUpper === 'IL')
1747
+ return 'IL';
1748
+ const normalizedPath = raw
1749
+ .toLowerCase()
1750
+ .replace(/\//g, '\\')
1751
+ .replace(/\\+/g, '\\')
1752
+ .trim();
1753
+ if (normalizedPath.endsWith('\\atp\\esuk') || normalizedPath === 'atp\\esuk')
783
1754
  return 'ESUK';
784
- if (/(^|[^a-z0-9])il([^a-z0-9]|$)/i.test(text))
1755
+ if (normalizedPath.endsWith('\\atp') || normalizedPath === 'atp')
785
1756
  return 'IL';
786
1757
  return '';
787
1758
  }
1759
+ isExcludedL3L4BySapWbs(value) {
1760
+ const responsibility = this.resolveMewpResponsibility(this.toMewpComparableText(value));
1761
+ return responsibility === 'ESUK';
1762
+ }
788
1763
  normalizeMewpRequirementCode(value) {
789
1764
  const text = String(value || '').trim();
790
1765
  if (!text)
@@ -794,6 +1769,18 @@ ORDER BY [System.Id]`;
794
1769
  return '';
795
1770
  return `SR${match[1]}`;
796
1771
  }
1772
+ normalizeMewpRequirementCodeWithSuffix(value) {
1773
+ const text = String(value || '').trim();
1774
+ if (!text)
1775
+ return '';
1776
+ const compact = text.replace(/\s+/g, '');
1777
+ const match = /^SR(\d+)(?:-(\d+))?$/i.exec(compact);
1778
+ if (!match)
1779
+ return '';
1780
+ if (match[2])
1781
+ return `SR${match[1]}-${match[2]}`;
1782
+ return `SR${match[1]}`;
1783
+ }
797
1784
  toMewpComparableText(value) {
798
1785
  if (value === null || value === undefined)
799
1786
  return '';
@@ -2650,14 +3637,24 @@ ORDER BY [System.Id]`;
2650
3637
  }
2651
3638
  }
2652
3639
  ResultDataProvider.MEWP_L2_COVERAGE_COLUMNS = [
2653
- 'Customer ID',
2654
- 'Title (Customer name)',
2655
- 'Responsibility - SAPWBS (ESUK/IL)',
2656
- 'Test case id',
2657
- 'Test case title',
2658
- 'Number of passed steps',
2659
- 'Number of failed steps',
2660
- 'Number of not run tests',
3640
+ 'L2 REQ ID',
3641
+ 'L2 REQ Title',
3642
+ 'L2 SubSystem',
3643
+ 'L2 Run Status',
3644
+ 'Bug ID',
3645
+ 'Bug Title',
3646
+ 'Bug Responsibility',
3647
+ 'L3 REQ ID',
3648
+ 'L3 REQ Title',
3649
+ 'L4 REQ ID',
3650
+ 'L4 REQ Title',
3651
+ ];
3652
+ ResultDataProvider.INTERNAL_VALIDATION_COLUMNS = [
3653
+ 'Test Case ID',
3654
+ 'Test Case Title',
3655
+ 'Mentioned but Not Linked',
3656
+ 'Linked but Not Mentioned',
3657
+ 'Validation Status',
2661
3658
  ];
2662
3659
  exports.default = ResultDataProvider;
2663
3660
  //# sourceMappingURL=ResultDataProvider.js.map