@elisra-devops/docgen-data-provider 1.18.0 → 1.20.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/helpers/tfs.d.ts +15 -0
- package/bin/helpers/tfs.js +148 -130
- package/bin/helpers/tfs.js.map +1 -1
- package/bin/modules/TestDataProvider.d.ts +9 -2
- package/bin/modules/TestDataProvider.js +73 -36
- package/bin/modules/TestDataProvider.js.map +1 -1
- package/package.json +1 -1
- package/src/helpers/tfs.ts +166 -131
- package/src/modules/TestDataProvider.ts +93 -55
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import { TFSServices } from '../helpers/tfs';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { Query, TestSteps, createBugRelation, createRequirementRelation } from '../models/tfs-data';
|
|
5
|
-
import { QueryType } from '../models/tfs-data';
|
|
6
|
-
import { QueryAllTypes } from '../models/tfs-data';
|
|
7
|
-
import { Column } from '../models/tfs-data';
|
|
8
|
-
import { value } from '../models/tfs-data';
|
|
2
|
+
import { Helper, suiteData } from '../helpers/helper';
|
|
3
|
+
import { TestSteps, createRequirementRelation } from '../models/tfs-data';
|
|
9
4
|
import { TestCase } from '../models/tfs-data';
|
|
10
5
|
import * as xml2js from 'xml2js';
|
|
11
|
-
|
|
12
6
|
import logger from '../utils/logger';
|
|
13
7
|
import TestStepParserHelper from '../utils/testStepParserHelper';
|
|
8
|
+
const pLimit = require('p-limit');
|
|
14
9
|
|
|
15
10
|
export default class TestDataProvider {
|
|
16
11
|
orgUrl: string = '';
|
|
17
12
|
token: string = '';
|
|
18
13
|
private testStepParserHelper: TestStepParserHelper;
|
|
14
|
+
private cache = new Map<string, any>(); // Cache for API responses
|
|
15
|
+
private limit = pLimit(10);
|
|
19
16
|
|
|
20
17
|
constructor(orgUrl: string, token: string) {
|
|
21
18
|
this.orgUrl = orgUrl;
|
|
@@ -23,26 +20,47 @@ export default class TestDataProvider {
|
|
|
23
20
|
this.testStepParserHelper = new TestStepParserHelper(orgUrl, token);
|
|
24
21
|
}
|
|
25
22
|
|
|
23
|
+
private async fetchWithCache(url: string, ttlMs = 60000): Promise<any> {
|
|
24
|
+
if (this.cache.has(url)) {
|
|
25
|
+
const cached = this.cache.get(url);
|
|
26
|
+
if (cached.timestamp + ttlMs > Date.now()) {
|
|
27
|
+
return cached.data;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const result = await TFSServices.getItemContent(url, this.token);
|
|
33
|
+
|
|
34
|
+
this.cache.set(url, {
|
|
35
|
+
data: result,
|
|
36
|
+
timestamp: Date.now(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return result;
|
|
40
|
+
} catch (error: any) {
|
|
41
|
+
logger.error(`Error fetching ${url}: ${error.message}`);
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
26
46
|
async GetTestSuiteByTestCase(testCaseId: string): Promise<any> {
|
|
27
47
|
let url = `${this.orgUrl}/_apis/testplan/suites?testCaseId=${testCaseId}`;
|
|
28
|
-
|
|
29
|
-
return testCaseData;
|
|
48
|
+
return this.fetchWithCache(url);
|
|
30
49
|
}
|
|
31
50
|
|
|
32
|
-
//get all test plans in the project
|
|
33
51
|
async GetTestPlans(project: string): Promise<string> {
|
|
34
52
|
let testPlanUrl: string = `${this.orgUrl}${project}/_apis/test/plans`;
|
|
35
|
-
return
|
|
53
|
+
return this.fetchWithCache(testPlanUrl);
|
|
36
54
|
}
|
|
37
|
-
//async get data test
|
|
38
55
|
|
|
39
|
-
// get all test suits in projct test plan
|
|
40
56
|
async GetTestSuites(project: string, planId: string): Promise<any> {
|
|
41
57
|
let testsuitesUrl: string = this.orgUrl + project + '/_apis/test/Plans/' + planId + '/suites';
|
|
42
58
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
return this.fetchWithCache(testsuitesUrl);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
logger.error(`Failed to get test suites: ${e}`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
46
64
|
}
|
|
47
65
|
|
|
48
66
|
async GetTestSuitesForPlan(project: string, planid: string): Promise<any> {
|
|
@@ -54,20 +72,16 @@ export default class TestDataProvider {
|
|
|
54
72
|
}
|
|
55
73
|
let url =
|
|
56
74
|
this.orgUrl + '/' + project + '/_api/_testManagement/GetTestSuitesForPlan?__v=5&planId=' + planid;
|
|
57
|
-
|
|
58
|
-
return suites;
|
|
75
|
+
return this.fetchWithCache(url);
|
|
59
76
|
}
|
|
60
77
|
|
|
61
78
|
async GetTestSuitesByPlan(project: string, planId: string, recursive: boolean): Promise<any> {
|
|
62
79
|
let suiteId = Number(planId) + 1;
|
|
63
|
-
|
|
64
|
-
return suites;
|
|
80
|
+
return this.GetTestSuiteById(project, planId, suiteId.toString(), recursive);
|
|
65
81
|
}
|
|
66
|
-
//gets all testsuits recorsivly under test suite
|
|
67
82
|
|
|
68
83
|
async GetTestSuiteById(project: string, planId: string, suiteId: string, recursive: boolean): Promise<any> {
|
|
69
84
|
let testSuites = await this.GetTestSuitesForPlan(project, planId);
|
|
70
|
-
// GetTestSuites(project, planId);
|
|
71
85
|
Helper.suitList = [];
|
|
72
86
|
let dataSuites: any = Helper.findSuitesRecursive(
|
|
73
87
|
planId,
|
|
@@ -78,17 +92,14 @@ export default class TestDataProvider {
|
|
|
78
92
|
recursive
|
|
79
93
|
);
|
|
80
94
|
Helper.first = true;
|
|
81
|
-
// let levledSuites: any = Helper.buildSuiteslevel(dataSuites);
|
|
82
|
-
|
|
83
95
|
return dataSuites;
|
|
84
96
|
}
|
|
85
|
-
//gets all testcase under test suite acording to recursive flag
|
|
86
97
|
|
|
87
98
|
async GetTestCasesBySuites(
|
|
88
99
|
project: string,
|
|
89
100
|
planId: string,
|
|
90
101
|
suiteId: string,
|
|
91
|
-
|
|
102
|
+
recursive: boolean,
|
|
92
103
|
includeRequirements: boolean,
|
|
93
104
|
CustomerRequirementId: boolean,
|
|
94
105
|
stepResultDetailsMap?: Map<string, any>
|
|
@@ -96,28 +107,48 @@ export default class TestDataProvider {
|
|
|
96
107
|
let testCasesList: Array<any> = new Array<any>();
|
|
97
108
|
const requirementToTestCaseTraceMap: Map<string, string[]> = new Map();
|
|
98
109
|
const testCaseToRequirementsTraceMap: Map<string, string[]> = new Map();
|
|
110
|
+
// const startTime = performance.now();
|
|
111
|
+
|
|
99
112
|
let suitesTestCasesList: Array<suiteData> = await this.GetTestSuiteById(
|
|
100
113
|
project,
|
|
101
114
|
planId,
|
|
102
115
|
suiteId,
|
|
103
|
-
|
|
116
|
+
recursive
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Create array of promises that each return their test cases
|
|
120
|
+
const testCaseListPromises = suitesTestCasesList.map((suite) =>
|
|
121
|
+
this.limit(async () => {
|
|
122
|
+
try {
|
|
123
|
+
const testCases = await this.GetTestCases(project, planId, suite.id);
|
|
124
|
+
// const structureStartTime = performance.now();
|
|
125
|
+
const testCasesWithSteps = await this.StructureTestCase(
|
|
126
|
+
project,
|
|
127
|
+
testCases,
|
|
128
|
+
suite,
|
|
129
|
+
includeRequirements,
|
|
130
|
+
CustomerRequirementId,
|
|
131
|
+
requirementToTestCaseTraceMap,
|
|
132
|
+
testCaseToRequirementsTraceMap,
|
|
133
|
+
stepResultDetailsMap
|
|
134
|
+
);
|
|
135
|
+
// logger.debug(
|
|
136
|
+
// `Performance: structured suite ${suite.id} in ${performance.now() - structureStartTime}ms`
|
|
137
|
+
// );
|
|
138
|
+
|
|
139
|
+
// Return the results instead of modifying shared array
|
|
140
|
+
return testCasesWithSteps || [];
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.error(`Error processing suite ${suite.id}: ${error}`);
|
|
143
|
+
return []; // Return empty array on error
|
|
144
|
+
}
|
|
145
|
+
})
|
|
104
146
|
);
|
|
105
|
-
for (let i = 0; i < suitesTestCasesList.length; i++) {
|
|
106
|
-
let testCases: any = await this.GetTestCases(project, planId, suitesTestCasesList[i].id);
|
|
107
|
-
let testCseseWithSteps: any = await this.StructureTestCase(
|
|
108
|
-
project,
|
|
109
|
-
testCases,
|
|
110
|
-
suitesTestCasesList[i],
|
|
111
|
-
includeRequirements,
|
|
112
|
-
CustomerRequirementId,
|
|
113
|
-
requirementToTestCaseTraceMap,
|
|
114
|
-
testCaseToRequirementsTraceMap,
|
|
115
|
-
stepResultDetailsMap
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
if (testCseseWithSteps.length > 0) testCasesList = [...testCasesList, ...testCseseWithSteps];
|
|
119
|
-
}
|
|
120
147
|
|
|
148
|
+
// Wait for all promises and only then combine the results
|
|
149
|
+
const results = await Promise.all(testCaseListPromises);
|
|
150
|
+
testCasesList = results.flat(); // Combine all results into a single array
|
|
151
|
+
// logger.debug(`Performance: GetTestCasesBySuites completed in ${performance.now() - startTime}ms`);
|
|
121
152
|
return { testCasesList, requirementToTestCaseTraceMap, testCaseToRequirementsTraceMap };
|
|
122
153
|
}
|
|
123
154
|
|
|
@@ -130,13 +161,14 @@ export default class TestDataProvider {
|
|
|
130
161
|
requirementToTestCaseTraceMap: Map<string, string[]>,
|
|
131
162
|
testCaseToRequirementsTraceMap: Map<string, string[]>,
|
|
132
163
|
stepResultDetailsMap?: Map<string, any>
|
|
133
|
-
): Promise<
|
|
164
|
+
): Promise<any[]> {
|
|
134
165
|
let url = this.orgUrl + project + '/_workitems/edit/';
|
|
135
|
-
let testCasesUrlList:
|
|
166
|
+
let testCasesUrlList: any[] = [];
|
|
136
167
|
logger.debug(`Trying to structure Test case for ${project} suite: ${suite.id}:${suite.name}`);
|
|
137
168
|
try {
|
|
138
|
-
if (!testCases) {
|
|
139
|
-
|
|
169
|
+
if (!testCases || !testCases.value || testCases.count === 0) {
|
|
170
|
+
logger.warn(`No test cases found for suite: ${suite.id}`);
|
|
171
|
+
return [];
|
|
140
172
|
}
|
|
141
173
|
|
|
142
174
|
for (let i = 0; i < testCases.count; i++) {
|
|
@@ -147,7 +179,7 @@ export default class TestDataProvider {
|
|
|
147
179
|
let newurl = !stepDetailObject?.testCaseRevision
|
|
148
180
|
? testCases.value[i].testCase.url + '?$expand=All'
|
|
149
181
|
: `${testCases.value[i].testCase.url}/revisions/${stepDetailObject.testCaseRevision}?$expand=All`;
|
|
150
|
-
let test: any = await
|
|
182
|
+
let test: any = await this.fetchWithCache(newurl);
|
|
151
183
|
let testCase: TestCase = new TestCase();
|
|
152
184
|
|
|
153
185
|
testCase.title = test.fields['System.Title'];
|
|
@@ -174,7 +206,7 @@ export default class TestDataProvider {
|
|
|
174
206
|
// Only proceed if the URL contains 'workItems'
|
|
175
207
|
if (relation.url.includes('/workItems/')) {
|
|
176
208
|
try {
|
|
177
|
-
let relatedItemContent: any = await
|
|
209
|
+
let relatedItemContent: any = await this.fetchWithCache(relation.url);
|
|
178
210
|
// Check if the WorkItemType is "Requirement" before adding to relations
|
|
179
211
|
if (relatedItemContent.fields['System.WorkItemType'] === 'Requirement') {
|
|
180
212
|
const newRequirementRelation = this.createNewRequirement(
|
|
@@ -212,8 +244,8 @@ export default class TestDataProvider {
|
|
|
212
244
|
}
|
|
213
245
|
testCasesUrlList.push(testCase);
|
|
214
246
|
} catch {
|
|
215
|
-
const errorMsg = `ran into an issue while
|
|
216
|
-
logger.error(`errorMsg`);
|
|
247
|
+
const errorMsg = `ran into an issue while retrieving testCase ${testCases.value[i].testCase.id}`;
|
|
248
|
+
logger.error(`Error: ${errorMsg}`);
|
|
217
249
|
throw new Error(errorMsg);
|
|
218
250
|
}
|
|
219
251
|
}
|
|
@@ -275,16 +307,14 @@ export default class TestDataProvider {
|
|
|
275
307
|
async GetTestCases(project: string, planId: string, suiteId: string): Promise<any> {
|
|
276
308
|
let testCaseUrl: string =
|
|
277
309
|
this.orgUrl + project + '/_apis/test/Plans/' + planId + '/suites/' + suiteId + '/testcases/';
|
|
278
|
-
let testCases: any = await
|
|
310
|
+
let testCases: any = await this.fetchWithCache(testCaseUrl);
|
|
279
311
|
logger.debug(`test cases for plan ${planId} and ${suiteId} were ${testCases ? 'found' : 'not found'}`);
|
|
280
312
|
return testCases;
|
|
281
313
|
}
|
|
282
314
|
|
|
283
|
-
//gets all test point in a test case
|
|
284
315
|
async GetTestPoint(project: string, planId: string, suiteId: string, testCaseId: string): Promise<any> {
|
|
285
316
|
let testPointUrl: string = `${this.orgUrl}${project}/_apis/test/Plans/${planId}/Suites/${suiteId}/points?testCaseId=${testCaseId}`;
|
|
286
|
-
|
|
287
|
-
return testPoints;
|
|
317
|
+
return this.fetchWithCache(testPointUrl);
|
|
288
318
|
}
|
|
289
319
|
|
|
290
320
|
async CreateTestRun(
|
|
@@ -415,4 +445,12 @@ export default class TestDataProvider {
|
|
|
415
445
|
}
|
|
416
446
|
map.get(key)?.push(value);
|
|
417
447
|
};
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Clears the cache to free memory
|
|
451
|
+
*/
|
|
452
|
+
public clearCache(): void {
|
|
453
|
+
this.cache.clear();
|
|
454
|
+
logger.debug('Cache cleared');
|
|
455
|
+
}
|
|
418
456
|
}
|