const request = require('supertest'); jest.mock('../src/lib/prisma', () => require('./__mocks__/prisma')); const prisma = require('../src/lib/prisma'); const app = require('../src/app'); const EXP_ID = 'bbbbbbbb-0000-0000-0000-000000000001'; const ANIMAL = { id: 'cccccccc-0000-0000-0000-000000000001', experiment_id: EXP_ID, animal_id_string: 'M-001', animal_name: 'Whiskers', _count: { daily_statuses: 0 }, }; beforeEach(() => jest.clearAllMocks()); describe('GET /api/animals', () => { it('returns animals filtered by experimentId', async () => { prisma.animal.findMany.mockResolvedValue([ANIMAL]); const res = await request(app).get(`/api/animals?experimentId=${EXP_ID}`); expect(res.status).toBe(200); expect(res.body).toHaveLength(1); expect(res.body[0].animal_id_string).toBe('M-001'); }); it('returns 422 for invalid UUID experimentId', async () => { const res = await request(app).get('/api/animals?experimentId=not-a-uuid'); expect(res.status).toBe(422); }); }); describe('GET /api/animals/:id', () => { it('returns animal with daily_statuses', async () => { prisma.animal.findUnique.mockResolvedValue({ ...ANIMAL, daily_statuses: [] }); const res = await request(app).get(`/api/animals/${ANIMAL.id}`); expect(res.status).toBe(200); expect(res.body.animal_name).toBe('Whiskers'); }); it('returns 404 for unknown animal', async () => { prisma.animal.findUnique.mockResolvedValue(null); const res = await request(app).get('/api/animals/00000000-0000-0000-0000-000000000000'); expect(res.status).toBe(404); }); }); describe('POST /api/animals', () => { it('creates animal with UUID and writes CREATE audit log', async () => { prisma.animal.create.mockResolvedValue(ANIMAL); prisma.auditLog.create.mockResolvedValue({}); const res = await request(app) .post('/api/animals') .send({ experiment_id: EXP_ID, animal_id_string: 'M-001', animal_name: 'Whiskers' }); expect(res.status).toBe(201); expect(res.body.id).toMatch(/^[0-9a-f-]{36}$/i); expect(prisma.auditLog.create).toHaveBeenCalled(); }); it('returns 422 when experiment_id is missing', async () => { const res = await request(app) .post('/api/animals') .send({ animal_id_string: 'M-001', animal_name: 'Whiskers' }); expect(res.status).toBe(422); expect(res.body.details).toEqual( expect.arrayContaining([expect.objectContaining({ field: 'experiment_id' })]) ); }); it('returns 422 when experiment_id is not a UUID', async () => { const res = await request(app) .post('/api/animals') .send({ experiment_id: 'bad-id', animal_id_string: 'M-001', animal_name: 'Whiskers' }); expect(res.status).toBe(422); }); it('returns 422 when animal_name is missing', async () => { const res = await request(app) .post('/api/animals') .send({ experiment_id: EXP_ID, animal_id_string: 'M-001' }); expect(res.status).toBe(422); }); }); describe('PUT /api/animals/:id', () => { it('updates animal and writes UPDATE audit log', async () => { const updated = { ...ANIMAL, animal_name: 'Mr Whiskers' }; prisma.animal.findUnique .mockResolvedValueOnce(ANIMAL) .mockResolvedValueOnce(updated); prisma.animal.update.mockResolvedValue(updated); prisma.auditLog.create.mockResolvedValue({}); const res = await request(app) .put(`/api/animals/${ANIMAL.id}`) .send({ animal_name: 'Mr Whiskers' }); expect(res.status).toBe(200); expect(res.body.animal_name).toBe('Mr Whiskers'); expect(prisma.auditLog.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ action: 'UPDATE' }) }) ); }); }); describe('DELETE /api/animals/:id', () => { it('deletes animal and writes DELETE audit log', async () => { prisma.animal.findUnique.mockResolvedValue(ANIMAL); prisma.animal.delete.mockResolvedValue(ANIMAL); prisma.auditLog.create.mockResolvedValue({}); const res = await request(app).delete(`/api/animals/${ANIMAL.id}`); expect(res.status).toBe(204); expect(prisma.auditLog.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ action: 'DELETE' }) }) ); }); });