@elisra-devops/docgen-data-provider 1.132.0 → 1.134.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.
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tfs_1 = require("../../helpers/tfs");
4
+ const TicketsDataProvider_1 = require("../../modules/TicketsDataProvider");
5
+ const logger_1 = require("../../utils/logger");
6
+ jest.mock('../../helpers/tfs');
7
+ jest.mock('../../utils/logger');
8
+ const mockGetItemContent = tfs_1.TFSServices.getItemContent;
9
+ // Minimal OneHop query result shape
10
+ const makeQueryResult = (columns, relations) => ({
11
+ columns,
12
+ workItemRelations: relations,
13
+ queryType: 'oneHop',
14
+ });
15
+ // Root relation (source = the root WI, no .source field)
16
+ const rootRelation = (targetId, targetUrl) => ({
17
+ source: null,
18
+ target: { id: targetId, url: targetUrl },
19
+ });
20
+ // Link relation (source WI → target WI)
21
+ const linkRelation = (sourceId, targetId, targetUrl) => ({
22
+ source: { id: sourceId },
23
+ target: { id: targetId, url: targetUrl },
24
+ });
25
+ const REQ_COLS = [
26
+ { referenceName: 'System.Id', name: 'ID' },
27
+ { referenceName: 'System.Title', name: 'Title' },
28
+ { referenceName: 'Custom.CustomerRequirementId', name: 'CustomerRequirementId' },
29
+ { referenceName: 'Microsoft.VSTS.Common.Priority', name: 'Priority' },
30
+ ];
31
+ const TC_COLS = [
32
+ { referenceName: 'System.Id', name: 'ID' },
33
+ { referenceName: 'System.Title', name: 'Title' },
34
+ { referenceName: 'Microsoft.VSTS.TCM.AutomationStatus', name: 'Automation Status' },
35
+ { referenceName: 'Microsoft.VSTS.Common.Priority', name: 'Priority' },
36
+ ];
37
+ const REQ_FIELDS_SCHEMA = {
38
+ value: [
39
+ { referenceName: 'System.Id' },
40
+ { referenceName: 'System.Title' },
41
+ { referenceName: 'Custom.CustomerRequirementId' },
42
+ { referenceName: 'Microsoft.VSTS.Common.Priority' },
43
+ ],
44
+ };
45
+ const TC_FIELDS_SCHEMA = {
46
+ value: [
47
+ { referenceName: 'System.Id' },
48
+ { referenceName: 'System.Title' },
49
+ { referenceName: 'Microsoft.VSTS.TCM.AutomationStatus' },
50
+ { referenceName: 'Microsoft.VSTS.Common.Priority' },
51
+ // Note: Custom.CustomerRequirementId is NOT in TC schema
52
+ ],
53
+ };
54
+ describe('TicketsDataProvider.GetTraceColumnsByType', () => {
55
+ let provider;
56
+ beforeEach(() => {
57
+ jest.clearAllMocks();
58
+ provider = new TicketsDataProvider_1.default('https://dev.azure.com/org/', 'mock-token');
59
+ });
60
+ it('splits columns correctly: CustomerRequirementId in Requirement only, not Test Case', async () => {
61
+ const reqRelations = [
62
+ rootRelation(1, 'https://ado/wi/1'),
63
+ linkRelation(1, 101, 'https://ado/wi/101'),
64
+ ];
65
+ const reqQueryResult = makeQueryResult(REQ_COLS, reqRelations);
66
+ mockGetItemContent
67
+ .mockResolvedValueOnce(reqQueryResult) // reqTest wiql fetch
68
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } }) // source WI sample
69
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } }) // target WI sample
70
+ .mockResolvedValueOnce(REQ_FIELDS_SCHEMA) // Requirement type fields
71
+ .mockResolvedValueOnce(TC_FIELDS_SCHEMA); // Test Case type fields
72
+ const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'my-project');
73
+ const reqRefs = result.Requirement.map((c) => c.referenceName);
74
+ const tcRefs = result['Test Case'].map((c) => c.referenceName);
75
+ expect(reqRefs).toContain('Custom.CustomerRequirementId');
76
+ expect(tcRefs).not.toContain('Custom.CustomerRequirementId');
77
+ expect(reqRefs).toContain('Microsoft.VSTS.Common.Priority');
78
+ expect(tcRefs).toContain('Microsoft.VSTS.Common.Priority');
79
+ });
80
+ it('merges columns from both queries (reqTest + testReq), deduped', async () => {
81
+ const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
82
+ const testRelations = [rootRelation(101, 'https://ado/wi/101'), linkRelation(101, 1, 'https://ado/wi/1')];
83
+ const extraCol = { referenceName: 'System.AreaPath', name: 'Area Path' };
84
+ mockGetItemContent
85
+ .mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations)) // reqTest wiql
86
+ .mockResolvedValueOnce(makeQueryResult([...TC_COLS, extraCol], testRelations)) // testReq wiql
87
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } }) // source sample reqTest
88
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } }) // target sample reqTest
89
+ .mockResolvedValueOnce(REQ_FIELDS_SCHEMA)
90
+ .mockResolvedValueOnce({ value: [...TC_FIELDS_SCHEMA.value, { referenceName: 'System.AreaPath' }] });
91
+ const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', 'https://ado/wiql/testreq', 'my-project');
92
+ const tcRefs = result['Test Case'].map((c) => c.referenceName);
93
+ expect(tcRefs).toContain('System.AreaPath'); // came only from testReq columns
94
+ });
95
+ it('raw display names passed through without rename (no CustomerRequirementId → Customer ID)', async () => {
96
+ const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
97
+ mockGetItemContent
98
+ .mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations))
99
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } })
100
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } })
101
+ .mockResolvedValueOnce(REQ_FIELDS_SCHEMA)
102
+ .mockResolvedValueOnce(TC_FIELDS_SCHEMA);
103
+ const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
104
+ const custCol = result.Requirement.find((c) => c.referenceName === 'Custom.CustomerRequirementId');
105
+ expect(custCol === null || custCol === void 0 ? void 0 : custCol.name).toBe('CustomerRequirementId'); // raw ADO name, NOT 'Customer ID'
106
+ });
107
+ it('gracefully degrades when WIT type cannot be sampled (no relations) — returns merged columns both sides', async () => {
108
+ const emptyRelations = [];
109
+ mockGetItemContent.mockResolvedValueOnce(makeQueryResult(REQ_COLS, emptyRelations));
110
+ const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
111
+ const reqRefs = result.Requirement.map((c) => c.referenceName);
112
+ const tcRefs = result['Test Case'].map((c) => c.referenceName);
113
+ // Both sides get the merged declared columns as fallback
114
+ expect(reqRefs).toContain('Custom.CustomerRequirementId');
115
+ expect(tcRefs).toContain('Custom.CustomerRequirementId');
116
+ });
117
+ it('returns empty arrays when wiql fetch fails', async () => {
118
+ mockGetItemContent.mockRejectedValueOnce(new Error('Network error'));
119
+ const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
120
+ expect(result).toEqual({ Requirement: [], 'Test Case': [] });
121
+ expect(logger_1.default.error).toHaveBeenCalled();
122
+ });
123
+ it('returns empty arrays when schema fetch fails', async () => {
124
+ const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
125
+ mockGetItemContent
126
+ .mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations))
127
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } })
128
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } })
129
+ .mockRejectedValueOnce(new Error('ADO 404')); // schema fetch fails
130
+ const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
131
+ expect(result).toEqual({ Requirement: [], 'Test Case': [] });
132
+ });
133
+ it('handles undefined schema value gracefully (no crash on null response)', async () => {
134
+ const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
135
+ mockGetItemContent
136
+ .mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations))
137
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } })
138
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } })
139
+ .mockResolvedValueOnce(undefined) // schema returns undefined (null guard fix)
140
+ .mockResolvedValueOnce(undefined);
141
+ const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
142
+ // Both field sets are empty → both sides empty after intersection
143
+ expect(result.Requirement).toEqual([]);
144
+ expect(result['Test Case']).toEqual([]);
145
+ });
146
+ });
147
+ //# sourceMappingURL=ticketsDataProvider.traceColumns.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticketsDataProvider.traceColumns.test.js","sourceRoot":"","sources":["../../../src/tests/modules/ticketsDataProvider.traceColumns.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elisra-devops/docgen-data-provider",
3
- "version": "1.132.0",
3
+ "version": "1.134.0",
4
4
  "description": "A document generator data provider, aimed to retrive data from azure devops",
