test(integration): add real-stack integration test suite with PGlite
- PGlite (in-memory WASM Postgres) hosted in a child process, exposed via TCP socket; real migrations applied with prisma migrate deploy - Vitest harness (vitest.integration.config.ts, global-setup spawns PGlite + built Nuxt server, per-test TRUNCATE, factories, cookie-jar API client); no mocks, zero production code changes - 50 tests across auth, hunt, team, submit, review, showcase, diashow incl. SuperJSON envelope check; *.itest.ts keeps bun test untouched - CI: parallel unit + integration jobs in .github/workflows/test.yml - tests/ covered by nuxt typecheck via tests/tsconfig.json reference
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user