mcp4openapi 0.2.0 → 0.2.2
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 +3 -0
- package/dist/src/composite-executor.d.ts.map +1 -1
- package/dist/src/composite-executor.js +10 -1
- package/dist/src/composite-executor.js.map +1 -1
- package/dist/src/generated-schemas.d.ts +40 -0
- package/dist/src/generated-schemas.d.ts.map +1 -1
- package/dist/src/generated-schemas.js +2 -1
- package/dist/src/generated-schemas.js.map +1 -1
- package/dist/src/http-transport.d.ts.map +1 -1
- package/dist/src/http-transport.js +9 -1
- package/dist/src/http-transport.js.map +1 -1
- package/dist/src/interceptors.d.ts.map +1 -1
- package/dist/src/interceptors.js +4 -0
- package/dist/src/interceptors.js.map +1 -1
- package/dist/src/logger.d.ts +0 -15
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +9 -92
- package/dist/src/logger.js.map +1 -1
- package/dist/src/mcp-server.d.ts +5 -0
- package/dist/src/mcp-server.d.ts.map +1 -1
- package/dist/src/mcp-server.js +40 -1
- package/dist/src/mcp-server.js.map +1 -1
- package/dist/src/oauth-provider.d.ts +5 -0
- package/dist/src/oauth-provider.d.ts.map +1 -1
- package/dist/src/oauth-provider.js +38 -0
- package/dist/src/oauth-provider.js.map +1 -1
- package/dist/src/openapi-parser.d.ts.map +1 -1
- package/dist/src/openapi-parser.js +6 -0
- package/dist/src/openapi-parser.js.map +1 -1
- package/dist/src/testing/fixtures.d.ts +157 -0
- package/dist/src/testing/fixtures.d.ts.map +1 -1
- package/dist/src/testing/fixtures.js +118 -0
- package/dist/src/testing/fixtures.js.map +1 -1
- package/dist/src/testing/mock-gitlab-server.d.ts +26 -17
- package/dist/src/testing/mock-gitlab-server.d.ts.map +1 -1
- package/dist/src/testing/mock-gitlab-server.js +541 -408
- package/dist/src/testing/mock-gitlab-server.js.map +1 -1
- package/dist/src/types/profile.d.ts +10 -0
- package/dist/src/types/profile.d.ts.map +1 -1
- package/dist/src/validation-utils.d.ts +22 -0
- package/dist/src/validation-utils.d.ts.map +1 -1
- package/dist/src/validation-utils.js +74 -0
- package/dist/src/validation-utils.js.map +1 -1
- package/package.json +6 -1
|
@@ -7,8 +7,16 @@
|
|
|
7
7
|
import { http, HttpResponse } from 'msw';
|
|
8
8
|
import { setupServer } from 'msw/node';
|
|
9
9
|
import * as fixtures from './fixtures.js';
|
|
10
|
-
import { parsePaginationParams, parseSearchParam, parseBranchParams, parseScopeParam } from './mock-utils.js';
|
|
11
|
-
|
|
10
|
+
import { parsePaginationParams, parseSearchParam, parseBranchParams, parseScopeParam, } from './mock-utils.js';
|
|
11
|
+
/** Default BASE_URL for GitLab API (used by MSW interceptor) */
|
|
12
|
+
export const DEFAULT_BASE_URL = 'https://gitlab.com/api/v4';
|
|
13
|
+
/** Default OAuth config */
|
|
14
|
+
const DEFAULT_OAUTH_CONFIG = {
|
|
15
|
+
oauthBaseUrl: 'http://localhost:4000',
|
|
16
|
+
accessToken: 'mock-access-token-12345',
|
|
17
|
+
refreshToken: 'mock-refresh-token-67890',
|
|
18
|
+
expiresIn: 3600,
|
|
19
|
+
};
|
|
12
20
|
/**
|
|
13
21
|
* Helper: Extract and validate IID from URL
|
|
14
22
|
*
|
|
@@ -18,7 +26,6 @@ const BASE_URL = 'https://gitlab.com/api/v4';
|
|
|
18
26
|
function extractIidFromUrl(url) {
|
|
19
27
|
const parts = url.split('/');
|
|
20
28
|
const iidStr = parts[parts.length - 1];
|
|
21
|
-
// Validate it's a positive integer
|
|
22
29
|
if (!iidStr || !/^\d+$/.test(iidStr)) {
|
|
23
30
|
return null;
|
|
24
31
|
}
|
|
@@ -33,7 +40,6 @@ function extractIidFromUrl(url) {
|
|
|
33
40
|
* URL format: /projects/{project}/merge_requests/{iid}/notes
|
|
34
41
|
*/
|
|
35
42
|
function extractMrIidFromNotesUrl(url) {
|
|
36
|
-
// Remove query parameters for matching
|
|
37
43
|
const urlWithoutQuery = url.split('?')[0];
|
|
38
44
|
const match = urlWithoutQuery.match(/\/merge_requests\/(\d+)\/notes/);
|
|
39
45
|
if (!match) {
|
|
@@ -46,436 +52,563 @@ function extractMrIidFromNotesUrl(url) {
|
|
|
46
52
|
return iid;
|
|
47
53
|
}
|
|
48
54
|
/**
|
|
49
|
-
*
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
* Create OAuth handlers for mock OAuth server
|
|
56
|
+
*/
|
|
57
|
+
export function createOAuthHandlers(config = DEFAULT_OAUTH_CONFIG) {
|
|
58
|
+
const { oauthBaseUrl, accessToken, refreshToken, expiresIn } = { ...DEFAULT_OAUTH_CONFIG, ...config };
|
|
59
|
+
return [
|
|
60
|
+
// OAuth Discovery endpoint
|
|
61
|
+
http.get(`${oauthBaseUrl}/.well-known/oauth-authorization-server`, () => {
|
|
62
|
+
return HttpResponse.json({
|
|
63
|
+
issuer: oauthBaseUrl,
|
|
64
|
+
authorization_endpoint: `${oauthBaseUrl}/oauth/authorize`,
|
|
65
|
+
token_endpoint: `${oauthBaseUrl}/oauth/token`,
|
|
66
|
+
response_types_supported: ['code'],
|
|
67
|
+
grant_types_supported: ['authorization_code', 'refresh_token'],
|
|
68
|
+
code_challenge_methods_supported: ['S256'],
|
|
69
|
+
});
|
|
70
|
+
}),
|
|
71
|
+
// OAuth Authorization endpoint - redirects with code
|
|
72
|
+
http.get(`${oauthBaseUrl}/oauth/authorize`, ({ request }) => {
|
|
73
|
+
const url = new URL(request.url);
|
|
74
|
+
const redirectUri = url.searchParams.get('redirect_uri');
|
|
75
|
+
const state = url.searchParams.get('state');
|
|
76
|
+
if (!redirectUri) {
|
|
77
|
+
return HttpResponse.json({ error: 'missing_redirect_uri' }, { status: 400 });
|
|
78
|
+
}
|
|
79
|
+
const code = 'mock-code-' + Math.random().toString(36).substring(7);
|
|
80
|
+
const redirectUrl = new URL(redirectUri);
|
|
81
|
+
redirectUrl.searchParams.set('code', code);
|
|
82
|
+
if (state) {
|
|
83
|
+
redirectUrl.searchParams.set('state', state);
|
|
84
|
+
}
|
|
85
|
+
return new HttpResponse(null, {
|
|
86
|
+
status: 302,
|
|
87
|
+
headers: { Location: redirectUrl.toString() },
|
|
88
|
+
});
|
|
89
|
+
}),
|
|
90
|
+
// OAuth Token endpoint
|
|
91
|
+
http.post(`${oauthBaseUrl}/oauth/token`, async ({ request }) => {
|
|
92
|
+
const contentType = request.headers.get('content-type') || '';
|
|
93
|
+
let params;
|
|
94
|
+
if (contentType.includes('application/json')) {
|
|
95
|
+
params = await request.json();
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const body = await request.text();
|
|
99
|
+
params = Object.fromEntries(new URLSearchParams(body));
|
|
100
|
+
}
|
|
101
|
+
const grantType = params.grant_type;
|
|
102
|
+
if (grantType === 'authorization_code') {
|
|
103
|
+
if (!params.code) {
|
|
104
|
+
return HttpResponse.json({ error: 'invalid_grant' }, { status: 400 });
|
|
105
|
+
}
|
|
106
|
+
return HttpResponse.json({
|
|
107
|
+
access_token: accessToken,
|
|
108
|
+
refresh_token: refreshToken,
|
|
109
|
+
token_type: 'Bearer',
|
|
110
|
+
expires_in: expiresIn,
|
|
111
|
+
scope: 'api',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (grantType === 'refresh_token') {
|
|
115
|
+
if (!params.refresh_token) {
|
|
116
|
+
return HttpResponse.json({ error: 'invalid_grant' }, { status: 400 });
|
|
117
|
+
}
|
|
118
|
+
return HttpResponse.json({
|
|
119
|
+
access_token: `${accessToken}-refreshed`,
|
|
120
|
+
refresh_token: `${refreshToken}-new`,
|
|
121
|
+
token_type: 'Bearer',
|
|
122
|
+
expires_in: expiresIn,
|
|
123
|
+
scope: 'api',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return HttpResponse.json({ error: 'unsupported_grant_type' }, { status: 400 });
|
|
127
|
+
}),
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Create GitLab API handlers with configurable base URL
|
|
53
132
|
*/
|
|
54
|
-
export
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
133
|
+
export function createGitLabHandlers(baseUrl = DEFAULT_BASE_URL) {
|
|
134
|
+
return [
|
|
135
|
+
// Groups
|
|
136
|
+
http.get(`${baseUrl}/groups`, ({ request }) => {
|
|
137
|
+
const { page } = parsePaginationParams(request);
|
|
138
|
+
const search = parseSearchParam(request);
|
|
139
|
+
let groups = fixtures.mockGroupsList;
|
|
140
|
+
if (search) {
|
|
141
|
+
groups = groups.filter(g => g.name.toLowerCase().includes(search.toLowerCase()) ||
|
|
142
|
+
g.path.toLowerCase().includes(search.toLowerCase()));
|
|
143
|
+
}
|
|
144
|
+
if (page > 1) {
|
|
145
|
+
return HttpResponse.json([]);
|
|
146
|
+
}
|
|
147
|
+
return HttpResponse.json(groups);
|
|
148
|
+
}),
|
|
149
|
+
http.get(`${baseUrl}/groups/:id`, ({ params }) => {
|
|
150
|
+
const groupId = params.id;
|
|
151
|
+
if (groupId === '36173' || groupId === 'davidruzicka') {
|
|
152
|
+
return HttpResponse.json(fixtures.mockGroup);
|
|
153
|
+
}
|
|
154
|
+
return HttpResponse.json({ message: 'Group Not Found' }, { status: 404 });
|
|
155
|
+
}),
|
|
156
|
+
http.get(`${baseUrl}/groups/:id/projects`, ({ request, params }) => {
|
|
157
|
+
const groupId = params.id;
|
|
158
|
+
const { page } = parsePaginationParams(request);
|
|
159
|
+
if (groupId === '36173' || groupId === 'davidruzicka') {
|
|
160
|
+
if (page > 1) {
|
|
161
|
+
return HttpResponse.json([]);
|
|
162
|
+
}
|
|
163
|
+
return HttpResponse.json(fixtures.mockProjectsList);
|
|
164
|
+
}
|
|
165
|
+
return HttpResponse.json({ message: 'Group Not Found' }, { status: 404 });
|
|
166
|
+
}),
|
|
167
|
+
http.get(`${baseUrl}/groups/:id/subgroups`, ({ request, params }) => {
|
|
168
|
+
const groupId = params.id;
|
|
169
|
+
const { page } = parsePaginationParams(request);
|
|
170
|
+
if (groupId === '36173' || groupId === 'davidruzicka') {
|
|
171
|
+
if (page > 1) {
|
|
172
|
+
return HttpResponse.json([]);
|
|
173
|
+
}
|
|
174
|
+
return HttpResponse.json(fixtures.mockSubgroupsList);
|
|
175
|
+
}
|
|
176
|
+
return HttpResponse.json({ message: 'Group Not Found' }, { status: 404 });
|
|
177
|
+
}),
|
|
178
|
+
// Projects
|
|
179
|
+
http.get(`${baseUrl}/projects`, ({ request }) => {
|
|
180
|
+
const { page } = parsePaginationParams(request);
|
|
181
|
+
const search = parseSearchParam(request);
|
|
182
|
+
let projects = fixtures.mockProjectsList;
|
|
183
|
+
if (search) {
|
|
184
|
+
projects = projects.filter(p => p.name.toLowerCase().includes(search.toLowerCase()) ||
|
|
185
|
+
p.description?.toLowerCase().includes(search.toLowerCase()));
|
|
186
|
+
}
|
|
187
|
+
if (page > 1) {
|
|
188
|
+
return HttpResponse.json([]);
|
|
189
|
+
}
|
|
190
|
+
return HttpResponse.json(projects);
|
|
191
|
+
}),
|
|
192
|
+
http.get(`${baseUrl}/projects/:id`, ({ params }) => {
|
|
193
|
+
const projectId = params.id;
|
|
194
|
+
if (projectId === '12345' || projectId === 'davidruzicka%2Fmcp4openapi') {
|
|
195
|
+
return HttpResponse.json(fixtures.mockProject);
|
|
196
|
+
}
|
|
197
|
+
return HttpResponse.json({ message: 'Project Not Found' }, { status: 404 });
|
|
198
|
+
}),
|
|
199
|
+
// Project Badges
|
|
200
|
+
http.get(`${baseUrl}/projects/*/badges`, ({ request }) => {
|
|
201
|
+
const { page } = parsePaginationParams(request);
|
|
202
|
+
if (page === 1) {
|
|
203
|
+
return HttpResponse.json(fixtures.mockBadgesList);
|
|
204
|
+
}
|
|
205
|
+
return HttpResponse.json([]);
|
|
206
|
+
}),
|
|
207
|
+
http.get(`${baseUrl}/projects/*/badges/*`, ({ request }) => {
|
|
208
|
+
const badgeId = extractIidFromUrl(request.url);
|
|
209
|
+
if (badgeId === null) {
|
|
210
|
+
return HttpResponse.json({ error: 'Invalid badge ID' }, { status: 400 });
|
|
211
|
+
}
|
|
212
|
+
if (badgeId === 1) {
|
|
213
|
+
return HttpResponse.json(fixtures.mockBadge);
|
|
214
|
+
}
|
|
215
|
+
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
216
|
+
}),
|
|
217
|
+
http.post(`${baseUrl}/projects/*/badges`, async ({ request }) => {
|
|
218
|
+
const body = await request.json();
|
|
219
|
+
if (!body.link_url || !body.image_url) {
|
|
220
|
+
return HttpResponse.json({ error: 'link_url and image_url are required' }, { status: 400 });
|
|
221
|
+
}
|
|
222
|
+
return HttpResponse.json({
|
|
223
|
+
...fixtures.mockBadge,
|
|
224
|
+
id: 3,
|
|
225
|
+
name: body.name || 'New Badge',
|
|
226
|
+
link_url: body.link_url,
|
|
227
|
+
image_url: body.image_url,
|
|
228
|
+
}, { status: 201 });
|
|
229
|
+
}),
|
|
230
|
+
http.put(`${baseUrl}/projects/*/badges/*`, async ({ request }) => {
|
|
231
|
+
const badgeId = extractIidFromUrl(request.url);
|
|
232
|
+
if (badgeId === null) {
|
|
233
|
+
return HttpResponse.json({ error: 'Invalid badge ID' }, { status: 400 });
|
|
234
|
+
}
|
|
235
|
+
const body = await request.json();
|
|
236
|
+
if (badgeId === 1) {
|
|
237
|
+
return HttpResponse.json({
|
|
238
|
+
...fixtures.mockBadge,
|
|
239
|
+
name: body.name || fixtures.mockBadge.name,
|
|
240
|
+
link_url: body.link_url || fixtures.mockBadge.link_url,
|
|
241
|
+
image_url: body.image_url || fixtures.mockBadge.image_url,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
245
|
+
}),
|
|
246
|
+
http.delete(`${baseUrl}/projects/*/badges/*`, ({ request }) => {
|
|
247
|
+
const badgeId = extractIidFromUrl(request.url);
|
|
248
|
+
if (badgeId === null) {
|
|
249
|
+
return HttpResponse.json({ error: 'Invalid badge ID' }, { status: 400 });
|
|
250
|
+
}
|
|
251
|
+
if (badgeId === 1) {
|
|
252
|
+
return new HttpResponse(null, { status: 204 });
|
|
253
|
+
}
|
|
254
|
+
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
255
|
+
}),
|
|
256
|
+
// Group Badges
|
|
257
|
+
http.get(`${baseUrl}/groups/*/badges`, () => {
|
|
60
258
|
return HttpResponse.json(fixtures.mockBadgesList);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
http.get(`${BASE_URL}/projects/*/badges/*`, ({ request }) => {
|
|
65
|
-
const badgeId = extractIidFromUrl(request.url);
|
|
66
|
-
if (badgeId === null) {
|
|
67
|
-
return HttpResponse.json({ error: 'Invalid badge ID' }, { status: 400 });
|
|
68
|
-
}
|
|
69
|
-
if (badgeId === 1) {
|
|
70
|
-
return HttpResponse.json(fixtures.mockBadge);
|
|
71
|
-
}
|
|
72
|
-
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
73
|
-
}),
|
|
74
|
-
http.post(`${BASE_URL}/projects/*/badges`, async ({ request }) => {
|
|
75
|
-
const body = await request.json();
|
|
76
|
-
if (!body.link_url || !body.image_url) {
|
|
77
|
-
return HttpResponse.json({ error: 'link_url and image_url are required' }, { status: 400 });
|
|
78
|
-
}
|
|
79
|
-
return HttpResponse.json({
|
|
80
|
-
...fixtures.mockBadge,
|
|
81
|
-
id: 3,
|
|
82
|
-
name: body.name || 'New Badge',
|
|
83
|
-
link_url: body.link_url,
|
|
84
|
-
image_url: body.image_url,
|
|
85
|
-
}, { status: 201 });
|
|
86
|
-
}),
|
|
87
|
-
http.put(`${BASE_URL}/projects/*/badges/*`, async ({ request }) => {
|
|
88
|
-
const badgeId = extractIidFromUrl(request.url);
|
|
89
|
-
if (badgeId === null) {
|
|
90
|
-
return HttpResponse.json({ error: 'Invalid badge ID' }, { status: 400 });
|
|
91
|
-
}
|
|
92
|
-
const body = await request.json();
|
|
93
|
-
if (badgeId === 1) {
|
|
259
|
+
}),
|
|
260
|
+
http.post(`${baseUrl}/groups/*/badges`, async ({ request }) => {
|
|
261
|
+
const body = await request.json();
|
|
94
262
|
return HttpResponse.json({
|
|
95
263
|
...fixtures.mockBadge,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
264
|
+
id: 4,
|
|
265
|
+
kind: 'group',
|
|
266
|
+
name: body.name || 'Group Badge',
|
|
267
|
+
}, { status: 201 });
|
|
268
|
+
}),
|
|
269
|
+
// Branches
|
|
270
|
+
http.get(`${baseUrl}/projects/*/repository/branches`, ({ request }) => {
|
|
271
|
+
const search = parseSearchParam(request);
|
|
272
|
+
if (search) {
|
|
273
|
+
return HttpResponse.json(fixtures.mockBranchesList.filter(b => b.name.includes(search)));
|
|
274
|
+
}
|
|
275
|
+
return HttpResponse.json(fixtures.mockBranchesList);
|
|
276
|
+
}),
|
|
277
|
+
http.get(`${baseUrl}/projects/*/repository/branches/*`, ({ request }) => {
|
|
278
|
+
const branch = decodeURIComponent(request.url.split('/').pop() || '');
|
|
279
|
+
const found = fixtures.mockBranchesList.find(b => b.name === branch);
|
|
280
|
+
if (found) {
|
|
281
|
+
return HttpResponse.json(found);
|
|
282
|
+
}
|
|
283
|
+
return HttpResponse.json({ message: 'Branch Not Found' }, { status: 404 });
|
|
284
|
+
}),
|
|
285
|
+
http.post(`${baseUrl}/projects/*/repository/branches`, async ({ request }) => {
|
|
286
|
+
const { branch, ref } = parseBranchParams(request);
|
|
287
|
+
if (!branch || !ref) {
|
|
288
|
+
return HttpResponse.json({ error: 'branch and ref parameters are required' }, { status: 400 });
|
|
289
|
+
}
|
|
290
|
+
return HttpResponse.json({
|
|
291
|
+
name: branch,
|
|
292
|
+
commit: fixtures.mockBranch.commit,
|
|
293
|
+
merged: false,
|
|
294
|
+
protected: false,
|
|
295
|
+
default: false,
|
|
296
|
+
}, { status: 201 });
|
|
297
|
+
}),
|
|
298
|
+
http.delete(`${baseUrl}/projects/*/repository/branches/*`, ({ request }) => {
|
|
299
|
+
const branch = decodeURIComponent(request.url.split('/').pop() || '');
|
|
300
|
+
if (branch !== 'main') {
|
|
301
|
+
return new HttpResponse(null, { status: 204 });
|
|
302
|
+
}
|
|
303
|
+
return HttpResponse.json({ message: 'Cannot delete default branch' }, { status: 403 });
|
|
304
|
+
}),
|
|
305
|
+
http.put(`${baseUrl}/projects/*/repository/branches/*/protect`, ({ request }) => {
|
|
306
|
+
const parts = request.url.split('/');
|
|
307
|
+
const branch = decodeURIComponent(parts[parts.length - 2]);
|
|
308
|
+
return HttpResponse.json({
|
|
309
|
+
...fixtures.mockBranch,
|
|
310
|
+
name: branch,
|
|
311
|
+
protected: true,
|
|
99
312
|
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
return HttpResponse.json(fixtures.mockBranchesList);
|
|
133
|
-
}),
|
|
134
|
-
http.get(`${BASE_URL}/projects/*/repository/branches/*`, ({ request }) => {
|
|
135
|
-
const branch = decodeURIComponent(request.url.split('/').pop() || '');
|
|
136
|
-
const found = fixtures.mockBranchesList.find(b => b.name === branch);
|
|
137
|
-
if (found) {
|
|
138
|
-
return HttpResponse.json(found);
|
|
139
|
-
}
|
|
140
|
-
return HttpResponse.json({ message: 'Branch Not Found' }, { status: 404 });
|
|
141
|
-
}),
|
|
142
|
-
http.post(`${BASE_URL}/projects/*/repository/branches`, async ({ request }) => {
|
|
143
|
-
const { branch, ref } = parseBranchParams(request);
|
|
144
|
-
if (!branch || !ref) {
|
|
145
|
-
return HttpResponse.json({ error: 'branch and ref parameters are required' }, { status: 400 });
|
|
146
|
-
}
|
|
147
|
-
return HttpResponse.json({
|
|
148
|
-
name: branch,
|
|
149
|
-
commit: fixtures.mockBranch.commit,
|
|
150
|
-
merged: false,
|
|
151
|
-
protected: false,
|
|
152
|
-
default: false,
|
|
153
|
-
}, { status: 201 });
|
|
154
|
-
}),
|
|
155
|
-
http.delete(`${BASE_URL}/projects/*/repository/branches/*`, ({ request }) => {
|
|
156
|
-
const branch = decodeURIComponent(request.url.split('/').pop() || '');
|
|
157
|
-
if (branch !== 'main') {
|
|
313
|
+
}),
|
|
314
|
+
http.put(`${baseUrl}/projects/*/repository/branches/*/unprotect`, ({ request }) => {
|
|
315
|
+
const parts = request.url.split('/');
|
|
316
|
+
const branch = decodeURIComponent(parts[parts.length - 2]);
|
|
317
|
+
return HttpResponse.json({
|
|
318
|
+
...fixtures.mockBranch,
|
|
319
|
+
name: branch,
|
|
320
|
+
protected: false,
|
|
321
|
+
});
|
|
322
|
+
}),
|
|
323
|
+
// Access Requests
|
|
324
|
+
http.get(`${baseUrl}/projects/*/access_requests`, () => {
|
|
325
|
+
return HttpResponse.json(fixtures.mockAccessRequestsList);
|
|
326
|
+
}),
|
|
327
|
+
http.get(`${baseUrl}/groups/*/access_requests`, () => {
|
|
328
|
+
return HttpResponse.json(fixtures.mockAccessRequestsList);
|
|
329
|
+
}),
|
|
330
|
+
http.post(`${baseUrl}/projects/*/access_requests`, () => {
|
|
331
|
+
return HttpResponse.json(fixtures.mockAccessRequest, { status: 201 });
|
|
332
|
+
}),
|
|
333
|
+
http.put(`${baseUrl}/projects/*/access_requests/*/approve`, async ({ request }) => {
|
|
334
|
+
const body = await request.json();
|
|
335
|
+
const parts = request.url.split('/');
|
|
336
|
+
const userId = parseInt(parts[parts.length - 2], 10);
|
|
337
|
+
return HttpResponse.json({
|
|
338
|
+
...fixtures.mockAccessRequest,
|
|
339
|
+
id: userId,
|
|
340
|
+
access_level: body.access_level || 30,
|
|
341
|
+
});
|
|
342
|
+
}),
|
|
343
|
+
http.delete(`${baseUrl}/projects/*/access_requests/*`, () => {
|
|
158
344
|
return new HttpResponse(null, { status: 204 });
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
status: 'pending',
|
|
228
|
-
});
|
|
229
|
-
}),
|
|
230
|
-
// Rate limiting simulation
|
|
231
|
-
http.get(`${BASE_URL}/rate-limit-test`, () => {
|
|
232
|
-
return HttpResponse.json({ message: 'Rate limit exceeded' }, { status: 429, headers: { 'Retry-After': '60' } });
|
|
233
|
-
}),
|
|
234
|
-
// Merge Request Notes (MUST be before generic merge_requests/* handlers)
|
|
235
|
-
// Why order matters: MSW matches first handler that fits, more specific patterns must come first
|
|
236
|
-
http.get(`${BASE_URL}/projects/*/merge_requests/*/notes`, ({ request }) => {
|
|
237
|
-
// Try multiple parsing strategies for URL-encoded paths
|
|
238
|
-
let mergeRequestIid = extractMrIidFromNotesUrl(request.url);
|
|
239
|
-
if (mergeRequestIid === null) {
|
|
240
|
-
// Try with URL-decoded path
|
|
241
|
-
const decodedUrl = decodeURIComponent(request.url);
|
|
242
|
-
mergeRequestIid = extractMrIidFromNotesUrl(decodedUrl);
|
|
243
|
-
}
|
|
244
|
-
if (mergeRequestIid === null) {
|
|
245
|
-
// Try alternative pattern matching
|
|
246
|
-
const altMatch = request.url.match(/merge_requests[\/%2F](\d+)[\/%2F]notes/);
|
|
247
|
-
if (altMatch) {
|
|
248
|
-
mergeRequestIid = parseInt(altMatch[1], 10);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
if (mergeRequestIid === null || isNaN(mergeRequestIid)) {
|
|
252
|
-
return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
|
|
253
|
-
}
|
|
254
|
-
if (mergeRequestIid === 1) {
|
|
255
|
-
return HttpResponse.json(fixtures.mockNotesList);
|
|
256
|
-
}
|
|
257
|
-
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
258
|
-
}),
|
|
259
|
-
http.post(`${BASE_URL}/projects/*/merge_requests/*/notes`, async ({ request }) => {
|
|
260
|
-
let mergeRequestIid = extractMrIidFromNotesUrl(request.url);
|
|
261
|
-
if (mergeRequestIid === null) {
|
|
262
|
-
const decodedUrl = decodeURIComponent(request.url);
|
|
263
|
-
mergeRequestIid = extractMrIidFromNotesUrl(decodedUrl);
|
|
264
|
-
}
|
|
265
|
-
if (mergeRequestIid === null) {
|
|
266
|
-
const altMatch = request.url.match(/merge_requests[\/%2F](\d+)[\/%2F]notes/);
|
|
267
|
-
if (altMatch) {
|
|
268
|
-
mergeRequestIid = parseInt(altMatch[1], 10);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
if (mergeRequestIid === null || isNaN(mergeRequestIid)) {
|
|
272
|
-
return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
|
|
273
|
-
}
|
|
274
|
-
const body = await request.json();
|
|
275
|
-
if (!body.body) {
|
|
276
|
-
return HttpResponse.json({ error: 'body is required' }, { status: 400 });
|
|
277
|
-
}
|
|
278
|
-
const createdNote = {
|
|
279
|
-
...fixtures.mockNote,
|
|
280
|
-
id: 3,
|
|
281
|
-
body: body.body,
|
|
282
|
-
confidential: body.confidential || false,
|
|
283
|
-
created_at: new Date().toISOString(),
|
|
284
|
-
};
|
|
285
|
-
return HttpResponse.json(createdNote, { status: 201 });
|
|
286
|
-
}),
|
|
287
|
-
http.put(`${BASE_URL}/projects/*/merge_requests/*/notes/*`, async ({ request }) => {
|
|
288
|
-
// Parse note ID from URL - handle both /notes/1 and /notes/1?params
|
|
289
|
-
const urlWithoutQuery = request.url.split('?')[0];
|
|
290
|
-
const urlParts = urlWithoutQuery.split('/notes/');
|
|
291
|
-
if (urlParts.length < 2) {
|
|
292
|
-
return HttpResponse.json({ error: 'Invalid note ID' }, { status: 400 });
|
|
293
|
-
}
|
|
294
|
-
const noteIdStr = urlParts[1].split('?')[0].split('/')[0];
|
|
295
|
-
const noteId = parseInt(noteIdStr, 10);
|
|
296
|
-
if (isNaN(noteId)) {
|
|
297
|
-
return HttpResponse.json({ error: 'Invalid note ID' }, { status: 400 });
|
|
298
|
-
}
|
|
299
|
-
if (noteId === 1) {
|
|
345
|
+
}),
|
|
346
|
+
// Jobs
|
|
347
|
+
http.get(`${baseUrl}/projects/*/jobs`, ({ request }) => {
|
|
348
|
+
const scope = parseScopeParam(request);
|
|
349
|
+
if (scope.length > 0 && scope.includes('failed')) {
|
|
350
|
+
return HttpResponse.json(fixtures.mockJobsList.filter(j => j.status === 'failed'));
|
|
351
|
+
}
|
|
352
|
+
return HttpResponse.json(fixtures.mockJobsList);
|
|
353
|
+
}),
|
|
354
|
+
http.get(`${baseUrl}/projects/*/jobs/*`, ({ request }) => {
|
|
355
|
+
const jobId = extractIidFromUrl(request.url);
|
|
356
|
+
if (jobId === null) {
|
|
357
|
+
return HttpResponse.json({ error: 'Invalid job ID' }, { status: 400 });
|
|
358
|
+
}
|
|
359
|
+
if (jobId === 1234) {
|
|
360
|
+
return HttpResponse.json(fixtures.mockJob);
|
|
361
|
+
}
|
|
362
|
+
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
363
|
+
}),
|
|
364
|
+
http.post(`${baseUrl}/projects/*/jobs/*/play`, ({ request }) => {
|
|
365
|
+
const parts = request.url.split('/');
|
|
366
|
+
const jobId = parseInt(parts[parts.length - 2], 10);
|
|
367
|
+
return HttpResponse.json({
|
|
368
|
+
...fixtures.mockJob,
|
|
369
|
+
id: jobId,
|
|
370
|
+
status: 'pending',
|
|
371
|
+
});
|
|
372
|
+
}),
|
|
373
|
+
// Rate limiting simulation
|
|
374
|
+
http.get(`${baseUrl}/rate-limit-test`, () => {
|
|
375
|
+
return HttpResponse.json({ message: 'Rate limit exceeded' }, { status: 429, headers: { 'Retry-After': '60' } });
|
|
376
|
+
}),
|
|
377
|
+
// Merge Request Notes (MUST be before generic merge_requests/* handlers)
|
|
378
|
+
http.get(`${baseUrl}/projects/*/merge_requests/*/notes`, ({ request }) => {
|
|
379
|
+
let mergeRequestIid = extractMrIidFromNotesUrl(request.url);
|
|
380
|
+
if (mergeRequestIid === null) {
|
|
381
|
+
const decodedUrl = decodeURIComponent(request.url);
|
|
382
|
+
mergeRequestIid = extractMrIidFromNotesUrl(decodedUrl);
|
|
383
|
+
}
|
|
384
|
+
if (mergeRequestIid === null) {
|
|
385
|
+
const altMatch = request.url.match(/merge_requests[\/%2F](\d+)[\/%2F]notes/);
|
|
386
|
+
if (altMatch) {
|
|
387
|
+
mergeRequestIid = parseInt(altMatch[1], 10);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (mergeRequestIid === null || isNaN(mergeRequestIid)) {
|
|
391
|
+
return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
|
|
392
|
+
}
|
|
393
|
+
if (mergeRequestIid === 1) {
|
|
394
|
+
return HttpResponse.json(fixtures.mockNotesList);
|
|
395
|
+
}
|
|
396
|
+
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
397
|
+
}),
|
|
398
|
+
http.post(`${baseUrl}/projects/*/merge_requests/*/notes`, async ({ request }) => {
|
|
399
|
+
let mergeRequestIid = extractMrIidFromNotesUrl(request.url);
|
|
400
|
+
if (mergeRequestIid === null) {
|
|
401
|
+
const decodedUrl = decodeURIComponent(request.url);
|
|
402
|
+
mergeRequestIid = extractMrIidFromNotesUrl(decodedUrl);
|
|
403
|
+
}
|
|
404
|
+
if (mergeRequestIid === null) {
|
|
405
|
+
const altMatch = request.url.match(/merge_requests[\/%2F](\d+)[\/%2F]notes/);
|
|
406
|
+
if (altMatch) {
|
|
407
|
+
mergeRequestIid = parseInt(altMatch[1], 10);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (mergeRequestIid === null || isNaN(mergeRequestIid)) {
|
|
411
|
+
return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
|
|
412
|
+
}
|
|
300
413
|
const body = await request.json();
|
|
301
414
|
if (!body.body) {
|
|
302
415
|
return HttpResponse.json({ error: 'body is required' }, { status: 400 });
|
|
303
416
|
}
|
|
304
|
-
const
|
|
417
|
+
const createdNote = {
|
|
305
418
|
...fixtures.mockNote,
|
|
306
|
-
id:
|
|
419
|
+
id: 3,
|
|
307
420
|
body: body.body,
|
|
308
|
-
confidential: body.confidential
|
|
309
|
-
|
|
421
|
+
confidential: body.confidential || false,
|
|
422
|
+
created_at: new Date().toISOString(),
|
|
310
423
|
};
|
|
311
|
-
return HttpResponse.json(
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
424
|
+
return HttpResponse.json(createdNote, { status: 201 });
|
|
425
|
+
}),
|
|
426
|
+
http.put(`${baseUrl}/projects/*/merge_requests/*/notes/*`, async ({ request }) => {
|
|
427
|
+
const urlWithoutQuery = request.url.split('?')[0];
|
|
428
|
+
const urlParts = urlWithoutQuery.split('/notes/');
|
|
429
|
+
if (urlParts.length < 2) {
|
|
430
|
+
return HttpResponse.json({ error: 'Invalid note ID' }, { status: 400 });
|
|
431
|
+
}
|
|
432
|
+
const noteIdStr = urlParts[1].split('?')[0].split('/')[0];
|
|
433
|
+
const noteId = parseInt(noteIdStr, 10);
|
|
434
|
+
if (isNaN(noteId)) {
|
|
435
|
+
return HttpResponse.json({ error: 'Invalid note ID' }, { status: 400 });
|
|
436
|
+
}
|
|
437
|
+
if (noteId === 1) {
|
|
438
|
+
const body = await request.json();
|
|
439
|
+
if (!body.body) {
|
|
440
|
+
return HttpResponse.json({ error: 'body is required' }, { status: 400 });
|
|
441
|
+
}
|
|
442
|
+
const updatedNote = {
|
|
443
|
+
...fixtures.mockNote,
|
|
444
|
+
id: noteId,
|
|
445
|
+
body: body.body,
|
|
446
|
+
confidential: body.confidential !== undefined ? body.confidential : fixtures.mockNote.confidential,
|
|
447
|
+
updated_at: new Date().toISOString(),
|
|
448
|
+
};
|
|
449
|
+
return HttpResponse.json(updatedNote, { status: 200 });
|
|
450
|
+
}
|
|
451
|
+
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
452
|
+
}),
|
|
453
|
+
http.delete(`${baseUrl}/projects/*/merge_requests/*/notes/*`, ({ request }) => {
|
|
454
|
+
const urlParts = request.url.split('/notes/');
|
|
455
|
+
if (urlParts.length < 2) {
|
|
456
|
+
return HttpResponse.json({ error: 'Invalid note ID' }, { status: 400 });
|
|
457
|
+
}
|
|
458
|
+
const noteId = parseInt(urlParts[1], 10);
|
|
459
|
+
if (isNaN(noteId)) {
|
|
460
|
+
return HttpResponse.json({ error: 'Invalid note ID' }, { status: 400 });
|
|
461
|
+
}
|
|
462
|
+
if (noteId === 1) {
|
|
463
|
+
return new HttpResponse(null, { status: 204 });
|
|
464
|
+
}
|
|
465
|
+
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
466
|
+
}),
|
|
467
|
+
// Merge Requests (generic handlers after more specific /notes handlers)
|
|
468
|
+
http.get(`${baseUrl}/projects/*/merge_requests`, ({ request }) => {
|
|
469
|
+
const { page } = parsePaginationParams(request);
|
|
470
|
+
if (page === 1) {
|
|
471
|
+
return HttpResponse.json(fixtures.mockMergeRequestsList);
|
|
472
|
+
}
|
|
473
|
+
return HttpResponse.json([]);
|
|
474
|
+
}),
|
|
475
|
+
http.get(`${baseUrl}/projects/*/merge_requests/*`, ({ request }) => {
|
|
476
|
+
const mergeRequestIid = extractIidFromUrl(request.url);
|
|
477
|
+
if (mergeRequestIid === null) {
|
|
478
|
+
return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
|
|
479
|
+
}
|
|
480
|
+
if (mergeRequestIid === 1) {
|
|
481
|
+
return HttpResponse.json(fixtures.mockMergeRequest);
|
|
482
|
+
}
|
|
483
|
+
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
484
|
+
}),
|
|
485
|
+
http.post(`${baseUrl}/projects/*/merge_requests`, async ({ request }) => {
|
|
372
486
|
const body = await request.json();
|
|
373
|
-
|
|
487
|
+
if (!body.source_branch || !body.target_branch || !body.title) {
|
|
488
|
+
return HttpResponse.json({ error: 'source_branch, target_branch, and title are required' }, { status: 400 });
|
|
489
|
+
}
|
|
490
|
+
const createdMR = {
|
|
374
491
|
...fixtures.mockMergeRequest,
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
492
|
+
iid: 3,
|
|
493
|
+
id: 3,
|
|
494
|
+
title: body.title,
|
|
495
|
+
source_branch: body.source_branch,
|
|
496
|
+
target_branch: body.target_branch,
|
|
497
|
+
description: body.description,
|
|
498
|
+
web_url: 'https://gitlab.com/my-org/my-project/-/merge_requests/3',
|
|
379
499
|
};
|
|
380
|
-
return HttpResponse.json(
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
return
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
500
|
+
return HttpResponse.json(createdMR, { status: 201 });
|
|
501
|
+
}),
|
|
502
|
+
http.put(`${baseUrl}/projects/*/merge_requests/*`, async ({ request }) => {
|
|
503
|
+
const mergeRequestIid = extractIidFromUrl(request.url);
|
|
504
|
+
if (mergeRequestIid === null) {
|
|
505
|
+
return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
|
|
506
|
+
}
|
|
507
|
+
if (mergeRequestIid === 1) {
|
|
508
|
+
const body = await request.json();
|
|
509
|
+
const updatedMR = {
|
|
510
|
+
...fixtures.mockMergeRequest,
|
|
511
|
+
title: body.title || fixtures.mockMergeRequest.title,
|
|
512
|
+
description: body.description !== undefined ? body.description : fixtures.mockMergeRequest.description,
|
|
513
|
+
state: body.state_event === 'close' ? 'closed' : fixtures.mockMergeRequest.state,
|
|
514
|
+
updated_at: new Date().toISOString(),
|
|
515
|
+
};
|
|
516
|
+
return HttpResponse.json(updatedMR, { status: 200 });
|
|
517
|
+
}
|
|
518
|
+
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
519
|
+
}),
|
|
520
|
+
http.delete(`${baseUrl}/projects/*/merge_requests/*`, ({ request }) => {
|
|
521
|
+
if (request.url.includes('/forbidden-project/')) {
|
|
522
|
+
return HttpResponse.json({ message: 'Forbidden', error: 'You do not have permission to delete this merge request' }, { status: 403 });
|
|
523
|
+
}
|
|
524
|
+
const mergeRequestIid = extractIidFromUrl(request.url);
|
|
525
|
+
if (mergeRequestIid === null) {
|
|
526
|
+
return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
|
|
527
|
+
}
|
|
528
|
+
if (mergeRequestIid === 1) {
|
|
529
|
+
return new HttpResponse(null, { status: 204 });
|
|
530
|
+
}
|
|
531
|
+
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
532
|
+
}),
|
|
533
|
+
// Issues
|
|
534
|
+
http.get(`${baseUrl}/projects/*/issues`, ({ request }) => {
|
|
535
|
+
const { page } = parsePaginationParams(request);
|
|
536
|
+
if (page === 1) {
|
|
537
|
+
return HttpResponse.json(fixtures.mockIssuesList);
|
|
538
|
+
}
|
|
539
|
+
return HttpResponse.json([]);
|
|
540
|
+
}),
|
|
541
|
+
http.get(`${baseUrl}/projects/*/issues/*`, ({ request }) => {
|
|
542
|
+
const issueIid = extractIidFromUrl(request.url);
|
|
543
|
+
if (issueIid === null) {
|
|
544
|
+
return HttpResponse.json({ error: 'Invalid issue IID' }, { status: 400 });
|
|
545
|
+
}
|
|
546
|
+
if (issueIid === 1) {
|
|
547
|
+
return HttpResponse.json(fixtures.mockIssue);
|
|
548
|
+
}
|
|
549
|
+
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
550
|
+
}),
|
|
551
|
+
http.post(`${baseUrl}/projects/*/issues`, async ({ request }) => {
|
|
552
|
+
const body = await request.json();
|
|
553
|
+
if (!body.title) {
|
|
554
|
+
return HttpResponse.json({ error: 'title is required' }, { status: 400 });
|
|
555
|
+
}
|
|
556
|
+
const createdIssue = {
|
|
557
|
+
...fixtures.mockIssue,
|
|
558
|
+
iid: 3,
|
|
559
|
+
id: 3,
|
|
560
|
+
title: body.title,
|
|
561
|
+
description: body.description || '',
|
|
562
|
+
state: 'opened',
|
|
563
|
+
web_url: 'https://gitlab.com/my-org/my-project/-/issues/3',
|
|
564
|
+
created_at: new Date().toISOString(),
|
|
565
|
+
};
|
|
566
|
+
return HttpResponse.json(createdIssue, { status: 201 });
|
|
567
|
+
}),
|
|
568
|
+
http.delete(`${baseUrl}/projects/*/issues/*`, ({ request }) => {
|
|
569
|
+
if (request.url.includes('/forbidden-project/')) {
|
|
570
|
+
return HttpResponse.json({ message: 'Forbidden', error: 'You do not have permission to delete this issue' }, { status: 403 });
|
|
571
|
+
}
|
|
572
|
+
const issueIid = extractIidFromUrl(request.url);
|
|
573
|
+
if (issueIid === null) {
|
|
574
|
+
return HttpResponse.json({ error: 'Invalid issue IID' }, { status: 400 });
|
|
575
|
+
}
|
|
576
|
+
if (issueIid === 1) {
|
|
577
|
+
return new HttpResponse(null, { status: 204 });
|
|
578
|
+
}
|
|
579
|
+
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
580
|
+
}),
|
|
581
|
+
// Server error simulation
|
|
582
|
+
http.get(`${baseUrl}/server-error-test`, () => {
|
|
583
|
+
return HttpResponse.json({ message: 'Internal Server Error' }, { status: 503 });
|
|
584
|
+
}),
|
|
585
|
+
];
|
|
586
|
+
}
|
|
455
587
|
/**
|
|
456
|
-
* Create
|
|
457
|
-
*
|
|
458
|
-
* Why setupServer: MSW's node integration for testing environments
|
|
588
|
+
* Create all handlers (GitLab API + OAuth) with configurable URLs
|
|
459
589
|
*/
|
|
590
|
+
export function createAllHandlers(gitlabBaseUrl = DEFAULT_BASE_URL, oauthConfig) {
|
|
591
|
+
const gitlabHandlers = createGitLabHandlers(gitlabBaseUrl);
|
|
592
|
+
const oauthHandlers = oauthConfig ? createOAuthHandlers(oauthConfig) : [];
|
|
593
|
+
return [...oauthHandlers, ...gitlabHandlers];
|
|
594
|
+
}
|
|
595
|
+
// Legacy exports for backward compatibility with existing unit tests
|
|
596
|
+
export const handlers = createGitLabHandlers(DEFAULT_BASE_URL);
|
|
460
597
|
export const mockServer = setupServer(...handlers);
|
|
461
|
-
/**
|
|
462
|
-
* Helper: start server before tests
|
|
463
|
-
*/
|
|
464
598
|
export function startMockServer() {
|
|
465
599
|
mockServer.listen({ onUnhandledRequest: 'error' });
|
|
466
600
|
}
|
|
467
|
-
/**
|
|
468
|
-
* Helper: reset handlers between tests
|
|
469
|
-
*
|
|
470
|
-
* Why: Prevents test pollution from runtime handler modifications
|
|
471
|
-
*/
|
|
472
601
|
export function resetMockServer() {
|
|
473
602
|
mockServer.resetHandlers();
|
|
474
603
|
}
|
|
475
|
-
/**
|
|
476
|
-
* Helper: stop server after tests
|
|
477
|
-
*/
|
|
478
604
|
export function stopMockServer() {
|
|
479
605
|
mockServer.close();
|
|
480
606
|
}
|
|
607
|
+
/**
|
|
608
|
+
* Create a new MSW server instance with custom handlers
|
|
609
|
+
*/
|
|
610
|
+
export function createMockServer(gitlabBaseUrl = DEFAULT_BASE_URL, oauthConfig) {
|
|
611
|
+
const allHandlers = createAllHandlers(gitlabBaseUrl, oauthConfig);
|
|
612
|
+
return setupServer(...allHandlers);
|
|
613
|
+
}
|
|
481
614
|
//# sourceMappingURL=mock-gitlab-server.js.map
|