Skip to content

Commit e9874fa

Browse files
fix(conversations): resolve disappearing route params (#119)
1 parent 597626d commit e9874fa

2 files changed

Lines changed: 133 additions & 2 deletions

File tree

src/app/api/conversations/[id]/disappearing-messages/route.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ function getServiceRoleClient() {
1616
// Create regular Supabase client for authentication
1717
const supabaseClient = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY);
1818

19+
async function resolveRouteParams(params) {
20+
return (await params) || {};
21+
}
22+
1923
/**
2024
* Authenticate user from request cookies
2125
* @param {Request} request - The request object
@@ -100,7 +104,7 @@ export async function GET(request, { params } = {}) {
100104
const userId = userData.id;
101105
console.log('🔐 [API] ✅ Internal user ID:', userId);
102106

103-
const conversationId = params.id;
107+
const { id: conversationId } = await resolveRouteParams(params);
104108
if (!conversationId) {
105109
return NextResponse.json({ error: 'Missing conversation ID' }, { status: 400 });
106110
}
@@ -169,7 +173,7 @@ export async function PUT(request, { params } = {}) {
169173
const userId = userData.id;
170174
console.log('🔐 [API] ✅ Internal user ID:', userId);
171175

172-
const conversationId = params.id;
176+
const { id: conversationId } = await resolveRouteParams(params);
173177
if (!conversationId) {
174178
return NextResponse.json({ error: 'Missing conversation ID' }, { status: 400 });
175179
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
const mocks = vi.hoisted(() => ({
4+
authGetUser: vi.fn(),
5+
serviceFrom: vi.fn(),
6+
userEq: vi.fn(),
7+
participantEq: vi.fn(),
8+
updateEq: vi.fn()
9+
}));
10+
11+
vi.mock('@supabase/supabase-js', () => ({
12+
createClient: vi.fn(() => ({
13+
auth: {
14+
getUser: mocks.authGetUser
15+
}
16+
}))
17+
}));
18+
19+
vi.mock('@/lib/supabase/service-role.js', () => ({
20+
createServiceRoleClient: vi.fn(() => ({
21+
from: mocks.serviceFrom
22+
}))
23+
}));
24+
25+
function createUsersQuery() {
26+
const query = {
27+
select: vi.fn(() => query),
28+
eq: mocks.userEq,
29+
single: vi.fn().mockResolvedValue({
30+
data: { id: 'internal-user-id' },
31+
error: null
32+
})
33+
};
34+
mocks.userEq.mockReturnValue(query);
35+
return query;
36+
}
37+
38+
function createParticipantReadQuery() {
39+
const query = {
40+
select: vi.fn(() => query),
41+
eq: mocks.participantEq,
42+
is: vi.fn(() => query),
43+
single: vi.fn().mockResolvedValue({
44+
data: { disappear_seconds: 60, start_on: 'read' },
45+
error: null
46+
})
47+
};
48+
mocks.participantEq.mockReturnValue(query);
49+
return query;
50+
}
51+
52+
function createParticipantUpdateQuery() {
53+
const query = {
54+
update: vi.fn(() => query),
55+
eq: mocks.updateEq,
56+
select: vi.fn(() => query),
57+
single: vi.fn().mockResolvedValue({
58+
data: { disappear_seconds: 300, start_on: 'delivered' },
59+
error: null
60+
})
61+
};
62+
mocks.updateEq.mockReturnValue(query);
63+
return query;
64+
}
65+
66+
function makeRequest(body) {
67+
return new Request('https://qrypt.chat/api/conversations/conversation-1/disappearing-messages', {
68+
method: body ? 'PUT' : 'GET',
69+
headers: {
70+
cookie: 'sb-access-token=valid-token'
71+
},
72+
body: body ? JSON.stringify(body) : undefined
73+
});
74+
}
75+
76+
describe('/api/conversations/[id]/disappearing-messages', () => {
77+
beforeEach(() => {
78+
vi.resetModules();
79+
vi.clearAllMocks();
80+
81+
mocks.authGetUser.mockResolvedValue({
82+
data: { user: { id: 'auth-user-id' } },
83+
error: null
84+
});
85+
});
86+
87+
it('resolves async route params before reading settings', async () => {
88+
mocks.serviceFrom.mockImplementation((table) => {
89+
if (table === 'users') return createUsersQuery();
90+
if (table === 'conversation_participants') return createParticipantReadQuery();
91+
throw new Error(`Unexpected table: ${table}`);
92+
});
93+
94+
const { GET } = await import('./route.js');
95+
const response = await GET(makeRequest(), {
96+
params: Promise.resolve({ id: 'conversation-1' })
97+
});
98+
const body = await response.json();
99+
100+
expect(response.status).toBe(200);
101+
expect(body.settings.disappear_seconds).toBe(60);
102+
expect(mocks.participantEq).toHaveBeenCalledWith('conversation_id', 'conversation-1');
103+
});
104+
105+
it('resolves async route params before updating settings', async () => {
106+
let participantCalls = 0;
107+
mocks.serviceFrom.mockImplementation((table) => {
108+
if (table === 'users') return createUsersQuery();
109+
if (table === 'conversation_participants') {
110+
participantCalls += 1;
111+
return participantCalls === 1 ? createParticipantReadQuery() : createParticipantUpdateQuery();
112+
}
113+
throw new Error(`Unexpected table: ${table}`);
114+
});
115+
116+
const { PUT } = await import('./route.js');
117+
const response = await PUT(makeRequest({ disappear_seconds: 300, start_on: 'delivered' }), {
118+
params: Promise.resolve({ id: 'conversation-1' })
119+
});
120+
const body = await response.json();
121+
122+
expect(response.status).toBe(200);
123+
expect(body.settings.disappear_seconds).toBe(300);
124+
expect(mocks.participantEq).toHaveBeenCalledWith('conversation_id', 'conversation-1');
125+
expect(mocks.updateEq).toHaveBeenCalledWith('conversation_id', 'conversation-1');
126+
});
127+
});

0 commit comments

Comments
 (0)