const request = require('supertest'); jest.mock('../src/lib/prisma', () => require('./__mocks__/prisma')); jest.mock('../src/lib/cache', () => ({ get: jest.fn(() => undefined), set: jest.fn(), del: jest.fn(), delByPrefix: jest.fn() })); const prisma = require('../src/lib/prisma'); const app = require('../src/app'); const EXP_ID = 'aaaaaaaa-0000-0000-0000-000000000001'; const EXPERIMENT = { id: EXP_ID, title: 'Calendar Test Study', created_at: new Date('2024-01-01').toISOString(), }; // Helper: build a DailyStatus record function makeStatus({ date, vitals = null, customFields = null }) { return { date: new Date(date), experiment_description: null, vitals, treatment: null, notes: null, custom_fields: customFields, }; } beforeEach(() => { jest.clearAllMocks(); }); describe('GET /api/experiments/:id/calendar', () => { it('returns 404 when experiment does not exist', async () => { prisma.experiment.findUnique.mockResolvedValue(null); const res = await request(app).get(`/api/experiments/${EXP_ID}/calendar?field=vitals`); expect(res.status).toBe(404); expect(res.body.error).toMatch(/not found/i); }); it('returns empty days object when no statuses exist', async () => { prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT); prisma.dailyStatus.findMany.mockResolvedValue([]); const res = await request(app).get(`/api/experiments/${EXP_ID}/calendar?field=vitals`); expect(res.status).toBe(200); expect(res.body.days).toEqual({}); expect(res.body.field).toBe('vitals'); }); it('counts subjects with non-empty builtin field per day', async () => { prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT); prisma.dailyStatus.findMany.mockResolvedValue([ makeStatus({ date: '2026-03-10', vitals: '120/80' }), makeStatus({ date: '2026-03-10', vitals: '110/70' }), makeStatus({ date: '2026-03-11', vitals: '115/75' }), makeStatus({ date: '2026-03-12', vitals: null }), // null — not counted makeStatus({ date: '2026-03-12', vitals: '' }), // empty — not counted ]); const res = await request(app).get(`/api/experiments/${EXP_ID}/calendar?field=vitals`); expect(res.status).toBe(200); expect(res.body.days).toEqual({ '2026-03-10': 2, '2026-03-11': 1, }); }); it('counts subjects with non-empty custom field per day', async () => { const FIELD_ID = 'cccccccc-0000-0000-0000-000000000099'; prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT); prisma.dailyStatus.findMany.mockResolvedValue([ makeStatus({ date: '2026-04-01', customFields: { [FIELD_ID]: '320' } }), makeStatus({ date: '2026-04-01', customFields: { [FIELD_ID]: '310' } }), makeStatus({ date: '2026-04-01', customFields: { [FIELD_ID]: null } }), // null — not counted makeStatus({ date: '2026-04-02', customFields: { [FIELD_ID]: '315' } }), ]); const res = await request(app).get(`/api/experiments/${EXP_ID}/calendar?field=${FIELD_ID}`); expect(res.status).toBe(200); expect(res.body.days).toEqual({ '2026-04-01': 2, '2026-04-02': 1, }); }); it('ignores whitespace-only custom field values', async () => { const FIELD_ID = 'dddddddd-0000-0000-0000-000000000099'; prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT); prisma.dailyStatus.findMany.mockResolvedValue([ makeStatus({ date: '2026-04-05', customFields: { [FIELD_ID]: ' ' } }), makeStatus({ date: '2026-04-05', customFields: { [FIELD_ID]: '280' } }), ]); const res = await request(app).get(`/api/experiments/${EXP_ID}/calendar?field=${FIELD_ID}`); expect(res.status).toBe(200); expect(res.body.days).toEqual({ '2026-04-05': 1 }); }); it('returns days from multiple months correctly', async () => { prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT); prisma.dailyStatus.findMany.mockResolvedValue([ makeStatus({ date: '2026-02-15', vitals: '100/60' }), makeStatus({ date: '2026-03-01', vitals: '105/65' }), makeStatus({ date: '2026-03-01', vitals: '108/68' }), ]); const res = await request(app).get(`/api/experiments/${EXP_ID}/calendar?field=vitals`); expect(res.status).toBe(200); expect(res.body.days['2026-02-15']).toBe(1); expect(res.body.days['2026-03-01']).toBe(2); }); it('returns field key in response body', async () => { prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT); prisma.dailyStatus.findMany.mockResolvedValue([]); const res = await request(app).get(`/api/experiments/${EXP_ID}/calendar?field=treatment`); expect(res.status).toBe(200); expect(res.body.field).toBe('treatment'); }); it('handles missing field param gracefully (empty days)', async () => { prisma.experiment.findUnique.mockResolvedValue(EXPERIMENT); prisma.dailyStatus.findMany.mockResolvedValue([ makeStatus({ date: '2026-04-01', vitals: 'present' }), ]); // No ?field= → all values undefined, nothing counted const res = await request(app).get(`/api/experiments/${EXP_ID}/calendar`); expect(res.status).toBe(200); expect(res.body.days).toEqual({}); }); });