Files
pichunt/tests/integration/review.itest.ts
T
Pascal 01d1e5fb60
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
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
2026-08-13 23:10:07 +02:00

187 lines
5.9 KiB
TypeScript

import superjson from 'superjson';
import { beforeEach, describe, expect, it } from 'vitest';
import { newClient } from '../helpers/api';
import { resetDatabase } from '../helpers/database';
import {
createAnswer,
createHunt,
createHuntMember,
createQuest,
createTeam,
createUser,
TEST_PASSWORD
} from '../helpers/factories';
import { prisma } from './setup';
beforeEach(resetDatabase);
/** Hunt with one quest, one team and a submitted (non-final) answer. */
async function seedAnswer() {
const creator = await createUser();
const hunt = await createHunt(creator.id);
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
});
return { creator, hunt, quest, owner, team, answer };
}
const reviewBody = {
score: 42,
review: 'Nice shot!',
showcase: true,
internalNote: 'check exposure',
rankingValue: 7
};
describe('review answer (POST /api/admin/answer/[answerId]/review)', () => {
it('requires a session', async () => {
const res = await newClient().post('/api/admin/answer/1/review', {
...reviewBody,
showcase: false
});
expect(res.status).toBe(401);
});
it('returns 404 for unknown answers', async () => {
const { hunt } = await seedAnswer();
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.post('/api/admin/answer/999999/review', {
...reviewBody,
showcase: false
});
expect(res.status).toBe(404);
});
it('denies non-members of the hunt with 404', async () => {
const { answer } = await seedAnswer();
await createUser({ email: 'stranger@test.dev' });
const client = newClient();
await client.login('stranger@test.dev', TEST_PASSWORD);
const res = await client.post(
`/api/admin/answer/${answer.id}/review`,
reviewBody
);
expect(res.status).toBe(404);
expect(await prisma.review.count()).toBe(0);
});
it('creates a review as a hunt member', async () => {
const { hunt, answer } = await seedAnswer();
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.post(
`/api/admin/answer/${answer.id}/review`,
reviewBody
);
expect(res.status).toBe(200);
expect(await res.json()).toEqual({ id: answer.id });
const review = await prisma.review.findUniqueOrThrow({
where: { answerId: answer.id }
});
expect(review).toMatchObject({
score: 42,
review: 'Nice shot!',
showcase: true,
internalNote: 'check exposure',
rankingValue: 7,
reviewerId: organizer.id
});
});
it('updates the existing review on a second pass', async () => {
const { hunt, answer } = await seedAnswer();
const first = await createUser({ email: 'first@test.dev' });
const second = await createUser({ email: 'second@test.dev' });
await createHuntMember(hunt.id, first.id);
await createHuntMember(hunt.id, second.id);
const firstClient = newClient();
await firstClient.login('first@test.dev', TEST_PASSWORD);
await firstClient.post(`/api/admin/answer/${answer.id}/review`, reviewBody);
const secondClient = newClient();
await secondClient.login('second@test.dev', TEST_PASSWORD);
const res = await secondClient.post(
`/api/admin/answer/${answer.id}/review`,
{ ...reviewBody, score: 100, showcase: false }
);
expect(res.status).toBe(200);
const reviews = await prisma.review.findMany({
where: { answerId: answer.id }
});
expect(reviews).toHaveLength(1);
expect(reviews[0]).toMatchObject({
score: 100,
showcase: false,
reviewerId: second.id
});
});
it('rejects invalid scores with 400', async () => {
const { hunt, answer } = await seedAnswer();
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.post(`/api/admin/answer/${answer.id}/review`, {
...reviewBody,
score: -5
});
expect(res.status).toBe(400);
});
});
describe('hunt admin detail (GET /api/admin/hunt/[huntId])', () => {
it('denies non-members with 404', async () => {
const { hunt } = await seedAnswer();
await createUser({ email: 'stranger@test.dev' });
const client = newClient();
await client.login('stranger@test.dev', TEST_PASSWORD);
const res = await client.get(`/api/admin/hunt/${hunt.id}`);
expect(res.status).toBe(404);
});
it('responds with a SuperJSON envelope containing real Dates', async () => {
const { hunt } = await seedAnswer();
const organizer = await createUser({ email: 'organizer@test.dev' });
await createHuntMember(hunt.id, organizer.id);
const start = new Date('2026-09-01T12:00:00Z');
await prisma.hunt.update({
where: { id: hunt.id },
data: { start }
});
const client = newClient();
await client.login('organizer@test.dev', TEST_PASSWORD);
const res = await client.get(`/api/admin/hunt/${hunt.id}`);
expect(res.status).toBe(200);
const raw = await res.text();
const envelope = JSON.parse(raw);
// SuperJSON wraps the payload in { json, meta }
expect(envelope.json).toBeDefined();
expect(envelope.meta).toBeDefined();
const huntDto = superjson.parse<{ start: Date; id: number }>(raw);
expect(huntDto.id).toBe(hunt.id);
expect(huntDto.start).toBeInstanceOf(Date);
expect(huntDto.start.toISOString()).toBe(start.toISOString());
});
});