import { beforeEach, describe, expect, it } from 'vitest'; import { newClient } from '../helpers/api'; import { resetDatabase } from '../helpers/database'; import { createAnswer, createFile, createHunt, createHuntMember, createQuest, createReview, createTeam, createTeamMember, createUser, TEST_PASSWORD } from '../helpers/factories'; import { prisma } from './setup'; beforeEach(resetDatabase); /** * Hunt with two teams and three showcase-worthy answers: * - a public picture with score 20 (team A) * - a private picture with score 50 (team A, must be hidden everywhere) * - a public picture with score 30 (team B) */ async function seedShowcase(reveal: boolean) { const creator = await createUser(); const hunt = await createHunt(creator.id, { revealAnswers: reveal, revealQuests: reveal }); const quest = await createQuest(hunt.id); const ownerA = await createUser(); const teamA = await createTeam(hunt.id, ownerA.id, { name: 'Team A' }); const memberA = await createUser(); await createTeamMember(teamA.id, memberA.id); const ownerB = await createUser(); const teamB = await createTeam(hunt.id, ownerB.id, { name: 'Team B' }); const answerA = await createAnswer({ teamId: teamA.id, questId: quest.id, memberId: ownerA.id }); const publicA = await createFile(ownerA.id, { answerId: answerA.id }); await prisma.questAnswer.update({ where: { id: answerA.id }, data: { pictureId: publicA.id } }); await createReview(answerA.id, creator.id, { score: 20, showcase: true }); const answerPrivate = await createAnswer({ teamId: teamA.id, questId: quest.id, memberId: ownerA.id }); const privateA = await createFile(ownerA.id, { answerId: answerPrivate.id, private: true }); await prisma.questAnswer.update({ where: { id: answerPrivate.id }, data: { pictureId: privateA.id } }); await createReview(answerPrivate.id, creator.id, { score: 50, showcase: true }); const answerB = await createAnswer({ teamId: teamB.id, questId: quest.id, memberId: ownerB.id }); const publicB = await createFile(ownerB.id, { answerId: answerB.id }); await prisma.questAnswer.update({ where: { id: answerB.id }, data: { pictureId: publicB.id } }); await createReview(answerB.id, creator.id, { score: 30, showcase: true }); return { creator, hunt, quest, teamA, teamB, answerA, answerB }; } describe('showcase (GET /api/hunt/[huntId]/showcase)', () => { it('returns 404 for unknown hunts', async () => { const res = await newClient().get('/api/hunt/999999/showcase'); expect(res.status).toBe(404); }); it('blocks anonymous users before answers are revealed', async () => { const { hunt } = await seedShowcase(false); const res = await newClient().get(`/api/hunt/${hunt.id}/showcase`); expect(res.status).toBe(403); }); it('lets hunt members peek before reveal', async () => { const { hunt } = await seedShowcase(false); const organizer = await createUser({ email: 'organizer@test.dev' }); await createHuntMember(hunt.id, organizer.id); const client = newClient(); await client.login('organizer@test.dev', TEST_PASSWORD); const res = await client.get(`/api/hunt/${hunt.id}/showcase`); expect(res.status).toBe(200); }); it('builds leaderboard, counts and showcase answers once revealed', async () => { const { hunt, quest, teamA, teamB, answerA, answerB } = await seedShowcase(true); const res = await newClient().get(`/api/hunt/${hunt.id}/showcase`); expect(res.status).toBe(200); const body = await res.json(); // 2 owners + 1 extra member expect(body.userCount).toBe(3); // all three answer pictures are counted, including the private one expect(body.pictureCount).toBe(3); // the private picture's score leaks into the leaderboard totals expect(body.totalScore).toBe(20 + 50 + 30); expect(body.leaderboard).toEqual([ expect.objectContaining({ name: 'Team A', score: 70, answers: 2 }), expect.objectContaining({ name: 'Team B', score: 30, answers: 1 }) ]); expect(body.leaderboard[0].id).toBe(teamA.id); expect(body.leaderboard[1].id).toBe(teamB.id); // only the quest with showcase answers is included expect(body.quests).toHaveLength(1); expect(body.quests[0].id).toBe(quest.id); // only public showcase answers cross teams, sorted by score ascending expect(body.quests[0].answers).toHaveLength(2); expect(body.quests[0].answers.map((a: { id: number }) => a.id)).toEqual([ answerA.id, answerB.id ]); for (const answer of body.quests[0].answers) { expect([teamA.id, teamB.id]).toContain(answer.team.id); } }); });