import { describe, it, expect, afterAll, beforeEach } from 'vitest' import request from 'supertest' import { format } from 'date-fns' import { tz } from '@date-fns/tz' import { createApp } from '@app/createApp' import { InsightPeriod, InsightType } from '@prisma/client' import { appDayWindow } from '@app/lib/queue/processors/insights/utils' import { buildPrisma, cleanupTestDb, createTestUser, generateTestToken, } from '../test-helper' const app = createApp() const db = buildPrisma() const SP = tz('America/Sao_Paulo') afterAll(() => db.$disconnect()) beforeEach(() => cleanupTestDb(db)) // The YYYY-MM-DD key the read endpoint expects for the app day containing date function appDayKey(date: Date) { return format(date, 'yyyy-MM-dd', { in: SP }) } let seedCounter = 0 // The schema is unique on (userId, type, periodStart), so give every seeded // row a distinct periodStart unless the test asks for a specific one function uniquePeriodStart() { seedCounter += 1 return new Date(Date.UTC(2026, 0, 1) + seedCounter * 60 * 1000) } async function seedInsight( userId: number, overrides: { type?: InsightType period?: InsightPeriod periodStart?: Date generatedAt?: Date title?: string } = {} ) { seedCounter += 1 return db.insight.create({ data: { userId, type: overrides.type ?? InsightType.MOOD_TREND, period: overrides.period ?? InsightPeriod.WEEKLY, title: overrides.title ?? `Insight ${seedCounter}`, body: 'Generated by the insight worker', periodStart: overrides.periodStart ?? uniquePeriodStart(), periodEnd: new Date(), generatedAt: overrides.generatedAt, }, }) } describe('GET /insights', () => { it('returns only the authenticated user\'s insights, newest first', async () => { const user = await createTestUser(db) const other = await createTestUser(db, { email: 'other@example.com' }) const token = generateTestToken(user.id, user.email) await seedInsight(user.id, { title: 'oldest', generatedAt: new Date('2026-01-01') }) await seedInsight(user.id, { title: 'newest', generatedAt: new Date('2026-03-01') }) await seedInsight(user.id, { title: 'middle', generatedAt: new Date('2026-02-01') }) await seedInsight(other.id, { title: 'not-mine' }) const res = await request(app) .get('/insights') .set('Authorization', `Bearer ${token}`) expect(res.status).toBe(200) expect(res.body.insights).toHaveLength(3) expect(res.body.insights.every((i: any) => i.userId === user.id)).toBe(true) expect(res.body.insights.map((i: any) => i.title)).toEqual([ 'newest', 'middle', 'oldest', ]) }) it('returns an empty list when the user has no insights', async () => { const user = await createTestUser(db) const token = generateTestToken(user.id, user.email) const res = await request(app) .get('/insights') .set('Authorization', `Bearer ${token}`) expect(res.status).toBe(200) expect(res.body.insights).toEqual([]) }) it('filters by type', async () => { const user = await createTestUser(db) const token = generateTestToken(user.id, user.email) await seedInsight(user.id, { type: InsightType.MOOD_TREND }) await seedInsight(user.id, { type: InsightType.STREAK }) await seedInsight(user.id, { type: InsightType.STREAK }) const res = await request(app) .get('/insights?type=STREAK') .set('Authorization', `Bearer ${token}`) expect(res.status).toBe(200) expect(res.body.insights).toHaveLength(2) expect(res.body.insights.every((i: any) => i.type === 'STREAK')).toBe(true) }) it('filters by period', async () => { const user = await createTestUser(db) const token = generateTestToken(user.id, user.email) await seedInsight(user.id, { period: InsightPeriod.DAILY }) await seedInsight(user.id, { period: InsightPeriod.MONTHLY }) await seedInsight(user.id, { period: InsightPeriod.MONTHLY }) const res = await request(app) .get('/insights?period=MONTHLY') .set('Authorization', `Bearer ${token}`) expect(res.status).toBe(200) expect(res.body.insights).toHaveLength(2) expect(res.body.insights.every((i: any) => i.period === 'MONTHLY')).toBe(true) }) it('caps the result set at the requested limit', async () => { const user = await createTestUser(db) const token = generateTestToken(user.id, user.email) await seedInsight(user.id) await seedInsight(user.id) await seedInsight(user.id) const res = await request(app) .get('/insights?limit=2') .set('Authorization', `Bearer ${token}`) expect(res.status).toBe(200) expect(res.body.insights).toHaveLength(2) }) it('returns 400 for an unknown type', async () => { const user = await createTestUser(db) const token = generateTestToken(user.id, user.email) const res = await request(app) .get('/insights?type=NOPE') .set('Authorization', `Bearer ${token}`) expect(res.status).toBe(400) expect(res.body.fields).toBeTruthy() }) it('returns 400 for a non-positive limit', async () => { const user = await createTestUser(db) const token = generateTestToken(user.id, user.email) const res = await request(app) .get('/insights?limit=0') .set('Authorization', `Bearer ${token}`) expect(res.status).toBe(400) }) it('returns 401 without a token', async () => { const res = await request(app).get('/insights') expect(res.status).toBe(401) }) it('resolves an unqualified daily request to today', async () => { const user = await createTestUser(db) const token = generateTestToken(user.id, user.email) const { start: today } = appDayWindow() const { start: yesterday } = appDayWindow(new Date(today.getTime() - 1)) await seedInsight(user.id, { type: InsightType.DAILY_ENERGY, period: InsightPeriod.DAILY, periodStart: yesterday, title: 'energy-yesterday', }) await seedInsight(user.id, { type: InsightType.DAILY_ENERGY, period: InsightPeriod.DAILY, periodStart: today, title: 'energy-today', }) await seedInsight(user.id, { type: InsightType.DAILY_SLEEP, period: InsightPeriod.DAILY, periodStart: yesterday, title: 'sleep-yesterday', }) await seedInsight(user.id, { type: InsightType.DAILY_SLEEP, period: InsightPeriod.DAILY, periodStart: today, title: 'sleep-today', }) const energy = await request(app) .get('/insights?type=DAILY_ENERGY') .set('Authorization', `Bearer ${token}`) const sleep = await request(app) .get('/insights?type=DAILY_SLEEP') .set('Authorization', `Bearer ${token}`) expect(energy.status).toBe(200) expect(energy.body.insights.map((i: any) => i.title)).toEqual([ 'energy-today', ]) expect(sleep.status).toBe(200) expect(sleep.body.insights.map((i: any) => i.title)).toEqual(['sleep-today']) }) it('filters the insight to an explicit day', async () => { const user = await createTestUser(db) const token = generateTestToken(user.id, user.email) const { start: today } = appDayWindow() const { start: yesterday } = appDayWindow(new Date(today.getTime() - 1)) await seedInsight(user.id, { type: InsightType.DAILY_ENERGY, period: InsightPeriod.DAILY, periodStart: yesterday, title: 'energy-yesterday', }) await seedInsight(user.id, { type: InsightType.DAILY_ENERGY, period: InsightPeriod.DAILY, periodStart: today, title: 'energy-today', }) const res = await request(app) .get(`/insights?type=DAILY_ENERGY&day=${appDayKey(yesterday)}`) .set('Authorization', `Bearer ${token}`) expect(res.status).toBe(200) expect(res.body.insights.map((i: any) => i.title)).toEqual([ 'energy-yesterday', ]) }) it('returns an empty list for a day with no generated insight', async () => { const user = await createTestUser(db) const token = generateTestToken(user.id, user.email) const { start: today } = appDayWindow() const { start: yesterday } = appDayWindow(new Date(today.getTime() - 1)) await seedInsight(user.id, { type: InsightType.DAILY_ENERGY, period: InsightPeriod.DAILY, periodStart: today, title: 'energy-today', }) const res = await request(app) .get(`/insights?type=DAILY_ENERGY&day=${appDayKey(yesterday)}`) .set('Authorization', `Bearer ${token}`) expect(res.status).toBe(200) expect(res.body.insights).toEqual([]) }) it('returns an empty list when today has no daily insight', async () => { const user = await createTestUser(db) const token = generateTestToken(user.id, user.email) const { start: today } = appDayWindow() const { start: yesterday } = appDayWindow(new Date(today.getTime() - 1)) await seedInsight(user.id, { type: InsightType.DAILY_ENERGY, period: InsightPeriod.DAILY, periodStart: yesterday, title: 'energy-yesterday', }) const res = await request(app) .get('/insights?type=DAILY_ENERGY') .set('Authorization', `Bearer ${token}`) expect(res.status).toBe(200) expect(res.body.insights).toEqual([]) }) it('returns 400 for a malformed day', async () => { const user = await createTestUser(db) const token = generateTestToken(user.id, user.email) const res = await request(app) .get('/insights?day=2026-13-40') .set('Authorization', `Bearer ${token}`) expect(res.status).toBe(400) expect(res.body.fields).toBeTruthy() }) })