import { describe, it, expect, vi, beforeEach } from "vitest"; import request from "supertest"; import type { GameSession, AnswerResult } from "@lila/shared"; type SuccessResponse = { success: true; data: T }; type ErrorResponse = { success: false; error: string }; type GameStartResponse = SuccessResponse; type GameAnswerResponse = SuccessResponse; vi.mock("@lila/db", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, getGameTerms: vi.fn(), getDistractors: vi.fn() }; }); vi.mock("../lib/auth.js", () => ({ auth: { api: { getSession: vi .fn() .mockResolvedValue({ session: { id: "session-1", userId: "user-1", token: "fake-token", expiresAt: new Date(Date.now() + 1000 * 60 * 60), createdAt: new Date(), updatedAt: new Date(), ipAddress: null, userAgent: null, }, user: { id: "user-1", name: "Test User", email: "test@test.com", emailVerified: false, image: null, createdAt: new Date(), updatedAt: new Date(), }, }), }, handler: vi.fn(), }, })); vi.mock("better-auth/node", () => ({ fromNodeHeaders: vi.fn().mockReturnValue({}), toNodeHandler: vi.fn().mockReturnValue(vi.fn()), })); import { getGameTerms, getDistractors } from "@lila/db"; import { createApp } from "../app.js"; const app = createApp(); const mockGetGameTerms = vi.mocked(getGameTerms); const mockGetDistractors = vi.mocked(getDistractors); const validBody = { source_language: "en", target_language: "it", pos: "noun", difficulty: "easy", rounds: "3", }; const fakeTerms = [ { termId: "t1", sourceText: "dog", targetText: "cane", sourceGloss: null }, { termId: "t2", sourceText: "cat", targetText: "gatto", sourceGloss: null }, { termId: "t3", sourceText: "house", targetText: "casa", sourceGloss: null }, ]; beforeEach(() => { vi.clearAllMocks(); mockGetGameTerms.mockResolvedValue(fakeTerms); mockGetDistractors.mockResolvedValue(["wrong1", "wrong2", "wrong3"]); }); describe("POST /api/v1/game/start", () => { it("returns 200 with a valid game session", async () => { const res = await request(app).post("/api/v1/game/start").send(validBody); const body = res.body as GameStartResponse; expect(res.status).toBe(200); expect(body.success).toBe(true); expect(body.data.sessionId).toBeDefined(); expect(body.data.questions).toHaveLength(3); }); it("returns 400 when the body is empty", async () => { const res = await request(app).post("/api/v1/game/start").send({}); const body = res.body as ErrorResponse; expect(res.status).toBe(400); expect(body.success).toBe(false); expect(body.error).toBeDefined(); }); it("returns 400 when required fields are missing", async () => { const res = await request(app) .post("/api/v1/game/start") .send({ source_language: "en" }); const body = res.body as ErrorResponse; expect(res.status).toBe(400); expect(body.success).toBe(false); }); it("returns 400 when a field has an invalid value", async () => { const res = await request(app) .post("/api/v1/game/start") .send({ ...validBody, difficulty: "impossible" }); const body = res.body as ErrorResponse; expect(res.status).toBe(400); expect(body.success).toBe(false); }); }); describe("POST /api/v1/game/answer", () => { it("returns 200 with an answer result for a valid submission", async () => { const startRes = await request(app) .post("/api/v1/game/start") .send(validBody); const startBody = startRes.body as GameStartResponse; const { sessionId, questions } = startBody.data; const question = questions[0]!; const res = await request(app) .post("/api/v1/game/answer") .send({ sessionId, questionId: question.questionId, selectedOptionId: 0, }); const body = res.body as GameAnswerResponse; expect(res.status).toBe(200); expect(body.success).toBe(true); expect(body.data.questionId).toBe(question.questionId); expect(typeof body.data.isCorrect).toBe("boolean"); expect(typeof body.data.correctOptionId).toBe("number"); expect(body.data.selectedOptionId).toBe(0); }); it("returns 400 when the body is empty", async () => { const res = await request(app).post("/api/v1/game/answer").send({}); const body = res.body as ErrorResponse; expect(res.status).toBe(400); expect(body.success).toBe(false); }); it("returns 404 when the session does not exist", async () => { const res = await request(app) .post("/api/v1/game/answer") .send({ sessionId: "00000000-0000-0000-0000-000000000000", questionId: "00000000-0000-0000-0000-000000000000", selectedOptionId: 0, }); const body = res.body as ErrorResponse; expect(res.status).toBe(404); expect(body.success).toBe(false); expect(body.error).toContain("Game session not found"); }); it("returns 404 when the question does not exist in the session", async () => { const startRes = await request(app) .post("/api/v1/game/start") .send(validBody); const startBody = startRes.body as GameStartResponse; const { sessionId } = startBody.data; const res = await request(app) .post("/api/v1/game/answer") .send({ sessionId, questionId: "00000000-0000-0000-0000-000000000000", selectedOptionId: 0, }); const body = res.body as ErrorResponse; expect(res.status).toBe(404); expect(body.success).toBe(false); expect(body.error).toContain("Question not found"); }); });