Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions src/app/api/conversations/delete/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,7 @@ export const POST = withAuth(async ({ request, locals }) => {
return NextResponse.json({ error: 'Failed to fetch conversation details' }, { status: 500 });
}

const { count: participantCount, error: countError } = await supabase
.from('conversation_participants')
.select('*', { count: 'exact', head: true })
.eq('conversation_id', conversationId);

if (countError) {
console.error('Error counting participants:', countError);
}

// Determine if this is a direct message (2 participants) or group
const isDirectMessage = conversation.type === 'direct' || participantCount === 2;
const isDirectMessage = conversation.type === 'direct';

if (isDirectMessage) {
// For direct messages, delete everything for all participants
Expand Down Expand Up @@ -278,4 +268,4 @@ export const POST = withAuth(async ({ request, locals }) => {
console.error('Delete conversation error:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
});
});
92 changes: 92 additions & 0 deletions src/app/api/conversations/delete/route.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';

const mocks = vi.hoisted(() => ({
from: vi.fn(),
getServiceRoleClient: vi.fn()
}));

vi.mock('@/lib/api/middleware/auth.js', () => ({
withAuth: (handler) => (request, context) =>
handler({
request,
locals: {
supabase: { from: mocks.from },
user: { id: 'auth-user-id' }
},
context
})
}));

vi.mock('@/lib/supabase/service-role.js', () => ({
getServiceRoleClient: mocks.getServiceRoleClient
}));

function selectSingle(data) {
const query = {
select: vi.fn(() => query),
eq: vi.fn(() => query),
single: vi.fn().mockResolvedValue({ data, error: null })
};
return query;
}

function participantLookup() {
const query = {
select: vi.fn(() => query),
eq: vi.fn(() => query),
is: vi.fn(() => query),
maybeSingle: vi.fn().mockResolvedValue({
data: { id: 'participant-id', left_at: null, archived_at: null },
error: null
})
};
return query;
}

function deleteQuery() {
const query = {
error: null,
delete: vi.fn(() => query),
eq: vi.fn(() => query)
};
return query;
}

describe('POST /api/conversations/delete', () => {
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
});

it('keeps a two-member group and removes only the requesting participant data', async () => {
const messages = deleteQuery();
const files = deleteQuery();
const participantDelete = deleteQuery();
participantDelete.count = 2;
participantDelete.select = vi.fn(() => participantDelete);
let participantQueries = 0;

mocks.from.mockImplementation((table) => {
if (table === 'users') return selectSingle({ id: 'internal-user-id' });
if (table === 'conversations') return selectSingle({ type: 'group' });
if (table === 'messages') return messages;
if (table === 'file_attachments') return files;
if (table === 'conversation_participants') {
participantQueries += 1;
return participantQueries === 1 ? participantLookup() : participantDelete;
}
throw new Error(`Unexpected table: ${table}`);
});

const { POST } = await import('./route.js');
const response = await POST({
json: vi.fn().mockResolvedValue({ conversationId: 'group-1' })
});

expect(response.status).toBe(200);
expect(mocks.getServiceRoleClient).not.toHaveBeenCalled();
expect(messages.eq).toHaveBeenCalledWith('sender_id', 'internal-user-id');
expect(files.eq).toHaveBeenCalledWith('uploaded_by', 'internal-user-id');
expect(participantDelete.eq).toHaveBeenCalledWith('user_id', 'internal-user-id');
});
});
Loading