@zereight/mcp-gitlab 2.0.34 โ 2.0.36
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 +327 -92
- package/build/gitlab-client-pool.js +6 -0
- package/build/index.js +2224 -52
- package/build/oauth-proxy.js +264 -0
- package/build/schemas.js +457 -201
- package/build/test/mcp-oauth-tests.js +552 -0
- package/build/test/multi-server-test.js +16 -8
- package/build/test/schema-tests.js +77 -3
- package/build/test/test-geteffectiveprojectid.js +211 -202
- package/build/test/test-mr-file-diffs.js +251 -0
- package/build/test/test-search-code.js +272 -0
- package/build/test/test-toolset-filtering.js +22 -17
- package/build/test/utils/mock-gitlab-server.js +263 -163
- package/build/test/utils/server-launcher.js +45 -41
- package/package.json +3 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
2
|
-
import { GetFileContentsSchema, GitLabFileContentSchema, CreatePipelineSchema } from '../schemas.js';
|
|
2
|
+
import { GetFileContentsSchema, GitLabFileContentSchema, CreatePipelineSchema, CreateIssueNoteSchema } from '../schemas.js';
|
|
3
3
|
function runGetFileContentsSchemaTests() {
|
|
4
4
|
console.log('๐งช Testing GetFileContentsSchema...');
|
|
5
5
|
const cases = [
|
|
@@ -298,12 +298,86 @@ function runCreatePipelineSchemaTests() {
|
|
|
298
298
|
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
299
299
|
return { passed, failed };
|
|
300
300
|
}
|
|
301
|
+
function runCreateIssueNoteSchemaTests() {
|
|
302
|
+
console.log('\n๐งช Testing CreateIssueNoteSchema...');
|
|
303
|
+
const cases = [
|
|
304
|
+
{
|
|
305
|
+
name: 'schema:create_issue_note:top-level-note-without-discussion-id',
|
|
306
|
+
input: { project_id: 'my/project', issue_iid: '42', body: 'A comment' },
|
|
307
|
+
expected: { project_id: 'my/project', issue_iid: '42', body: 'A comment', discussion_id: undefined }
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: 'schema:create_issue_note:reply-with-discussion-id',
|
|
311
|
+
input: { project_id: 'my/project', issue_iid: '42', discussion_id: 'abc123', body: 'A reply' },
|
|
312
|
+
expected: { project_id: 'my/project', issue_iid: '42', discussion_id: 'abc123', body: 'A reply' }
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: 'schema:create_issue_note:with-created-at',
|
|
316
|
+
input: { project_id: 'my/project', issue_iid: '7', body: 'Note', created_at: '2025-01-01T00:00:00Z' },
|
|
317
|
+
expected: { project_id: 'my/project', issue_iid: '7', body: 'Note', created_at: '2025-01-01T00:00:00Z' }
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: 'schema:create_issue_note:numeric-issue-iid-coerced',
|
|
321
|
+
input: { project_id: 'my/project', issue_iid: 99, body: 'Coerced' },
|
|
322
|
+
expected: { project_id: 'my/project', issue_iid: '99', body: 'Coerced' }
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: 'schema:create_issue_note:reject-missing-body',
|
|
326
|
+
input: { project_id: 'my/project', issue_iid: '1' },
|
|
327
|
+
shouldFail: true
|
|
328
|
+
}
|
|
329
|
+
];
|
|
330
|
+
let passed = 0;
|
|
331
|
+
let failed = 0;
|
|
332
|
+
cases.forEach(testCase => {
|
|
333
|
+
const result = {
|
|
334
|
+
name: testCase.name,
|
|
335
|
+
status: 'failed'
|
|
336
|
+
};
|
|
337
|
+
const parsed = CreateIssueNoteSchema.safeParse(testCase.input);
|
|
338
|
+
if (testCase.shouldFail) {
|
|
339
|
+
if (parsed.success) {
|
|
340
|
+
result.error = 'Expected schema validation to fail';
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
result.status = 'passed';
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else if (parsed.success) {
|
|
347
|
+
const expected = testCase.expected || {};
|
|
348
|
+
const matches = Object.entries(expected).every(([key, value]) => {
|
|
349
|
+
const actual = parsed.data[key];
|
|
350
|
+
return actual === value;
|
|
351
|
+
});
|
|
352
|
+
if (matches) {
|
|
353
|
+
result.status = 'passed';
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
result.error = `Unexpected parsed result: ${JSON.stringify(parsed.data)}`;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
result.error = parsed.error?.message || 'Schema validation failed';
|
|
361
|
+
}
|
|
362
|
+
if (result.status === 'passed') {
|
|
363
|
+
passed++;
|
|
364
|
+
console.log(`โ
${result.name}`);
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
failed++;
|
|
368
|
+
console.log(`โ ${result.name}: ${result.error}`);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
372
|
+
return { passed, failed };
|
|
373
|
+
}
|
|
301
374
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
302
375
|
const getFileContentsResult = runGetFileContentsSchemaTests();
|
|
303
376
|
const fileContentResult = runGitLabFileContentSchemaTests();
|
|
304
377
|
const createPipelineResult = runCreatePipelineSchemaTests();
|
|
305
|
-
const
|
|
306
|
-
const
|
|
378
|
+
const createIssueNoteResult = runCreateIssueNoteSchemaTests();
|
|
379
|
+
const totalPassed = getFileContentsResult.passed + fileContentResult.passed + createPipelineResult.passed + createIssueNoteResult.passed;
|
|
380
|
+
const totalFailed = getFileContentsResult.failed + fileContentResult.failed + createPipelineResult.failed + createIssueNoteResult.failed;
|
|
307
381
|
console.log(`\nTotal Results: ${totalPassed} passed, ${totalFailed} failed`);
|
|
308
382
|
if (totalFailed > 0) {
|
|
309
383
|
process.exit(1);
|
|
@@ -11,226 +11,235 @@ import { StreamableHTTPTestClient } from './clients/streamable-http-client.js';
|
|
|
11
11
|
const MOCK_TOKEN = process.env.GITLAB_TOKEN_TEST || 'glpat-mock-token-12345';
|
|
12
12
|
const DEFAULT_PROJECT_ID = '123';
|
|
13
13
|
const OTHER_PROJECT_ID = '456';
|
|
14
|
+
// Ensure GITLAB_TOKEN_TEST is set for launchServer() validation
|
|
15
|
+
if (!process.env.GITLAB_TOKEN_TEST && !process.env.GITLAB_TOKEN) {
|
|
16
|
+
process.env.GITLAB_TOKEN_TEST = MOCK_TOKEN;
|
|
17
|
+
}
|
|
18
|
+
if (!process.env.TEST_PROJECT_ID) {
|
|
19
|
+
process.env.TEST_PROJECT_ID = DEFAULT_PROJECT_ID;
|
|
20
|
+
}
|
|
14
21
|
console.log('๐ Testing getEffectiveProjectId functionality');
|
|
15
22
|
console.log('');
|
|
16
|
-
describe('getEffectiveProjectId
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
describe('getEffectiveProjectId', { concurrency: 1 }, () => {
|
|
24
|
+
describe('getEffectiveProjectId - No GITLAB_ALLOWED_PROJECT_IDS', () => {
|
|
25
|
+
let mcpUrl;
|
|
26
|
+
let mockGitLab;
|
|
27
|
+
let servers = [];
|
|
28
|
+
let client;
|
|
29
|
+
before(async () => {
|
|
30
|
+
// Start mock GitLab server
|
|
31
|
+
const mockPort = await findMockServerPort(9100);
|
|
32
|
+
mockGitLab = new MockGitLabServer({
|
|
33
|
+
port: mockPort,
|
|
34
|
+
validTokens: [MOCK_TOKEN]
|
|
35
|
+
});
|
|
36
|
+
await mockGitLab.start();
|
|
37
|
+
const mockGitLabUrl = mockGitLab.getUrl();
|
|
38
|
+
// Start MCP server WITHOUT GITLAB_ALLOWED_PROJECT_IDS
|
|
39
|
+
const mcpPort = await findAvailablePort(3100);
|
|
40
|
+
const server = await launchServer({
|
|
41
|
+
mode: TransportMode.STREAMABLE_HTTP,
|
|
42
|
+
port: mcpPort,
|
|
43
|
+
timeout: 5000,
|
|
44
|
+
env: {
|
|
45
|
+
STREAMABLE_HTTP: 'true',
|
|
46
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
47
|
+
GITLAB_PROJECT_ID: DEFAULT_PROJECT_ID,
|
|
48
|
+
GITLAB_READ_ONLY_MODE: 'true',
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
servers.push(server);
|
|
52
|
+
mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
|
|
53
|
+
client = new StreamableHTTPTestClient();
|
|
54
|
+
await client.connect(mcpUrl);
|
|
55
|
+
console.log(`Mock GitLab: ${mockGitLabUrl}`);
|
|
56
|
+
console.log(`MCP Server: ${mcpUrl}`);
|
|
57
|
+
console.log(`Default Project: ${DEFAULT_PROJECT_ID}`);
|
|
27
58
|
});
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
timeout: 5000,
|
|
36
|
-
env: {
|
|
37
|
-
STREAMABLE_HTTP: 'true',
|
|
38
|
-
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
39
|
-
GITLAB_PROJECT_ID: DEFAULT_PROJECT_ID,
|
|
40
|
-
GITLAB_READ_ONLY_MODE: 'true',
|
|
59
|
+
after(async () => {
|
|
60
|
+
if (client) {
|
|
61
|
+
await client.disconnect();
|
|
62
|
+
}
|
|
63
|
+
cleanupServers(servers);
|
|
64
|
+
if (mockGitLab) {
|
|
65
|
+
await mockGitLab.stop();
|
|
41
66
|
}
|
|
42
67
|
});
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
cleanupServers(servers);
|
|
56
|
-
if (mockGitLab) {
|
|
57
|
-
await mockGitLab.stop();
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
test('should use GITLAB_PROJECT_ID when no project_id is provided', async () => {
|
|
61
|
-
// Call get_project without specifying project_id
|
|
62
|
-
const result = await client.callTool('get_project', {
|
|
63
|
-
project_id: ''
|
|
68
|
+
test('should use GITLAB_PROJECT_ID when no project_id is provided', async () => {
|
|
69
|
+
// Call get_project without specifying project_id
|
|
70
|
+
const result = await client.callTool('get_project', {
|
|
71
|
+
project_id: ''
|
|
72
|
+
});
|
|
73
|
+
assert.ok(result.content, 'Should have content');
|
|
74
|
+
const content = result.content[0];
|
|
75
|
+
assert.ok('text' in content, 'Content should have text');
|
|
76
|
+
const project = JSON.parse(content.text);
|
|
77
|
+
// The mock server should receive a request for the default project
|
|
78
|
+
assert.strictEqual(project.id.toString(), DEFAULT_PROJECT_ID, 'Should use GITLAB_PROJECT_ID as default');
|
|
79
|
+
console.log(` โ Used default project ${DEFAULT_PROJECT_ID} when no project_id provided`);
|
|
64
80
|
});
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
project_id
|
|
81
|
+
test('should prioritize passed project_id over GITLAB_PROJECT_ID', async () => {
|
|
82
|
+
// Call get_project with a different project_id
|
|
83
|
+
const result = await client.callTool('get_project', {
|
|
84
|
+
project_id: OTHER_PROJECT_ID
|
|
85
|
+
});
|
|
86
|
+
assert.ok(result.content, 'Should have content');
|
|
87
|
+
const content = result.content[0];
|
|
88
|
+
assert.ok('text' in content, 'Content should have text');
|
|
89
|
+
const project = JSON.parse(content.text);
|
|
90
|
+
// Should use the passed project_id, not GITLAB_PROJECT_ID
|
|
91
|
+
assert.strictEqual(project.id.toString(), OTHER_PROJECT_ID, 'Should use passed project_id');
|
|
92
|
+
console.log(` โ Used passed project_id ${OTHER_PROJECT_ID} instead of default ${DEFAULT_PROJECT_ID}`);
|
|
77
93
|
});
|
|
78
|
-
assert.ok(result.content, 'Should have content');
|
|
79
|
-
const content = result.content[0];
|
|
80
|
-
assert.ok('text' in content, 'Content should have text');
|
|
81
|
-
const project = JSON.parse(content.text);
|
|
82
|
-
// Should use the passed project_id, not GITLAB_PROJECT_ID
|
|
83
|
-
assert.strictEqual(project.id.toString(), OTHER_PROJECT_ID, 'Should use passed project_id');
|
|
84
|
-
console.log(` โ Used passed project_id ${OTHER_PROJECT_ID} instead of default ${DEFAULT_PROJECT_ID}`);
|
|
85
94
|
});
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
describe('getEffectiveProjectId - With single GITLAB_ALLOWED_PROJECT_IDS', () => {
|
|
96
|
+
let mcpUrl;
|
|
97
|
+
let mockGitLab;
|
|
98
|
+
let servers = [];
|
|
99
|
+
let client;
|
|
100
|
+
before(async () => {
|
|
101
|
+
// Start mock GitLab server
|
|
102
|
+
const mockPort = await findMockServerPort(9200);
|
|
103
|
+
mockGitLab = new MockGitLabServer({
|
|
104
|
+
port: mockPort,
|
|
105
|
+
validTokens: [MOCK_TOKEN]
|
|
106
|
+
});
|
|
107
|
+
await mockGitLab.start();
|
|
108
|
+
const mockGitLabUrl = mockGitLab.getUrl();
|
|
109
|
+
// Start MCP server WITH single GITLAB_ALLOWED_PROJECT_IDS
|
|
110
|
+
const mcpPort = await findAvailablePort(3200);
|
|
111
|
+
const server = await launchServer({
|
|
112
|
+
mode: TransportMode.STREAMABLE_HTTP,
|
|
113
|
+
port: mcpPort,
|
|
114
|
+
timeout: 5000,
|
|
115
|
+
env: {
|
|
116
|
+
STREAMABLE_HTTP: 'true',
|
|
117
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
118
|
+
GITLAB_PROJECT_ID: DEFAULT_PROJECT_ID,
|
|
119
|
+
GITLAB_ALLOWED_PROJECT_IDS: DEFAULT_PROJECT_ID,
|
|
120
|
+
GITLAB_READ_ONLY_MODE: 'true',
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
servers.push(server);
|
|
124
|
+
mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
|
|
125
|
+
client = new StreamableHTTPTestClient();
|
|
126
|
+
await client.connect(mcpUrl);
|
|
127
|
+
console.log(`Mock GitLab: ${mockGitLabUrl}`);
|
|
128
|
+
console.log(`MCP Server: ${mcpUrl}`);
|
|
129
|
+
console.log(`Allowed Project: ${DEFAULT_PROJECT_ID}`);
|
|
98
130
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
timeout: 5000,
|
|
107
|
-
env: {
|
|
108
|
-
STREAMABLE_HTTP: 'true',
|
|
109
|
-
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
110
|
-
GITLAB_PROJECT_ID: DEFAULT_PROJECT_ID,
|
|
111
|
-
GITLAB_ALLOWED_PROJECT_IDS: DEFAULT_PROJECT_ID,
|
|
112
|
-
GITLAB_READ_ONLY_MODE: 'true',
|
|
131
|
+
after(async () => {
|
|
132
|
+
if (client) {
|
|
133
|
+
await client.disconnect();
|
|
134
|
+
}
|
|
135
|
+
cleanupServers(servers);
|
|
136
|
+
if (mockGitLab) {
|
|
137
|
+
await mockGitLab.stop();
|
|
113
138
|
}
|
|
114
139
|
});
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
test('should use single allowed project as default', async () => {
|
|
141
|
+
const result = await client.callTool('get_project', {
|
|
142
|
+
project_id: ''
|
|
143
|
+
});
|
|
144
|
+
assert.ok(result.content, 'Should have content');
|
|
145
|
+
const content = result.content[0];
|
|
146
|
+
assert.ok('text' in content, 'Content should have text');
|
|
147
|
+
const project = JSON.parse(content.text);
|
|
148
|
+
assert.strictEqual(project.id.toString(), DEFAULT_PROJECT_ID, 'Should use allowed project as default');
|
|
149
|
+
console.log(` โ Used allowed project ${DEFAULT_PROJECT_ID} as default`);
|
|
150
|
+
});
|
|
151
|
+
test('should reject access to non-allowed project', async () => {
|
|
152
|
+
try {
|
|
153
|
+
await client.callTool('get_project', {
|
|
154
|
+
project_id: OTHER_PROJECT_ID
|
|
155
|
+
});
|
|
156
|
+
assert.fail('Should have rejected access to non-allowed project');
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
assert.ok(error instanceof Error);
|
|
160
|
+
assert.ok(error.message.includes('Access denied'), 'Should indicate access denied');
|
|
161
|
+
console.log(' โ Correctly rejected access to non-allowed project');
|
|
162
|
+
}
|
|
135
163
|
});
|
|
136
|
-
assert.ok(result.content, 'Should have content');
|
|
137
|
-
const content = result.content[0];
|
|
138
|
-
assert.ok('text' in content, 'Content should have text');
|
|
139
|
-
const project = JSON.parse(content.text);
|
|
140
|
-
assert.strictEqual(project.id.toString(), DEFAULT_PROJECT_ID, 'Should use allowed project as default');
|
|
141
|
-
console.log(` โ Used allowed project ${DEFAULT_PROJECT_ID} as default`);
|
|
142
164
|
});
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
165
|
+
describe('getEffectiveProjectId - With multiple GITLAB_ALLOWED_PROJECT_IDS', () => {
|
|
166
|
+
let mcpUrl;
|
|
167
|
+
let mockGitLab;
|
|
168
|
+
let servers = [];
|
|
169
|
+
let client;
|
|
170
|
+
before(async () => {
|
|
171
|
+
// Start mock GitLab server
|
|
172
|
+
const mockPort = await findMockServerPort(9300);
|
|
173
|
+
mockGitLab = new MockGitLabServer({
|
|
174
|
+
port: mockPort,
|
|
175
|
+
validTokens: [MOCK_TOKEN]
|
|
147
176
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
177
|
+
await mockGitLab.start();
|
|
178
|
+
const mockGitLabUrl = mockGitLab.getUrl();
|
|
179
|
+
// Start MCP server WITH multiple GITLAB_ALLOWED_PROJECT_IDS
|
|
180
|
+
const mcpPort = await findAvailablePort(3300);
|
|
181
|
+
const server = await launchServer({
|
|
182
|
+
mode: TransportMode.STREAMABLE_HTTP,
|
|
183
|
+
port: mcpPort,
|
|
184
|
+
timeout: 5000,
|
|
185
|
+
env: {
|
|
186
|
+
STREAMABLE_HTTP: 'true',
|
|
187
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
188
|
+
GITLAB_ALLOWED_PROJECT_IDS: `${DEFAULT_PROJECT_ID},${OTHER_PROJECT_ID}`,
|
|
189
|
+
GITLAB_READ_ONLY_MODE: 'true',
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
servers.push(server);
|
|
193
|
+
mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
|
|
194
|
+
client = new StreamableHTTPTestClient();
|
|
195
|
+
await client.connect(mcpUrl);
|
|
196
|
+
console.log(`Mock GitLab: ${mockGitLabUrl}`);
|
|
197
|
+
console.log(`MCP Server: ${mcpUrl}`);
|
|
198
|
+
console.log(`Allowed Projects: ${DEFAULT_PROJECT_ID},${OTHER_PROJECT_ID}`);
|
|
168
199
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
timeout: 5000,
|
|
177
|
-
env: {
|
|
178
|
-
STREAMABLE_HTTP: 'true',
|
|
179
|
-
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
180
|
-
GITLAB_ALLOWED_PROJECT_IDS: `${DEFAULT_PROJECT_ID},${OTHER_PROJECT_ID}`,
|
|
181
|
-
GITLAB_READ_ONLY_MODE: 'true',
|
|
200
|
+
after(async () => {
|
|
201
|
+
if (client) {
|
|
202
|
+
await client.disconnect();
|
|
203
|
+
}
|
|
204
|
+
cleanupServers(servers);
|
|
205
|
+
if (mockGitLab) {
|
|
206
|
+
await mockGitLab.stop();
|
|
182
207
|
}
|
|
183
208
|
});
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
await
|
|
199
|
-
|
|
200
|
-
});
|
|
201
|
-
test('should require explicit project_id when multiple projects allowed', async () => {
|
|
202
|
-
try {
|
|
203
|
-
await client.callTool('get_project', {
|
|
204
|
-
project_id: ''
|
|
209
|
+
test('should require explicit project_id when multiple projects allowed', async () => {
|
|
210
|
+
try {
|
|
211
|
+
await client.callTool('get_project', {
|
|
212
|
+
project_id: ''
|
|
213
|
+
});
|
|
214
|
+
assert.fail('Should have required explicit project_id');
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
assert.ok(error instanceof Error);
|
|
218
|
+
assert.ok(error.message.includes('Please specify a project ID'), 'Should require project ID');
|
|
219
|
+
console.log(' โ Correctly required explicit project_id');
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
test('should allow access to first allowed project', async () => {
|
|
223
|
+
const result = await client.callTool('get_project', {
|
|
224
|
+
project_id: DEFAULT_PROJECT_ID
|
|
205
225
|
});
|
|
206
|
-
assert.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
assert.
|
|
211
|
-
console.log(
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
test('should allow access to first allowed project', async () => {
|
|
215
|
-
const result = await client.callTool('get_project', {
|
|
216
|
-
project_id: DEFAULT_PROJECT_ID
|
|
226
|
+
assert.ok(result.content, 'Should have content');
|
|
227
|
+
const content = result.content[0];
|
|
228
|
+
assert.ok('text' in content, 'Content should have text');
|
|
229
|
+
const project = JSON.parse(content.text);
|
|
230
|
+
assert.strictEqual(project.id.toString(), DEFAULT_PROJECT_ID, 'Should allow first project');
|
|
231
|
+
console.log(` โ Allowed access to first project ${DEFAULT_PROJECT_ID}`);
|
|
217
232
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
233
|
+
test('should allow access to second allowed project', async () => {
|
|
234
|
+
const result = await client.callTool('get_project', {
|
|
235
|
+
project_id: OTHER_PROJECT_ID
|
|
236
|
+
});
|
|
237
|
+
assert.ok(result.content, 'Should have content');
|
|
238
|
+
const content = result.content[0];
|
|
239
|
+
assert.ok('text' in content, 'Content should have text');
|
|
240
|
+
const project = JSON.parse(content.text);
|
|
241
|
+
assert.strictEqual(project.id.toString(), OTHER_PROJECT_ID, 'Should allow second project');
|
|
242
|
+
console.log(` โ Allowed access to second project ${OTHER_PROJECT_ID}`);
|
|
228
243
|
});
|
|
229
|
-
assert.ok(result.content, 'Should have content');
|
|
230
|
-
const content = result.content[0];
|
|
231
|
-
assert.ok('text' in content, 'Content should have text');
|
|
232
|
-
const project = JSON.parse(content.text);
|
|
233
|
-
assert.strictEqual(project.id.toString(), OTHER_PROJECT_ID, 'Should allow second project');
|
|
234
|
-
console.log(` โ Allowed access to second project ${OTHER_PROJECT_ID}`);
|
|
235
244
|
});
|
|
236
|
-
});
|
|
245
|
+
}); // end wrapper describe
|