@elisra-devops/docgen-data-provider 1.99.0 → 1.100.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elisra-devops/docgen-data-provider",
3
- "version": "1.99.0",
3
+ "version": "1.100.0",
4
4
  "description": "A document generator data provider, aimed to retrive data from azure devops",
5
5
  "repository": {
6
6
  "type": "git",
@@ -4382,12 +4382,35 @@ export default class ResultDataProvider {
4382
4382
  continue;
4383
4383
  }
4384
4384
  }
4385
- const iterationKey =
4386
- !point.lastRunId || !point.lastResultId
4387
- ? `${testCase.workItem.id}`
4388
- : `${point.lastRunId}-${point.lastResultId}-${testCase.workItem.id}`;
4385
+ const iterationKey = this.buildIterationLookupKey({
4386
+ testCaseId: testCase.workItem.id,
4387
+ lastRunId: point?.lastRunId,
4388
+ lastResultId: point?.lastResultId,
4389
+ testPointId: point?.testPointId,
4390
+ testCaseRevision:
4391
+ this.resolveSuiteTestCaseRevision(testCase) ||
4392
+ this.resolveSuiteTestCaseRevision(point?.suiteTestCase),
4393
+ });
4394
+ const fallbackRevision = Number(
4395
+ this.resolveSuiteTestCaseRevision(testCase) ||
4396
+ this.resolveSuiteTestCaseRevision(point?.suiteTestCase) ||
4397
+ 0
4398
+ );
4399
+ const fallbackRevisionKey =
4400
+ Number.isFinite(fallbackRevision) && fallbackRevision > 0
4401
+ ? this.buildIterationLookupKey({
4402
+ testCaseId: testCase.workItem.id,
4403
+ testCaseRevision: fallbackRevision,
4404
+ })
4405
+ : '';
4406
+ const fallbackCaseOnlyKey = `${testCase.workItem.id}`;
4389
4407
  const fetchedTestCase =
4390
- iterationsMap[iterationKey] || (includeNotRunTestCases ? testCase : undefined);
4408
+ iterationsMap[iterationKey] ||
4409
+ (fallbackRevisionKey && fallbackRevisionKey !== iterationKey
4410
+ ? iterationsMap[fallbackRevisionKey]
4411
+ : undefined) ||
4412
+ (iterationKey !== fallbackCaseOnlyKey ? iterationsMap[fallbackCaseOnlyKey] : undefined) ||
4413
+ (includeNotRunTestCases ? testCase : undefined);
4391
4414
  // First check if fetchedTestCase exists
4392
4415
  if (!fetchedTestCase) continue;
4393
4416
 
