import { describe, it, expect, vi, beforeEach } from 'vitest' const { mockGetSession } = vi.hoisted(() => ({ mockGetSession: vi.fn(), })) vi.mock('next/cache', () => ({ revalidatePath: vi.fn() })) vi.mock('@/lib/auth', () => ({ getSession: mockGetSession, })) vi.mock('@/lib/prisma', () => ({ prisma: { claudeQuestion: { findFirst: vi.fn(), updateMany: vi.fn(), }, product: { findFirst: vi.fn().mockResolvedValue({ id: 'product-1' }), }, }, })) import { revalidatePath } from 'next/cache' import { prisma } from '@/lib/prisma' import { answerQuestion } from '@/actions/questions' const mockPrisma = prisma as unknown as { claudeQuestion: { findFirst: ReturnType updateMany: ReturnType } } const mockRevalidate = revalidatePath as ReturnType const VALID_ID = 'cmohrz0jra1aaaaaaaaaaaaaa' const VALID_ANSWER = 'Antwoord van de gebruiker' const SESSION_USER = { userId: 'user-1', isDemo: false } const SESSION_DEMO = { userId: 'demo-1', isDemo: true } beforeEach(() => { vi.clearAllMocks() }) describe('actions/questions — answerQuestion', () => { it('happy: status pending→answered, revalidatePath geroepen', async () => { mockGetSession.mockResolvedValue(SESSION_USER) mockPrisma.claudeQuestion.findFirst.mockResolvedValueOnce({ id: VALID_ID, story_id: 'story-1', idea_id: null, product_id: 'product-1', idea: null, }) mockPrisma.claudeQuestion.updateMany.mockResolvedValueOnce({ count: 1 }) const res = await answerQuestion(VALID_ID, VALID_ANSWER) expect(res).toEqual({ ok: true }) const updateArg = mockPrisma.claudeQuestion.updateMany.mock.calls[0][0] expect(updateArg.where).toMatchObject({ id: VALID_ID, status: 'open', }) expect(updateArg.where.expires_at).toMatchObject({ gt: expect.any(Date) }) expect(updateArg.data).toMatchObject({ status: 'answered', answer: VALID_ANSWER, answered_by: 'user-1', }) expect(mockRevalidate).toHaveBeenCalledWith('/', 'layout') }) it('demo-user wordt geblokkeerd, geen DB-call', async () => { mockGetSession.mockResolvedValue(SESSION_DEMO) const res = await answerQuestion(VALID_ID, VALID_ANSWER) expect(res).toEqual({ ok: false, error: 'Niet beschikbaar in demo-modus' }) expect(mockPrisma.claudeQuestion.findFirst).not.toHaveBeenCalled() expect(mockPrisma.claudeQuestion.updateMany).not.toHaveBeenCalled() expect(mockRevalidate).not.toHaveBeenCalled() }) it('user zonder product-access: error, geen update', async () => { mockGetSession.mockResolvedValue(SESSION_USER) mockPrisma.claudeQuestion.findFirst.mockResolvedValueOnce(null) const res = await answerQuestion(VALID_ID, VALID_ANSWER) expect(res).toEqual({ ok: false, error: 'Vraag niet gevonden of geen toegang' }) expect(mockPrisma.claudeQuestion.updateMany).not.toHaveBeenCalled() }) it('al-answered: race-error met begrijpelijke melding', async () => { mockGetSession.mockResolvedValue(SESSION_USER) mockPrisma.claudeQuestion.findFirst.mockResolvedValueOnce({ id: VALID_ID, story_id: 'story-1', idea_id: null, product_id: 'product-1', idea: null, }) mockPrisma.claudeQuestion.updateMany.mockResolvedValueOnce({ count: 0 }) mockPrisma.claudeQuestion.findFirst.mockResolvedValueOnce({ status: 'answered', expires_at: new Date(Date.now() + 60_000), }) const res = await answerQuestion(VALID_ID, VALID_ANSWER) expect(res).toEqual({ ok: false, error: 'Vraag is al answered' }) expect(mockRevalidate).not.toHaveBeenCalled() }) it('verlopen: updateMany count=0, nog open status maar voorbij expiry', async () => { mockGetSession.mockResolvedValue(SESSION_USER) mockPrisma.claudeQuestion.findFirst.mockResolvedValueOnce({ id: VALID_ID, story_id: 'story-1', idea_id: null, product_id: 'product-1', idea: null, }) mockPrisma.claudeQuestion.updateMany.mockResolvedValueOnce({ count: 0 }) mockPrisma.claudeQuestion.findFirst.mockResolvedValueOnce({ status: 'open', expires_at: new Date(Date.now() - 60_000), }) const res = await answerQuestion(VALID_ID, VALID_ANSWER) expect(res).toEqual({ ok: false, error: 'Vraag is verlopen' }) }) it('lege answer: Zod-validatie faalt', async () => { mockGetSession.mockResolvedValue(SESSION_USER) const res = await answerQuestion(VALID_ID, '') expect(res.ok).toBe(false) if (!res.ok) { expect(res.error.toLowerCase()).toMatch(/string|character|leeg|empty|small/i) } expect(mockPrisma.claudeQuestion.findFirst).not.toHaveBeenCalled() }) })