@elisra-devops/docgen-data-provider 1.95.0 → 1.97.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/bin/modules/ResultDataProvider.d.ts +5 -0
- package/bin/modules/ResultDataProvider.js +63 -9
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +56 -0
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/ResultDataProvider.ts +81 -8
- package/src/tests/modules/ResultDataProvider.test.ts +75 -0
package/package.json
CHANGED
|
@@ -3362,6 +3362,11 @@ export default class ResultDataProvider {
|
|
|
3362
3362
|
const parsedAsOf = new Date(String(asOfTimestamp || '').trim());
|
|
3363
3363
|
if (!Number.isFinite(id) || id <= 0 || Number.isNaN(parsedAsOf.getTime())) return null;
|
|
3364
3364
|
|
|
3365
|
+
logger.debug(
|
|
3366
|
+
`[RunlessResolver] Fetching work item ${id} by asOf (raw="${String(
|
|
3367
|
+
asOfTimestamp || ''
|
|
3368
|
+
)}", normalized="${parsedAsOf.toISOString()}", expandAll=${String(expandAll)})`
|
|
3369
|
+
);
|
|
3365
3370
|
const query: string[] = [`asOf=${encodeURIComponent(parsedAsOf.toISOString())}`];
|
|
3366
3371
|
if (expandAll) {
|
|
3367
3372
|
query.push(`$expand=all`);
|
|
@@ -3395,6 +3400,29 @@ export default class ResultDataProvider {
|
|
|
3395
3400
|
}
|
|
3396
3401
|
}
|
|
3397
3402
|
|
|
3403
|
+
/**
|
|
3404
|
+
* Returns true when the snapshot includes a non-empty test steps XML payload.
|
|
3405
|
+
*/
|
|
3406
|
+
private hasStepsInWorkItemSnapshot(workItemData: any): boolean {
|
|
3407
|
+
const stepsXml = this.extractStepsXmlFromFieldsMap(workItemData?.fields || {});
|
|
3408
|
+
return String(stepsXml || '').trim() !== '';
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
private logRunlessSnapshotDecision(testCaseId: number, source: string, snapshot: any | null): void {
|
|
3412
|
+
if (!snapshot) {
|
|
3413
|
+
logger.debug(`[RunlessResolver] TC ${testCaseId}: source=${source}, snapshot=none`);
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
const stepsXml = this.extractStepsXmlFromFieldsMap(snapshot?.fields || {});
|
|
3418
|
+
const stepsLength = String(stepsXml || '').trim().length;
|
|
3419
|
+
logger.debug(
|
|
3420
|
+
`[RunlessResolver] TC ${testCaseId}: source=${source}, rev=${String(
|
|
3421
|
+
snapshot?.rev ?? ''
|
|
3422
|
+
)}, hasSteps=${String(stepsLength > 0)}, stepsLength=${String(stepsLength)}`
|
|
3423
|
+
);
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3398
3426
|
/**
|
|
3399
3427
|
* Resolves runless test case data using ordered fallbacks:
|
|
3400
3428
|
* 1) point-based `asOf` snapshot, 2) explicit revision, 3) suite payload snapshot, 4) latest WI.
|
|
@@ -3407,6 +3435,8 @@ export default class ResultDataProvider {
|
|
|
3407
3435
|
fallbackSnapshot: any,
|
|
3408
3436
|
expandAll: boolean
|
|
3409
3437
|
): Promise<any | null> {
|
|
3438
|
+
let bestSnapshotWithoutSteps: any | null = null;
|
|
3439
|
+
|
|
3410
3440
|
if (pointAsOfTimestamp) {
|
|
3411
3441
|
const asOfSnapshot = await this.fetchWorkItemByAsOf(
|
|
3412
3442
|
projectName,
|
|
@@ -3414,7 +3444,13 @@ export default class ResultDataProvider {
|
|
|
3414
3444
|
pointAsOfTimestamp,
|
|
3415
3445
|
expandAll
|
|
3416
3446
|
);
|
|
3417
|
-
|
|
3447
|
+
this.logRunlessSnapshotDecision(testCaseId, 'asOf', asOfSnapshot);
|
|
3448
|
+
if (asOfSnapshot) {
|
|
3449
|
+
if (this.hasStepsInWorkItemSnapshot(asOfSnapshot)) return asOfSnapshot;
|
|
3450
|
+
bestSnapshotWithoutSteps = asOfSnapshot;
|
|
3451
|
+
}
|
|
3452
|
+
} else {
|
|
3453
|
+
logger.debug(`[RunlessResolver] TC ${testCaseId}: asOf timestamp is empty, skipping asOf fetch`);
|
|
3418
3454
|
}
|
|
3419
3455
|
|
|
3420
3456
|
const revisionSnapshot = await this.fetchWorkItemByRevision(
|
|
@@ -3423,11 +3459,26 @@ export default class ResultDataProvider {
|
|
|
3423
3459
|
suiteTestCaseRevision,
|
|
3424
3460
|
expandAll
|
|
3425
3461
|
);
|
|
3426
|
-
|
|
3462
|
+
this.logRunlessSnapshotDecision(testCaseId, `revision:${String(suiteTestCaseRevision)}`, revisionSnapshot);
|
|
3463
|
+
if (revisionSnapshot) {
|
|
3464
|
+
if (this.hasStepsInWorkItemSnapshot(revisionSnapshot)) return revisionSnapshot;
|
|
3465
|
+
bestSnapshotWithoutSteps = bestSnapshotWithoutSteps || revisionSnapshot;
|
|
3466
|
+
}
|
|
3427
3467
|
|
|
3428
|
-
|
|
3468
|
+
this.logRunlessSnapshotDecision(testCaseId, 'suiteSnapshot', fallbackSnapshot);
|
|
3469
|
+
if (fallbackSnapshot) {
|
|
3470
|
+
if (this.hasStepsInWorkItemSnapshot(fallbackSnapshot)) return fallbackSnapshot;
|
|
3471
|
+
bestSnapshotWithoutSteps = bestSnapshotWithoutSteps || fallbackSnapshot;
|
|
3472
|
+
}
|
|
3429
3473
|
|
|
3430
|
-
|
|
3474
|
+
const latestSnapshot = await this.fetchWorkItemLatest(projectName, testCaseId, expandAll);
|
|
3475
|
+
this.logRunlessSnapshotDecision(testCaseId, 'latest', latestSnapshot);
|
|
3476
|
+
if (latestSnapshot) {
|
|
3477
|
+
if (this.hasStepsInWorkItemSnapshot(latestSnapshot)) return latestSnapshot;
|
|
3478
|
+
bestSnapshotWithoutSteps = bestSnapshotWithoutSteps || latestSnapshot;
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
return bestSnapshotWithoutSteps;
|
|
3431
3482
|
}
|
|
3432
3483
|
|
|
3433
3484
|
/**
|
|
@@ -3473,6 +3524,13 @@ export default class ResultDataProvider {
|
|
|
3473
3524
|
const pointAsOfTimestamp = useRunlessAsOf
|
|
3474
3525
|
? String(point?.pointAsOfTimestamp || '').trim()
|
|
3475
3526
|
: '';
|
|
3527
|
+
logger.debug(
|
|
3528
|
+
`[RunlessResolver] Start TC ${String(testCaseId)}: useRunlessAsOf=${String(
|
|
3529
|
+
useRunlessAsOf
|
|
3530
|
+
)}, pointAsOfTimestamp="${pointAsOfTimestamp}", suiteRevision=${String(
|
|
3531
|
+
suiteTestCaseRevision
|
|
3532
|
+
)}, pointOutcome="${String(point?.outcome || '')}"`
|
|
3533
|
+
);
|
|
3476
3534
|
const fallbackSnapshot = this.buildWorkItemSnapshotFromSuiteTestCase(
|
|
3477
3535
|
suiteTestCaseItem,
|
|
3478
3536
|
testCaseId,
|
|
@@ -4441,15 +4499,30 @@ export default class ResultDataProvider {
|
|
|
4441
4499
|
includeNotRunTestCases: boolean
|
|
4442
4500
|
): Record<string, any> {
|
|
4443
4501
|
return iterations.reduce((map, iterationItem) => {
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
iterationItem
|
|
4447
|
-
|
|
4502
|
+
const hasRunIdentifiers =
|
|
4503
|
+
iterationItem?.lastRunId !== undefined &&
|
|
4504
|
+
iterationItem?.lastRunId !== null &&
|
|
4505
|
+
String(iterationItem?.lastRunId).trim() !== '' &&
|
|
4506
|
+
iterationItem?.lastResultId !== undefined &&
|
|
4507
|
+
iterationItem?.lastResultId !== null &&
|
|
4508
|
+
String(iterationItem?.lastResultId).trim() !== '';
|
|
4509
|
+
|
|
4510
|
+
if (hasRunIdentifiers) {
|
|
4448
4511
|
const key = `${iterationItem.lastRunId}-${iterationItem.lastResultId}-${iterationItem.testCaseId}`;
|
|
4449
4512
|
map[key] = iterationItem;
|
|
4450
4513
|
} else if (includeNotRunTestCases) {
|
|
4451
4514
|
const key = `${iterationItem.testCaseId}`;
|
|
4452
4515
|
map[key] = iterationItem;
|
|
4516
|
+
if (isTestReporter && iterationItem?.iteration) {
|
|
4517
|
+
logger.debug(
|
|
4518
|
+
`[RunlessResolver] createIterationsMap: mapped runless testCaseId=${String(
|
|
4519
|
+
iterationItem?.testCaseId
|
|
4520
|
+
)} to case-only key`
|
|
4521
|
+
);
|
|
4522
|
+
}
|
|
4523
|
+
} else if (iterationItem?.iteration && !isTestReporter) {
|
|
4524
|
+
const key = `${iterationItem.lastRunId}-${iterationItem.lastResultId}-${iterationItem.testCaseId}`;
|
|
4525
|
+
map[key] = iterationItem;
|
|
4453
4526
|
}
|
|
4454
4527
|
return map;
|
|
4455
4528
|
}, {} as Record<string, any>);
|
|
@@ -4012,6 +4012,70 @@ describe('ResultDataProvider', () => {
|
|
|
4012
4012
|
expect(res).toEqual(expect.objectContaining({ testCaseRevision: 6 }));
|
|
4013
4013
|
});
|
|
4014
4014
|
|
|
4015
|
+
it('should fallback from asOf snapshot without steps to revision snapshot with steps', async () => {
|
|
4016
|
+
(TFSServices.getItemContent as jest.Mock).mockReset();
|
|
4017
|
+
const point = {
|
|
4018
|
+
testCaseId: '777',
|
|
4019
|
+
testCaseName: 'TC 777',
|
|
4020
|
+
outcome: 'Not Run',
|
|
4021
|
+
pointAsOfTimestamp: '2025-03-01T00:00:00Z',
|
|
4022
|
+
suiteTestCase: {
|
|
4023
|
+
workItem: {
|
|
4024
|
+
id: 777,
|
|
4025
|
+
workItemFields: [{ key: 'System.Rev', value: '21' }],
|
|
4026
|
+
},
|
|
4027
|
+
},
|
|
4028
|
+
testSuite: { id: '1', name: 'Suite' },
|
|
4029
|
+
};
|
|
4030
|
+
|
|
4031
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
4032
|
+
.mockResolvedValueOnce({
|
|
4033
|
+
id: 777,
|
|
4034
|
+
rev: 18,
|
|
4035
|
+
fields: {
|
|
4036
|
+
'System.State': 'Design',
|
|
4037
|
+
'System.CreatedDate': '2024-01-01T00:00:00',
|
|
4038
|
+
'Microsoft.VSTS.TCM.Priority': 1,
|
|
4039
|
+
'System.Title': 'TC 777',
|
|
4040
|
+
},
|
|
4041
|
+
relations: [],
|
|
4042
|
+
})
|
|
4043
|
+
.mockResolvedValueOnce({
|
|
4044
|
+
id: 777,
|
|
4045
|
+
rev: 21,
|
|
4046
|
+
fields: {
|
|
4047
|
+
'System.State': 'Design',
|
|
4048
|
+
'System.CreatedDate': '2024-01-01T00:00:00',
|
|
4049
|
+
'Microsoft.VSTS.TCM.Priority': 1,
|
|
4050
|
+
'System.Title': 'TC 777',
|
|
4051
|
+
'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
|
|
4052
|
+
},
|
|
4053
|
+
relations: [],
|
|
4054
|
+
});
|
|
4055
|
+
|
|
4056
|
+
const res = await (resultDataProvider as any).fetchResultDataBasedOnWiBase(
|
|
4057
|
+
mockProjectName,
|
|
4058
|
+
'0',
|
|
4059
|
+
'0',
|
|
4060
|
+
true,
|
|
4061
|
+
[],
|
|
4062
|
+
false,
|
|
4063
|
+
point,
|
|
4064
|
+
false,
|
|
4065
|
+
true
|
|
4066
|
+
);
|
|
4067
|
+
|
|
4068
|
+
const calledUrls = (TFSServices.getItemContent as jest.Mock).mock.calls.map((args: any[]) => String(args[0]));
|
|
4069
|
+
expect(calledUrls.some((url: string) => url.includes('/_apis/wit/workItems/777?asOf='))).toBe(true);
|
|
4070
|
+
expect(calledUrls.some((url: string) => url.includes('/_apis/wit/workItems/777/revisions/21'))).toBe(true);
|
|
4071
|
+
expect(res).toEqual(
|
|
4072
|
+
expect.objectContaining({
|
|
4073
|
+
testCaseRevision: 21,
|
|
4074
|
+
stepsResultXml: '<steps></steps>',
|
|
4075
|
+
})
|
|
4076
|
+
);
|
|
4077
|
+
});
|
|
4078
|
+
|
|
4015
4079
|
it('should fallback to suite revision when asOf fetch fails', async () => {
|
|
4016
4080
|
(TFSServices.getItemContent as jest.Mock).mockReset();
|
|
4017
4081
|
const point = {
|
|
@@ -5064,6 +5128,17 @@ describe('ResultDataProvider', () => {
|
|
|
5064
5128
|
// Assert
|
|
5065
5129
|
expect(result['1']).toBeDefined();
|
|
5066
5130
|
});
|
|
5131
|
+
|
|
5132
|
+
it('should map runless test reporter item with iteration by testCaseId key', () => {
|
|
5133
|
+
const iterations = [
|
|
5134
|
+
{ testCaseId: 217897, lastRunId: undefined, lastResultId: undefined, iteration: { actionResults: [] } },
|
|
5135
|
+
];
|
|
5136
|
+
|
|
5137
|
+
const result = (resultDataProvider as any).createIterationsMap(iterations, true, true);
|
|
5138
|
+
|
|
5139
|
+
expect(result['217897']).toBeDefined();
|
|
5140
|
+
expect(result['undefined-undefined-217897']).toBeUndefined();
|
|
5141
|
+
});
|
|
5067
5142
|
});
|
|
5068
5143
|
|
|
5069
5144
|
describe('alignStepsWithIterationsBase', () => {
|