Skip to content
Closed
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
2 changes: 2 additions & 0 deletions apps/backend/src/__tests__/app.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
process.env.NODE_ENV = 'test';
process.env.JWT_SECRET = 'test-jwt-secret-mock-which-is-at-least-thirty-two-chars-long';
process.env.ENCRYPTION_KEY = 'test-encryption-key-mock-32-char-min';

import { describe, it, expect } from 'vitest';
import { buildApp } from '../app';
Expand Down
15 changes: 12 additions & 3 deletions apps/backend/src/__tests__/event.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ async function buildApp(): Promise<FastifyInstance> {

// Decorate jwtVerify on the request prototype so request.jwtVerify() resolves
// to whatever the current test wants.
app.decorateRequest('jwtVerify', function () {
return mockJwtVerify();
app.decorateRequest('jwtVerify', async function () {
const user = await mockJwtVerify();
(this as any).user = user;
return user;
});

// Register with the same prefix used in production (app.ts) so that
Expand Down Expand Up @@ -494,6 +496,7 @@ describe('Events API', () => {

prismaMock.event.findUnique.mockResolvedValue({
...MOCK_EVENT,
_count: { attendees: attendeeRows.length },
attendees: attendeeRows,
});

Expand Down Expand Up @@ -522,6 +525,7 @@ describe('Events API', () => {
it('200 β€” respects custom page and limit query params', async () => {
prismaMock.event.findUnique.mockResolvedValue({
...MOCK_EVENT,
_count: { attendees: 1 },
attendees: [makeAttendeeRow(MOCK_OTHER_USER_PROFILE)],
});

Expand All @@ -544,6 +548,7 @@ describe('Events API', () => {
it('200 β€” caps limit at 50 even if higher value is requested', async () => {
prismaMock.event.findUnique.mockResolvedValue({
...MOCK_EVENT,
_count: { attendees: 0 },
attendees: [],
});

Expand All @@ -560,6 +565,7 @@ describe('Events API', () => {
it('200 β€” treats page < 1 as page 1', async () => {
prismaMock.event.findUnique.mockResolvedValue({
...MOCK_EVENT,
_count: { attendees: 0 },
attendees: [],
});

Expand All @@ -576,6 +582,7 @@ describe('Events API', () => {
it('200 β€” returns empty attendees list for event with no attendees', async () => {
prismaMock.event.findUnique.mockResolvedValue({
...MOCK_EVENT,
_count: { attendees: 0 },
attendees: [],
});

Expand All @@ -593,6 +600,7 @@ describe('Events API', () => {
it('200 β€” public profiles do not leak sensitive fields', async () => {
prismaMock.event.findUnique.mockResolvedValue({
...MOCK_EVENT,
_count: { attendees: 1 },
attendees: [makeAttendeeRow(MOCK_USER_PROFILE)],
});

Expand Down Expand Up @@ -631,6 +639,7 @@ describe('Events API', () => {
it('200 β€” attendees are ordered by joinedAt desc (latest first)', async () => {
prismaMock.event.findUnique.mockResolvedValue({
...MOCK_EVENT,
_count: { attendees: 0 },
attendees: [],
});

Expand All @@ -657,7 +666,7 @@ describe('Events API', () => {
prismaMock.event.findUnique.mockResolvedValue(null);
prismaMock.event.create.mockResolvedValue({ ...MOCK_EVENT, slug: 'my-awesome-event' });

await createEvent(app, { ...baseBody, name: 'My Awesome Event!!!' });
const res = await createEvent(app, { ...baseBody, name: 'My Awesome Event!!!' });

const slug: string = prismaMock.event.create.mock.calls[0][0].data.slug;
expect(slug).toBe('my-awesome-event');
Expand Down
23 changes: 12 additions & 11 deletions apps/backend/src/__tests__/team.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import Fastify, { FastifyInstance } from 'fastify';
import { PrismaClient, TeamRole } from '@prisma/client';
import { PrismaClient } from '@prisma/client';
import { teamRoutes } from '../routes/team';

// ─── Shared mock data ─────────────────────────────────────────────────────────
Expand Down Expand Up @@ -56,15 +56,15 @@ const MOCK_TEAM_WITH_MEMBERS = {
id: 'tm-uuid-001',
teamId: MOCK_TEAM.id,
userId: MOCK_OWNER_ID,
role: TeamRole.OWNER,
role: 'OWNER',
joinedAt: new Date('2024-01-01T00:00:00Z'),
user: { ...MOCK_OWNER, platformLinks: MOCK_PLATFORM_LINKS },
},
{
id: 'tm-uuid-002',
teamId: MOCK_TEAM.id,
userId: MOCK_MEMBER_ID,
role: TeamRole.MEMBER,
role: 'MEMBER',
joinedAt: new Date('2024-02-01T00:00:00Z'),
user: { ...MOCK_MEMBER_USER, platformLinks: [] },
},
Expand Down Expand Up @@ -99,8 +99,10 @@ async function buildApp(): Promise<FastifyInstance> {

app.decorate('prisma', prismaMock as unknown as PrismaClient);

app.decorateRequest('jwtVerify', function () {
return mockJwtVerify();
app.decorateRequest('jwtVerify', async function () {
const user = await mockJwtVerify();
(this as any).user = user;
return user;
});

await app.register(teamRoutes);
Expand Down Expand Up @@ -161,7 +163,6 @@ describe('Teams API', () => {
});

const res = await createTeam(app, validBody);

expect(res.statusCode).toBe(201);
const body = res.json();
expect(body.name).toBe('DevCard Core');
Expand Down Expand Up @@ -318,7 +319,7 @@ describe('Teams API', () => {
id: 'tm-uuid-001',
teamId: MOCK_TEAM.id,
userId: MOCK_OWNER_ID,
role: TeamRole.OWNER,
role: 'OWNER',
joinedAt: new Date(),
user: MOCK_OWNER,
},
Expand All @@ -342,7 +343,7 @@ describe('Teams API', () => {

const callData = prismaMock.teamMember.create.mock.calls[0][0].data;
expect(callData.userId).toBe(MOCK_MEMBER_ID);
expect(callData.role).toBe(TeamRole.MEMBER);
expect(callData.role).toBe('MEMBER');
});

it('401 β€” rejects unauthenticated request', async () => {
Expand Down Expand Up @@ -381,7 +382,7 @@ describe('Teams API', () => {
id: 'tm-uuid-002',
teamId: MOCK_TEAM.id,
userId: MOCK_MEMBER_ID,
role: TeamRole.MEMBER,
role: 'MEMBER',
joinedAt: new Date(),
user: MOCK_MEMBER_USER,
},
Expand Down Expand Up @@ -461,15 +462,15 @@ describe('Teams API', () => {
id: 'tm-uuid-001',
teamId: MOCK_TEAM.id,
userId: MOCK_OWNER_ID,
role: TeamRole.OWNER,
role: 'OWNER',
joinedAt: new Date(),
user: MOCK_OWNER,
},
{
id: 'tm-uuid-002',
teamId: MOCK_TEAM.id,
userId: MOCK_MEMBER_ID,
role: TeamRole.MEMBER,
role: 'MEMBER',
joinedAt: new Date(),
user: MOCK_MEMBER_USER,
},
Expand Down
30 changes: 13 additions & 17 deletions apps/backend/src/routes/team.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Prisma, TeamRole } from '@prisma/client';
import {Prisma } from '@prisma/client';
import QRCode from 'qrcode'

import {generateUniqueSlug} from '../utils/slug'
Expand All @@ -8,7 +8,7 @@ import type {PlatformLink, PublicProfile} from '@devcard/shared'
import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';

type TeamMember = PublicProfile & {
teamRole: TeamRole
teamRole: 'OWNER' | 'ADMIN' | 'MEMBER'
joinedAt: Date;
}

Expand Down Expand Up @@ -62,27 +62,23 @@ export async function teamRoutes(app:FastifyInstance){
data: {
teamId : team.id,
userId,
role: TeamRole.OWNER,
role: 'OWNER',
joinedAt: new Date(),
}
})
return team
})
return reply.status(201).send(team)

}catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
switch (error.code) {
case 'P2002':
return reply.status(409).send({
error: 'Team slug already exists'
});

case 'P2003':
return reply.status(400).send({
error: 'Invalid organizer'
});
}
}catch (error:any) {
if (error?.code === 'P2002') {
return reply.status(409).send({
error: 'Team slug already exists'
});
} else if (error?.code === 'P2003') {
return reply.status(400).send({
error: 'Invalid organizer'
});
}
app.log.error('Failed to create a team');
return reply.status(500).send({
Expand Down Expand Up @@ -211,7 +207,7 @@ export async function teamRoutes(app:FastifyInstance){
data: {
teamId: teamDetails.id,
userId: invitedUserDetails.id,
role: TeamRole.MEMBER,
role: 'MEMBER',
joinedAt: new Date()
}
})
Expand Down
Loading