Files
Experiments DB Dev ee2cf19b74 test: add analysisOnly regression tests; fix cache isolation in tests
New file tests/experimentDailyStatuses.test.js — 9 tests covering
GET /api/experiments/:id/daily-statuses:
  - 404 on unknown experiment
  - all statuses returned when no filter
  - analysisOnly=true keeps only non-null analysis_summary records
  - analysisOnly=true returns [] when none have analysis_summary
  - the exact null/object mix that caused the production 500 now passes
  - analysisOnly=false (explicit) returns all records
  - ?date= param is forwarded to Prisma correctly
  - empty-array response on zero statuses
  - returned records include expected fields

Fixed tests/calendar.test.js: existing tests were silently broken by the
cache added in the perf commit. Both test files now mock lib/cache to
always miss, so each test exercises the real route logic in isolation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 15:11:21 -04:00

132 lines
5.1 KiB
JavaScript

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({});
});
});