@elisra-devops/docgen-data-provider 1.94.0 → 1.95.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 +22 -0
- package/bin/modules/ResultDataProvider.js +102 -30
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +114 -1
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/ResultDataProvider.ts +184 -51
- package/src/tests/modules/ResultDataProvider.test.ts +161 -1
package/package.json
CHANGED
|
@@ -410,7 +410,8 @@ export default class ResultDataProvider {
|
|
|
410
410
|
projectName,
|
|
411
411
|
selectedFields,
|
|
412
412
|
false,
|
|
413
|
-
includeAllHistory
|
|
413
|
+
includeAllHistory,
|
|
414
|
+
true
|
|
414
415
|
);
|
|
415
416
|
|
|
416
417
|
const rows = this.alignStepsWithIterationsFlatReport(
|
|
@@ -3086,40 +3087,108 @@ export default class ResultDataProvider {
|
|
|
3086
3087
|
* Maps raw test point data to a simplified object.
|
|
3087
3088
|
*/
|
|
3088
3089
|
private mapTestPoint(testPoint: any, projectName: string): any {
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3090
|
+
const pointAsOfTimestamp = this.resolvePointAsOfTimestamp(testPoint);
|
|
3091
|
+
return this.buildMappedTestPoint(
|
|
3092
|
+
{
|
|
3093
|
+
testPointId: testPoint.id,
|
|
3094
|
+
testCaseId: testPoint.testCaseReference.id,
|
|
3095
|
+
testCaseName: testPoint.testCaseReference.name,
|
|
3096
|
+
configurationName: testPoint.configuration?.name,
|
|
3097
|
+
outcome: testPoint.results?.outcome,
|
|
3098
|
+
testSuite: testPoint.testSuite,
|
|
3099
|
+
lastRunId: testPoint.results?.lastTestRunId,
|
|
3100
|
+
lastResultId: testPoint.results?.lastResultId,
|
|
3101
|
+
lastResultDetails: testPoint.results?.lastResultDetails,
|
|
3102
|
+
},
|
|
3103
|
+
projectName,
|
|
3104
|
+
pointAsOfTimestamp
|
|
3105
|
+
);
|
|
3101
3106
|
}
|
|
3102
3107
|
|
|
3103
3108
|
/**
|
|
3104
3109
|
* Maps raw test point data to a simplified object.
|
|
3105
3110
|
*/
|
|
3106
3111
|
private mapTestPointForCrossPlans(testPoint: any, projectName: string): any {
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3112
|
+
const pointAsOfTimestamp = this.resolvePointAsOfTimestamp(testPoint);
|
|
3113
|
+
return this.buildMappedTestPoint(
|
|
3114
|
+
{
|
|
3115
|
+
testPointId: testPoint.id,
|
|
3116
|
+
testCaseId: testPoint.testCase.id,
|
|
3117
|
+
testCaseName: testPoint.testCase.name,
|
|
3118
|
+
configurationName: testPoint.configuration?.name,
|
|
3119
|
+
outcome: testPoint.outcome,
|
|
3120
|
+
testSuite: testPoint.testSuite,
|
|
3121
|
+
lastRunId: testPoint.lastTestRun?.id,
|
|
3122
|
+
lastResultId: testPoint.lastResult?.id,
|
|
3123
|
+
lastResultDetails: testPoint.lastResultDetails || {
|
|
3124
|
+
duration: 0,
|
|
3125
|
+
dateCompleted: '0000-00-00T00:00:00.000Z',
|
|
3126
|
+
runBy: { displayName: 'No tester', id: '00000000-0000-0000-0000-000000000000' },
|
|
3127
|
+
},
|
|
3121
3128
|
},
|
|
3129
|
+
projectName,
|
|
3130
|
+
pointAsOfTimestamp
|
|
3131
|
+
);
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
/**
|
|
3135
|
+
* Resolves a point timestamp that can be used as WIT `asOf` anchor.
|
|
3136
|
+
* Returns an ISO string when a valid date exists, otherwise an empty string.
|
|
3137
|
+
*/
|
|
3138
|
+
private resolvePointAsOfTimestamp(testPoint: any): string {
|
|
3139
|
+
const candidates = [
|
|
3140
|
+
testPoint?.lastUpdatedDate,
|
|
3141
|
+
testPoint?.lastUpdatedOn,
|
|
3142
|
+
testPoint?.updatedDate,
|
|
3143
|
+
testPoint?.updatedOn,
|
|
3144
|
+
];
|
|
3145
|
+
|
|
3146
|
+
for (const candidate of candidates) {
|
|
3147
|
+
const raw = String(candidate || '').trim();
|
|
3148
|
+
if (!raw) continue;
|
|
3149
|
+
const parsed = new Date(raw);
|
|
3150
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
3151
|
+
return parsed.toISOString();
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
return '';
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
/**
|
|
3159
|
+
* Builds a normalized test point shape used by downstream result fetch flows.
|
|
3160
|
+
*/
|
|
3161
|
+
private buildMappedTestPoint(
|
|
3162
|
+
pointData: {
|
|
3163
|
+
testPointId: any;
|
|
3164
|
+
testCaseId: any;
|
|
3165
|
+
testCaseName: any;
|
|
3166
|
+
configurationName?: any;
|
|
3167
|
+
outcome?: any;
|
|
3168
|
+
testSuite?: any;
|
|
3169
|
+
lastRunId?: any;
|
|
3170
|
+
lastResultId?: any;
|
|
3171
|
+
lastResultDetails?: any;
|
|
3172
|
+
},
|
|
3173
|
+
projectName: string,
|
|
3174
|
+
pointAsOfTimestamp: string
|
|
3175
|
+
): any {
|
|
3176
|
+
const mappedPoint: any = {
|
|
3177
|
+
testPointId: pointData.testPointId,
|
|
3178
|
+
testCaseId: pointData.testCaseId,
|
|
3179
|
+
testCaseName: pointData.testCaseName,
|
|
3180
|
+
testCaseUrl: `${this.orgUrl}${projectName}/_workitems/edit/${pointData.testCaseId}`,
|
|
3181
|
+
configurationName: pointData.configurationName,
|
|
3182
|
+
outcome: pointData.outcome || 'Not Run',
|
|
3183
|
+
testSuite: pointData.testSuite,
|
|
3184
|
+
lastRunId: pointData.lastRunId,
|
|
3185
|
+
lastResultId: pointData.lastResultId,
|
|
3186
|
+
lastResultDetails: pointData.lastResultDetails,
|
|
3122
3187
|
};
|
|
3188
|
+
if (pointAsOfTimestamp) {
|
|
3189
|
+
mappedPoint.pointAsOfTimestamp = pointAsOfTimestamp;
|
|
3190
|
+
}
|
|
3191
|
+
return mappedPoint;
|
|
3123
3192
|
}
|
|
3124
3193
|
|
|
3125
3194
|
// Helper method to get all test points for a test case
|
|
@@ -3186,6 +3255,9 @@ export default class ResultDataProvider {
|
|
|
3186
3255
|
return fields;
|
|
3187
3256
|
}
|
|
3188
3257
|
|
|
3258
|
+
/**
|
|
3259
|
+
* Reads a field value by reference name with case-insensitive key matching.
|
|
3260
|
+
*/
|
|
3189
3261
|
private getFieldValueByName(fields: Record<string, any>, fieldName: string): any {
|
|
3190
3262
|
if (!fields || typeof fields !== 'object') return undefined;
|
|
3191
3263
|
if (Object.prototype.hasOwnProperty.call(fields, fieldName)) {
|
|
@@ -3277,12 +3349,25 @@ export default class ResultDataProvider {
|
|
|
3277
3349
|
|
|
3278
3350
|
const expandParam = expandAll ? '?$expand=all' : '';
|
|
3279
3351
|
const url = `${this.orgUrl}${projectName}/_apis/wit/workItems/${id}/revisions/${rev}${expandParam}`;
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3352
|
+
return this.fetchWorkItemByUrl(url, `work item ${id} by revision ${rev}`);
|
|
3353
|
+
}
|
|
3354
|
+
|
|
3355
|
+
private async fetchWorkItemByAsOf(
|
|
3356
|
+
projectName: string,
|
|
3357
|
+
workItemId: number,
|
|
3358
|
+
asOfTimestamp: string,
|
|
3359
|
+
expandAll: boolean = false
|
|
3360
|
+
): Promise<any | null> {
|
|
3361
|
+
const id = Number(workItemId || 0);
|
|
3362
|
+
const parsedAsOf = new Date(String(asOfTimestamp || '').trim());
|
|
3363
|
+
if (!Number.isFinite(id) || id <= 0 || Number.isNaN(parsedAsOf.getTime())) return null;
|
|
3364
|
+
|
|
3365
|
+
const query: string[] = [`asOf=${encodeURIComponent(parsedAsOf.toISOString())}`];
|
|
3366
|
+
if (expandAll) {
|
|
3367
|
+
query.push(`$expand=all`);
|
|
3285
3368
|
}
|
|
3369
|
+
const url = `${this.orgUrl}${projectName}/_apis/wit/workItems/${id}?${query.join('&')}`;
|
|
3370
|
+
return this.fetchWorkItemByUrl(url, `work item ${id} by asOf ${parsedAsOf.toISOString()}`);
|
|
3286
3371
|
}
|
|
3287
3372
|
|
|
3288
3373
|
private async fetchWorkItemLatest(
|
|
@@ -3295,14 +3380,56 @@ export default class ResultDataProvider {
|
|
|
3295
3380
|
|
|
3296
3381
|
const expandParam = expandAll ? '?$expand=all' : '';
|
|
3297
3382
|
const url = `${this.orgUrl}${projectName}/_apis/wit/workItems/${id}${expandParam}`;
|
|
3383
|
+
return this.fetchWorkItemByUrl(url, `latest work item ${id}`);
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3386
|
+
/**
|
|
3387
|
+
* Executes a work-item GET by URL and applies consistent warning/error handling.
|
|
3388
|
+
*/
|
|
3389
|
+
private async fetchWorkItemByUrl(url: string, failureContext: string): Promise<any | null> {
|
|
3298
3390
|
try {
|
|
3299
3391
|
return await TFSServices.getItemContent(url, this.token);
|
|
3300
3392
|
} catch (error: any) {
|
|
3301
|
-
logger.warn(`Failed to fetch
|
|
3393
|
+
logger.warn(`Failed to fetch ${failureContext}: ${error?.message || error}`);
|
|
3302
3394
|
return null;
|
|
3303
3395
|
}
|
|
3304
3396
|
}
|
|
3305
3397
|
|
|
3398
|
+
/**
|
|
3399
|
+
* Resolves runless test case data using ordered fallbacks:
|
|
3400
|
+
* 1) point-based `asOf` snapshot, 2) explicit revision, 3) suite payload snapshot, 4) latest WI.
|
|
3401
|
+
*/
|
|
3402
|
+
private async resolveRunlessTestCaseData(
|
|
3403
|
+
projectName: string,
|
|
3404
|
+
testCaseId: number,
|
|
3405
|
+
suiteTestCaseRevision: number,
|
|
3406
|
+
pointAsOfTimestamp: string,
|
|
3407
|
+
fallbackSnapshot: any,
|
|
3408
|
+
expandAll: boolean
|
|
3409
|
+
): Promise<any | null> {
|
|
3410
|
+
if (pointAsOfTimestamp) {
|
|
3411
|
+
const asOfSnapshot = await this.fetchWorkItemByAsOf(
|
|
3412
|
+
projectName,
|
|
3413
|
+
testCaseId,
|
|
3414
|
+
pointAsOfTimestamp,
|
|
3415
|
+
expandAll
|
|
3416
|
+
);
|
|
3417
|
+
if (asOfSnapshot) return asOfSnapshot;
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
const revisionSnapshot = await this.fetchWorkItemByRevision(
|
|
3421
|
+
projectName,
|
|
3422
|
+
testCaseId,
|
|
3423
|
+
suiteTestCaseRevision,
|
|
3424
|
+
expandAll
|
|
3425
|
+
);
|
|
3426
|
+
if (revisionSnapshot) return revisionSnapshot;
|
|
3427
|
+
|
|
3428
|
+
if (fallbackSnapshot) return fallbackSnapshot;
|
|
3429
|
+
|
|
3430
|
+
return this.fetchWorkItemLatest(projectName, testCaseId, expandAll);
|
|
3431
|
+
}
|
|
3432
|
+
|
|
3306
3433
|
/**
|
|
3307
3434
|
* Fetches result data based on the Work Item Test Reporter.
|
|
3308
3435
|
*
|
|
@@ -3325,7 +3452,8 @@ export default class ResultDataProvider {
|
|
|
3325
3452
|
selectedFields?: string[],
|
|
3326
3453
|
isQueryMode?: boolean,
|
|
3327
3454
|
point?: any,
|
|
3328
|
-
includeAllHistory: boolean = false
|
|
3455
|
+
includeAllHistory: boolean = false,
|
|
3456
|
+
useRunlessAsOf: boolean = false
|
|
3329
3457
|
): Promise<any> {
|
|
3330
3458
|
try {
|
|
3331
3459
|
let filteredFields: any = {};
|
|
@@ -3342,23 +3470,22 @@ export default class ResultDataProvider {
|
|
|
3342
3470
|
point?.testCaseId || suiteTestCaseItem?.workItem?.id || suiteTestCaseItem?.testCaseId || 0
|
|
3343
3471
|
);
|
|
3344
3472
|
const suiteTestCaseRevision = this.resolveSuiteTestCaseRevision(suiteTestCaseItem);
|
|
3473
|
+
const pointAsOfTimestamp = useRunlessAsOf
|
|
3474
|
+
? String(point?.pointAsOfTimestamp || '').trim()
|
|
3475
|
+
: '';
|
|
3345
3476
|
const fallbackSnapshot = this.buildWorkItemSnapshotFromSuiteTestCase(
|
|
3346
3477
|
suiteTestCaseItem,
|
|
3347
3478
|
testCaseId,
|
|
3348
3479
|
String(point?.testCaseName || '')
|
|
3349
3480
|
);
|
|
3350
|
-
|
|
3481
|
+
const testCaseData = await this.resolveRunlessTestCaseData(
|
|
3351
3482
|
projectName,
|
|
3352
3483
|
testCaseId,
|
|
3353
3484
|
suiteTestCaseRevision,
|
|
3485
|
+
pointAsOfTimestamp,
|
|
3486
|
+
fallbackSnapshot,
|
|
3354
3487
|
isTestReporter
|
|
3355
3488
|
);
|
|
3356
|
-
if (!testCaseData) {
|
|
3357
|
-
testCaseData = fallbackSnapshot;
|
|
3358
|
-
}
|
|
3359
|
-
if (!testCaseData) {
|
|
3360
|
-
testCaseData = await this.fetchWorkItemLatest(projectName, testCaseId, isTestReporter);
|
|
3361
|
-
}
|
|
3362
3489
|
if (!testCaseData) {
|
|
3363
3490
|
logger.warn(`Could not resolve test case ${point.testCaseId} for runless point fallback.`);
|
|
3364
3491
|
return null;
|
|
@@ -4992,7 +5119,8 @@ export default class ResultDataProvider {
|
|
|
4992
5119
|
selectedFields?: string[],
|
|
4993
5120
|
isQueryMode?: boolean,
|
|
4994
5121
|
point?: any,
|
|
4995
|
-
includeAllHistory: boolean = false
|
|
5122
|
+
includeAllHistory: boolean = false,
|
|
5123
|
+
useRunlessAsOf: boolean = false
|
|
4996
5124
|
): Promise<any> {
|
|
4997
5125
|
return this.fetchResultDataBasedOnWiBase(
|
|
4998
5126
|
projectName,
|
|
@@ -5002,7 +5130,8 @@ export default class ResultDataProvider {
|
|
|
5002
5130
|
selectedFields,
|
|
5003
5131
|
isQueryMode,
|
|
5004
5132
|
point,
|
|
5005
|
-
includeAllHistory
|
|
5133
|
+
includeAllHistory,
|
|
5134
|
+
useRunlessAsOf
|
|
5006
5135
|
);
|
|
5007
5136
|
}
|
|
5008
5137
|
|
|
@@ -5023,22 +5152,24 @@ export default class ResultDataProvider {
|
|
|
5023
5152
|
projectName: string,
|
|
5024
5153
|
selectedFields?: string[],
|
|
5025
5154
|
isQueryMode?: boolean,
|
|
5026
|
-
includeAllHistory: boolean = false
|
|
5155
|
+
includeAllHistory: boolean = false,
|
|
5156
|
+
useRunlessAsOf: boolean = false
|
|
5027
5157
|
): Promise<any[]> {
|
|
5028
5158
|
return this.fetchAllResultDataBase(
|
|
5029
5159
|
testData,
|
|
5030
5160
|
projectName,
|
|
5031
5161
|
true,
|
|
5032
|
-
(projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory) =>
|
|
5162
|
+
(projectName, testSuiteId, point, selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf) =>
|
|
5033
5163
|
this.fetchResultDataForTestReporter(
|
|
5034
5164
|
projectName,
|
|
5035
5165
|
testSuiteId,
|
|
5036
5166
|
point,
|
|
5037
5167
|
selectedFields,
|
|
5038
5168
|
isQueryMode,
|
|
5039
|
-
includeAllHistory
|
|
5169
|
+
includeAllHistory,
|
|
5170
|
+
useRunlessAsOf
|
|
5040
5171
|
),
|
|
5041
|
-
[selectedFields, isQueryMode, includeAllHistory]
|
|
5172
|
+
[selectedFields, isQueryMode, includeAllHistory, useRunlessAsOf]
|
|
5042
5173
|
);
|
|
5043
5174
|
}
|
|
5044
5175
|
|
|
@@ -5196,13 +5327,14 @@ export default class ResultDataProvider {
|
|
|
5196
5327
|
point: any,
|
|
5197
5328
|
selectedFields?: string[],
|
|
5198
5329
|
isQueryMode?: boolean,
|
|
5199
|
-
includeAllHistory: boolean = false
|
|
5330
|
+
includeAllHistory: boolean = false,
|
|
5331
|
+
useRunlessAsOf: boolean = false
|
|
5200
5332
|
) {
|
|
5201
5333
|
return this.fetchResultDataBase(
|
|
5202
5334
|
projectName,
|
|
5203
5335
|
testSuiteId,
|
|
5204
5336
|
point,
|
|
5205
|
-
(project, runId, resultId, fields, isQueryMode, point, includeAllHistory) =>
|
|
5337
|
+
(project, runId, resultId, fields, isQueryMode, point, includeAllHistory, useRunlessAsOf) =>
|
|
5206
5338
|
this.fetchResultDataBasedOnWiTestReporter(
|
|
5207
5339
|
project,
|
|
5208
5340
|
runId,
|
|
@@ -5210,7 +5342,8 @@ export default class ResultDataProvider {
|
|
|
5210
5342
|
fields,
|
|
5211
5343
|
isQueryMode,
|
|
5212
5344
|
point,
|
|
5213
|
-
includeAllHistory
|
|
5345
|
+
includeAllHistory,
|
|
5346
|
+
useRunlessAsOf
|
|
5214
5347
|
),
|
|
5215
5348
|
(resultData, testSuiteId, point, selectedFields) => {
|
|
5216
5349
|
const { lastRunId, lastResultId, configurationName, lastResultDetails } = point;
|
|
@@ -5330,7 +5463,7 @@ export default class ResultDataProvider {
|
|
|
5330
5463
|
return null;
|
|
5331
5464
|
}
|
|
5332
5465
|
},
|
|
5333
|
-
[selectedFields, isQueryMode, point, includeAllHistory]
|
|
5466
|
+
[selectedFields, isQueryMode, point, includeAllHistory, useRunlessAsOf]
|
|
5334
5467
|
);
|
|
5335
5468
|
}
|
|
5336
5469
|
|
|
@@ -333,6 +333,21 @@ describe('ResultDataProvider', () => {
|
|
|
333
333
|
testCaseUrl: 'https://dev.azure.com/organization/test-project/_workitems/edit/1',
|
|
334
334
|
});
|
|
335
335
|
});
|
|
336
|
+
|
|
337
|
+
it('should include pointAsOfTimestamp when lastUpdatedDate is available', () => {
|
|
338
|
+
const testPoint = {
|
|
339
|
+
testCaseReference: { id: 1, name: 'Test Case 1' },
|
|
340
|
+
lastUpdatedDate: '2025-01-01T12:34:56Z',
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const result = (resultDataProvider as any).mapTestPoint(testPoint, mockProjectName);
|
|
344
|
+
|
|
345
|
+
expect(result).toEqual(
|
|
346
|
+
expect.objectContaining({
|
|
347
|
+
pointAsOfTimestamp: '2025-01-01T12:34:56.000Z',
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
});
|
|
336
351
|
});
|
|
337
352
|
|
|
338
353
|
describe('calculateGroupResultSummary', () => {
|
|
@@ -3890,7 +3905,9 @@ describe('ResultDataProvider', () => {
|
|
|
3890
3905
|
true,
|
|
3891
3906
|
[],
|
|
3892
3907
|
false,
|
|
3893
|
-
point
|
|
3908
|
+
point,
|
|
3909
|
+
false,
|
|
3910
|
+
true
|
|
3894
3911
|
);
|
|
3895
3912
|
|
|
3896
3913
|
expect(TFSServices.getItemContent).toHaveBeenCalledWith(
|
|
@@ -3902,7 +3919,150 @@ describe('ResultDataProvider', () => {
|
|
|
3902
3919
|
expect(res).toEqual(expect.objectContaining({ testCaseRevision: 9 }));
|
|
3903
3920
|
});
|
|
3904
3921
|
|
|
3922
|
+
it('should ignore point asOf timestamp when runless asOf mode is disabled', async () => {
|
|
3923
|
+
const point = {
|
|
3924
|
+
testCaseId: '123',
|
|
3925
|
+
testCaseName: 'TC 123',
|
|
3926
|
+
outcome: 'passed',
|
|
3927
|
+
pointAsOfTimestamp: '2025-01-01T12:34:56Z',
|
|
3928
|
+
suiteTestCase: {
|
|
3929
|
+
workItem: {
|
|
3930
|
+
id: 123,
|
|
3931
|
+
rev: 9,
|
|
3932
|
+
workItemFields: [{ key: 'Microsoft.VSTS.TCM.Steps', value: '<steps></steps>' }],
|
|
3933
|
+
},
|
|
3934
|
+
},
|
|
3935
|
+
testSuite: { id: '1', name: 'Suite' },
|
|
3936
|
+
};
|
|
3937
|
+
|
|
3938
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({
|
|
3939
|
+
id: 123,
|
|
3940
|
+
rev: 9,
|
|
3941
|
+
fields: {
|
|
3942
|
+
'System.State': 'Active',
|
|
3943
|
+
'System.CreatedDate': '2024-01-01T00:00:00',
|
|
3944
|
+
'Microsoft.VSTS.TCM.Priority': 1,
|
|
3945
|
+
'System.Title': 'Title 123',
|
|
3946
|
+
'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
|
|
3947
|
+
},
|
|
3948
|
+
relations: null,
|
|
3949
|
+
});
|
|
3950
|
+
|
|
3951
|
+
const res = await (resultDataProvider as any).fetchResultDataBasedOnWiBase(
|
|
3952
|
+
mockProjectName,
|
|
3953
|
+
'0',
|
|
3954
|
+
'0',
|
|
3955
|
+
true,
|
|
3956
|
+
[],
|
|
3957
|
+
false,
|
|
3958
|
+
point
|
|
3959
|
+
);
|
|
3960
|
+
|
|
3961
|
+
const calledUrls = (TFSServices.getItemContent as jest.Mock).mock.calls.map((args: any[]) => String(args[0]));
|
|
3962
|
+
expect(calledUrls.some((url: string) => url.includes('/_apis/wit/workItems/123?asOf='))).toBe(false);
|
|
3963
|
+
expect(calledUrls.some((url: string) => url.includes('/_apis/wit/workItems/123/revisions/9'))).toBe(true);
|
|
3964
|
+
expect(res).toEqual(expect.objectContaining({ testCaseRevision: 9 }));
|
|
3965
|
+
});
|
|
3966
|
+
|
|
3967
|
+
it('should fetch no-run test case by asOf timestamp when pointAsOfTimestamp is available', async () => {
|
|
3968
|
+
(TFSServices.getItemContent as jest.Mock).mockReset();
|
|
3969
|
+
const point = {
|
|
3970
|
+
testCaseId: '123',
|
|
3971
|
+
testCaseName: 'TC 123',
|
|
3972
|
+
outcome: 'passed',
|
|
3973
|
+
pointAsOfTimestamp: '2025-01-01T12:34:56Z',
|
|
3974
|
+
suiteTestCase: {
|
|
3975
|
+
workItem: {
|
|
3976
|
+
id: 123,
|
|
3977
|
+
rev: 9,
|
|
3978
|
+
workItemFields: [{ key: 'Microsoft.VSTS.TCM.Steps', value: '<steps></steps>' }],
|
|
3979
|
+
},
|
|
3980
|
+
},
|
|
3981
|
+
testSuite: { id: '1', name: 'Suite' },
|
|
3982
|
+
};
|
|
3983
|
+
|
|
3984
|
+
(TFSServices.getItemContent as jest.Mock).mockResolvedValueOnce({
|
|
3985
|
+
id: 123,
|
|
3986
|
+
rev: 6,
|
|
3987
|
+
fields: {
|
|
3988
|
+
'System.State': 'Active',
|
|
3989
|
+
'System.CreatedDate': '2024-01-01T00:00:00',
|
|
3990
|
+
'Microsoft.VSTS.TCM.Priority': 1,
|
|
3991
|
+
'System.Title': 'Title 123',
|
|
3992
|
+
'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
|
|
3993
|
+
},
|
|
3994
|
+
relations: null,
|
|
3995
|
+
});
|
|
3996
|
+
|
|
3997
|
+
const res = await (resultDataProvider as any).fetchResultDataBasedOnWiBase(
|
|
3998
|
+
mockProjectName,
|
|
3999
|
+
'0',
|
|
4000
|
+
'0',
|
|
4001
|
+
true,
|
|
4002
|
+
[],
|
|
4003
|
+
false,
|
|
4004
|
+
point,
|
|
4005
|
+
false,
|
|
4006
|
+
true
|
|
4007
|
+
);
|
|
4008
|
+
|
|
4009
|
+
const calledUrls = (TFSServices.getItemContent as jest.Mock).mock.calls.map((args: any[]) => String(args[0]));
|
|
4010
|
+
expect(calledUrls.some((url: string) => url.includes('/_apis/wit/workItems/123?asOf='))).toBe(true);
|
|
4011
|
+
expect(calledUrls.some((url: string) => url.includes('/revisions/9'))).toBe(false);
|
|
4012
|
+
expect(res).toEqual(expect.objectContaining({ testCaseRevision: 6 }));
|
|
4013
|
+
});
|
|
4014
|
+
|
|
4015
|
+
it('should fallback to suite revision when asOf fetch fails', async () => {
|
|
4016
|
+
(TFSServices.getItemContent as jest.Mock).mockReset();
|
|
4017
|
+
const point = {
|
|
4018
|
+
testCaseId: '456',
|
|
4019
|
+
testCaseName: 'TC 456',
|
|
4020
|
+
outcome: 'Not Run',
|
|
4021
|
+
pointAsOfTimestamp: '2025-02-01T00:00:00Z',
|
|
4022
|
+
suiteTestCase: {
|
|
4023
|
+
workItem: {
|
|
4024
|
+
id: 456,
|
|
4025
|
+
workItemFields: [{ key: 'System.Rev', value: '11' }],
|
|
4026
|
+
},
|
|
4027
|
+
},
|
|
4028
|
+
testSuite: { id: '1', name: 'Suite' },
|
|
4029
|
+
};
|
|
4030
|
+
|
|
4031
|
+
(TFSServices.getItemContent as jest.Mock)
|
|
4032
|
+
.mockRejectedValueOnce(new Error('asOf failed'))
|
|
4033
|
+
.mockResolvedValueOnce({
|
|
4034
|
+
id: 456,
|
|
4035
|
+
rev: 11,
|
|
4036
|
+
fields: {
|
|
4037
|
+
'System.State': 'Active',
|
|
4038
|
+
'System.CreatedDate': '2024-01-01T00:00:00',
|
|
4039
|
+
'Microsoft.VSTS.TCM.Priority': 1,
|
|
4040
|
+
'System.Title': 'Title 456',
|
|
4041
|
+
'Microsoft.VSTS.TCM.Steps': '<steps></steps>',
|
|
4042
|
+
},
|
|
4043
|
+
relations: [],
|
|
4044
|
+
});
|
|
4045
|
+
|
|
4046
|
+
const res = await (resultDataProvider as any).fetchResultDataBasedOnWiBase(
|
|
4047
|
+
mockProjectName,
|
|
4048
|
+
'0',
|
|
4049
|
+
'0',
|
|
4050
|
+
true,
|
|
4051
|
+
[],
|
|
4052
|
+
false,
|
|
4053
|
+
point,
|
|
4054
|
+
false,
|
|
4055
|
+
true
|
|
4056
|
+
);
|
|
4057
|
+
|
|
4058
|
+
const calledUrls = (TFSServices.getItemContent as jest.Mock).mock.calls.map((args: any[]) => String(args[0]));
|
|
4059
|
+
expect(calledUrls.some((url: string) => url.includes('/_apis/wit/workItems/456?asOf='))).toBe(true);
|
|
4060
|
+
expect(calledUrls.some((url: string) => url.includes('/_apis/wit/workItems/456/revisions/11'))).toBe(true);
|
|
4061
|
+
expect(res).toEqual(expect.objectContaining({ testCaseRevision: 11 }));
|
|
4062
|
+
});
|
|
4063
|
+
|
|
3905
4064
|
it('should resolve no-run revision from System.Rev in suite test-case fields', async () => {
|
|
4065
|
+
(TFSServices.getItemContent as jest.Mock).mockReset();
|
|
3906
4066
|
const point = {
|
|
3907
4067
|
testCaseId: '321',
|
|
3908
4068
|
testCaseName: 'TC 321',
|