Skip to content

Commit 4d74b67

Browse files
authored
fix(events): add missing organizerId to response and fix incomplete test mocks causing 500 crashes (#632)
* fix(events): add organizerId to EventDetails response * fix(events): fix import order and add missing organizer fields in event test * fix(event.test.ts): fix makeAttendeeRow return type annotation * fix(event.test.ts): use type import for LightMyRequestResponse Signed-off-by: Yachika Sharma <shakuntalaramphalsharma@gmail.com> --------- Signed-off-by: Yachika Sharma <shakuntalaramphalsharma@gmail.com>
1 parent f819079 commit 4d74b67

2 files changed

Lines changed: 52 additions & 39 deletions

File tree

apps/backend/src/__tests__/event.test.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import Fastify from 'fastify';
12
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2-
import Fastify, { FastifyInstance } from 'fastify';
3-
import { PrismaClient } from '@prisma/client';
3+
44
import { eventRoutes } from '../routes/event';
55

6+
import type { PrismaClient } from '@prisma/client';
7+
import type { FastifyInstance,LightMyRequestResponse } from 'fastify';
8+
69
// ─── Shared mock data ────────────────────────────────────────────────────────
710

811
const MOCK_USER_ID = 'user-uuid-001';
@@ -64,7 +67,7 @@ const prismaMock = {
6467
//
6568
// This mirrors the real app setup without touching a real DB or real JWT keys.
6669

67-
let mockJwtVerify = vi.fn();
70+
const mockJwtVerify = vi.fn();
6871

6972
async function buildApp(): Promise<FastifyInstance> {
7073
const app = Fastify({ logger: false });
@@ -77,7 +80,14 @@ async function buildApp(): Promise<FastifyInstance> {
7780
app.decorateRequest('jwtVerify', function () {
7881
return mockJwtVerify();
7982
});
80-
83+
app.decorate('authenticate', async function (request, reply) {
84+
try {
85+
const payload = await request.jwtVerify();
86+
if (payload) { request.user = payload as typeof request.user; }
87+
} catch {
88+
return reply.status(401).send({ error: 'Unauthorized' });
89+
}
90+
});
8191
// Register with the same prefix used in production (app.ts) so that
8292
// tests exercise routes at their real paths — /api/events, /api/events/:slug, etc.
8393
await app.register(eventRoutes, { prefix: '/api/events' });
@@ -97,7 +107,7 @@ async function createEvent(
97107
app: FastifyInstance,
98108
body: Record<string, unknown>,
99109
authenticated = true,
100-
) {
110+
): Promise<LightMyRequestResponse> {
101111
return app.inject({
102112
method: 'POST',
103113
url: '/api/events',
@@ -251,14 +261,15 @@ describe('Events API', () => {
251261
it('200 — returns event info with attendee count', async () => {
252262
prismaMock.event.findUnique.mockResolvedValue({
253263
...MOCK_EVENT,
264+
organizer: { username: 'johndoe', displayName: 'John Doe' },
254265
_count: { attendees: 42 },
255266
});
256267

257268
const res = await app.inject({
258269
method: 'GET',
259270
url: '/api/events/devcard-conf-2025',
260271
});
261-
272+
console.log(JSON.stringify(res.json(), null, 2));
262273
expect(res.statusCode).toBe(200);
263274
const body = res.json();
264275
expect(body.slug).toBe('devcard-conf-2025');
@@ -275,7 +286,7 @@ describe('Events API', () => {
275286
method: 'GET',
276287
url: '/api/events/ghost-event',
277288
});
278-
289+
279290
expect(res.statusCode).toBe(404);
280291
expect(res.json()).toMatchObject({ error: 'Event not found' });
281292
});
@@ -285,6 +296,7 @@ describe('Events API', () => {
285296
mockJwtVerify.mockRejectedValue(new Error('Should not be called'));
286297
prismaMock.event.findUnique.mockResolvedValue({
287298
...MOCK_EVENT,
299+
organizer: { username: 'johndoe', displayName: 'John Doe' },
288300
_count: { attendees: 0 },
289301
});
290302

@@ -476,7 +488,13 @@ describe('Events API', () => {
476488
/** Builds a raw EventAttendee row as Prisma returns it (with nested user) */
477489
function makeAttendeeRow(
478490
profile: typeof MOCK_USER_PROFILE | typeof MOCK_OTHER_USER_PROFILE,
479-
) {
491+
) : {
492+
id: string;
493+
userId: string;
494+
eventId: string;
495+
joinedAt: Date;
496+
user: typeof MOCK_USER_PROFILE | typeof MOCK_OTHER_USER_PROFILE;
497+
} {
480498
return {
481499
id: `attendee-${profile.id}`,
482500
userId: profile.id,
@@ -495,6 +513,7 @@ describe('Events API', () => {
495513
prismaMock.event.findUnique.mockResolvedValue({
496514
...MOCK_EVENT,
497515
attendees: attendeeRows,
516+
_count: { attendees: 2 },
498517
});
499518

500519
const res = await app.inject({
@@ -523,6 +542,7 @@ describe('Events API', () => {
523542
prismaMock.event.findUnique.mockResolvedValue({
524543
...MOCK_EVENT,
525544
attendees: [makeAttendeeRow(MOCK_OTHER_USER_PROFILE)],
545+
_count: { attendees: 1 },
526546
});
527547

528548
const res = await app.inject({
@@ -545,6 +565,7 @@ describe('Events API', () => {
545565
prismaMock.event.findUnique.mockResolvedValue({
546566
...MOCK_EVENT,
547567
attendees: [],
568+
_count: { attendees: 0 },
548569
});
549570

550571
const res = await app.inject({
@@ -561,6 +582,7 @@ describe('Events API', () => {
561582
prismaMock.event.findUnique.mockResolvedValue({
562583
...MOCK_EVENT,
563584
attendees: [],
585+
_count: { attendees: 0 },
564586
});
565587

566588
const res = await app.inject({
@@ -577,6 +599,7 @@ describe('Events API', () => {
577599
prismaMock.event.findUnique.mockResolvedValue({
578600
...MOCK_EVENT,
579601
attendees: [],
602+
_count: { attendees: 0 },
580603
});
581604

582605
const res = await app.inject({
@@ -594,6 +617,7 @@ describe('Events API', () => {
594617
prismaMock.event.findUnique.mockResolvedValue({
595618
...MOCK_EVENT,
596619
attendees: [makeAttendeeRow(MOCK_USER_PROFILE)],
620+
_count: { attendees: 1 },
597621
});
598622

599623
const res = await app.inject({
@@ -683,4 +707,4 @@ describe('Events API', () => {
683707
expect(slug).not.toMatch(/--/);
684708
});
685709
});
686-
});
710+
});

apps/backend/src/routes/event.ts

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
2-
import { createEventSchema, joinEventSchema} from '../validations/event.validation.js';
3-
41
import {generateUniqueSlug} from '../utils/slug.js'
2+
import { createEventSchema} from '../validations/event.validation.js';
3+
4+
import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
55

66

77
type EventDetails = {
@@ -10,6 +10,7 @@ type EventDetails = {
1010
slug: string;
1111
location: string;
1212
description: string | null;
13+
organizerId: string;
1314
organizerUsername: string;
1415
organizerDisplayName: string;
1516
startDate: Date;
@@ -57,31 +58,18 @@ type EventWithAttendees = {
5758
}[];
5859
}
5960

60-
export async function eventRoutes(app:FastifyInstance) {
61-
app.post('/', { preHandler: [async (request, reply) => {
62-
const server = request.server as any;
63-
if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return }
64-
if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return }
65-
try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) }
66-
}] }, async (request: FastifyRequest<{
67-
Body: {
68-
name: string,
69-
description?: string,
70-
startDate: string,
71-
location: string,
72-
endDate: string,
73-
isPublic?: boolean
74-
}}>, reply: FastifyReply) => {
75-
const userId = (request.user as any).id;
61+
export async function eventRoutes(app:FastifyInstance): Promise<void> {
62+
app.post<{Body: { name: string; description?: string; startDate: string; location: string; endDate: string; isPublic?: boolean; }}>('/', { preHandler: [(req, reply) => app.authenticate(req, reply)] }, async (request, reply) => {
63+
const userId = request.user.id;
7664
const parsed = createEventSchema.safeParse(request.body);
7765
if(!parsed.success){
7866
return reply.status(400).send({error: 'Bad request'})
7967
}
8068

8169
const {name, description, startDate, endDate, isPublic ,location} = parsed.data
8270

83-
let finalSlug = await generateUniqueSlug(name, async(slug) => {
84-
const existing = await app.prisma.event.findUnique({where: {slug : slug}})
71+
const finalSlug = await generateUniqueSlug(name, async(slug) => {
72+
const existing = await app.prisma.event.findUnique({where: {slug}})
8573

8674
return !!existing
8775
})
@@ -95,7 +83,7 @@ export async function eventRoutes(app:FastifyInstance) {
9583
name,
9684
description,
9785
slug: finalSlug,
98-
location: location,
86+
location,
9987
startDate: startDateObj,
10088
endDate: endDateObj,
10189
isPublic: isPublic ?? true,
@@ -104,7 +92,7 @@ export async function eventRoutes(app:FastifyInstance) {
10492
})
10593

10694
return reply.status(201).send(newEvent);
107-
} catch (error) {
95+
} catch (_error) {
10896
app.log.error('Failed to create event');
10997
return reply.status(500).send({error: 'Failed to create event'})
11098
}
@@ -142,6 +130,7 @@ export async function eventRoutes(app:FastifyInstance) {
142130
slug: details.slug,
143131
description: details.description,
144132
location: details.location,
133+
organizerId: details.organizerId,
145134
organizerUsername: details.organizer.username,
146135
organizerDisplayName: details.organizer.displayName,
147136
startDate: details.startDate,
@@ -153,8 +142,8 @@ export async function eventRoutes(app:FastifyInstance) {
153142
return response;
154143
})
155144

156-
app.post('/:slug/join', { preHandler: [async (request, reply) => { const server = request.server as any; if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } }] }, async(request: FastifyRequest<{Params: {slug: string}}>, reply: FastifyReply) => {
157-
const userId = (request.user as any).id;
145+
app.post<{ Params: { slug: string } }>('/:slug/join', {preHandler: [(req, reply) => app.authenticate(req, reply)]}, async(request, reply) => {
146+
const userId = request.user.id;
158147
const paramsSlug = request.params.slug;
159148

160149
const event = await app.prisma.event.findUnique({
@@ -171,7 +160,7 @@ export async function eventRoutes(app:FastifyInstance) {
171160
await app.prisma.eventAttendee.create({
172161
data: {
173162
eventId: event.id,
174-
userId: userId,
163+
userId,
175164
joinedAt: new Date()
176165
}
177166
})
@@ -186,9 +175,9 @@ export async function eventRoutes(app:FastifyInstance) {
186175
}
187176

188177
})
178+
app.delete<{Params: {slug: string}}>('/:slug/leave',{preHandler: [(req, reply) => app.authenticate(req, reply)]}, async(request, reply) => {
189179

190-
app.delete('/:slug/leave', { preHandler: [async (request, reply) => { const server = request.server as any; if (typeof server?.authenticate === 'function') { await server.authenticate(request, reply); return } if (typeof (app as any).authenticate === 'function') { await (app as any).authenticate(request, reply); return } try { await request.jwtVerify() } catch (e) { reply.status(401).send({ error: 'Unauthorized' }) } }] }, async(request: FastifyRequest<{Params: {slug: string}}>, reply: FastifyReply) => {
191-
const userId = (request.user as any).id;
180+
const userId = request.user.id;
192181
const paramsSlug = request.params.slug;
193182

194183
const event = await app.prisma.event.findUnique({
@@ -205,12 +194,12 @@ export async function eventRoutes(app:FastifyInstance) {
205194
await app.prisma.eventAttendee.delete({
206195
where: {
207196
userId_eventId: {
208-
userId: userId,
197+
userId,
209198
eventId: event.id
210199
}
211200
}
212201
})
213-
return reply.status(204).send({message: 'User left'})
202+
return reply.status(204).send()
214203
} catch (error:any) {
215204
if(error.code === 'P2025'){
216205
return reply.status(404).send({error: 'User not found'})

0 commit comments

Comments
 (0)