@@ -4530,26 +4553,80 @@ export default class ResultDataProvider {
4530
4553
  String(iterationItem?.lastResultId).trim() !== '';
4531
4554
 
4532
4555
  if (hasRunIdentifiers) {
4533
- const key = `${iterationItem.lastRunId}-${iterationItem.lastResultId}-${iterationItem.testCaseId}`;
4556
+ const key = this.buildIterationLookupKey({
4557
+ testCaseId: iterationItem?.testCaseId,
4558
+ lastRunId: iterationItem?.lastRunId,
4559
+ lastResultId: iterationItem?.lastResultId,
4560
+ testPointId: iterationItem?.testPointId,
4561
+ testCaseRevision: iterationItem?.testCaseRevision,
4562
+ });
4534
4563
  map[key] = iterationItem;
4535
4564
  } else if (includeNotRunTestCases) {
4536
- const key = `${iterationItem.testCaseId}`;
4565
+ const key = this.buildIterationLookupKey({
4566
+ testCaseId: iterationItem?.testCaseId,
4567
+ lastRunId: iterationItem?.lastRunId,
4568
+ lastResultId: iterationItem?.lastResultId,
4569
+ testPointId: iterationItem?.testPointId,
4570
+ testCaseRevision: iterationItem?.testCaseRevision,
4571
+ });
4537
4572
  map[key] = iterationItem;
4538
4573
  if (isTestReporter && iterationItem?.iteration) {
4539
4574
  logger.debug(
4540
4575
  `[RunlessResolver] createIterationsMap: mapped runless testCaseId=${String(
4541
4576
  iterationItem?.testCaseId
4542
- )} to case-only key`
4577
+ )} to key=${key}`
4543
4578
  );
4544
4579
  }
4545
4580
  } else if (iterationItem?.iteration && !isTestReporter) {
4546
- const key = `${iterationItem.lastRunId}-${iterationItem.lastResultId}-${iterationItem.testCaseId}`;
4581
+ const key = this.buildIterationLookupKey({
4582
+ testCaseId: iterationItem?.testCaseId,
4583
+ lastRunId: iterationItem?.lastRunId,
4584
+ lastResultId: iterationItem?.lastResultId,
4585
+ testPointId: iterationItem?.testPointId,
4586
+ testCaseRevision: iterationItem?.testCaseRevision,
4587
+ });
4547
4588
  map[key] = iterationItem;
4548
4589
  }
4549
4590
  return map;
4550
4591
  }, {} as Record<string, any>);
4551
4592
  }
4552
4593
 
4594
+ /**
4595
+ * Builds a stable lookup key for joining points to fetched iteration payloads.
4596
+ * Run-backed items are keyed by run/result/testCase, while runless items prefer
4597
+ * testPointId to avoid collisions across suites that share the same testCaseId.
4598
+ */
4599
+ private buildIterationLookupKey(input: {
4600
+ testCaseId: any;
4601
+ lastRunId?: any;
4602
+ lastResultId?: any;
4603
+ testPointId?: any;
4604
+ testCaseRevision?: any;
4605
+ }): string {
4606
+ const testCaseId = Number(input?.testCaseId || 0);
4607
+ const hasRunIdentifiers =
4608
+ input?.lastRunId !== undefined &&
4609
+ input?.lastRunId !== null &&
4610
+ String(input?.lastRunId).trim() !== '' &&
4611
+ input?.lastResultId !== undefined &&
4612
+ input?.lastResultId !== null &&
4613
+ String(input?.lastResultId).trim() !== '';
4614
+ if (hasRunIdentifiers) {
4615
+ return `${input?.lastRunId}-${input?.lastResultId}-${testCaseId}`;
4616
+ }
4617
+
4618
+ const testPointId = Number(input?.testPointId || 0);
4619
+ if (Number.isFinite(testPointId) && testPointId > 0) {
4620
+ return `point-${testPointId}-${testCaseId}`;
4621
+ }
4622
+
4623
+ const testCaseRevision = Number(input?.testCaseRevision || 0);
4624
+ if (Number.isFinite(testCaseRevision) && testCaseRevision > 0) {
4625
+ return `rev-${testCaseId}-${testCaseRevision}`;
4626
+ }
4627
+ return `${testCaseId}`;
4628
+ }
4629
+
4553
4630
  /**
4554
4631
  * Fetches test data for all suites, including test points and test cases.
4555
4632
  */
@@ -5478,6 +5555,7 @@ export default class ResultDataProvider {
5478
5555
  const resultDataResponse: any = {
5479
5556
  testCaseName: `${resultData?.testCase?.name ?? ''} - ${resultData?.testCase?.id ?? ''}`,
5480
5557
  testCaseId: resultData?.testCase?.id,
5558
+ testPointId: point?.testPointId,
5481
5559
  testSuiteName: `${resultData?.testSuite?.name ?? ''}`,
5482
5560
  testSuiteId,
5483
5561
  lastRunId,
@@ -5129,7 +5129,7 @@ describe('ResultDataProvider', () => {
5129
5129
  expect(result['1']).toBeDefined();
5130
5130
  });
5131
5131
 
5132
- it('should map runless test reporter item with iteration by testCaseId key', () => {
5132
+ it('should map runless test reporter item with iteration by testCaseId key when point id is missing', () => {
5133
5133
  const iterations = [
5134
5134
  { testCaseId: 217897, lastRunId: undefined, lastResultId: undefined, iteration: { actionResults: [] } },
5135
5135
  ];
@@ -5139,6 +5139,40 @@ describe('ResultDataProvider', () => {
5139
5139
  expect(result['217897']).toBeDefined();
5140
5140
  expect(result['undefined-undefined-217897']).toBeUndefined();
5141
5141
  });
5142
+
5143
+ it('should map runless test reporter item with iteration by point-aware key when point id exists', () => {
5144
+ const iterations = [
5145
+ {
5146
+ testCaseId: 217916,
5147
+ testPointId: 1001,
5148
+ lastRunId: undefined,
5149
+ lastResultId: undefined,
5150
+ iteration: { actionResults: [] },
5151
+ },
5152
+ ];
5153
+
5154
+ const result = (resultDataProvider as any).createIterationsMap(iterations, true, true);
5155
+
5156
+ expect(result['point-1001-217916']).toBeDefined();
5157
+ expect(result['217916']).toBeUndefined();
5158
+ });
5159
+
5160
+ it('should map runless test reporter item with iteration by revision key when point id is missing', () => {
5161
+ const iterations = [
5162
+ {
5163
+ testCaseId: 217916,
5164
+ testCaseRevision: 18,
5165
+ lastRunId: undefined,
5166
+ lastResultId: undefined,
5167
+ iteration: { actionResults: [] },
5168
+ },
5169
+ ];
5170
+
5171
+ const result = (resultDataProvider as any).createIterationsMap(iterations, true, true);
5172
+
5173
+ expect(result['rev-217916-18']).toBeDefined();
5174
+ expect(result['217916']).toBeUndefined();
5175
+ });
5142
5176
  });
5143
5177
 
5144
5178
  describe('alignStepsWithIterationsBase', () => {
@@ -6085,13 +6119,14 @@ describe('ResultDataProvider', () => {
6085
6119
  const testData = [
6086
6120
  {
6087
6121
  testGroupName: 'G',
6088
- testPointsItems: [{ testCaseId: 217916, testCaseName: 'TC 217916', testCaseUrl: 'u' }],
6122
+ testPointsItems: [{ testCaseId: 217916, testPointId: 1001, testCaseName: 'TC 217916', testCaseUrl: 'u' }],
6089
6123
  testCasesItems: [],
6090
6124
  },
6091
6125
  ];
6092
6126
  const iterations = [
6093
6127
  {
6094
6128
  testCaseId: 217916,
6129
+ testPointId: 1001,
6095
6130
  lastRunId: undefined,
6096
6131
  lastResultId: undefined,
6097
6132
  iteration: {
@@ -6130,6 +6165,176 @@ describe('ResultDataProvider', () => {
6130
6165
  expect(res).toHaveLength(1);
6131
6166
  expect(res[0]).toEqual(expect.objectContaining({ stepNo: '1', stepStatus: 'Not Run' }));
6132
6167
  });
6168
+
6169
+ it('should match runless iterations by testPointId when the same testCase appears in multiple suites', () => {
6170
+ const testData = [
6171
+ {
6172
+ testGroupName: 'Suite A',
6173
+ testPointsItems: [{ testCaseId: 217916, testPointId: 2001, testCaseName: 'TC', testCaseUrl: 'u1' }],
6174
+ testCasesItems: [],
6175
+ },
6176
+ {
6177
+ testGroupName: 'Suite B',
6178
+ testPointsItems: [{ testCaseId: 217916, testPointId: 2002, testCaseName: 'TC', testCaseUrl: 'u2' }],
6179
+ testCasesItems: [],
6180
+ },
6181
+ ];
6182
+
6183
+ const iterations = [
6184
+ {
6185
+ testCaseId: 217916,
6186
+ testPointId: 2001,
6187
+ lastRunId: undefined,
6188
+ lastResultId: undefined,
6189
+ iteration: {
6190
+ actionResults: [
6191
+ {
6192
+ stepIdentifier: '16',
6193
+ stepPosition: '1',
6194
+ action: 'A',
6195
+ expected: 'E',
6196
+ outcome: 'Unspecified',
6197
+ isSharedStepTitle: false,
6198
+ errorMessage: '',
6199
+ },
6200
+ ],
6201
+ },
6202
+ testCaseResult: 'Not Run',
6203
+ comment: '',
6204
+ runBy: { displayName: 'u' },
6205
+ failureType: '',
6206
+ executionDate: '',
6207
+ configurationName: '',
6208
+ relatedRequirements: [],
6209
+ relatedBugs: [],
6210
+ relatedCRs: [],
6211
+ customFields: {},
6212
+ },
6213
+ {
6214
+ testCaseId: 217916,
6215
+ testPointId: 2002,
6216
+ lastRunId: undefined,
6217
+ lastResultId: undefined,
6218
+ iteration: { actionResults: [] },
6219
+ testCaseResult: 'Not Run',
6220
+ comment: '',
6221
+ runBy: { displayName: 'u' },
6222
+ failureType: '',
6223
+ executionDate: '',
6224
+ configurationName: '',
6225
+ relatedRequirements: [],
6226
+ relatedBugs: [],
6227
+ relatedCRs: [],
6228
+ customFields: {},
6229
+ },
6230
+ ];
6231
+
6232
+ const res = (resultDataProvider as any).alignStepsWithIterationsTestReporter(
6233
+ testData,
6234
+ iterations,
6235
+ ['includeSteps@stepsRunProperties', 'stepRunStatus@stepsRunProperties'],
6236
+ true
6237
+ );
6238
+
6239
+ expect(res).toHaveLength(2);
6240
+ const suiteA = res.find((row: any) => row?.suiteName === 'Suite A');
6241
+ const suiteB = res.find((row: any) => row?.suiteName === 'Suite B');
6242
+ expect(suiteA).toEqual(expect.objectContaining({ stepNo: '1', stepStatus: 'Not Run' }));
6243
+ expect(suiteB).toBeDefined();
6244
+ expect(Object.prototype.hasOwnProperty.call(suiteB, 'stepNo')).toBe(false);
6245
+ });
6246
+
6247
+ it('should match runless iterations by revision when point id is missing and same testCase appears in multiple suites', () => {
6248
+ const testData = [
6249
+ {
6250
+ testGroupName: 'Suite A',
6251
+ testPointsItems: [{ testCaseId: 217916, testCaseName: 'TC', testCaseUrl: 'u1' }],
6252
+ testCasesItems: [
6253
+ {
6254
+ workItem: {
6255
+ id: 217916,
6256
+ workItemFields: [{ key: 'System.Rev', value: 18 }],
6257
+ },
6258
+ },
6259
+ ],
6260
+ },
6261
+ {
6262
+ testGroupName: 'Suite B',
6263
+ testPointsItems: [{ testCaseId: 217916, testCaseName: 'TC', testCaseUrl: 'u2' }],
6264
+ testCasesItems: [
6265
+ {
6266
+ workItem: {
6267
+ id: 217916,
6268
+ workItemFields: [{ key: 'System.Rev', value: 23 }],
6269
+ },
6270
+ },
6271
+ ],
6272
+ },
6273
+ ];
6274
+
6275
+ const iterations = [
6276
+ {
6277
+ testCaseId: 217916,
6278
+ testCaseRevision: 18,
6279
+ lastRunId: undefined,
6280
+ lastResultId: undefined,
6281
+ iteration: {
6282
+ actionResults: [
6283
+ {
6284
+ stepIdentifier: '16',
6285
+ stepPosition: '1',
6286
+ action: 'A',
6287
+ expected: 'E',
6288
+ outcome: 'Unspecified',
6289
+ isSharedStepTitle: false,
6290
+ errorMessage: '',
6291
+ },
6292
+ ],
6293
+ },
6294
+ testCaseResult: 'Not Run',
6295
+ comment: '',
6296
+ runBy: { displayName: 'u' },
6297
+ failureType: '',
6298
+ executionDate: '',
6299
+ configurationName: '',
6300
+ relatedRequirements: [],
6301
+ relatedBugs: [],
6302
+ relatedCRs: [],
6303
+ customFields: {},
6304
+ },
6305
+ {
6306
+ testCaseId: 217916,
6307
+ testCaseRevision: 23,
6308
+ lastRunId: undefined,
6309
+ lastResultId: undefined,
6310
+ iteration: { actionResults: [] },
6311
+ testCaseResult: 'Not Run',
6312
+ comment: '',
6313
+ runBy: { displayName: 'u' },
6314
+ failureType: '',
6315
+ executionDate: '',
6316
+ configurationName: '',
6317
+ relatedRequirements: [],
6318
+ relatedBugs: [],
6319
+ relatedCRs: [],
6320
+ customFields: {},
6321
+ },
6322
+ ];
6323
+
6324
+ const res = (resultDataProvider as any).alignStepsWithIterationsTestReporter(
6325
+ testData,
6326
+ iterations,
6327
+ ['includeSteps@stepsRunProperties', 'stepRunStatus@stepsRunProperties'],
6328
+ true
6329
+ );
6330
+
6331
+ expect(res).toHaveLength(2);
6332
+ const suiteA = res.find((row: any) => row?.suiteName === 'Suite A');
6333
+ const suiteB = res.find((row: any) => row?.suiteName === 'Suite B');
6334
+ expect(suiteA).toEqual(expect.objectContaining({ stepNo: '1', stepStatus: 'Not Run' }));
6335
+ expect(suiteB).toBeDefined();
6336
+ expect(Object.prototype.hasOwnProperty.call(suiteB, 'stepNo')).toBe(false);
6337
+ });
6133
6338
  });
6134
6339
 
6135
6340
  describe('fetchOpenPcrData', () => {