@zereight/mcp-gitlab 2.0.22 → 2.0.24
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 +36 -5
- package/build/index.js +500 -198
- package/build/schemas.js +100 -12
- package/build/test/test-list-project-members.js +132 -0
- package/build/test/test-merge-request-approvals.js +187 -0
- package/build/test/test-mr-diffs-filter.js +132 -0
- package/build/test/utils/mock-gitlab-server.js +54 -0
- package/package.json +6 -6
- package/build/test/readonly-mcp-tests.js +0 -381
|
@@ -327,6 +327,60 @@ export class MockGitLabServer {
|
|
|
327
327
|
}
|
|
328
328
|
]);
|
|
329
329
|
});
|
|
330
|
+
// GET /api/v4/projects/:projectId/merge_requests/:mr_iid/changes - Get MR diffs
|
|
331
|
+
this.app.get('/api/v4/projects/:projectId/merge_requests/:mr_iid/changes', (req, res) => {
|
|
332
|
+
const mrIid = parseInt(req.params.mr_iid);
|
|
333
|
+
res.json({
|
|
334
|
+
id: mrIid,
|
|
335
|
+
iid: mrIid,
|
|
336
|
+
project_id: parseInt(req.params.projectId),
|
|
337
|
+
title: `Test MR ${mrIid}`,
|
|
338
|
+
state: 'opened',
|
|
339
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
340
|
+
changes: [
|
|
341
|
+
{
|
|
342
|
+
old_path: 'src/index.ts',
|
|
343
|
+
new_path: 'src/index.ts',
|
|
344
|
+
a_mode: '100644',
|
|
345
|
+
b_mode: '100644',
|
|
346
|
+
diff: '@@ -1,1 +1,2 @@\n-line 1\n+line 1 modified\n+new line 2\n',
|
|
347
|
+
new_file: false,
|
|
348
|
+
renamed_file: false,
|
|
349
|
+
deleted_file: false
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
old_path: 'vendor/package/file.js',
|
|
353
|
+
new_path: 'vendor/package/file.js',
|
|
354
|
+
a_mode: '100644',
|
|
355
|
+
b_mode: '100644',
|
|
356
|
+
diff: '@@ -1,1 +1,1 @@\n-vendor content old\n+vendor content new\n',
|
|
357
|
+
new_file: false,
|
|
358
|
+
renamed_file: false,
|
|
359
|
+
deleted_file: false
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
old_path: 'README.md',
|
|
363
|
+
new_path: 'README.md',
|
|
364
|
+
a_mode: '100644',
|
|
365
|
+
b_mode: '100644',
|
|
366
|
+
diff: '@@ -1,1 +1,1 @@\n-old readme\n+new readme\n',
|
|
367
|
+
new_file: false,
|
|
368
|
+
renamed_file: false,
|
|
369
|
+
deleted_file: false
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
old_path: 'package-lock.json',
|
|
373
|
+
new_path: 'package-lock.json',
|
|
374
|
+
a_mode: '100644',
|
|
375
|
+
b_mode: '100644',
|
|
376
|
+
diff: '{\n- "version": "1.0.0"\n+ "version": "1.0.1"\n}\n',
|
|
377
|
+
new_file: false,
|
|
378
|
+
renamed_file: false,
|
|
379
|
+
deleted_file: false
|
|
380
|
+
}
|
|
381
|
+
]
|
|
382
|
+
});
|
|
383
|
+
});
|
|
330
384
|
// Health check endpoint
|
|
331
385
|
this.app.get('/health', (req, res) => {
|
|
332
386
|
res.json({ status: 'ok', message: 'Mock GitLab API is running' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.24",
|
|
4
4
|
"description": "MCP server for using the GitLab API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "zereight",
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"watch": "tsc --watch",
|
|
28
28
|
"deploy": "npm publish --access public",
|
|
29
29
|
"changelog": "auto-changelog -p",
|
|
30
|
-
"test": "
|
|
31
|
-
"test:
|
|
30
|
+
"test": "npm run test:all",
|
|
31
|
+
"test:all": "npm run build && npm run test:mock && npm run test:live",
|
|
32
|
+
"test:mock": "npx tsx --test test/remote-auth-simple-test.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && tsx test/test-list-project-members.ts",
|
|
33
|
+
"test:live": "node test/validate-api.js",
|
|
32
34
|
"test:remote-auth": "npm run build && npx tsx --test test/remote-auth-simple-test.ts",
|
|
33
|
-
"test:server": "npm run build && node build/test/test-all-transport-server.js",
|
|
34
|
-
"test:mcp:readonly": "tsx test/readonly-mcp-tests.ts",
|
|
35
35
|
"test:oauth": "tsx test/oauth-tests.ts",
|
|
36
36
|
"test:list-merge-requests": "npm run build && tsx test/test-list-merge-requests.ts",
|
|
37
|
-
"test:
|
|
37
|
+
"test:approvals": "npm run build && tsx test/test-merge-request-approvals.ts",
|
|
38
38
|
"lint": "eslint . --ext .ts",
|
|
39
39
|
"lint:fix": "eslint . --ext .ts --fix",
|
|
40
40
|
"format": "prettier --write \"**/*.{js,ts,json,md}\"",
|
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ts-node
|
|
2
|
-
/**
|
|
3
|
-
* 읽기 전용 GitLab MCP 도구 테스트 스크립트 (TypeScript)
|
|
4
|
-
* 읽기 전용 GitLab MCP 도구들을 자동으로 테스트합니다.
|
|
5
|
-
*/
|
|
6
|
-
import { spawn } from 'child_process';
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
import * as dotenv from 'dotenv';
|
|
9
|
-
// .env 파일 로드
|
|
10
|
-
dotenv.config();
|
|
11
|
-
// 환경 변수 설정
|
|
12
|
-
const GITLAB_API_URL = process.env.GITLAB_API_URL || "https://gitlab.com/api/v4";
|
|
13
|
-
const GITLAB_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN || process.env.GITLAB_TOKEN;
|
|
14
|
-
const TEST_PROJECT_ID = process.env.GITLAB_PROJECT_ID || process.env.TEST_PROJECT_ID;
|
|
15
|
-
// 테스트 결과 저장
|
|
16
|
-
const testResults = {
|
|
17
|
-
passed: 0,
|
|
18
|
-
failed: 0,
|
|
19
|
-
skipped: 0,
|
|
20
|
-
total: 0,
|
|
21
|
-
details: []
|
|
22
|
-
};
|
|
23
|
-
// 테스트 도구 목록 (GitLab MCP에서 제공하는 모든 도구들)
|
|
24
|
-
const mcpTools = [
|
|
25
|
-
// 프로젝트 관련
|
|
26
|
-
{ name: 'list_projects', category: 'project', required: false },
|
|
27
|
-
{ name: 'search_repositories', category: 'project', required: false },
|
|
28
|
-
{ name: 'get_project', category: 'project', required: true },
|
|
29
|
-
{ name: 'list_project_members', category: 'project', required: true },
|
|
30
|
-
{ name: 'list_group_projects', category: 'project', required: false },
|
|
31
|
-
// 이슈 관련
|
|
32
|
-
{ name: 'list_issues', category: 'issue', required: true },
|
|
33
|
-
{ name: 'my_issues', category: 'issue', required: false },
|
|
34
|
-
{ name: 'get_issue', category: 'issue', required: true },
|
|
35
|
-
{ name: 'list_issue_discussions', category: 'issue', required: true },
|
|
36
|
-
{ name: 'list_issue_links', category: 'issue', required: true },
|
|
37
|
-
// 머지 리퀘스트 관련
|
|
38
|
-
{ name: 'list_merge_requests', category: 'merge_request', required: true },
|
|
39
|
-
{ name: 'get_merge_request', category: 'merge_request', required: true },
|
|
40
|
-
{ name: 'get_merge_request_diffs', category: 'merge_request', required: true },
|
|
41
|
-
{ name: 'list_merge_request_diffs', category: 'merge_request', required: true },
|
|
42
|
-
{ name: 'get_branch_diffs', category: 'merge_request', required: true },
|
|
43
|
-
{ name: 'mr_discussions', category: 'merge_request', required: true },
|
|
44
|
-
// 파이프라인 관련
|
|
45
|
-
{ name: 'list_pipelines', category: 'pipeline', required: true },
|
|
46
|
-
{ name: 'get_pipeline', category: 'pipeline', required: true },
|
|
47
|
-
{ name: 'list_pipeline_jobs', category: 'pipeline', required: true },
|
|
48
|
-
{ name: 'list_pipeline_trigger_jobs', category: 'pipeline', required: true },
|
|
49
|
-
{ name: 'get_pipeline_job', category: 'pipeline', required: true },
|
|
50
|
-
{ name: 'get_pipeline_job_output', category: 'pipeline', required: true },
|
|
51
|
-
// 파일 관리
|
|
52
|
-
{ name: 'get_file_contents', category: 'file', required: true },
|
|
53
|
-
{ name: 'get_repository_tree', category: 'file', required: true },
|
|
54
|
-
// 커밋 관련
|
|
55
|
-
{ name: 'list_commits', category: 'commit', required: true },
|
|
56
|
-
{ name: 'get_commit', category: 'commit', required: true },
|
|
57
|
-
{ name: 'get_commit_diff', category: 'commit', required: true },
|
|
58
|
-
// 라벨 관련
|
|
59
|
-
{ name: 'list_labels', category: 'label', required: true },
|
|
60
|
-
{ name: 'get_label', category: 'label', required: true },
|
|
61
|
-
// 네임스페이스 관련
|
|
62
|
-
{ name: 'list_namespaces', category: 'namespace', required: false },
|
|
63
|
-
{ name: 'get_namespace', category: 'namespace', required: false },
|
|
64
|
-
{ name: 'verify_namespace', category: 'namespace', required: false },
|
|
65
|
-
// 사용자 관련
|
|
66
|
-
{ name: 'get_users', category: 'user', required: false },
|
|
67
|
-
// 이벤트 관련
|
|
68
|
-
{ name: 'list_events', category: 'event', required: false },
|
|
69
|
-
{ name: 'get_project_events', category: 'event', required: true },
|
|
70
|
-
// 마일스톤 관련 (선택적)
|
|
71
|
-
{ name: 'list_milestones', category: 'milestone', required: true },
|
|
72
|
-
{ name: 'get_milestone', category: 'milestone', required: true },
|
|
73
|
-
// 위키 관련 (선택적)
|
|
74
|
-
{ name: 'list_wiki_pages', category: 'wiki', required: true },
|
|
75
|
-
{ name: 'get_wiki_page', category: 'wiki', required: true },
|
|
76
|
-
// 그룹 이터레이션 관련
|
|
77
|
-
{ name: 'list_group_iterations', category: 'iteration', required: false }
|
|
78
|
-
];
|
|
79
|
-
// MCP 응답 파싱 함수
|
|
80
|
-
function parseMCPResponse(output) {
|
|
81
|
-
const lines = output.trim().split('\n');
|
|
82
|
-
for (const line of lines) {
|
|
83
|
-
if (line.startsWith('{')) {
|
|
84
|
-
try {
|
|
85
|
-
const result = JSON.parse(line);
|
|
86
|
-
if (result.result !== undefined) {
|
|
87
|
-
return result.result;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
catch (e) {
|
|
91
|
-
// JSON 파싱 실패 시 계속 시도
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return { success: true, raw: output };
|
|
96
|
-
}
|
|
97
|
-
// MCP 서버와 통신하는 함수
|
|
98
|
-
async function callMCPTool(toolName, parameters = {}) {
|
|
99
|
-
return new Promise((resolve, reject) => {
|
|
100
|
-
const mcpProcess = spawn('node', ['build/index.js'], {
|
|
101
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
102
|
-
env: {
|
|
103
|
-
...process.env,
|
|
104
|
-
GITLAB_PERSONAL_ACCESS_TOKEN: GITLAB_TOKEN,
|
|
105
|
-
GITLAB_API_URL: GITLAB_API_URL,
|
|
106
|
-
GITLAB_PROJECT_ID: TEST_PROJECT_ID
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
let output = '';
|
|
110
|
-
let errorOutput = '';
|
|
111
|
-
mcpProcess.stdout?.on('data', (data) => {
|
|
112
|
-
output += data.toString();
|
|
113
|
-
});
|
|
114
|
-
mcpProcess.stderr?.on('data', (data) => {
|
|
115
|
-
errorOutput += data.toString();
|
|
116
|
-
});
|
|
117
|
-
mcpProcess.on('close', (code) => {
|
|
118
|
-
if (code === 0) {
|
|
119
|
-
try {
|
|
120
|
-
const result = parseMCPResponse(output);
|
|
121
|
-
resolve(result);
|
|
122
|
-
}
|
|
123
|
-
catch (e) {
|
|
124
|
-
reject(new Error(`MCP 응답 파싱 실패: ${e.message}`));
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
reject(new Error(`MCP 프로세스 종료 코드 ${code}: ${errorOutput}`));
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
// MCP 요청 전송
|
|
132
|
-
const request = {
|
|
133
|
-
jsonrpc: "2.0",
|
|
134
|
-
id: 1,
|
|
135
|
-
method: "tools/call",
|
|
136
|
-
params: {
|
|
137
|
-
name: toolName,
|
|
138
|
-
arguments: parameters
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
mcpProcess.stdin?.write(JSON.stringify(request) + '\n');
|
|
142
|
-
mcpProcess.stdin?.end();
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
// 도구별 파라미터 설정 함수
|
|
146
|
-
async function setupToolParameters(tool) {
|
|
147
|
-
let parameters = {};
|
|
148
|
-
if (tool.required && TEST_PROJECT_ID) {
|
|
149
|
-
parameters.project_id = TEST_PROJECT_ID;
|
|
150
|
-
}
|
|
151
|
-
// 특정 도구들의 추가 파라미터 설정
|
|
152
|
-
switch (tool.name) {
|
|
153
|
-
case 'get_issue':
|
|
154
|
-
if (TEST_PROJECT_ID) {
|
|
155
|
-
const listResult = await callMCPTool('list_issues', parameters);
|
|
156
|
-
if (Array.isArray(listResult) && listResult.length > 0) {
|
|
157
|
-
parameters.issue_iid = listResult[0].iid;
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
throw new Error('No issues found to test with');
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
break;
|
|
164
|
-
case 'get_merge_request':
|
|
165
|
-
if (TEST_PROJECT_ID) {
|
|
166
|
-
const listResult = await callMCPTool('list_merge_requests', parameters);
|
|
167
|
-
if (Array.isArray(listResult) && listResult.length > 0) {
|
|
168
|
-
parameters.merge_request_iid = listResult[0].iid;
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
throw new Error('No merge requests found to test with');
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
break;
|
|
175
|
-
case 'get_pipeline':
|
|
176
|
-
if (TEST_PROJECT_ID) {
|
|
177
|
-
const pipelinesResult = await callMCPTool('list_pipelines', parameters);
|
|
178
|
-
if (Array.isArray(pipelinesResult) && pipelinesResult.length > 0) {
|
|
179
|
-
parameters.pipeline_id = pipelinesResult[0].id;
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
throw new Error('No pipelines found to test with');
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
break;
|
|
186
|
-
case 'get_pipeline_job':
|
|
187
|
-
if (TEST_PROJECT_ID) {
|
|
188
|
-
const pipelinesResult = await callMCPTool('list_pipelines', parameters);
|
|
189
|
-
if (Array.isArray(pipelinesResult) && pipelinesResult.length > 0) {
|
|
190
|
-
parameters.pipeline_id = pipelinesResult[0].id;
|
|
191
|
-
const jobsResult = await callMCPTool('list_pipeline_jobs', parameters);
|
|
192
|
-
if (Array.isArray(jobsResult) && jobsResult.length > 0) {
|
|
193
|
-
parameters.job_id = jobsResult[0].id;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
throw new Error('No pipelines found to test with');
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
break;
|
|
201
|
-
case 'get_commit':
|
|
202
|
-
if (TEST_PROJECT_ID) {
|
|
203
|
-
const commitsResult = await callMCPTool('list_commits', parameters);
|
|
204
|
-
if (Array.isArray(commitsResult) && commitsResult.length > 0) {
|
|
205
|
-
parameters.sha = commitsResult[0].id;
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
throw new Error('No commits found to test with');
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
break;
|
|
212
|
-
case 'get_label':
|
|
213
|
-
if (TEST_PROJECT_ID) {
|
|
214
|
-
const labelsResult = await callMCPTool('list_labels', parameters);
|
|
215
|
-
if (Array.isArray(labelsResult) && labelsResult.length > 0) {
|
|
216
|
-
parameters.label_id = labelsResult[0].id;
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
throw new Error('No labels found to test with');
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
break;
|
|
223
|
-
case 'get_wiki_page':
|
|
224
|
-
if (TEST_PROJECT_ID) {
|
|
225
|
-
const wikiResult = await callMCPTool('list_wiki_pages', parameters);
|
|
226
|
-
if (Array.isArray(wikiResult) && wikiResult.length > 0) {
|
|
227
|
-
parameters.slug = wikiResult[0].slug;
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
throw new Error('No wiki pages found to test with');
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
break;
|
|
234
|
-
case 'get_milestone':
|
|
235
|
-
if (TEST_PROJECT_ID) {
|
|
236
|
-
const milestonesResult = await callMCPTool('list_milestones', parameters);
|
|
237
|
-
if (Array.isArray(milestonesResult) && milestonesResult.length > 0) {
|
|
238
|
-
parameters.milestone_id = milestonesResult[0].id;
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
throw new Error('No milestones found to test with');
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
break;
|
|
245
|
-
case 'search_repositories':
|
|
246
|
-
parameters.search = 'test';
|
|
247
|
-
break;
|
|
248
|
-
case 'get_users':
|
|
249
|
-
parameters.usernames = ['root'];
|
|
250
|
-
break;
|
|
251
|
-
case 'verify_namespace':
|
|
252
|
-
parameters.path = 'root';
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
return parameters;
|
|
256
|
-
}
|
|
257
|
-
// 개별 도구 테스트 함수
|
|
258
|
-
async function testTool(tool) {
|
|
259
|
-
const startTime = Date.now();
|
|
260
|
-
let result = {
|
|
261
|
-
name: tool.name,
|
|
262
|
-
category: tool.category,
|
|
263
|
-
status: 'skipped',
|
|
264
|
-
duration: 0,
|
|
265
|
-
response: null
|
|
266
|
-
};
|
|
267
|
-
try {
|
|
268
|
-
console.log(`🧪 Testing ${tool.name}...`);
|
|
269
|
-
const parameters = await setupToolParameters(tool);
|
|
270
|
-
const response = await callMCPTool(tool.name, parameters);
|
|
271
|
-
result.response = response;
|
|
272
|
-
result.status = 'passed';
|
|
273
|
-
result.duration = Date.now() - startTime;
|
|
274
|
-
console.log(`✅ ${tool.name} - PASSED (${result.duration}ms)`);
|
|
275
|
-
}
|
|
276
|
-
catch (error) {
|
|
277
|
-
result.status = 'failed';
|
|
278
|
-
result.error = error.message;
|
|
279
|
-
result.duration = Date.now() - startTime;
|
|
280
|
-
console.log(`❌ ${tool.name} - FAILED (${result.duration}ms)`);
|
|
281
|
-
console.log(` Error: ${error.message}`);
|
|
282
|
-
}
|
|
283
|
-
return result;
|
|
284
|
-
}
|
|
285
|
-
// 메인 테스트 실행 함수
|
|
286
|
-
async function runReadOnlyTests() {
|
|
287
|
-
console.log('🚀 GitLab MCP 읽기 전용 도구 테스트 시작\n');
|
|
288
|
-
console.log(`📊 총 ${mcpTools.length}개의 읽기 전용 도구를 테스트합니다.\n`);
|
|
289
|
-
// 환경 변수 확인
|
|
290
|
-
if (!GITLAB_TOKEN) {
|
|
291
|
-
console.error('❌ GITLAB_PERSONAL_ACCESS_TOKEN 환경 변수가 설정되지 않았습니다.');
|
|
292
|
-
process.exit(1);
|
|
293
|
-
}
|
|
294
|
-
if (!TEST_PROJECT_ID) {
|
|
295
|
-
console.warn('⚠️ GITLAB_PROJECT_ID가 설정되지 않았습니다. 일부 테스트가 건너뛰어질 수 있습니다.');
|
|
296
|
-
}
|
|
297
|
-
// MCP 서버 빌드 확인
|
|
298
|
-
if (!fs.existsSync('build/index.js')) {
|
|
299
|
-
console.log('🔨 MCP 서버를 빌드합니다...');
|
|
300
|
-
const buildProcess = spawn('npm', ['run', 'build'], { stdio: 'inherit' });
|
|
301
|
-
await new Promise((resolve, reject) => {
|
|
302
|
-
buildProcess.on('close', (code) => {
|
|
303
|
-
if (code === 0)
|
|
304
|
-
resolve();
|
|
305
|
-
else
|
|
306
|
-
reject(new Error(`Build failed with code ${code}`));
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
// 각 도구 테스트 실행
|
|
311
|
-
for (const tool of mcpTools) {
|
|
312
|
-
const result = await testTool(tool);
|
|
313
|
-
testResults.details.push(result);
|
|
314
|
-
testResults.total++;
|
|
315
|
-
if (result.status === 'passed') {
|
|
316
|
-
testResults.passed++;
|
|
317
|
-
}
|
|
318
|
-
else if (result.status === 'failed') {
|
|
319
|
-
testResults.failed++;
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
testResults.skipped++;
|
|
323
|
-
}
|
|
324
|
-
// 요청 간 간격 (API 제한 방지)
|
|
325
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
326
|
-
}
|
|
327
|
-
// 결과 출력
|
|
328
|
-
console.log('\n📊 테스트 결과 요약');
|
|
329
|
-
console.log('='.repeat(50));
|
|
330
|
-
console.log(`총 테스트: ${testResults.total}`);
|
|
331
|
-
console.log(`✅ 성공: ${testResults.passed}`);
|
|
332
|
-
console.log(`❌ 실패: ${testResults.failed}`);
|
|
333
|
-
console.log(`⏭️ 건너뜀: ${testResults.skipped}`);
|
|
334
|
-
console.log(`성공률: ${((testResults.passed / testResults.total) * 100).toFixed(1)}%`);
|
|
335
|
-
// 카테고리별 결과
|
|
336
|
-
const categoryResults = {};
|
|
337
|
-
testResults.details.forEach(result => {
|
|
338
|
-
if (!categoryResults[result.category]) {
|
|
339
|
-
categoryResults[result.category] = { passed: 0, failed: 0, total: 0 };
|
|
340
|
-
}
|
|
341
|
-
categoryResults[result.category].total++;
|
|
342
|
-
if (result.status === 'passed') {
|
|
343
|
-
categoryResults[result.category].passed++;
|
|
344
|
-
}
|
|
345
|
-
else if (result.status === 'failed') {
|
|
346
|
-
categoryResults[result.category].failed++;
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
console.log('\n📈 카테고리별 결과');
|
|
350
|
-
console.log('-'.repeat(30));
|
|
351
|
-
Object.entries(categoryResults).forEach(([category, stats]) => {
|
|
352
|
-
const successRate = ((stats.passed / stats.total) * 100).toFixed(1);
|
|
353
|
-
console.log(`${category.padEnd(15)}: ${stats.passed}/${stats.total} (${successRate}%)`);
|
|
354
|
-
});
|
|
355
|
-
// 실패한 테스트 상세 정보
|
|
356
|
-
const failedTests = testResults.details.filter(r => r.status === 'failed');
|
|
357
|
-
if (failedTests.length > 0) {
|
|
358
|
-
console.log('\n❌ 실패한 테스트 상세 정보');
|
|
359
|
-
console.log('-'.repeat(40));
|
|
360
|
-
failedTests.forEach(test => {
|
|
361
|
-
console.log(`${test.name}: ${test.error}`);
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
// 결과를 JSON 파일로 저장
|
|
365
|
-
const reportPath = 'test-results-readonly.json';
|
|
366
|
-
fs.writeFileSync(reportPath, JSON.stringify(testResults, null, 2));
|
|
367
|
-
console.log(`\n📄 상세 결과가 ${reportPath}에 저장되었습니다.`);
|
|
368
|
-
return testResults.failed === 0;
|
|
369
|
-
}
|
|
370
|
-
// 테스트 실행
|
|
371
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
372
|
-
runReadOnlyTests()
|
|
373
|
-
.then(success => {
|
|
374
|
-
process.exit(success ? 0 : 1);
|
|
375
|
-
})
|
|
376
|
-
.catch(error => {
|
|
377
|
-
console.error('테스트 실행 중 오류 발생:', error);
|
|
378
|
-
process.exit(1);
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
export { runReadOnlyTests, mcpTools, testResults };
|