feat(backend): PATCH /animals/:id/csv-config endpoint
Implement TDD for new route that accepts CSV configuration with validated buckets. Route uses auditMiddleware to log config changes and delegates validation to validateCsvConfig helper from Task 2. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -118,3 +118,73 @@ describe('DELETE /api/animals/:id', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /api/animals/:id/csv-config', () => {
|
||||
const VALID_CONFIG = {
|
||||
buckets: [
|
||||
{ name: 'Success', type: 'note', notes: ['s'] },
|
||||
{ name: 'Failure', type: 'note', notes: ['f'] },
|
||||
{ name: 'Other', type: 'note', notes: [] },
|
||||
{ name: 'Lick latency', type: 'numerical', notes: ['L25', 'L30'] },
|
||||
],
|
||||
};
|
||||
|
||||
it('updates csv_analysis_config and writes audit log', async () => {
|
||||
prisma.animal.findUnique.mockResolvedValue(ANIMAL);
|
||||
prisma.animal.update.mockResolvedValue({ ...ANIMAL, csv_analysis_config: VALID_CONFIG });
|
||||
prisma.auditLog.create.mockResolvedValue({});
|
||||
|
||||
const res = await request(app)
|
||||
.patch(`/api/animals/${ANIMAL.id}/csv-config`)
|
||||
.send(VALID_CONFIG);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.csv_analysis_config).toEqual(VALID_CONFIG);
|
||||
expect(prisma.animal.update).toHaveBeenCalledWith({
|
||||
where: { id: ANIMAL.id },
|
||||
data: { csv_analysis_config: VALID_CONFIG },
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 400 when buckets is missing', async () => {
|
||||
const res = await request(app)
|
||||
.patch(`/api/animals/${ANIMAL.id}/csv-config`)
|
||||
.send({});
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toMatch(/buckets/);
|
||||
});
|
||||
|
||||
it('returns 400 for empty bucket name', async () => {
|
||||
const bad = { buckets: [...VALID_CONFIG.buckets, { name: ' ', type: 'note', notes: [] }] };
|
||||
const res = await request(app)
|
||||
.patch(`/api/animals/${ANIMAL.id}/csv-config`)
|
||||
.send(bad);
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('returns 400 for duplicate names', async () => {
|
||||
const bad = { buckets: [...VALID_CONFIG.buckets, { name: 'Success', type: 'note', notes: [] }] };
|
||||
const res = await request(app)
|
||||
.patch(`/api/animals/${ANIMAL.id}/csv-config`)
|
||||
.send(bad);
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toMatch(/duplicate/i);
|
||||
});
|
||||
|
||||
it('returns 400 for invalid type', async () => {
|
||||
const bad = { buckets: [...VALID_CONFIG.buckets, { name: 'X', type: 'weird', notes: [] }] };
|
||||
const res = await request(app)
|
||||
.patch(`/api/animals/${ANIMAL.id}/csv-config`)
|
||||
.send(bad);
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('returns 400 when missing default bucket', async () => {
|
||||
const bad = { buckets: VALID_CONFIG.buckets.filter((b) => b.name !== 'Success') };
|
||||
const res = await request(app)
|
||||
.patch(`/api/animals/${ANIMAL.id}/csv-config`)
|
||||
.send(bad);
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toMatch(/Success/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user