mcp4openapi 0.1.0 → 0.2.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/README.md +137 -95
- package/dist/scripts/validate-profile.js +3 -3
- package/dist/scripts/validate-profile.js.map +1 -1
- package/dist/src/composite-executor.d.ts +3 -1
- package/dist/src/composite-executor.d.ts.map +1 -1
- package/dist/src/composite-executor.js +16 -5
- package/dist/src/composite-executor.js.map +1 -1
- package/dist/src/constants.d.ts +49 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +49 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/errors.d.ts +6 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +13 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/generated-schemas.d.ts +832 -52
- package/dist/src/generated-schemas.d.ts.map +1 -1
- package/dist/src/generated-schemas.js +31 -8
- package/dist/src/generated-schemas.js.map +1 -1
- package/dist/src/http-client-factory.d.ts.map +1 -1
- package/dist/src/http-client-factory.js +14 -3
- package/dist/src/http-client-factory.js.map +1 -1
- package/dist/src/http-transport.d.ts +65 -0
- package/dist/src/http-transport.d.ts.map +1 -1
- package/dist/src/http-transport.js +921 -77
- package/dist/src/http-transport.js.map +1 -1
- package/dist/src/index.js +108 -8
- package/dist/src/index.js.map +1 -1
- package/dist/src/interceptors.d.ts +3 -0
- package/dist/src/interceptors.d.ts.map +1 -1
- package/dist/src/interceptors.js +76 -8
- package/dist/src/interceptors.js.map +1 -1
- package/dist/src/logger.d.ts +1 -1
- package/dist/src/logger.js +3 -3
- package/dist/src/logger.js.map +1 -1
- package/dist/src/mcp-server.d.ts +33 -0
- package/dist/src/mcp-server.d.ts.map +1 -1
- package/dist/src/mcp-server.js +263 -54
- package/dist/src/mcp-server.js.map +1 -1
- package/dist/src/oauth-provider.d.ts +92 -0
- package/dist/src/oauth-provider.d.ts.map +1 -0
- package/dist/src/oauth-provider.js +588 -0
- package/dist/src/oauth-provider.js.map +1 -0
- package/dist/src/openapi-parser.d.ts +16 -0
- package/dist/src/openapi-parser.d.ts.map +1 -1
- package/dist/src/openapi-parser.js +141 -6
- package/dist/src/openapi-parser.js.map +1 -1
- package/dist/src/profile-loader.d.ts +2 -2
- package/dist/src/profile-loader.d.ts.map +1 -1
- package/dist/src/profile-loader.js +45 -24
- package/dist/src/profile-loader.js.map +1 -1
- package/dist/src/testing/fixtures.d.ts +189 -0
- package/dist/src/testing/fixtures.d.ts.map +1 -1
- package/dist/src/testing/fixtures.js +144 -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 +567 -304
- package/dist/src/testing/mock-gitlab-server.js.map +1 -1
- package/dist/src/types/http-transport.d.ts +16 -0
- package/dist/src/types/http-transport.d.ts.map +1 -1
- package/dist/src/types/openapi.d.ts +5 -0
- package/dist/src/types/openapi.d.ts.map +1 -1
- package/dist/src/types/profile.d.ts +112 -3
- package/dist/src/types/profile.d.ts.map +1 -1
- package/dist/src/validation-utils.d.ts +12 -0
- package/dist/src/validation-utils.d.ts.map +1 -1
- package/dist/src/validation-utils.js +17 -0
- package/dist/src/validation-utils.js.map +1 -1
- package/package.json +12 -3
- package/profile-schema.json +169 -7
- package/dist/composite-executor.d.ts +0 -65
- package/dist/composite-executor.d.ts.map +0 -1
- package/dist/composite-executor.js +0 -147
- package/dist/composite-executor.js.map +0 -1
- package/dist/constants.d.ts +0 -36
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -36
- package/dist/constants.js.map +0 -1
- package/dist/http-transport.d.ts +0 -195
- package/dist/http-transport.d.ts.map +0 -1
- package/dist/http-transport.js +0 -760
- package/dist/http-transport.js.map +0 -1
- package/dist/interceptors.d.ts +0 -74
- package/dist/interceptors.d.ts.map +0 -1
- package/dist/interceptors.js +0 -220
- package/dist/interceptors.js.map +0 -1
- package/dist/logger.d.ts +0 -81
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -264
- package/dist/logger.js.map +0 -1
- package/dist/mcp-server.d.ts +0 -110
- package/dist/mcp-server.d.ts.map +0 -1
- package/dist/mcp-server.js +0 -568
- package/dist/mcp-server.js.map +0 -1
- package/dist/metrics.d.ts +0 -86
- package/dist/metrics.d.ts.map +0 -1
- package/dist/metrics.js +0 -229
- package/dist/metrics.js.map +0 -1
- package/dist/openapi-parser.d.ts +0 -35
- package/dist/openapi-parser.d.ts.map +0 -1
- package/dist/openapi-parser.js +0 -160
- package/dist/openapi-parser.js.map +0 -1
- package/dist/profile-loader.d.ts +0 -25
- package/dist/profile-loader.d.ts.map +0 -1
- package/dist/profile-loader.js +0 -134
- package/dist/profile-loader.js.map +0 -1
- package/dist/schema-validator.d.ts +0 -32
- package/dist/schema-validator.d.ts.map +0 -1
- package/dist/schema-validator.js +0 -126
- package/dist/schema-validator.js.map +0 -1
- package/dist/testing/fixtures.d.ts +0 -186
- package/dist/testing/fixtures.d.ts.map +0 -1
- package/dist/testing/fixtures.js +0 -135
- package/dist/testing/fixtures.js.map +0 -1
- package/dist/testing/http-integration.test.d.ts +0 -7
- package/dist/testing/http-integration.test.d.ts.map +0 -1
- package/dist/testing/http-integration.test.js +0 -383
- package/dist/testing/http-integration.test.js.map +0 -1
- package/dist/testing/http-multiuser.test.d.ts +0 -10
- package/dist/testing/http-multiuser.test.d.ts.map +0 -1
- package/dist/testing/http-multiuser.test.js +0 -255
- package/dist/testing/http-multiuser.test.js.map +0 -1
- package/dist/testing/integration.test.d.ts +0 -8
- package/dist/testing/integration.test.d.ts.map +0 -1
- package/dist/testing/integration.test.js +0 -247
- package/dist/testing/integration.test.js.map +0 -1
- package/dist/testing/mock-gitlab-server.d.ts +0 -34
- package/dist/testing/mock-gitlab-server.d.ts.map +0 -1
- package/dist/testing/mock-gitlab-server.js +0 -224
- package/dist/testing/mock-gitlab-server.js.map +0 -1
- package/dist/testing/test-types.d.ts +0 -59
- package/dist/testing/test-types.d.ts.map +0 -1
- package/dist/testing/test-types.js +0 -7
- package/dist/testing/test-types.js.map +0 -1
- package/dist/tool-generator.d.ts +0 -43
- package/dist/tool-generator.d.ts.map +0 -1
- package/dist/tool-generator.js +0 -123
- package/dist/tool-generator.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/dist/types/http-transport.d.ts +0 -39
- package/dist/types/http-transport.d.ts.map +0 -1
- package/dist/types/http-transport.js +0 -8
- package/dist/types/http-transport.js.map +0 -1
- package/dist/types/openapi.d.ts +0 -50
- package/dist/types/openapi.d.ts.map +0 -1
- package/dist/types/openapi.js +0 -9
- package/dist/types/openapi.js.map +0 -1
- package/dist/types/profile.d.ts +0 -76
- package/dist/types/profile.d.ts.map +0 -1
- package/dist/types/profile.js +0 -9
- package/dist/types/profile.js.map +0 -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
|
}
|
|
@@ -29,323 +36,579 @@ function extractIidFromUrl(url) {
|
|
|
29
36
|
return iid;
|
|
30
37
|
}
|
|
31
38
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* Why ordered by resource: Mirrors actual GitLab API structure for maintainability
|
|
35
|
-
* Why wildcard patterns: GitLab accepts URL-encoded paths like my-org/my-project
|
|
39
|
+
* Extract merge request IID from notes URL
|
|
40
|
+
* URL format: /projects/{project}/merge_requests/{iid}/notes
|
|
36
41
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
function extractMrIidFromNotesUrl(url) {
|
|
43
|
+
const urlWithoutQuery = url.split('?')[0];
|
|
44
|
+
const match = urlWithoutQuery.match(/\/merge_requests\/(\d+)\/notes/);
|
|
45
|
+
if (!match) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const iid = parseInt(match[1], 10);
|
|
49
|
+
if (isNaN(iid) || iid < 1 || iid > 2147483647) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return iid;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
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
|
|
132
|
+
*/
|
|
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`, () => {
|
|
43
258
|
return HttpResponse.json(fixtures.mockBadgesList);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
http.get(`${BASE_URL}/projects/*/badges/*`, ({ request }) => {
|
|
48
|
-
const badgeId = extractIidFromUrl(request.url);
|
|
49
|
-
if (badgeId === null) {
|
|
50
|
-
return HttpResponse.json({ error: 'Invalid badge ID' }, { status: 400 });
|
|
51
|
-
}
|
|
52
|
-
if (badgeId === 1) {
|
|
53
|
-
return HttpResponse.json(fixtures.mockBadge);
|
|
54
|
-
}
|
|
55
|
-
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
56
|
-
}),
|
|
57
|
-
http.post(`${BASE_URL}/projects/*/badges`, async ({ request }) => {
|
|
58
|
-
const body = await request.json();
|
|
59
|
-
if (!body.link_url || !body.image_url) {
|
|
60
|
-
return HttpResponse.json({ error: 'link_url and image_url are required' }, { status: 400 });
|
|
61
|
-
}
|
|
62
|
-
return HttpResponse.json({
|
|
63
|
-
...fixtures.mockBadge,
|
|
64
|
-
id: 3,
|
|
65
|
-
name: body.name || 'New Badge',
|
|
66
|
-
link_url: body.link_url,
|
|
67
|
-
image_url: body.image_url,
|
|
68
|
-
}, { status: 201 });
|
|
69
|
-
}),
|
|
70
|
-
http.put(`${BASE_URL}/projects/*/badges/*`, async ({ request }) => {
|
|
71
|
-
const badgeId = extractIidFromUrl(request.url);
|
|
72
|
-
if (badgeId === null) {
|
|
73
|
-
return HttpResponse.json({ error: 'Invalid badge ID' }, { status: 400 });
|
|
74
|
-
}
|
|
75
|
-
const body = await request.json();
|
|
76
|
-
if (badgeId === 1) {
|
|
259
|
+
}),
|
|
260
|
+
http.post(`${baseUrl}/groups/*/badges`, async ({ request }) => {
|
|
261
|
+
const body = await request.json();
|
|
77
262
|
return HttpResponse.json({
|
|
78
263
|
...fixtures.mockBadge,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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,
|
|
82
312
|
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
return HttpResponse.json(fixtures.mockBranchesList);
|
|
116
|
-
}),
|
|
117
|
-
http.get(`${BASE_URL}/projects/*/repository/branches/*`, ({ request }) => {
|
|
118
|
-
const branch = decodeURIComponent(request.url.split('/').pop() || '');
|
|
119
|
-
const found = fixtures.mockBranchesList.find(b => b.name === branch);
|
|
120
|
-
if (found) {
|
|
121
|
-
return HttpResponse.json(found);
|
|
122
|
-
}
|
|
123
|
-
return HttpResponse.json({ message: 'Branch Not Found' }, { status: 404 });
|
|
124
|
-
}),
|
|
125
|
-
http.post(`${BASE_URL}/projects/*/repository/branches`, async ({ request }) => {
|
|
126
|
-
const { branch, ref } = parseBranchParams(request);
|
|
127
|
-
if (!branch || !ref) {
|
|
128
|
-
return HttpResponse.json({ error: 'branch and ref parameters are required' }, { status: 400 });
|
|
129
|
-
}
|
|
130
|
-
return HttpResponse.json({
|
|
131
|
-
name: branch,
|
|
132
|
-
commit: fixtures.mockBranch.commit,
|
|
133
|
-
merged: false,
|
|
134
|
-
protected: false,
|
|
135
|
-
default: false,
|
|
136
|
-
}, { status: 201 });
|
|
137
|
-
}),
|
|
138
|
-
http.delete(`${BASE_URL}/projects/*/repository/branches/*`, ({ request }) => {
|
|
139
|
-
const branch = decodeURIComponent(request.url.split('/').pop() || '');
|
|
140
|
-
if (branch !== 'main') {
|
|
141
|
-
return new HttpResponse(null, { status: 204 });
|
|
142
|
-
}
|
|
143
|
-
return HttpResponse.json({ message: 'Cannot delete default branch' }, { status: 403 });
|
|
144
|
-
}),
|
|
145
|
-
http.put(`${BASE_URL}/projects/*/repository/branches/*/protect`, ({ request }) => {
|
|
146
|
-
const parts = request.url.split('/');
|
|
147
|
-
const branch = decodeURIComponent(parts[parts.length - 2]); // second-to-last part
|
|
148
|
-
return HttpResponse.json({
|
|
149
|
-
...fixtures.mockBranch,
|
|
150
|
-
name: branch,
|
|
151
|
-
protected: true,
|
|
152
|
-
});
|
|
153
|
-
}),
|
|
154
|
-
http.put(`${BASE_URL}/projects/*/repository/branches/*/unprotect`, ({ request }) => {
|
|
155
|
-
const parts = request.url.split('/');
|
|
156
|
-
const branch = decodeURIComponent(parts[parts.length - 2]); // second-to-last part
|
|
157
|
-
return HttpResponse.json({
|
|
158
|
-
...fixtures.mockBranch,
|
|
159
|
-
name: branch,
|
|
160
|
-
protected: false,
|
|
161
|
-
});
|
|
162
|
-
}),
|
|
163
|
-
// Access Requests
|
|
164
|
-
http.get(`${BASE_URL}/projects/*/access_requests`, () => {
|
|
165
|
-
return HttpResponse.json(fixtures.mockAccessRequestsList);
|
|
166
|
-
}),
|
|
167
|
-
http.get(`${BASE_URL}/groups/*/access_requests`, () => {
|
|
168
|
-
return HttpResponse.json(fixtures.mockAccessRequestsList);
|
|
169
|
-
}),
|
|
170
|
-
http.post(`${BASE_URL}/projects/*/access_requests`, () => {
|
|
171
|
-
return HttpResponse.json(fixtures.mockAccessRequest, { status: 201 });
|
|
172
|
-
}),
|
|
173
|
-
http.put(`${BASE_URL}/projects/*/access_requests/*/approve`, async ({ request }) => {
|
|
174
|
-
const body = await request.json();
|
|
175
|
-
const parts = request.url.split('/');
|
|
176
|
-
const userId = parseInt(parts[parts.length - 2], 10); // second-to-last part
|
|
177
|
-
return HttpResponse.json({
|
|
178
|
-
...fixtures.mockAccessRequest,
|
|
179
|
-
id: userId,
|
|
180
|
-
access_level: body.access_level || 30,
|
|
181
|
-
});
|
|
182
|
-
}),
|
|
183
|
-
http.delete(`${BASE_URL}/projects/*/access_requests/*`, () => {
|
|
184
|
-
return new HttpResponse(null, { status: 204 });
|
|
185
|
-
}),
|
|
186
|
-
// Jobs
|
|
187
|
-
http.get(`${BASE_URL}/projects/*/jobs`, ({ request }) => {
|
|
188
|
-
const scope = parseScopeParam(request);
|
|
189
|
-
if (scope.length > 0 && scope.includes('failed')) {
|
|
190
|
-
return HttpResponse.json(fixtures.mockJobsList.filter(j => j.status === 'failed'));
|
|
191
|
-
}
|
|
192
|
-
return HttpResponse.json(fixtures.mockJobsList);
|
|
193
|
-
}),
|
|
194
|
-
http.get(`${BASE_URL}/projects/*/jobs/*`, ({ request }) => {
|
|
195
|
-
const jobId = extractIidFromUrl(request.url);
|
|
196
|
-
if (jobId === null) {
|
|
197
|
-
return HttpResponse.json({ error: 'Invalid job ID' }, { status: 400 });
|
|
198
|
-
}
|
|
199
|
-
if (jobId === 1234) {
|
|
200
|
-
return HttpResponse.json(fixtures.mockJob);
|
|
201
|
-
}
|
|
202
|
-
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
203
|
-
}),
|
|
204
|
-
http.post(`${BASE_URL}/projects/*/jobs/*/play`, ({ request }) => {
|
|
205
|
-
const parts = request.url.split('/');
|
|
206
|
-
const jobId = parseInt(parts[parts.length - 2], 10); // second-to-last part
|
|
207
|
-
return HttpResponse.json({
|
|
208
|
-
...fixtures.mockJob,
|
|
209
|
-
id: jobId,
|
|
210
|
-
status: 'pending',
|
|
211
|
-
});
|
|
212
|
-
}),
|
|
213
|
-
// Rate limiting simulation
|
|
214
|
-
http.get(`${BASE_URL}/rate-limit-test`, () => {
|
|
215
|
-
return HttpResponse.json({ message: 'Rate limit exceeded' }, { status: 429, headers: { 'Retry-After': '60' } });
|
|
216
|
-
}),
|
|
217
|
-
// Merge Requests
|
|
218
|
-
http.get(`${BASE_URL}/projects/*/merge_requests`, ({ request, params }) => {
|
|
219
|
-
const { page } = parsePaginationParams(request);
|
|
220
|
-
// Simple pagination
|
|
221
|
-
if (page === 1) {
|
|
222
|
-
return HttpResponse.json(fixtures.mockMergeRequestsList);
|
|
223
|
-
}
|
|
224
|
-
return HttpResponse.json([]);
|
|
225
|
-
}),
|
|
226
|
-
http.get(`${BASE_URL}/projects/*/merge_requests/*`, ({ request }) => {
|
|
227
|
-
const mergeRequestIid = extractIidFromUrl(request.url);
|
|
228
|
-
if (mergeRequestIid === null) {
|
|
229
|
-
return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
|
|
230
|
-
}
|
|
231
|
-
if (mergeRequestIid === 1) {
|
|
232
|
-
return HttpResponse.json(fixtures.mockMergeRequest);
|
|
233
|
-
}
|
|
234
|
-
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
235
|
-
}),
|
|
236
|
-
http.post(`${BASE_URL}/projects/*/merge_requests`, async ({ request }) => {
|
|
237
|
-
const body = await request.json();
|
|
238
|
-
if (!body.source_branch || !body.target_branch || !body.title) {
|
|
239
|
-
return HttpResponse.json({ error: 'source_branch, target_branch, and title are required' }, { status: 400 });
|
|
240
|
-
}
|
|
241
|
-
// Return created merge request
|
|
242
|
-
const createdMR = {
|
|
243
|
-
...fixtures.mockMergeRequest,
|
|
244
|
-
iid: 3,
|
|
245
|
-
id: 3,
|
|
246
|
-
title: body.title,
|
|
247
|
-
source_branch: body.source_branch,
|
|
248
|
-
target_branch: body.target_branch,
|
|
249
|
-
description: body.description,
|
|
250
|
-
web_url: 'https://gitlab.com/my-org/my-project/-/merge_requests/3',
|
|
251
|
-
};
|
|
252
|
-
return HttpResponse.json(createdMR, { status: 201 });
|
|
253
|
-
}),
|
|
254
|
-
http.delete(`${BASE_URL}/projects/*/merge_requests/*`, ({ request }) => {
|
|
255
|
-
// Check for forbidden project
|
|
256
|
-
if (request.url.includes('/forbidden-project/')) {
|
|
257
|
-
return HttpResponse.json({ message: 'Forbidden', error: 'You do not have permission to delete this merge request' }, { status: 403 });
|
|
258
|
-
}
|
|
259
|
-
const mergeRequestIid = extractIidFromUrl(request.url);
|
|
260
|
-
if (mergeRequestIid === null) {
|
|
261
|
-
return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
|
|
262
|
-
}
|
|
263
|
-
if (mergeRequestIid === 1) {
|
|
264
|
-
return new HttpResponse(null, { status: 204 });
|
|
265
|
-
}
|
|
266
|
-
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
267
|
-
}),
|
|
268
|
-
// Issues
|
|
269
|
-
http.get(`${BASE_URL}/projects/*/issues`, ({ request }) => {
|
|
270
|
-
const { page } = parsePaginationParams(request);
|
|
271
|
-
// Simple pagination
|
|
272
|
-
if (page === 1) {
|
|
273
|
-
return HttpResponse.json(fixtures.mockIssuesList);
|
|
274
|
-
}
|
|
275
|
-
return HttpResponse.json([]);
|
|
276
|
-
}),
|
|
277
|
-
http.get(`${BASE_URL}/projects/*/issues/*`, ({ request }) => {
|
|
278
|
-
const issueIid = extractIidFromUrl(request.url);
|
|
279
|
-
if (issueIid === null) {
|
|
280
|
-
return HttpResponse.json({ error: 'Invalid issue IID' }, { status: 400 });
|
|
281
|
-
}
|
|
282
|
-
if (issueIid === 1) {
|
|
283
|
-
return HttpResponse.json(fixtures.mockIssue);
|
|
284
|
-
}
|
|
285
|
-
return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
|
|
286
|
-
}),
|
|
287
|
-
http.post(`${BASE_URL}/projects/*/issues`, async ({ request }) => {
|
|
288
|
-
const body = await request.json();
|
|
289
|
-
// Validate required fields
|
|
290
|
-
if (!body.title) {
|
|
291
|
-
return HttpResponse.json({ error: 'title is required' }, { status: 400 });
|
|
292
|
-
}
|
|
293
|
-
// Return created issue
|
|
294
|
-
const createdIssue = {
|
|
295
|
-
...fixtures.mockIssue,
|
|
296
|
-
iid: 3,
|
|
297
|
-
id: 3,
|
|
298
|
-
title: body.title,
|
|
299
|
-
description: body.description || '',
|
|
300
|
-
state: 'opened',
|
|
301
|
-
web_url: 'https://gitlab.com/my-org/my-project/-/issues/3',
|
|
302
|
-
created_at: new Date().toISOString(),
|
|
303
|
-
};
|
|
304
|
-
return HttpResponse.json(createdIssue, { status: 201 });
|
|
305
|
-
}),
|
|
306
|
-
http.delete(`${BASE_URL}/projects/*/issues/*`, ({ request }) => {
|
|
307
|
-
// Check for forbidden project
|
|
308
|
-
if (request.url.includes('/forbidden-project/')) {
|
|
309
|
-
return HttpResponse.json({ message: 'Forbidden', error: 'You do not have permission to delete this issue' }, { status: 403 });
|
|
310
|
-
}
|
|
311
|
-
const issueIid = extractIidFromUrl(request.url);
|
|
312
|
-
if (issueIid === null) {
|
|
313
|
-
return HttpResponse.json({ error: 'Invalid issue IID' }, { status: 400 });
|
|
314
|
-
}
|
|
315
|
-
if (issueIid === 1) {
|
|
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/*`, () => {
|
|
316
344
|
return new HttpResponse(null, { status: 204 });
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
+
}
|
|
413
|
+
const body = await request.json();
|
|
414
|
+
if (!body.body) {
|
|
415
|
+
return HttpResponse.json({ error: 'body is required' }, { status: 400 });
|
|
416
|
+
}
|
|
417
|
+
const createdNote = {
|
|
418
|
+
...fixtures.mockNote,
|
|
419
|
+
id: 3,
|
|
420
|
+
body: body.body,
|
|
421
|
+
confidential: body.confidential || false,
|
|
422
|
+
created_at: new Date().toISOString(),
|
|
423
|
+
};
|
|
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 }) => {
|
|
486
|
+
const body = await request.json();
|
|
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 = {
|
|
491
|
+
...fixtures.mockMergeRequest,
|
|
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',
|
|
499
|
+
};
|
|
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
|
+
}
|
|
325
587
|
/**
|
|
326
|
-
* Create
|
|
327
|
-
*
|
|
328
|
-
* Why setupServer: MSW's node integration for testing environments
|
|
588
|
+
* Create all handlers (GitLab API + OAuth) with configurable URLs
|
|
329
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);
|
|
330
597
|
export const mockServer = setupServer(...handlers);
|
|
331
|
-
/**
|
|
332
|
-
* Helper: start server before tests
|
|
333
|
-
*/
|
|
334
598
|
export function startMockServer() {
|
|
335
599
|
mockServer.listen({ onUnhandledRequest: 'error' });
|
|
336
600
|
}
|
|
337
|
-
/**
|
|
338
|
-
* Helper: reset handlers between tests
|
|
339
|
-
*
|
|
340
|
-
* Why: Prevents test pollution from runtime handler modifications
|
|
341
|
-
*/
|
|
342
601
|
export function resetMockServer() {
|
|
343
602
|
mockServer.resetHandlers();
|
|
344
603
|
}
|
|
345
|
-
/**
|
|
346
|
-
* Helper: stop server after tests
|
|
347
|
-
*/
|
|
348
604
|
export function stopMockServer() {
|
|
349
605
|
mockServer.close();
|
|
350
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
|
+
}
|
|
351
614
|
//# sourceMappingURL=mock-gitlab-server.js.map
|