Skip to content

Commit 11281ad

Browse files
fix(conversations): load with internal user id (#129)
1 parent d1cf290 commit 11281ad

2 files changed

Lines changed: 130 additions & 3 deletions

File tree

‎src/app/api/conversations/load/route.js‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,29 @@ export const POST = withAuth(async ({ locals }) => {
1010
try {
1111
const { supabase, user } = locals;
1212

13+
const { data: userData, error: userError } = await supabase
14+
.from('users')
15+
.select('id')
16+
.eq('auth_user_id', user.id)
17+
.single();
18+
19+
if (userError || !userData) {
20+
console.error('Failed to resolve internal user:', userError);
21+
return NextResponse.json({ error: 'User not found' }, { status: 404 });
22+
}
23+
1324
// Load user's conversations with participants and last message
1425
const { data: participations, error: partError } = await supabase
1526
.from('conversation_participants')
1627
.select('conversation_id')
17-
.eq('user_id', user.id);
28+
.eq('user_id', userData.id);
1829

1930
if (partError) {
2031
console.error('Failed to load participations:', partError);
2132
return NextResponse.json({ error: 'Failed to load conversations' }, { status: 500 });
2233
}
2334

24-
const conversationIds = participations.map(p => p.conversation_id);
35+
const conversationIds = (participations || []).map(p => p.conversation_id);
2536

2637
if (conversationIds.length === 0) {
2738
return NextResponse.json({ success: true, conversations: [] });
@@ -52,4 +63,4 @@ export const POST = withAuth(async ({ locals }) => {
5263
console.error('Load conversations error:', error);
5364
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
5465
}
55-
});
66+
});
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
const mocks = vi.hoisted(() => ({
4+
from: vi.fn(),
5+
userEq: vi.fn(),
6+
participationEq: vi.fn(),
7+
conversationsIn: vi.fn()
8+
}));
9+
10+
vi.mock('@/lib/api/middleware/auth.js', () => ({
11+
withAuth: (handler) => (request, context) =>
12+
handler({
13+
request,
14+
locals: {
15+
supabase: {
16+
from: mocks.from
17+
},
18+
user: { id: 'auth-user-id' }
19+
},
20+
context
21+
})
22+
}));
23+
24+
function createUserQuery(result) {
25+
const query = {
26+
select: vi.fn(() => query),
27+
eq: mocks.userEq,
28+
single: vi.fn().mockResolvedValue(result)
29+
};
30+
mocks.userEq.mockReturnValue(query);
31+
return query;
32+
}
33+
34+
function createParticipationsQuery(result) {
35+
const query = {
36+
select: vi.fn(() => query),
37+
eq: mocks.participationEq
38+
};
39+
mocks.participationEq.mockResolvedValue(result);
40+
return query;
41+
}
42+
43+
function createConversationsQuery(result) {
44+
const query = {
45+
select: vi.fn(() => query),
46+
in: mocks.conversationsIn,
47+
order: vi.fn().mockResolvedValue(result)
48+
};
49+
mocks.conversationsIn.mockReturnValue(query);
50+
return query;
51+
}
52+
53+
describe('POST /api/conversations/load', () => {
54+
beforeEach(() => {
55+
vi.resetModules();
56+
vi.clearAllMocks();
57+
58+
mocks.from.mockImplementation((table) => {
59+
if (table === 'users') {
60+
return createUserQuery({
61+
data: { id: 'internal-user-id' },
62+
error: null
63+
});
64+
}
65+
66+
if (table === 'conversation_participants') {
67+
return createParticipationsQuery({
68+
data: [{ conversation_id: 'conversation-1' }],
69+
error: null
70+
});
71+
}
72+
73+
if (table === 'conversations') {
74+
return createConversationsQuery({
75+
data: [{ id: 'conversation-1' }],
76+
error: null
77+
});
78+
}
79+
80+
throw new Error(`Unexpected table: ${table}`);
81+
});
82+
});
83+
84+
it('loads participations with the internal user id resolved from auth_user_id', async () => {
85+
const { POST } = await import('./route.js');
86+
const response = await POST(new Request('https://qrypt.chat/api/conversations/load', { method: 'POST' }));
87+
const body = await response.json();
88+
89+
expect(response.status).toBe(200);
90+
expect(body.conversations).toEqual([{ id: 'conversation-1' }]);
91+
expect(mocks.userEq).toHaveBeenCalledWith('auth_user_id', 'auth-user-id');
92+
expect(mocks.participationEq).toHaveBeenCalledWith('user_id', 'internal-user-id');
93+
expect(mocks.conversationsIn).toHaveBeenCalledWith('id', ['conversation-1']);
94+
});
95+
96+
it('returns 404 when the authenticated user has no internal profile', async () => {
97+
mocks.from.mockImplementation((table) => {
98+
if (table === 'users') {
99+
return createUserQuery({
100+
data: null,
101+
error: { message: 'not found' }
102+
});
103+
}
104+
105+
throw new Error(`Unexpected table: ${table}`);
106+
});
107+
108+
const { POST } = await import('./route.js');
109+
const response = await POST(new Request('https://qrypt.chat/api/conversations/load', { method: 'POST' }));
110+
const body = await response.json();
111+
112+
expect(response.status).toBe(404);
113+
expect(body).toEqual({ error: 'User not found' });
114+
expect(mocks.from).not.toHaveBeenCalledWith('conversation_participants');
115+
});
116+
});

0 commit comments

Comments
 (0)