5
5
  "repository": {
6
6
  "type": "git",
@@ -290,7 +290,8 @@ export default class ResultDataProvider {
290
290
  enableRunStepStatusFilter: boolean,
291
291
  linkedQueryRequest: any,
292
292
  errorFilterMode: string = 'none',
293
- includeAllHistory: boolean = false
293
+ includeAllHistory: boolean = false,
294
+ useLatestTestCaseProperties: boolean = false
294
295
  ) {
295
296
  const fetchedTestResults: any[] = [];
296
297
  logger.debug(
@@ -322,7 +323,9 @@ export default class ResultDataProvider {
322
323
  projectName,
323
324
  selectedFields,
324
325
  isQueryMode,
325
- includeAllHistory
326
+ includeAllHistory,
327
+ false,
328
+ useLatestTestCaseProperties
326
329
  );
327
330
  logger.debug(`Aligning steps with iterations for test reporter results`);
328
331
  const testReporterData = this.alignStepsWithIterationsTestReporter(
@@ -3656,10 +3659,16 @@ export default class ResultDataProvider {
3656
3659
  suiteTestCaseRevision: number,
3657
3660
  pointAsOfTimestamp: string,
3658
3661
  fallbackSnapshot: any,
3659
- expandAll: boolean
3662
+ expandAll: boolean,
3663
+ useLatestTestCaseProperties: boolean = false
3660
3664
  ): Promise<any | null> {
3661
3665
  let bestSnapshotWithoutSteps: any | null = null;
3662
3666
 
3667
+ if (useLatestTestCaseProperties) {
3668
+ const latest = await this.fetchWorkItemLatest(projectName, testCaseId, expandAll);
3669
+ if (latest) return latest;
3670
+ }
3671
+
3663
3672
  if (pointAsOfTimestamp) {
3664
3673
  const asOfSnapshot = await this.fetchWorkItemByAsOf(
3665
3674
  projectName,
@@ -3721,14 +3730,15 @@ export default class ResultDataProvider {
3721
3730
  isQueryMode?: boolean,
3722
3731
  point?: any,
3723
3732
  includeAllHistory: boolean = false,
3724
- useRunlessAsOf: boolean = false
3733
+ useRunlessAsOf: boolean = false,
3734
+ useLatestTestCaseProperties: boolean = false
3725
3735
  ): Promise<any> {
3726
3736
  try {
3727
3737
  let filteredFields: any = {};
3728
3738
  let relatedRequirements: any[] = [];
3729
3739
  let relatedBugs: any[] = [];
3730
3740
  let relatedCRs: any[] = [];
3731
- if (runId === '0' || resultId === '0') {
3741
+ if (useLatestTestCaseProperties || runId === '0' || resultId === '0') {
3732
3742
  if (!point) {
3733
3743
  logger.warn(`Invalid run result ${runId} or result ${resultId}`);
3734
3744
  return null;
@@ -3752,7 +3762,8 @@ export default class ResultDataProvider {
3752
3762
  suiteTestCaseRevision,
3753
3763
  pointAsOfTimestamp,
3754
3764
  fallbackSnapshot,
3755
- isTestReporter
3765
+ isTestReporter,
3766
+ useLatestTestCaseProperties
3756
3767
  );
3757
3768
  if (!testCaseData) {
3758
3769
  logger.warn(`Could not resolve test case ${point.testCaseId} for runless point fallback.`);
@@ -5465,7 +5476,8 @@ export default class ResultDataProvider {
5465
5476
  isQueryMode?: boolean,
5466
5477
  point?: any,
5467
5478
  includeAllHistory: boolean = false,
5468
- useRunlessAsOf: boolean = false
5479
+ useRunlessAsOf: boolean = false,
5480
+ useLatestTestCaseProperties: boolean = false
5469
5481
  ): Promise<any> {
5470
5482
  return this.fetchResultDataBasedOnWiBase(
5471
5483
  projectName,
@@ -5476,7 +5488,8 @@ export default class ResultDataProvider {
5476
5488
  isQueryMode,
5477
5489
  point,
5478
5490
  includeAllHistory,
5479
- useRunlessAsOf
5491
+ useRunlessAsOf,
5492
+ useLatestTestCaseProperties
5480
5493
  );
5481
5494
  }
5482
5495
 
@@ -5498,13 +5511,14 @@ export default class ResultDataProvider {
5498
5511
  selectedFields?: string[],
5499
5512
  isQueryMode?: boolean,
5500
5513
  includeAllHistory: boolean = false,
5501
- useRunlessAsOf: boolean = false
5514
+ useRunlessAsOf: boolean = false,
5515
+ useLatestTestCaseProperties: boolean = false
5502
5516
  ): Promise<any[]> {
5503
5517
  return this.fetchAllResultDataBase(
5504
5518
  testData,
5505
5519
  projectName,
5506
5520
  true,
5507
- (projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf) =>
5521
+ (projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf, useLatestTestCaseProperties) =>
5508
5522
  this.fetchResultDataForTestReporter(
5509
5523
  projectName,
5510
5524
  testSuiteId,
@@ -5512,9 +5526,10 @@ export default class ResultDataProvider {
5512
5526
  selectedFields,
5513
5527
  isQueryMode,
5514
5528
  includeAllHistory,
5515
- useRunlessAsOf
5529
+ useRunlessAsOf,
5530
+ useLatestTestCaseProperties
5516
5531
  ),
5517
- [selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf]
5532
+ [selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf, useLatestTestCaseProperties]
5518
5533
  );
5519
5534
  }
5520
5535
 
@@ -5673,13 +5688,14 @@ export default class ResultDataProvider {
5673
5688
  selectedFields?: string[],
5674
5689
  isQueryMode?: boolean,
5675
5690
  includeAllHistory: boolean = false,
5676
- useRunlessAsOf: boolean = false
5691
+ useRunlessAsOf: boolean = false,
5692
+ useLatestTestCaseProperties: boolean = false
5677
5693
  ) {
5678
5694
  return this.fetchResultDataBase(
5679
5695
  projectName,
5680
5696
  testSuiteId,
5681
5697
  point,
5682
- (project, runId, resultId, fields, isQueryMode, point, includeAllHistory, useRunlessAsOf) =>
5698
+ (project, runId, resultId, fields, isQueryMode, point, includeAllHistory, useRunlessAsOf, useLatestTestCaseProperties) =>
5683
5699
  this.fetchResultDataBasedOnWiTestReporter(
5684
5700
  project,
5685
5701
  runId,
@@ -5688,7 +5704,8 @@ export default class ResultDataProvider {
5688
5704
  isQueryMode,
5689
5705
  point,
5690
5706
  includeAllHistory,
5691
- useRunlessAsOf
5707
+ useRunlessAsOf,
5708
+ useLatestTestCaseProperties
5692
5709
  ),
5693
5710
  (resultData, testSuiteId, point, selectedFields) => {
5694
5711
  const { lastRunId, lastResultId, configurationName, lastResultDetails } = point;
@@ -3478,4 +3478,129 @@ export default class TicketsDataProvider {
3478
3478
  throw err;
3479
3479
  }
3480
3480
  }
3481
+
3482
+ /**
3483
+ * Returns per-side valid column lists for trace analysis Column Settings dialog.
3484
+ * Uses the WIT field schema (~6 ADO calls, constant cost) to determine which
3485
+ * declared query columns are valid for the Requirement side vs the Test Case side.
3486
+ *
3487
+ * Known limitation: assumes one WIT type per side (determined by sampling the first
3488
+ * source + first target work item). Trace queries are single-type per side in practice.
3489
+ */
3490
+ async GetTraceColumnsByType(
3491
+ reqTestWiqlHref: string | undefined,
3492
+ testReqWiqlHref: string | undefined,
3493
+ project: string,
3494
+ ): Promise<{ Requirement: any[]; 'Test Case': any[] }> {
3495
+ const normalizeCols = (columns: any[]): any[] =>
3496
+ (columns || []).map((c: any) => ({
3497
+ referenceName: c.referenceName,
3498
+ name: c.name,
3499
+ }));
3500
+
3501
+ const mergeCols = (a: any[], b: any[]): any[] => {
3502
+ const seen = new Set<string>();
3503
+ const result: any[] = [];
3504
+ for (const col of [...a, ...b]) {
3505
+ if (!seen.has(col.referenceName)) {
3506
+ seen.add(col.referenceName);
3507
+ result.push(col);
3508
+ }
3509
+ }
3510
+ return result;
3511
+ };
3512
+
3513
+ // Sample one work item to read its System.WorkItemType.
3514
+ // In OneHop query relations: !relation.source means root (target = source WI);
3515
+ // relation.source present means link (target = target WI).
3516
+ const sampleWitType = async (relations: any[], isSource: boolean): Promise<string | undefined> => {
3517
+ let url: string | undefined;
3518
+ if (isSource) {
3519
+ const rootRel = relations.find((r) => !r.source && r.target?.url);
3520
+ url = rootRel?.target?.url;
3521
+ } else {
3522
+ const linkRel = relations.find((r) => r.source && r.target?.url);
3523
+ url = linkRel?.target?.url;
3524
+ }
3525
+ if (!url) return undefined;
3526
+ try {
3527
+ const wi: any = await TFSServices.getItemContent(url, this.token);
3528
+ return wi?.fields?.['System.WorkItemType'];
3529
+ } catch {
3530
+ return undefined;
3531
+ }
3532
+ };
3533
+
3534
+ const fetchTypeFieldSet = async (typeName: string): Promise<Set<string>> => {
3535
+ const query = new URLSearchParams({ 'api-version': '5.1' }).toString();
3536
+ const url = `${this.orgUrl}${encodeURIComponent(project)}/_apis/wit/workitemtypes/${encodeURIComponent(typeName)}/fields?${query}`;
3537
+ const res = await TFSServices.getItemContent(url, this.token);
3538
+ const fields = res?.value ?? [];
3539
+ return new Set<string>((fields as any[]).map((f: any) => f.referenceName));
3540
+ };
3541
+
3542
+ try {
3543
+ // Step 1: fetch declared columns from provided query hrefs
3544
+ let reqTestCols: any[] = [];
3545
+ let testReqCols: any[] = [];
3546
+ let reqTestRelations: any[] = [];
3547
+ let testReqRelations: any[] = [];
3548
+
3549
+ if (reqTestWiqlHref) {
3550
+ const qr: any = await TFSServices.getItemContent(reqTestWiqlHref, this.token);
3551
+ reqTestCols = normalizeCols(qr?.columns || []);
3552
+ reqTestRelations = qr?.workItemRelations || [];
3553
+ }
3554
+ if (testReqWiqlHref) {
3555
+ const qr: any = await TFSServices.getItemContent(testReqWiqlHref, this.token);
3556
+ testReqCols = normalizeCols(qr?.columns || []);
3557
+ testReqRelations = qr?.workItemRelations || [];
3558
+ }
3559
+
3560
+ const declaredCols = mergeCols(reqTestCols, testReqCols);
3561
+ if (declaredCols.length === 0) {
3562
+ return { Requirement: [], 'Test Case': [] };
3563
+ }
3564
+
3565
+ // Step 2: sample WIT type names
3566
+ // reqTest: source = Requirement, target = Test Case
3567
+ // testReq: source = Test Case, target = Requirement
3568
+ let reqTypeName: string | undefined;
3569
+ let tcTypeName: string | undefined;
3570
+
3571
+ if (reqTestRelations.length > 0) {
3572
+ [reqTypeName, tcTypeName] = await Promise.all([
3573
+ sampleWitType(reqTestRelations, true),
3574
+ sampleWitType(reqTestRelations, false),
3575
+ ]);
3576
+ }
3577
+ if ((!reqTypeName || !tcTypeName) && testReqRelations.length > 0) {
3578
+ const [tSrc, tTgt] = await Promise.all([
3579
+ sampleWitType(testReqRelations, true),
3580
+ sampleWitType(testReqRelations, false),
3581
+ ]);
3582
+ if (!tcTypeName) tcTypeName = tSrc;
3583
+ if (!reqTypeName) reqTypeName = tTgt;
3584
+ }
3585
+
3586
+ if (!reqTypeName || !tcTypeName) {
3587
+ logger.warn('GetTraceColumnsByType: could not determine WIT type names — returning merged columns');
3588
+ return { Requirement: declaredCols, 'Test Case': declaredCols };
3589
+ }
3590
+
3591
+ // Step 3: fetch WIT field schemas and intersect with declared columns
3592
+ const [reqFieldSet, tcFieldSet] = await Promise.all([
3593
+ fetchTypeFieldSet(reqTypeName),
3594
+ fetchTypeFieldSet(tcTypeName),
3595
+ ]);
3596
+
3597
+ return {
3598
+ Requirement: declaredCols.filter((c) => reqFieldSet.has(c.referenceName)),
3599
+ 'Test Case': declaredCols.filter((c) => tcFieldSet.has(c.referenceName)),
3600
+ };
3601
+ } catch (err: any) {
3602
+ logger.error(`GetTraceColumnsByType failed: ${err.message}`);
3603
+ return { Requirement: [], 'Test Case': [] };
3604
+ }
3605
+ }
3481
3606
  }
@@ -0,0 +1,192 @@
1
+ import { TFSServices } from '../../helpers/tfs';
2
+ import TicketsDataProvider from '../../modules/TicketsDataProvider';
3
+ import logger from '../../utils/logger';
4
+
5
+ jest.mock('../../helpers/tfs');
6
+ jest.mock('../../utils/logger');
7
+
8
+ const mockGetItemContent = TFSServices.getItemContent as jest.Mock;
9
+
10
+ // Minimal OneHop query result shape
11
+ const makeQueryResult = (columns: any[], relations: any[]) => ({
12
+ columns,
13
+ workItemRelations: relations,
14
+ queryType: 'oneHop',
15
+ });
16
+
17
+ // Root relation (source = the root WI, no .source field)
18
+ const rootRelation = (targetId: number, targetUrl: string) => ({
19
+ source: null,
20
+ target: { id: targetId, url: targetUrl },
21
+ });
22
+
23
+ // Link relation (source WI → target WI)
24
+ const linkRelation = (sourceId: number, targetId: number, targetUrl: string) => ({
25
+ source: { id: sourceId },
26
+ target: { id: targetId, url: targetUrl },
27
+ });
28
+
29
+ const REQ_COLS = [
30
+ { referenceName: 'System.Id', name: 'ID' },
31
+ { referenceName: 'System.Title', name: 'Title' },
32
+ { referenceName: 'Custom.CustomerRequirementId', name: 'CustomerRequirementId' },
33
+ { referenceName: 'Microsoft.VSTS.Common.Priority', name: 'Priority' },
34
+ ];
35
+
36
+ const TC_COLS = [
37
+ { referenceName: 'System.Id', name: 'ID' },
38
+ { referenceName: 'System.Title', name: 'Title' },
39
+ { referenceName: 'Microsoft.VSTS.TCM.AutomationStatus', name: 'Automation Status' },
40
+ { referenceName: 'Microsoft.VSTS.Common.Priority', name: 'Priority' },
41
+ ];
42
+
43
+ const REQ_FIELDS_SCHEMA = {
44
+ value: [
45
+ { referenceName: 'System.Id' },
46
+ { referenceName: 'System.Title' },
47
+ { referenceName: 'Custom.CustomerRequirementId' },
48
+ { referenceName: 'Microsoft.VSTS.Common.Priority' },
49
+ ],
50
+ };
51
+
52
+ const TC_FIELDS_SCHEMA = {
53
+ value: [
54
+ { referenceName: 'System.Id' },
55
+ { referenceName: 'System.Title' },
56
+ { referenceName: 'Microsoft.VSTS.TCM.AutomationStatus' },
57
+ { referenceName: 'Microsoft.VSTS.Common.Priority' },
58
+ // Note: Custom.CustomerRequirementId is NOT in TC schema
59
+ ],
60
+ };
61
+
62
+ describe('TicketsDataProvider.GetTraceColumnsByType', () => {
63
+ let provider: TicketsDataProvider;
64
+
65
+ beforeEach(() => {
66
+ jest.clearAllMocks();
67
+ provider = new TicketsDataProvider('https://dev.azure.com/org/', 'mock-token');
68
+ });
69
+
70
+ it('splits columns correctly: CustomerRequirementId in Requirement only, not Test Case', async () => {
71
+ const reqRelations = [
72
+ rootRelation(1, 'https://ado/wi/1'),
73
+ linkRelation(1, 101, 'https://ado/wi/101'),
74
+ ];
75
+ const reqQueryResult = makeQueryResult(REQ_COLS, reqRelations);
76
+
77
+ mockGetItemContent
78
+ .mockResolvedValueOnce(reqQueryResult) // reqTest wiql fetch
79
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } }) // source WI sample
80
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } }) // target WI sample
81
+ .mockResolvedValueOnce(REQ_FIELDS_SCHEMA) // Requirement type fields
82
+ .mockResolvedValueOnce(TC_FIELDS_SCHEMA); // Test Case type fields
83
+
84
+ const result = await provider.GetTraceColumnsByType(
85
+ 'https://ado/wiql/reqtest',
86
+ undefined,
87
+ 'my-project',
88
+ );
89
+
90
+ const reqRefs = result.Requirement.map((c: any) => c.referenceName);
91
+ const tcRefs = result['Test Case'].map((c: any) => c.referenceName);
92
+
93
+ expect(reqRefs).toContain('Custom.CustomerRequirementId');
94
+ expect(tcRefs).not.toContain('Custom.CustomerRequirementId');
95
+ expect(reqRefs).toContain('Microsoft.VSTS.Common.Priority');
96
+ expect(tcRefs).toContain('Microsoft.VSTS.Common.Priority');
97
+ });
98
+
99
+ it('merges columns from both queries (reqTest + testReq), deduped', async () => {
100
+ const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
101
+ const testRelations = [rootRelation(101, 'https://ado/wi/101'), linkRelation(101, 1, 'https://ado/wi/1')];
102
+
103
+ const extraCol = { referenceName: 'System.AreaPath', name: 'Area Path' };
104
+
105
+ mockGetItemContent
106
+ .mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations)) // reqTest wiql
107
+ .mockResolvedValueOnce(makeQueryResult([...TC_COLS, extraCol], testRelations)) // testReq wiql
108
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } }) // source sample reqTest
109
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } }) // target sample reqTest
110
+ .mockResolvedValueOnce(REQ_FIELDS_SCHEMA)
111
+ .mockResolvedValueOnce({ value: [...TC_FIELDS_SCHEMA.value, { referenceName: 'System.AreaPath' }] });
112
+
113
+ const result = await provider.GetTraceColumnsByType(
114
+ 'https://ado/wiql/reqtest',
115
+ 'https://ado/wiql/testreq',
116
+ 'my-project',
117
+ );
118
+
119
+ const tcRefs = result['Test Case'].map((c: any) => c.referenceName);
120
+ expect(tcRefs).toContain('System.AreaPath'); // came only from testReq columns
121
+ });
122
+
123
+ it('raw display names passed through without rename (no CustomerRequirementId → Customer ID)', async () => {
124
+ const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
125
+
126
+ mockGetItemContent
127
+ .mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations))
128
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } })
129
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } })
130
+ .mockResolvedValueOnce(REQ_FIELDS_SCHEMA)
131
+ .mockResolvedValueOnce(TC_FIELDS_SCHEMA);
132
+
133
+ const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
134
+
135
+ const custCol = result.Requirement.find((c: any) => c.referenceName === 'Custom.CustomerRequirementId');
136
+ expect(custCol?.name).toBe('CustomerRequirementId'); // raw ADO name, NOT 'Customer ID'
137
+ });
138
+
139
+ it('gracefully degrades when WIT type cannot be sampled (no relations) — returns merged columns both sides', async () => {
140
+ const emptyRelations: any[] = [];
141
+
142
+ mockGetItemContent.mockResolvedValueOnce(makeQueryResult(REQ_COLS, emptyRelations));
143
+
144
+ const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
145
+
146
+ const reqRefs = result.Requirement.map((c: any) => c.referenceName);
147
+ const tcRefs = result['Test Case'].map((c: any) => c.referenceName);
148
+ // Both sides get the merged declared columns as fallback
149
+ expect(reqRefs).toContain('Custom.CustomerRequirementId');
150
+ expect(tcRefs).toContain('Custom.CustomerRequirementId');
151
+ });
152
+
153
+ it('returns empty arrays when wiql fetch fails', async () => {
154
+ mockGetItemContent.mockRejectedValueOnce(new Error('Network error'));
155
+
156
+ const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
157
+
158
+ expect(result).toEqual({ Requirement: [], 'Test Case': [] });
159
+ expect((logger.error as jest.Mock)).toHaveBeenCalled();
160
+ });
161
+
162
+ it('returns empty arrays when schema fetch fails', async () => {
163
+ const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
164
+
165
+ mockGetItemContent
166
+ .mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations))
167
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } })
168
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } })
169
+ .mockRejectedValueOnce(new Error('ADO 404')); // schema fetch fails
170
+
171
+ const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
172
+
173
+ expect(result).toEqual({ Requirement: [], 'Test Case': [] });
174
+ });
175
+
176
+ it('handles undefined schema value gracefully (no crash on null response)', async () => {
177
+ const reqRelations = [rootRelation(1, 'https://ado/wi/1'), linkRelation(1, 101, 'https://ado/wi/101')];
178
+
179
+ mockGetItemContent
180
+ .mockResolvedValueOnce(makeQueryResult(REQ_COLS, reqRelations))
181
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Requirement' } })
182
+ .mockResolvedValueOnce({ fields: { 'System.WorkItemType': 'Test Case' } })
183
+ .mockResolvedValueOnce(undefined) // schema returns undefined (null guard fix)
184
+ .mockResolvedValueOnce(undefined);
185
+
186
+ const result = await provider.GetTraceColumnsByType('https://ado/wiql/reqtest', undefined, 'proj');
187
+
188
+ // Both field sets are empty → both sides empty after intersection
189
+ expect(result.Requirement).toEqual([]);
190
+ expect(result['Test Case']).toEqual([]);
191
+ });
192
+ });