@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.
@@ -1,21 +1,18 @@
1
1
  import { TFSServices } from '../helpers/tfs';
2
- import { Workitem } from '../models/tfs-data';
3
- import { Helper, suiteData, Links, Trace, Relations } from '../helpers/helper';
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
- let testCaseData = await TFSServices.getItemContent(url, this.token);
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 TFSServices.getItemContent(testPlanUrl, this.token);
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
- let testSuites = await TFSServices.getItemContent(testsuitesUrl, this.token);
44
- return testSuites;
45
- } catch (e) {}
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
- let suites = await TFSServices.getItemContent(url, this.token);
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
- let suites = await this.GetTestSuiteById(project, planId, suiteId.toString(), recursive);
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
- recursiv: boolean,
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
- recursiv
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<Array<any>> {
164
+ ): Promise<any[]> {
134
165
  let url = this.orgUrl + project + '/_workitems/edit/';
135
- let testCasesUrlList: Array<any> = new Array<any>();
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
- throw new Error('test cases were not found');
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 TFSServices.getItemContent(newurl, this.token);
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 TFSServices.getItemContent(relation.url, this.token);
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 retriving testCase ${testCases.value[i].testCase.id}`;
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 TFSServices.getItemContent(testCaseUrl, this.token);
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
- let testPoints: any = await TFSServices.getItemContent(testPointUrl, this.token);
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
  }