@uarlouski/testrail-mcp-server 1.3.0 → 1.3.1
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/dist/client/testrail.js +167 -0
- package/{src/index.ts → dist/index.js} +25 -35
- package/{src/tools/add_attachment_to_run.ts → dist/tools/add_attachment_to_run.js} +8 -18
- package/{src/tools/add_results.ts → dist/tools/add_results.js} +3 -7
- package/{src/tools/add_run.ts → dist/tools/add_run.js} +3 -8
- package/{src/tools/create_case.ts → dist/tools/create_case.js} +1 -7
- package/{src/tools/get_case.ts → dist/tools/get_case.js} +2 -11
- package/{src/tools/get_case_fields.ts → dist/tools/get_case_fields.js} +8 -37
- package/{src/tools/get_cases.ts → dist/tools/get_cases.js} +5 -19
- package/{src/tools/get_projects.ts → dist/tools/get_projects.js} +1 -6
- package/{src/tools/get_sections.ts → dist/tools/get_sections.js} +1 -6
- package/{src/tools/get_statuses.ts → dist/tools/get_statuses.js} +2 -7
- package/{src/tools/get_templates.ts → dist/tools/get_templates.js} +1 -6
- package/{src/tools/get_tests.ts → dist/tools/get_tests.js} +5 -10
- package/{src/tools/update_case.ts → dist/tools/update_case.js} +1 -7
- package/{src/tools/update_cases.ts → dist/tools/update_cases.js} +1 -6
- package/dist/types/custom.js +1 -0
- package/{src/types/testrail.ts → dist/types/testrail.js} +2 -42
- package/{src/utils/mapper.ts → dist/utils/mapper.js} +16 -36
- package/{src/utils/sanitizer.ts → dist/utils/sanitizer.js} +4 -8
- package/package.json +8 -2
- package/.env.example +0 -3
- package/.github/workflows/ci.yml +0 -46
- package/.github/workflows/publish.yml +0 -35
- package/CHANGELOG.md +0 -38
- package/jest.config.js +0 -24
- package/src/client/testrail.ts +0 -227
- package/src/types/custom.ts +0 -19
- package/test/client/testrail.test.ts +0 -545
- package/test/tools/add_results.test.ts +0 -94
- package/test/tools/add_run.test.ts +0 -83
- package/test/tools/create_case.test.ts +0 -101
- package/test/tools/get_case.test.ts +0 -185
- package/test/tools/get_case_fields.test.ts +0 -132
- package/test/tools/get_cases.test.ts +0 -104
- package/test/tools/get_projects.test.ts +0 -38
- package/test/tools/get_sections.test.ts +0 -39
- package/test/tools/get_statuses.test.ts +0 -56
- package/test/tools/get_templates.test.ts +0 -38
- package/test/tools/get_tests.test.ts +0 -47
- package/test/tools/update_case.test.ts +0 -102
- package/test/tools/update_cases.test.ts +0 -89
- package/test/utils/mapper.test.ts +0 -112
- package/test/utils/sanitizer.test.ts +0 -143
- package/tsconfig.json +0 -24
package/src/client/testrail.ts
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import { Case, Section, Priority, CaseType, CaseField, Template, Project, Run, Status, Test, Result, Attachment } from "../types/testrail.js";
|
|
2
|
-
|
|
3
|
-
import * as fs from "fs";
|
|
4
|
-
|
|
5
|
-
interface PaginatedCasesResponse {
|
|
6
|
-
cases: Case[];
|
|
7
|
-
_links: { next: string | null };
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface PaginatedTestsResponse {
|
|
11
|
-
tests: Test[];
|
|
12
|
-
_links: { next: string | null };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const API_INDEX = '/index.php?';
|
|
16
|
-
const API_BASE_V2 = `${API_INDEX}/api/v2`;
|
|
17
|
-
|
|
18
|
-
export class TestRailClient {
|
|
19
|
-
private baseUrl: string;
|
|
20
|
-
private headers: HeadersInit;
|
|
21
|
-
private auth: string;
|
|
22
|
-
private prioritiesPromise: Promise<Priority[]> | null = null;
|
|
23
|
-
private caseTypesPromise: Promise<CaseType[]> | null = null;
|
|
24
|
-
private caseFieldsPromise: Promise<CaseField[]> | null = null;
|
|
25
|
-
private statusesPromise: Promise<Status[]> | null = null;
|
|
26
|
-
private projectsPromise: Promise<Project[]> | null = null;
|
|
27
|
-
private templatesPromiseMap: Map<string, Promise<Template[]>> = new Map();
|
|
28
|
-
|
|
29
|
-
constructor(baseUrl: string, email: string, apiKey: string) {
|
|
30
|
-
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
31
|
-
const auth = Buffer.from(`${email}:${apiKey}`).toString('base64');
|
|
32
|
-
this.auth = `Basic ${auth}`;
|
|
33
|
-
this.headers = {
|
|
34
|
-
"Content-Type": "application/json",
|
|
35
|
-
"Authorization": this.auth,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async getCase(caseId: string): Promise<Case> {
|
|
40
|
-
return this.get<Case>(`${API_BASE_V2}/get_case/${caseId}`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async getCases(projectId: string, sectionId?: string, filter?: Record<string, string>): Promise<Case[]> {
|
|
44
|
-
let url = `${API_BASE_V2}/get_cases/${projectId}`;
|
|
45
|
-
|
|
46
|
-
if (sectionId) {
|
|
47
|
-
url += `§ion_id=${sectionId}`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (filter) {
|
|
51
|
-
for (const [key, value] of Object.entries(filter)) {
|
|
52
|
-
url += `&${key}=${encodeURIComponent(value)}`;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const allCases: Case[] = [];
|
|
57
|
-
let nextUrl: string | null = url;
|
|
58
|
-
|
|
59
|
-
while (nextUrl) {
|
|
60
|
-
const response: PaginatedCasesResponse = await this.get<PaginatedCasesResponse>(nextUrl);
|
|
61
|
-
|
|
62
|
-
allCases.push(...response.cases);
|
|
63
|
-
|
|
64
|
-
nextUrl = response._links?.next;
|
|
65
|
-
if (nextUrl) {
|
|
66
|
-
nextUrl = `${API_INDEX}${nextUrl}`;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return allCases;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async getSection(sectionId: string): Promise<Section> {
|
|
74
|
-
return this.get<Section>(`${API_BASE_V2}/get_section/${sectionId}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async getPriorities(): Promise<Priority[]> {
|
|
78
|
-
if (!this.prioritiesPromise) {
|
|
79
|
-
this.prioritiesPromise = this.get<Priority[]>(`${API_BASE_V2}/get_priorities`);
|
|
80
|
-
}
|
|
81
|
-
return this.prioritiesPromise;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async getCaseTypes(): Promise<CaseType[]> {
|
|
85
|
-
if (!this.caseTypesPromise) {
|
|
86
|
-
this.caseTypesPromise = this.get<CaseType[]>(`${API_BASE_V2}/get_case_types`);
|
|
87
|
-
}
|
|
88
|
-
return this.caseTypesPromise;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async getCaseFields(): Promise<CaseField[]> {
|
|
92
|
-
if (!this.caseFieldsPromise) {
|
|
93
|
-
this.caseFieldsPromise = this.get<CaseField[]>(`${API_BASE_V2}/get_case_fields`);
|
|
94
|
-
}
|
|
95
|
-
return this.caseFieldsPromise;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async getStatuses(): Promise<Status[]> {
|
|
99
|
-
if (!this.statusesPromise) {
|
|
100
|
-
this.statusesPromise = this.get<Status[]>(`${API_BASE_V2}/get_statuses`);
|
|
101
|
-
}
|
|
102
|
-
return this.statusesPromise;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async getTemplates(projectId: string): Promise<Template[]> {
|
|
106
|
-
if (!this.templatesPromiseMap.has(projectId)) {
|
|
107
|
-
this.templatesPromiseMap.set(
|
|
108
|
-
projectId,
|
|
109
|
-
this.get<Template[]>(`${API_BASE_V2}/get_templates/${projectId}`)
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
return this.templatesPromiseMap.get(projectId)!;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async updateCase(caseId: string, fields: Record<string, any>): Promise<Case> {
|
|
116
|
-
return this.post<Case>(`${API_BASE_V2}/update_case/${caseId}`, fields);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async updateCases(suiteId: number, caseIds: number[], fields: Record<string, any>): Promise<Case[]> {
|
|
120
|
-
const response = await this.post<{ updated_cases: Case[] }>(`${API_BASE_V2}/update_cases/${suiteId}`, {
|
|
121
|
-
case_ids: caseIds,
|
|
122
|
-
...fields,
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
return response.updated_cases;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async createCase(sectionId: string, fields: Record<string, any>): Promise<Case> {
|
|
129
|
-
return this.post<Case>(`${API_BASE_V2}/add_case/${sectionId}`, fields);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async addRun(projectId: string, fields: Record<string, any>): Promise<Run> {
|
|
133
|
-
return this.post<Run>(`${API_BASE_V2}/add_run/${projectId}`, fields);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async getSections(projectId: string): Promise<Section[]> {
|
|
137
|
-
const response = await this.get<{ sections: Section[] }>(`${API_BASE_V2}/get_sections/${projectId}`);
|
|
138
|
-
return response.sections;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async getProjects(): Promise<Project[]> {
|
|
142
|
-
if (!this.projectsPromise) {
|
|
143
|
-
this.projectsPromise = this.get<{ projects: Project[] }>(`${API_BASE_V2}/get_projects`)
|
|
144
|
-
.then(response => response.projects);
|
|
145
|
-
}
|
|
146
|
-
return this.projectsPromise;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
async getTests(runId: number, statusId?: number[]): Promise<Test[]> {
|
|
151
|
-
let url = `${API_BASE_V2}/get_tests/${runId}`;
|
|
152
|
-
|
|
153
|
-
if (statusId && statusId.length > 0) {
|
|
154
|
-
url += `&status_id=${statusId.join(',')}`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const allTests: Test[] = [];
|
|
158
|
-
let nextUrl: string | null = url;
|
|
159
|
-
|
|
160
|
-
while (nextUrl) {
|
|
161
|
-
const response: PaginatedTestsResponse = await this.get<PaginatedTestsResponse>(nextUrl);
|
|
162
|
-
allTests.push(...response.tests);
|
|
163
|
-
|
|
164
|
-
nextUrl = response._links?.next;
|
|
165
|
-
if (nextUrl) {
|
|
166
|
-
nextUrl = `${API_INDEX}${nextUrl}`;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return allTests;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
async addResults(runId: number, results: Array<Record<string, any>>): Promise<Result[]> {
|
|
175
|
-
return this.post<Result[]>(`${API_BASE_V2}/add_results/${runId}`, { results });
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async addAttachmentToRun(runId: number, filePath: string, filename: string): Promise<Attachment> {
|
|
179
|
-
const fileBuffer = await fs.promises.readFile(filePath);
|
|
180
|
-
const blob = new Blob([fileBuffer]);
|
|
181
|
-
const formData = new FormData();
|
|
182
|
-
formData.append('attachment', blob, filename);
|
|
183
|
-
|
|
184
|
-
const headers: HeadersInit = {
|
|
185
|
-
'Authorization': this.auth,
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
return this._executeRequest<Attachment>('POST', `${API_BASE_V2}/add_attachment_to_run/${runId}`, headers, formData);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
private async get<T>(endpoint: string): Promise<T> {
|
|
192
|
-
return this.executeRequest<T>('GET', endpoint);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
private async post<T>(endpoint: string, data: Record<string, any>): Promise<T> {
|
|
196
|
-
return this.executeRequest<T>('POST', endpoint, data);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
private async executeRequest<T>(method: 'GET' | 'POST', endpoint: string, data?: Record<string, any>): Promise<T> {
|
|
200
|
-
let jsonData = undefined;
|
|
201
|
-
if (data) {
|
|
202
|
-
jsonData = JSON.stringify(data);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return this._executeRequest<T>(method, endpoint, this.headers, jsonData);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
private async _executeRequest<T>(method: 'GET' | 'POST', endpoint: string, headers: HeadersInit, body?: any): Promise<T> {
|
|
209
|
-
const url = `${this.baseUrl}${endpoint}`;
|
|
210
|
-
const params: RequestInit = {
|
|
211
|
-
method,
|
|
212
|
-
headers,
|
|
213
|
-
body,
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const response = await fetch(url, params);
|
|
217
|
-
|
|
218
|
-
if (!response.ok) {
|
|
219
|
-
let errorMessage = `TestRail: ${response.status} ${response.statusText}`;
|
|
220
|
-
const errorText = await response.text();
|
|
221
|
-
errorMessage += ` - ${errorText}`;
|
|
222
|
-
throw new Error(errorMessage);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return await response.json() as T;
|
|
226
|
-
}
|
|
227
|
-
}
|
package/src/types/custom.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
export interface TestCaseResponse {
|
|
4
|
-
id: number;
|
|
5
|
-
title: string;
|
|
6
|
-
section: string;
|
|
7
|
-
type: string;
|
|
8
|
-
priority: string;
|
|
9
|
-
labels: string[];
|
|
10
|
-
references: string | null;
|
|
11
|
-
[key: string]: any;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface ToolDefinition<T extends z.ZodRawShape, Context = any> {
|
|
15
|
-
name: string;
|
|
16
|
-
description: string;
|
|
17
|
-
parameters: T;
|
|
18
|
-
handler: (args: z.infer<z.ZodObject<T>>, context: Context) => Promise<Record<string, any>>;
|
|
19
|
-
}
|