zephyr-enterprise-tools 1.2.5 → 1.2.7
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/README.md +2 -0
- package/mcp-server.js +8 -6
- package/package.json +1 -1
- package/zephyr-enterprise-tools.d.ts +15 -2
- package/zephyr-enterprise-tools.js +220 -71
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ Comprehensive tools for Zephyr Enterprise - Release Readiness, Project Health, T
|
|
|
25
25
|
| `test-trends` | Test execution trends over time |
|
|
26
26
|
| `search-tests` | Search test cases by query |
|
|
27
27
|
| `user-activity` | User activity and productivity metrics |
|
|
28
|
+
| `user-trend` | Full audit log history for a user — every action across the system |
|
|
28
29
|
| `execution-burndown` | Day-by-day execution burndown (remaining vs ideal) |
|
|
29
30
|
|
|
30
31
|
---
|
|
@@ -73,6 +74,7 @@ Use zephyr-enterprise-tools as an MCP (Model Context Protocol) server with your
|
|
|
73
74
|
| `test_trends` | Get execution trends over time |
|
|
74
75
|
| `search_test_cases` | Search test cases by query |
|
|
75
76
|
| `user_activity` | Get user activity metrics |
|
|
77
|
+
| `user_trend` | Full audit log history for a user — filter by date range, entity type, and operation |
|
|
76
78
|
| `execution_burndown` | Day-by-day execution burndown chart data (remaining vs ideal) |
|
|
77
79
|
|
|
78
80
|
### Claude Desktop
|
package/mcp-server.js
CHANGED
|
@@ -20,12 +20,13 @@ import QualityGates from './zephyr-enterprise-tools.js';
|
|
|
20
20
|
const TOOLS = [
|
|
21
21
|
{
|
|
22
22
|
name: 'release_readiness',
|
|
23
|
-
description: 'Run all 4 quality gates to
|
|
23
|
+
description: 'Run all 4 quality gates with calculation details and action items. Optionally scope Test Plan Analysis to a Zephyr ZQL expression. Returns GO, CONDITIONAL GO, or NO GO status.',
|
|
24
24
|
inputSchema: {
|
|
25
25
|
type: 'object',
|
|
26
26
|
properties: {
|
|
27
27
|
projectId: { type: 'number', description: 'Zephyr project ID' },
|
|
28
28
|
releaseId: { type: 'number', description: 'Zephyr release ID' },
|
|
29
|
+
query: { type: 'string', description: 'Optional Zephyr ZQL expression for Test Plan Analysis, e.g. priority = "P1"' },
|
|
29
30
|
},
|
|
30
31
|
required: ['projectId', 'releaseId'],
|
|
31
32
|
},
|
|
@@ -44,12 +45,13 @@ const TOOLS = [
|
|
|
44
45
|
},
|
|
45
46
|
{
|
|
46
47
|
name: 'test_plan_analysis',
|
|
47
|
-
description: 'Analyze test planning status. Threshold: <80% = NO GO, 80-90% = CONDITIONAL, ≥90% = GO.',
|
|
48
|
+
description: 'Analyze test planning status, optionally scoped to testcase IDs returned by a Zephyr ZQL expression. Threshold: <80% = NO GO, 80-90% = CONDITIONAL, ≥90% = GO.',
|
|
48
49
|
inputSchema: {
|
|
49
50
|
type: 'object',
|
|
50
51
|
properties: {
|
|
51
52
|
projectId: { type: 'number', description: 'Zephyr project ID' },
|
|
52
53
|
releaseId: { type: 'number', description: 'Zephyr release ID' },
|
|
54
|
+
query: { type: 'string', description: 'Optional Zephyr ZQL expression, e.g. priority = "P1"' },
|
|
53
55
|
},
|
|
54
56
|
required: ['projectId', 'releaseId'],
|
|
55
57
|
},
|
|
@@ -142,13 +144,13 @@ const TOOLS = [
|
|
|
142
144
|
},
|
|
143
145
|
{
|
|
144
146
|
name: 'search_test_cases',
|
|
145
|
-
description: 'Search test cases
|
|
147
|
+
description: 'Search test cases using a Zephyr ZQL expression, such as priority = "P1".',
|
|
146
148
|
inputSchema: {
|
|
147
149
|
type: 'object',
|
|
148
150
|
properties: {
|
|
149
151
|
projectId: { type: 'number', description: 'Zephyr project ID' },
|
|
150
152
|
releaseId: { type: 'number', description: 'Zephyr release ID' },
|
|
151
|
-
query: { type: 'string', description: '
|
|
153
|
+
query: { type: 'string', description: 'Zephyr ZQL expression, e.g. priority = "P1"' },
|
|
152
154
|
limit: { type: 'number', description: 'Maximum results (default: 50)' },
|
|
153
155
|
},
|
|
154
156
|
required: ['projectId', 'releaseId'],
|
|
@@ -264,7 +266,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
264
266
|
|
|
265
267
|
switch (name) {
|
|
266
268
|
case 'release_readiness':
|
|
267
|
-
result = await tools.runAllGates(projectId, releaseId);
|
|
269
|
+
result = await tools.runAllGates(projectId, releaseId, { query: args.query });
|
|
268
270
|
break;
|
|
269
271
|
|
|
270
272
|
case 'requirement_coverage':
|
|
@@ -272,7 +274,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
272
274
|
break;
|
|
273
275
|
|
|
274
276
|
case 'test_plan_analysis':
|
|
275
|
-
result = await tools.testPlanAnalysisGate(projectId, releaseId);
|
|
277
|
+
result = await tools.testPlanAnalysisGate(projectId, releaseId, { query: args.query });
|
|
276
278
|
break;
|
|
277
279
|
|
|
278
280
|
case 'test_execution':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zephyr-enterprise-tools",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"description": "Comprehensive Zephyr Enterprise Tools - Release Readiness, Project Health, Test Analytics & More",
|
|
5
5
|
"main": "zephyr-enterprise-tools.js",
|
|
6
6
|
"types": "zephyr-enterprise-tools.d.ts",
|
|
@@ -38,6 +38,11 @@ export interface TestPlanResult extends GateResult {
|
|
|
38
38
|
assignedTests: number;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
export interface TestPlanOptions {
|
|
42
|
+
/** Optional Zephyr ZQL expression, e.g. `priority = "P1"`. */
|
|
43
|
+
query?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
export interface TestExecutionResult extends GateResult {
|
|
42
47
|
executionPercentage: number;
|
|
43
48
|
completedTests: number;
|
|
@@ -65,8 +70,15 @@ export interface ReleaseReadinessResult {
|
|
|
65
70
|
projectId: number;
|
|
66
71
|
releaseId: number;
|
|
67
72
|
timestamp: string;
|
|
73
|
+
report: {
|
|
74
|
+
projectName: string;
|
|
75
|
+
releaseName: string;
|
|
76
|
+
generatedAt: string;
|
|
77
|
+
fileName: string;
|
|
78
|
+
};
|
|
68
79
|
overallStatus: GateStatus;
|
|
69
80
|
summary: { passed: number; failed: number; conditional: number };
|
|
81
|
+
details: Record<string, unknown>;
|
|
70
82
|
recommendation: string;
|
|
71
83
|
gates: {
|
|
72
84
|
requirementCoverage: RequirementCoverageResult;
|
|
@@ -133,6 +145,7 @@ export interface TestTrendsResult {
|
|
|
133
145
|
}
|
|
134
146
|
|
|
135
147
|
export interface SearchTestCasesOptions {
|
|
148
|
+
/** Zephyr ZQL expression, e.g. `priority = "P1"`. */
|
|
136
149
|
query?: string;
|
|
137
150
|
limit?: number;
|
|
138
151
|
}
|
|
@@ -176,10 +189,10 @@ export declare class QualityGates {
|
|
|
176
189
|
|
|
177
190
|
// Quality Gates (Release Readiness)
|
|
178
191
|
requirementCoverageGate(projectId: number, releaseId: number): Promise<RequirementCoverageResult>;
|
|
179
|
-
testPlanAnalysisGate(projectId: number, releaseId: number): Promise<TestPlanResult>;
|
|
192
|
+
testPlanAnalysisGate(projectId: number, releaseId: number, options?: TestPlanOptions): Promise<TestPlanResult>;
|
|
180
193
|
testExecutionGate(projectId: number, releaseId: number): Promise<TestExecutionResult>;
|
|
181
194
|
defectQualityGate(projectId: number, releaseId: number): Promise<DefectQualityResult>;
|
|
182
|
-
runAllGates(projectId: number, releaseId: number): Promise<ReleaseReadinessResult>;
|
|
195
|
+
runAllGates(projectId: number, releaseId: number, options?: TestPlanOptions): Promise<ReleaseReadinessResult>;
|
|
183
196
|
|
|
184
197
|
// Analytics & Insights
|
|
185
198
|
getProjectHealth(projectId: number, releaseId: number): Promise<ProjectHealthResult>;
|
|
@@ -113,6 +113,50 @@ export class QualityGates {
|
|
|
113
113
|
return res.json();
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
async POSTv3(path, body) {
|
|
117
|
+
const v3BaseUrl = this.baseUrl.replace('/latest', '/v3');
|
|
118
|
+
const res = await fetch(`${v3BaseUrl}${path}`, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: { Accept: "application/json", "Content-Type": "application/json", ...this.authHeader() },
|
|
121
|
+
body: JSON.stringify(body),
|
|
122
|
+
});
|
|
123
|
+
if (!res.ok) {
|
|
124
|
+
const text = await res.text().catch(() => "");
|
|
125
|
+
throw new Error(`Zephyr v3 API ${res.status}: ${text}`);
|
|
126
|
+
}
|
|
127
|
+
return res.json();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async searchExecutionsByZql(releaseId, query) {
|
|
131
|
+
const pageSize = 50;
|
|
132
|
+
let firstResult = 0;
|
|
133
|
+
const executions = [];
|
|
134
|
+
|
|
135
|
+
while (true) {
|
|
136
|
+
const response = await this.POSTv3("/advancesearch/zql", {
|
|
137
|
+
firstresult: firstResult,
|
|
138
|
+
maxresults: pageSize,
|
|
139
|
+
entitytype: "execution",
|
|
140
|
+
order: "testcaseId",
|
|
141
|
+
isascorder: true,
|
|
142
|
+
is_cfield: false,
|
|
143
|
+
releaseid: String(releaseId),
|
|
144
|
+
projectid: "",
|
|
145
|
+
word: query,
|
|
146
|
+
zql: true,
|
|
147
|
+
isOld: false,
|
|
148
|
+
});
|
|
149
|
+
const page = response[0]?.results || response.results || [];
|
|
150
|
+
|
|
151
|
+
if (!Array.isArray(page) || page.length === 0) break;
|
|
152
|
+
|
|
153
|
+
executions.push(...page);
|
|
154
|
+
firstResult += page.length;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return [...new Map(executions.map(execution => [execution.id, execution])).values()];
|
|
158
|
+
}
|
|
159
|
+
|
|
116
160
|
async PUT(path, params, body) {
|
|
117
161
|
const url = new URL(`${this.baseUrl}${path}`);
|
|
118
162
|
for (const [k, v] of Object.entries(params)) {
|
|
@@ -163,22 +207,36 @@ export class QualityGates {
|
|
|
163
207
|
|
|
164
208
|
// ─── Gate 2: Test Plan Analysis ─────────────────────────────────────────────
|
|
165
209
|
|
|
166
|
-
async testPlanAnalysisGate(projectId, releaseId) {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
210
|
+
async testPlanAnalysisGate(projectId, releaseId, options = {}) {
|
|
211
|
+
const { query } = options;
|
|
212
|
+
let totalTestcases;
|
|
213
|
+
let mappedRequirements;
|
|
214
|
+
let totalRequirements;
|
|
215
|
+
let executions;
|
|
216
|
+
|
|
217
|
+
if (query) {
|
|
218
|
+
const [matchingTestcases, matchingExecutions] = await Promise.all([
|
|
219
|
+
this.searchTestCases(projectId, releaseId, {
|
|
220
|
+
query,
|
|
221
|
+
limit: Number.MAX_SAFE_INTEGER,
|
|
222
|
+
}),
|
|
223
|
+
this.searchExecutionsByZql(releaseId, query),
|
|
224
|
+
]);
|
|
225
|
+
totalTestcases = matchingTestcases.results.length;
|
|
226
|
+
executions = matchingExecutions;
|
|
227
|
+
} else {
|
|
228
|
+
const summary = await this.GET(`/summary/release/${releaseId}`, { isHideCycleEnabled: false });
|
|
229
|
+
totalTestcases = summary.testcase?.totalTestcaseCount || 0;
|
|
230
|
+
mappedRequirements = summary.requirement?.mappedRequirementCount || 0;
|
|
231
|
+
totalRequirements = summary.requirement?.totalRequirementCount || 0;
|
|
232
|
+
const executionData = await this.GET("/execution", {
|
|
233
|
+
releaseid: releaseId,
|
|
234
|
+
offset: 0,
|
|
235
|
+
pagesize: 10000,
|
|
236
|
+
includeanyoneuser: true,
|
|
237
|
+
});
|
|
238
|
+
executions = executionData.results || executionData || [];
|
|
239
|
+
}
|
|
182
240
|
const totalExecutions = Array.isArray(executions) ? executions.length : 0;
|
|
183
241
|
|
|
184
242
|
// Count assigned executions
|
|
@@ -223,6 +281,7 @@ export class QualityGates {
|
|
|
223
281
|
gate: "Test Plan Analysis",
|
|
224
282
|
projectId,
|
|
225
283
|
releaseId,
|
|
284
|
+
query: query || undefined,
|
|
226
285
|
analysis: {
|
|
227
286
|
testcasePlanning: {
|
|
228
287
|
totalTestcases,
|
|
@@ -234,7 +293,7 @@ export class QualityGates {
|
|
|
234
293
|
assignedExecutions,
|
|
235
294
|
percentage: executionAssignmentPct,
|
|
236
295
|
},
|
|
237
|
-
requirementCoverage: {
|
|
296
|
+
requirementCoverage: query ? undefined : {
|
|
238
297
|
totalRequirements,
|
|
239
298
|
mappedRequirements,
|
|
240
299
|
percentage: totalRequirements > 0 ? Math.round((mappedRequirements / totalRequirements) * 100 * 100) / 100 : 0,
|
|
@@ -481,13 +540,20 @@ export class QualityGates {
|
|
|
481
540
|
|
|
482
541
|
// ─── Run All Gates ──────────────────────────────────────────────────────────
|
|
483
542
|
|
|
484
|
-
async runAllGates(projectId, releaseId) {
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
543
|
+
async runAllGates(projectId, releaseId, options = {}) {
|
|
544
|
+
const { query } = options;
|
|
545
|
+
const [results, project, releases] = await Promise.all([
|
|
546
|
+
Promise.all([
|
|
547
|
+
this.requirementCoverageGate(projectId, releaseId),
|
|
548
|
+
this.testPlanAnalysisGate(projectId, releaseId, { query }),
|
|
549
|
+
this.testExecutionGate(projectId, releaseId),
|
|
550
|
+
this.defectQualityGate(projectId, releaseId),
|
|
551
|
+
]),
|
|
552
|
+
this.GET(`/project/${projectId}`).catch(() => ({})),
|
|
553
|
+
this.GET('/release', { projectid: projectId, isaliasallowed: false }).catch(() => []),
|
|
490
554
|
]);
|
|
555
|
+
const release = Array.isArray(releases) ? releases.find(item => Number(item.id) === Number(releaseId)) : undefined;
|
|
556
|
+
const generatedAt = new Date().toISOString();
|
|
491
557
|
|
|
492
558
|
const gates = {
|
|
493
559
|
requirementCoverage: results[0],
|
|
@@ -517,7 +583,13 @@ export class QualityGates {
|
|
|
517
583
|
return {
|
|
518
584
|
projectId,
|
|
519
585
|
releaseId,
|
|
520
|
-
timestamp:
|
|
586
|
+
timestamp: generatedAt,
|
|
587
|
+
report: {
|
|
588
|
+
projectName: project.name || `Project ${projectId}`,
|
|
589
|
+
releaseName: release?.name || `Release ${releaseId}`,
|
|
590
|
+
generatedAt,
|
|
591
|
+
fileName: this.getReportFileName(project.name || `Project ${projectId}`, release?.name || `Release ${releaseId}`, generatedAt),
|
|
592
|
+
},
|
|
521
593
|
overallStatus,
|
|
522
594
|
summary: {
|
|
523
595
|
passed: passedGates,
|
|
@@ -526,10 +598,72 @@ export class QualityGates {
|
|
|
526
598
|
total: 4,
|
|
527
599
|
},
|
|
528
600
|
gates,
|
|
601
|
+
details: this.getReadinessDetails(gates, query),
|
|
529
602
|
recommendation: this.getRecommendation(overallStatus, gates),
|
|
530
603
|
};
|
|
531
604
|
}
|
|
532
605
|
|
|
606
|
+
getReportFileName(projectName, releaseName, generatedAt) {
|
|
607
|
+
const safeSegment = value => String(value)
|
|
608
|
+
.trim()
|
|
609
|
+
.replace(/[^a-zA-Z0-9]+/g, '_')
|
|
610
|
+
.replace(/^_+|_+$/g, '');
|
|
611
|
+
const timestamp = new Date(generatedAt).toISOString();
|
|
612
|
+
const date = timestamp.slice(0, 10);
|
|
613
|
+
const time = timestamp.slice(11, 19).replace(/:/g, '-');
|
|
614
|
+
|
|
615
|
+
return `Release_Rediness_${safeSegment(projectName)}_${safeSegment(releaseName)}_${date}_${time}_UTC.pdf`;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
getReadinessDetails(gates, query) {
|
|
619
|
+
const plan = gates.testPlanAnalysis;
|
|
620
|
+
const coverage = gates.requirementCoverage;
|
|
621
|
+
const execution = gates.testExecution;
|
|
622
|
+
const defects = gates.defectQuality;
|
|
623
|
+
const planAnalysis = plan.analysis;
|
|
624
|
+
const remainingToPlan = Math.max(0, planAnalysis.testcasePlanning.totalTestcases - planAnalysis.testcasePlanning.plannedTestcases);
|
|
625
|
+
const remainingToExecute = execution.incompleteTests;
|
|
626
|
+
const additionalTestsForGo = Math.max(0, Math.ceil((0.97 * execution.totalPlannedTests) - execution.completedTests));
|
|
627
|
+
|
|
628
|
+
return {
|
|
629
|
+
testPlanAnalysis: {
|
|
630
|
+
scope: query ? "ZQL-filtered test cases" : "Release-wide",
|
|
631
|
+
query: query || undefined,
|
|
632
|
+
calculation: {
|
|
633
|
+
testcasePlanning: `${planAnalysis.testcasePlanning.plannedTestcases} / ${planAnalysis.testcasePlanning.totalTestcases} = ${planAnalysis.testcasePlanning.percentage}%`,
|
|
634
|
+
executionAssignment: `${planAnalysis.executionAssignment.assignedExecutions} / ${planAnalysis.executionAssignment.totalExecutions} = ${planAnalysis.executionAssignment.percentage}%`,
|
|
635
|
+
overallPlanning: `(${planAnalysis.testcasePlanning.percentage}% + ${planAnalysis.executionAssignment.percentage}%) / 2 = ${plan.overallPlanningPercentage}%`,
|
|
636
|
+
},
|
|
637
|
+
actionItems: remainingToPlan > 0
|
|
638
|
+
? [`Plan the remaining ${remainingToPlan} test cases.`, "Maintain execution assignment as tests are planned."]
|
|
639
|
+
: ["Maintain complete test planning and execution assignment."],
|
|
640
|
+
},
|
|
641
|
+
requirementCoverage: {
|
|
642
|
+
scope: "Release-wide",
|
|
643
|
+
calculation: `${coverage.coveredRequirements} / ${coverage.totalRequirements} = ${coverage.coveragePercentage}%`,
|
|
644
|
+
uncoveredRequirementIds: [],
|
|
645
|
+
detailAvailability: "Zephyr release summaries provide the uncovered count but not requirement IDs.",
|
|
646
|
+
actionItems: coverage.notCoveredRequirements > 0
|
|
647
|
+
? [`Create and map tests for ${coverage.notCoveredRequirements} uncovered requirements.`, "Confirm requirement mappings before release sign-off."]
|
|
648
|
+
: ["Maintain requirement-to-test traceability through release sign-off."],
|
|
649
|
+
},
|
|
650
|
+
testExecution: {
|
|
651
|
+
scope: "Release-wide",
|
|
652
|
+
calculation: `${execution.completedTests} / ${execution.totalPlannedTests} = ${execution.executionPercentage}%`,
|
|
653
|
+
actionItems: remainingToExecute > 0
|
|
654
|
+
? [`Execute or resolve ${remainingToExecute} incomplete tests.`, `Complete at least ${additionalTestsForGo} additional tests to reach the 97% GO threshold.`, ...(execution.breakdown.failed > 0 ? [`Investigate and rerun ${execution.breakdown.failed} failed test(s).`] : [])]
|
|
655
|
+
: ["Maintain execution completion through release sign-off."],
|
|
656
|
+
},
|
|
657
|
+
defectQuality: {
|
|
658
|
+
scope: "Release-wide",
|
|
659
|
+
defects: defects.breakdown,
|
|
660
|
+
actionItems: defects.unresolvedDefects > 0
|
|
661
|
+
? ["Resolve all blocker defects.", "Reduce unresolved high-risk defects to 10 or fewer before approval."]
|
|
662
|
+
: ["Verify resolved defect fixes with targeted regression coverage.", "Confirm product-owner approval for any Won't Fix resolutions."],
|
|
663
|
+
},
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
533
667
|
getRecommendation(status, gates) {
|
|
534
668
|
const issues = [];
|
|
535
669
|
|
|
@@ -1031,53 +1165,65 @@ export class QualityGates {
|
|
|
1031
1165
|
async searchTestCases(projectId, releaseId, options = {}) {
|
|
1032
1166
|
const { query = '', status, priority, limit = 50 } = options;
|
|
1033
1167
|
|
|
1034
|
-
// Get test cases
|
|
1035
|
-
const params = {
|
|
1036
|
-
projectId: projectId,
|
|
1037
|
-
releaseId: releaseId,
|
|
1038
|
-
offset: 0,
|
|
1039
|
-
maxRecords: 500,
|
|
1040
|
-
};
|
|
1041
|
-
|
|
1042
|
-
if (query) params.word = query;
|
|
1043
|
-
|
|
1044
1168
|
let testcases = [];
|
|
1045
|
-
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1169
|
+
if (query) {
|
|
1170
|
+
const pageSize = 5000;
|
|
1171
|
+
let firstResult = 0;
|
|
1172
|
+
|
|
1173
|
+
while (true) {
|
|
1174
|
+
const tcData = await this.POSTv3("/advancesearch/zql", {
|
|
1175
|
+
firstresult: firstResult,
|
|
1176
|
+
maxresults: pageSize,
|
|
1177
|
+
entitytype: "testcase",
|
|
1178
|
+
order: "orderId",
|
|
1179
|
+
isascorder: true,
|
|
1180
|
+
is_cfield: false,
|
|
1181
|
+
releaseid: String(releaseId),
|
|
1182
|
+
projectid: String(projectId),
|
|
1183
|
+
word: query,
|
|
1184
|
+
zql: true,
|
|
1185
|
+
isOld: false,
|
|
1186
|
+
});
|
|
1187
|
+
const page = tcData[0]?.results || tcData.results || [];
|
|
1188
|
+
|
|
1189
|
+
if (!Array.isArray(page) || page.length === 0) break;
|
|
1190
|
+
|
|
1191
|
+
testcases.push(...page);
|
|
1192
|
+
firstResult += page.length;
|
|
1052
1193
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1194
|
+
testcases = [...new Map(testcases.map(testcase => [testcase.testcase?.id || testcase.id, testcase])).values()];
|
|
1195
|
+
} else {
|
|
1196
|
+
const params = {
|
|
1197
|
+
projectId,
|
|
1198
|
+
releaseId,
|
|
1199
|
+
offset: 0,
|
|
1200
|
+
maxRecords: 500,
|
|
1201
|
+
};
|
|
1055
1202
|
try {
|
|
1056
|
-
const tcData = await this.GET("/testcase", params);
|
|
1203
|
+
const tcData = await this.GET("/testcase/tree", params);
|
|
1057
1204
|
testcases = tcData.results || tcData || [];
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
results
|
|
1065
|
-
}
|
|
1205
|
+
if (!Array.isArray(testcases)) {
|
|
1206
|
+
testcases = this.flattenTestcaseTree(tcData);
|
|
1207
|
+
}
|
|
1208
|
+
} catch (e) {
|
|
1209
|
+
try {
|
|
1210
|
+
const tcData = await this.GET("/testcase", params);
|
|
1211
|
+
testcases = tcData.results || tcData || [];
|
|
1212
|
+
} catch (e2) {
|
|
1213
|
+
return {
|
|
1214
|
+
tool: "Search Test Cases",
|
|
1215
|
+
projectId,
|
|
1216
|
+
releaseId,
|
|
1217
|
+
error: "Unable to fetch test cases",
|
|
1218
|
+
results: [],
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1066
1221
|
}
|
|
1067
1222
|
}
|
|
1068
1223
|
|
|
1069
1224
|
// Filter results
|
|
1070
1225
|
let filtered = testcases;
|
|
1071
1226
|
|
|
1072
|
-
if (query) {
|
|
1073
|
-
const q = query.toLowerCase();
|
|
1074
|
-
filtered = filtered.filter(tc =>
|
|
1075
|
-
(tc.name || '').toLowerCase().includes(q) ||
|
|
1076
|
-
(tc.testcaseKey || tc.alternateId || '').toLowerCase().includes(q) ||
|
|
1077
|
-
(tc.description || '').toLowerCase().includes(q)
|
|
1078
|
-
);
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
1227
|
if (status) {
|
|
1082
1228
|
filtered = filtered.filter(tc =>
|
|
1083
1229
|
(tc.status || '').toLowerCase() === status.toLowerCase()
|
|
@@ -1091,17 +1237,20 @@ export class QualityGates {
|
|
|
1091
1237
|
}
|
|
1092
1238
|
|
|
1093
1239
|
// Map to clean output
|
|
1094
|
-
const results = filtered.slice(0, limit).map(tc =>
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1240
|
+
const results = filtered.slice(0, limit).map(tc => {
|
|
1241
|
+
const testcase = tc.testcase || tc;
|
|
1242
|
+
return {
|
|
1243
|
+
id: testcase.id,
|
|
1244
|
+
key: testcase.testcaseKey || testcase.alternateId || testcase.externalId,
|
|
1245
|
+
name: testcase.name,
|
|
1246
|
+
status: testcase.status,
|
|
1247
|
+
priority: testcase.priority,
|
|
1248
|
+
automated: testcase.automated || testcase.isAutomated || false,
|
|
1101
1249
|
folder: tc.folderPath || tc.tcrCatalogTreeId?.name,
|
|
1102
|
-
estimatedTime:
|
|
1103
|
-
tags:
|
|
1104
|
-
|
|
1250
|
+
estimatedTime: testcase.estimatedTime,
|
|
1251
|
+
tags: testcase.tags || [],
|
|
1252
|
+
};
|
|
1253
|
+
});
|
|
1105
1254
|
|
|
1106
1255
|
return {
|
|
1107
1256
|
tool: "Search Test Cases",
|