diff --git a/src/app/api/chat/messages/route.js b/src/app/api/chat/messages/route.js index d81cce1..255c161 100644 --- a/src/app/api/chat/messages/route.js +++ b/src/app/api/chat/messages/route.js @@ -106,7 +106,14 @@ export async function POST(request) { const userId = userData.id; console.log('🔐 [API] ✅ Internal user ID:', userId); - const { conversation_id, encrypted_contents, content_type = 'text', has_attachments = false, reply_to_id } = await request.json(); + let body; + try { + body = await request.json(); + } catch { + return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 }); + } + + const { conversation_id, encrypted_contents, content_type = 'text', has_attachments = false, reply_to_id } = body; // Validate required fields if (!conversation_id || !encrypted_contents) { diff --git a/src/app/api/chat/messages/route.test.js b/src/app/api/chat/messages/route.test.js new file mode 100644 index 0000000..f76e8f1 --- /dev/null +++ b/src/app/api/chat/messages/route.test.js @@ -0,0 +1,66 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const mocks = vi.hoisted(() => ({ + authGetUser: vi.fn(), + serviceFrom: vi.fn(), + userEq: vi.fn() +})); + +vi.mock('@supabase/supabase-js', () => ({ + createClient: vi.fn(() => ({ + auth: { + getUser: mocks.authGetUser + } + })) +})); + +vi.mock('@/lib/supabase/service-role.js', () => ({ + createServiceRoleClient: vi.fn(() => ({ + from: mocks.serviceFrom + })) +})); + +function createUsersQuery() { + const query = { + select: vi.fn(() => query), + eq: mocks.userEq, + single: vi.fn().mockResolvedValue({ + data: { id: 'internal-user-id' }, + error: null + }) + }; + mocks.userEq.mockReturnValue(query); + return query; +} + +describe('POST /api/chat/messages validation', () => { + beforeEach(() => { + vi.resetModules(); + vi.clearAllMocks(); + + mocks.authGetUser.mockResolvedValue({ + data: { user: { id: 'auth-user-id' } }, + error: null + }); + }); + + it('returns 400 for malformed JSON before participant lookup', async () => { + mocks.serviceFrom.mockImplementation((table) => { + if (table === 'users') return createUsersQuery(); + if (table === 'conversation_participants') throw new Error('Participant query should not run'); + throw new Error(`Unexpected table: ${table}`); + }); + + const { POST } = await import('./route.js'); + const response = await POST({ + headers: new Headers({ cookie: 'sb-access-token=valid-token' }), + json: vi.fn().mockRejectedValue(new SyntaxError('Unexpected token')) + }); + const body = await response.json(); + + expect(response.status).toBe(400); + expect(body.error).toBe('Invalid JSON body'); + expect(mocks.userEq).toHaveBeenCalledWith('auth_user_id', 'auth-user-id'); + expect(mocks.serviceFrom).toHaveBeenCalledTimes(1); + }); +});