test(integration): add real-stack integration test suite with PGlite
Docker Image CI / test (push) Successful in 1m47s
Tests / unit (push) Successful in 57s
Tests / integration (push) Successful in 1m38s
Docker Image CI / deploy (push) Failing after 8s

- 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:
2026-08-13 23:10:07 +02:00
parent 28714a8cbc
commit 01d1e5fb60
22 changed files with 1813 additions and 49 deletions
+137
View File
@@ -0,0 +1,137 @@
import { beforeEach, describe, expect, it } from 'vitest';
import { newClient } from '../helpers/api';
import { resetDatabase } from '../helpers/database';
import {
createAnswer,
createFile,
createHunt,
createQuest,
createTeam,
createUser,
TEST_PASSWORD
} from '../helpers/factories';
import { prisma } from './setup';
beforeEach(resetDatabase);
/**
* Hunt with one quest and three finalized answers:
* - a public picture (shown)
* - a private picture (hidden)
* - no picture at all (hidden)
*/
async function seedDiashow(reveal: boolean) {
const creator = await createUser();
const hunt = await createHunt(creator.id, {
revealAnswers: reveal,
revealQuests: reveal
});
const quest = await createQuest(hunt.id);
const owner = await createUser();
const team = await createTeam(hunt.id, owner.id);
const answerPublic = await createAnswer({
teamId: team.id,
questId: quest.id,
memberId: owner.id
});
const publicFile = await createFile(owner.id, { answerId: answerPublic.id });
await prisma.questAnswer.update({
where: { id: answerPublic.id },
data: { pictureId: publicFile.id, final: true }
});
const answerPrivate = await createAnswer({
teamId: team.id,
questId: quest.id,
memberId: owner.id
});
const privateFile = await createFile(owner.id, {
answerId: answerPrivate.id,
private: true
});
await prisma.questAnswer.update({
where: { id: answerPrivate.id },
data: { pictureId: privateFile.id, final: true }
});
await createAnswer(
{ teamId: team.id, questId: quest.id, memberId: owner.id },
{ final: true, text: 'no picture' }
);
return { hunt, quest, team, publicFile };
}
describe('diashow (GET /api/hunt/[huntId]/diashow)', () => {
it('returns 404 for unknown hunts', async () => {
const res = await newClient().get('/api/hunt/999999/diashow');
expect(res.status).toBe(404);
});
it('blocks anonymous users before answers are revealed', async () => {
const { hunt } = await seedDiashow(false);
const res = await newClient().get(`/api/hunt/${hunt.id}/diashow`);
expect(res.status).toBe(403);
});
it('lets the hunt creator peek before reveal', async () => {
const { hunt } = await seedDiashow(false);
const creator = await prisma.user.findFirstOrThrow({
where: { huntsCreated: { some: { id: hunt.id } } }
});
const client = newClient();
await client.login(creator.email, TEST_PASSWORD);
const res = await client.get(`/api/hunt/${hunt.id}/diashow`);
expect(res.status).toBe(200);
});
it('only shows finalized answers with public pictures', async () => {
const { hunt, quest, team, publicFile } = await seedDiashow(true);
const res = await newClient().get(`/api/hunt/${hunt.id}/diashow`);
expect(res.status).toBe(200);
const body = await res.json();
expect(body.id).toBe(hunt.id);
expect(body.quests).toHaveLength(1);
expect(body.quests[0].id).toBe(quest.id);
expect(body.quests[0].answers).toHaveLength(1);
expect(body.quests[0].answers[0].picture.url).toBe(publicFile.url);
expect(body.quests[0].answers[0].team).toMatchObject({
id: team.id,
name: team.name
});
});
it('omits quests whose final answers are all private', async () => {
const creator = await createUser();
const hunt = await createHunt(creator.id, {
revealAnswers: true,
revealQuests: true
});
const quest = await createQuest(hunt.id);
const owner = await createUser();
const team = await createTeam(hunt.id, owner.id);
const answer = await createAnswer({
teamId: team.id,
questId: quest.id,
memberId: owner.id
});
const privateFile = await createFile(owner.id, {
answerId: answer.id,
private: true
});
await prisma.questAnswer.update({
where: { id: answer.id },
data: { pictureId: privateFile.id, final: true }
});
const res = await newClient().get(`/api/hunt/${hunt.id}/diashow`);
expect(res.status).toBe(200);
const body = await res.json();
expect(body.quests).toEqual([]);
});
});