Skip to content

Commit 8c1be7b

Browse files
committed
feat: test deleting a branch
1 parent 1b17bf4 commit 8c1be7b

File tree

3 files changed

+143
-51
lines changed

3 files changed

+143
-51
lines changed

packages/mcp-server-supabase/src/server.test.ts

Lines changed: 112 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -375,14 +375,12 @@ describe('tools', () => {
375375
test('invalid access token', async () => {
376376
const { callTool } = await setup({ accessToken: 'bad-token' });
377377

378-
async function run() {
379-
return await callTool({
380-
name: 'list_organizations',
381-
arguments: {},
382-
});
383-
}
378+
const listOrganizationsPromise = callTool({
379+
name: 'list_organizations',
380+
arguments: {},
381+
});
384382

385-
await expect(run()).rejects.toThrow(
383+
await expect(listOrganizationsPromise).rejects.toThrow(
386384
'Unauthorized. Please provide a valid access token to the MCP server via the --access-token flag.'
387385
);
388386
});
@@ -394,18 +392,18 @@ describe('tools', () => {
394392
const name = 'test-migration';
395393
const query = 'invalid sql';
396394

397-
async function run() {
398-
return await callTool({
399-
name: 'apply_migration',
400-
arguments: {
401-
project_id: project.id,
402-
name,
403-
query,
404-
},
405-
});
406-
}
395+
const applyMigrationPromise = callTool({
396+
name: 'apply_migration',
397+
arguments: {
398+
project_id: project.id,
399+
name,
400+
query,
401+
},
402+
});
407403

408-
await expect(run()).rejects.toThrow('syntax error at or near "invalid"');
404+
await expect(applyMigrationPromise).rejects.toThrow(
405+
'syntax error at or near "invalid"'
406+
);
409407
});
410408

411409
test('invalid sql for execute_sql', async () => {
@@ -414,17 +412,17 @@ describe('tools', () => {
414412
const project = mockProjects.values().next().value!;
415413
const query = 'invalid sql';
416414

417-
async function run() {
418-
return await callTool({
419-
name: 'execute_sql',
420-
arguments: {
421-
project_id: project.id,
422-
query,
423-
},
424-
});
425-
}
415+
const executeSqlPromise = callTool({
416+
name: 'execute_sql',
417+
arguments: {
418+
project_id: project.id,
419+
query,
420+
},
421+
});
426422

427-
await expect(run()).rejects.toThrow('syntax error at or near "invalid"');
423+
await expect(executeSqlPromise).rejects.toThrow(
424+
'syntax error at or near "invalid"'
425+
);
428426
});
429427

430428
test('enable branching', async () => {
@@ -479,7 +477,7 @@ describe('tools', () => {
479477
},
480478
});
481479

482-
expect(listResult.length).toBe(1);
480+
expect(listResult).toHaveLength(1);
483481
expect(listResult[0]).toEqual(firstResult);
484482
expect(firstResult).toEqual(secondResult);
485483
});
@@ -521,6 +519,91 @@ describe('tools', () => {
521519
});
522520
});
523521

522+
test('delete branch', async () => {
523+
const { callTool } = await setup();
524+
const project = mockProjects.values().next().value!;
525+
526+
await callTool({
527+
name: 'enable_branching',
528+
arguments: {
529+
project_id: project.id,
530+
},
531+
});
532+
533+
const branch = await callTool({
534+
name: 'create_branch',
535+
arguments: {
536+
project_id: project.id,
537+
name: 'test-branch',
538+
},
539+
});
540+
541+
const listBranchesResult = await callTool({
542+
name: 'list_branches',
543+
arguments: {
544+
project_id: project.id,
545+
},
546+
});
547+
548+
expect(listBranchesResult).toContainEqual(
549+
expect.objectContaining({ id: branch.id })
550+
);
551+
expect(listBranchesResult).toHaveLength(2);
552+
553+
await callTool({
554+
name: 'delete_branch',
555+
arguments: {
556+
branch_id: branch.id,
557+
},
558+
});
559+
560+
const listBranchesResultAfterDelete = await callTool({
561+
name: 'list_branches',
562+
arguments: {
563+
project_id: project.id,
564+
},
565+
});
566+
567+
expect(listBranchesResultAfterDelete).not.toContainEqual(
568+
expect.objectContaining({ id: branch.id })
569+
);
570+
expect(listBranchesResultAfterDelete).toHaveLength(1);
571+
});
572+
573+
test('delete main branch fails', async () => {
574+
const { callTool } = await setup();
575+
const project = mockProjects.values().next().value!;
576+
577+
await callTool({
578+
name: 'enable_branching',
579+
arguments: {
580+
project_id: project.id,
581+
},
582+
});
583+
584+
const listBranchesResult = await callTool({
585+
name: 'list_branches',
586+
arguments: {
587+
project_id: project.id,
588+
},
589+
});
590+
591+
expect(listBranchesResult).toHaveLength(1);
592+
593+
const mainBranch = listBranchesResult[0]!;
594+
595+
const deleteBranchPromise = callTool({
596+
name: 'delete_branch',
597+
arguments: {
598+
branch_id: mainBranch.id,
599+
},
600+
});
601+
602+
await expect(deleteBranchPromise).rejects.toThrow(
603+
'Cannot delete the default branch.'
604+
);
605+
});
606+
524607
test('list branches', async () => {
525608
const { callTool } = await setup();
526609
const project = mockProjects.values().next().value!;

packages/mcp-server-supabase/src/server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,8 @@ export function createSupabaseMcpServer(options: SupabaseMcpServerOptions) {
437437
},
438438
}),
439439
list_branches: tool({
440-
description: 'Lists all development branches of a Supabase project.',
440+
description:
441+
'Lists all development branches of a Supabase project. This will return branch details including status which you can use to check when operations like merge/rebase/reset complete.',
441442
parameters: z.object({
442443
project_id: z.string(),
443444
}),

packages/mcp-server-supabase/test/mocks.ts

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const mockManagementApi = [
4646

4747
const accessToken = authHeader?.replace('Bearer ', '');
4848
if (accessToken !== ACCESS_TOKEN) {
49-
return HttpResponse.json({ error: 'Unauthorized' }, { status: 401 });
49+
return HttpResponse.json({ message: 'Unauthorized' }, { status: 401 });
5050
}
5151
}),
5252

@@ -69,7 +69,7 @@ export const mockManagementApi = [
6969
const project = mockProjects.get(params.projectId);
7070
if (!project) {
7171
return HttpResponse.json(
72-
{ error: 'Project not found' },
72+
{ message: 'Project not found' },
7373
{ status: 404 }
7474
);
7575
}
@@ -122,7 +122,7 @@ export const mockManagementApi = [
122122
const project = mockProjects.get(params.projectId);
123123
if (!project) {
124124
return HttpResponse.json(
125-
{ error: 'Project not found' },
125+
{ message: 'Project not found' },
126126
{ status: 404 }
127127
);
128128
}
@@ -132,7 +132,7 @@ export const mockManagementApi = [
132132

133133
if (!results) {
134134
return HttpResponse.json(
135-
{ error: 'Failed to execute query' },
135+
{ message: 'Failed to execute query' },
136136
{ status: 500 }
137137
);
138138
}
@@ -147,7 +147,7 @@ export const mockManagementApi = [
147147
const project = mockProjects.get(params.projectId);
148148
if (!project) {
149149
return HttpResponse.json(
150-
{ error: 'Project not found' },
150+
{ message: 'Project not found' },
151151
{ status: 404 }
152152
);
153153
}
@@ -168,7 +168,7 @@ export const mockManagementApi = [
168168
const project = mockProjects.get(params.projectId);
169169
if (!project) {
170170
return HttpResponse.json(
171-
{ error: 'Project not found' },
171+
{ message: 'Project not found' },
172172
{ status: 404 }
173173
);
174174
}
@@ -178,7 +178,7 @@ export const mockManagementApi = [
178178

179179
if (!results) {
180180
return HttpResponse.json(
181-
{ error: 'Failed to execute query' },
181+
{ message: 'Failed to execute query' },
182182
{ status: 500 }
183183
);
184184
}
@@ -201,7 +201,7 @@ export const mockManagementApi = [
201201
const project = mockProjects.get(params.projectId);
202202
if (!project) {
203203
return HttpResponse.json(
204-
{ error: 'Project not found' },
204+
{ message: 'Project not found' },
205205
{ status: 404 }
206206
);
207207
}
@@ -260,15 +260,23 @@ export const mockManagementApi = [
260260

261261
if (!branch) {
262262
return HttpResponse.json(
263-
{ error: 'Branch not found' },
263+
{ message: 'Branch not found' },
264264
{ status: 404 }
265265
);
266266
}
267267

268+
// if default branch, return error
269+
if (branch.is_default) {
270+
return HttpResponse.json(
271+
{ message: 'Cannot delete the default branch.' },
272+
{ status: 400 }
273+
);
274+
}
275+
268276
const project = mockProjects.get(branch.project_ref);
269277
if (!project) {
270278
return HttpResponse.json(
271-
{ error: 'Project not found' },
279+
{ message: 'Project not found' },
272280
{ status: 404 }
273281
);
274282
}
@@ -288,23 +296,23 @@ export const mockManagementApi = [
288296
const branch = mockBranches.get(params.branchId);
289297
if (!branch) {
290298
return HttpResponse.json(
291-
{ error: 'Branch not found' },
299+
{ message: 'Branch not found' },
292300
{ status: 404 }
293301
);
294302
}
295303

296304
const parentProject = mockProjects.get(branch.parent_project_ref);
297305
if (!parentProject) {
298306
return HttpResponse.json(
299-
{ error: 'Parent project not found' },
307+
{ message: 'Parent project not found' },
300308
{ status: 404 }
301309
);
302310
}
303311

304312
const project = mockProjects.get(branch.project_ref);
305313
if (!project) {
306314
return HttpResponse.json(
307-
{ error: 'Project not found' },
315+
{ message: 'Project not found' },
308316
{ status: 404 }
309317
);
310318
}
@@ -316,7 +324,7 @@ export const mockManagementApi = [
316324
await parentProject.applyMigrations();
317325
} catch (error) {
318326
return HttpResponse.json(
319-
{ error: 'Failed to apply migrations' },
327+
{ message: 'Failed to apply migrations' },
320328
{ status: 500 }
321329
);
322330
}
@@ -334,15 +342,15 @@ export const mockManagementApi = [
334342
const branch = mockBranches.get(params.branchId);
335343
if (!branch) {
336344
return HttpResponse.json(
337-
{ error: 'Branch not found' },
345+
{ message: 'Branch not found' },
338346
{ status: 404 }
339347
);
340348
}
341349

342350
const project = mockProjects.get(branch.project_ref);
343351
if (!project) {
344352
return HttpResponse.json(
345-
{ error: 'Project not found' },
353+
{ message: 'Project not found' },
346354
{ status: 404 }
347355
);
348356
}
@@ -355,7 +363,7 @@ export const mockManagementApi = [
355363
} catch (error) {
356364
branch.status = 'MIGRATIONS_FAILED';
357365
return HttpResponse.json(
358-
{ error: 'Failed to apply migrations' },
366+
{ message: 'Failed to apply migrations' },
359367
{ status: 500 }
360368
);
361369
}
@@ -373,23 +381,23 @@ export const mockManagementApi = [
373381
const branch = mockBranches.get(params.branchId);
374382
if (!branch) {
375383
return HttpResponse.json(
376-
{ error: 'Branch not found' },
384+
{ message: 'Branch not found' },
377385
{ status: 404 }
378386
);
379387
}
380388

381389
const parentProject = mockProjects.get(branch.parent_project_ref);
382390
if (!parentProject) {
383391
return HttpResponse.json(
384-
{ error: 'Parent project not found' },
392+
{ message: 'Parent project not found' },
385393
{ status: 404 }
386394
);
387395
}
388396

389397
const project = mockProjects.get(branch.project_ref);
390398
if (!project) {
391399
return HttpResponse.json(
392-
{ error: 'Project not found' },
400+
{ message: 'Project not found' },
393401
{ status: 404 }
394402
);
395403
}
@@ -403,7 +411,7 @@ export const mockManagementApi = [
403411
} catch (error) {
404412
branch.status = 'MIGRATIONS_FAILED';
405413
return HttpResponse.json(
406-
{ error: 'Failed to apply migrations' },
414+
{ message: 'Failed to apply migrations' },
407415
{ status: 500 }
408416
);
409417
}

0 commit comments

Comments
 (0)