fix(api): prevent self-loop routes, map P2003 to 400, forward DELETE error body

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 01:44:48 +05:30
parent 9d1799eab1
commit 5d9df64849
3 changed files with 25 additions and 3 deletions
@@ -87,6 +87,19 @@ describe('RoutesService', () => {
mockPrisma.syncRoute.create.mockRejectedValueOnce(p2002);
await expect(service.create('grp_1', 'grp_2')).rejects.toThrow(ConflictException);
});
it('throws BadRequestException when source and target are the same group', async () => {
await expect(service.create('grp_1', 'grp_1')).rejects.toThrow(BadRequestException);
});
it('throws BadRequestException when a group ID does not exist (Prisma P2003)', async () => {
const p2003 = new Prisma.PrismaClientKnownRequestError('Foreign key constraint', {
code: 'P2003',
clientVersion: '6.0.0',
});
mockPrisma.syncRoute.create.mockRejectedValueOnce(p2003);
await expect(service.create('grp_1', 'bad_grp')).rejects.toThrow(BadRequestException);
});
});
describe('remove', () => {
+10 -2
View File
@@ -23,14 +23,22 @@ export class RoutesService {
if (!sourceGroupId || !targetGroupId) {
throw new BadRequestException('sourceGroupId and targetGroupId are required');
}
if (sourceGroupId === targetGroupId) {
throw new BadRequestException('Source and target groups cannot be the same');
}
try {
return await this.prisma.syncRoute.create({
data: { sourceGroupId, targetGroupId },
include: routeInclude,
});
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') {
throw new ConflictException('A route between these groups already exists');
if (e instanceof Prisma.PrismaClientKnownRequestError) {
if (e.code === 'P2002') {
throw new ConflictException('A route between these groups already exists');
}
if (e.code === 'P2003') {
throw new BadRequestException('One or both group IDs do not exist');
}
}
throw e;
}
+2 -1
View File
@@ -6,5 +6,6 @@ export async function DELETE(
) {
const { id } = await params;
const res = await fetch(`${API_URL}/routes/${id}`, { method: 'DELETE' });
return new Response(null, { status: res.status });
if (res.status === 204) return new Response(null, { status: 204 });
return Response.json(await res.json(), { status: res.status });
}