eletrotupi / tcc/ api/tests/insights/index.test.tsb36ca93521cd90a436cca219f26ba8cb906448f4
9.5 KB | 304 lines Raw
1
import { describe, it, expect, afterAll, beforeEach } from 'vitest'
2
import request from 'supertest'
3
import { format } from 'date-fns'
4
import { tz } from '@date-fns/tz'
5
import { createApp } from '@app/createApp'
6
import { InsightPeriod, InsightType } from '@prisma/client'
7
import { appDayWindow } from '@app/lib/queue/processors/insights/utils'
8
import {
9
  buildPrisma,
10
  cleanupTestDb,
11
  createTestUser,
12
  generateTestToken,
13
} from '../test-helper'
14

15
const app = createApp()
16
const db = buildPrisma()
17
const SP = tz('America/Sao_Paulo')
18

19
afterAll(() => db.$disconnect())
20
beforeEach(() => cleanupTestDb(db))
21

22
// The YYYY-MM-DD key the read endpoint expects for the app day containing date
23
function appDayKey(date: Date) {
24
  return format(date, 'yyyy-MM-dd', { in: SP })
25
}
26

27
let seedCounter = 0
28
// The schema is unique on (userId, type, periodStart), so give every seeded
29
// row a distinct periodStart unless the test asks for a specific one
30
function uniquePeriodStart() {
31
  seedCounter += 1
32
  return new Date(Date.UTC(2026, 0, 1) + seedCounter * 60 * 1000)
33
}
34

35
async function seedInsight(
36
  userId: number,
37
  overrides: {
38
    type?: InsightType
39
    period?: InsightPeriod
40
    periodStart?: Date
41
    generatedAt?: Date
42
    title?: string
43
  } = {}
44
) {
45
  seedCounter += 1
46

47
  return db.insight.create({
48
    data: {
49
      userId,
50
      type: overrides.type ?? InsightType.MOOD_TREND,
51
      period: overrides.period ?? InsightPeriod.WEEKLY,
52
      title: overrides.title ?? `Insight ${seedCounter}`,
53
      body: 'Generated by the insight worker',
54
      periodStart: overrides.periodStart ?? uniquePeriodStart(),
55
      periodEnd: new Date(),
56
      generatedAt: overrides.generatedAt,
57
    },
58
  })
59
}
60

61
describe('GET /insights', () => {
62
  it('returns only the authenticated user\'s insights, newest first', async () => {
63
    const user = await createTestUser(db)
64
    const other = await createTestUser(db, { email: 'other@example.com' })
65
    const token = generateTestToken(user.id, user.email)
66

67
    await seedInsight(user.id, { title: 'oldest', generatedAt: new Date('2026-01-01') })
68
    await seedInsight(user.id, { title: 'newest', generatedAt: new Date('2026-03-01') })
69
    await seedInsight(user.id, { title: 'middle', generatedAt: new Date('2026-02-01') })
70
    await seedInsight(other.id, { title: 'not-mine' })
71

72
    const res = await request(app)
73
      .get('/insights')
74
      .set('Authorization', `Bearer ${token}`)
75

76
    expect(res.status).toBe(200)
77
    expect(res.body.insights).toHaveLength(3)
78
    expect(res.body.insights.every((i: any) => i.userId === user.id)).toBe(true)
79
    expect(res.body.insights.map((i: any) => i.title)).toEqual([
80
      'newest',
81
      'middle',
82
      'oldest',
83
    ])
84
  })
85

86
  it('returns an empty list when the user has no insights', async () => {
87
    const user = await createTestUser(db)
88
    const token = generateTestToken(user.id, user.email)
89

90
    const res = await request(app)
91
      .get('/insights')
92
      .set('Authorization', `Bearer ${token}`)
93

94
    expect(res.status).toBe(200)
95
    expect(res.body.insights).toEqual([])
96
  })
97

98
  it('filters by type', async () => {
99
    const user = await createTestUser(db)
100
    const token = generateTestToken(user.id, user.email)
101

102
    await seedInsight(user.id, { type: InsightType.MOOD_TREND })
103
    await seedInsight(user.id, { type: InsightType.STREAK })
104
    await seedInsight(user.id, { type: InsightType.STREAK })
105

106
    const res = await request(app)
107
      .get('/insights?type=STREAK')
108
      .set('Authorization', `Bearer ${token}`)
109

110
    expect(res.status).toBe(200)
111
    expect(res.body.insights).toHaveLength(2)
112
    expect(res.body.insights.every((i: any) => i.type === 'STREAK')).toBe(true)
113
  })
114

115
  it('filters by period', async () => {
116
    const user = await createTestUser(db)
117
    const token = generateTestToken(user.id, user.email)
118

119
    await seedInsight(user.id, { period: InsightPeriod.DAILY })
120
    await seedInsight(user.id, { period: InsightPeriod.MONTHLY })
121
    await seedInsight(user.id, { period: InsightPeriod.MONTHLY })
122

123
    const res = await request(app)
124
      .get('/insights?period=MONTHLY')
125
      .set('Authorization', `Bearer ${token}`)
126

127
    expect(res.status).toBe(200)
128
    expect(res.body.insights).toHaveLength(2)
129
    expect(res.body.insights.every((i: any) => i.period === 'MONTHLY')).toBe(true)
130
  })
131

132
  it('caps the result set at the requested limit', async () => {
133
    const user = await createTestUser(db)
134
    const token = generateTestToken(user.id, user.email)
135

136
    await seedInsight(user.id)
137
    await seedInsight(user.id)
138
    await seedInsight(user.id)
139

140
    const res = await request(app)
141
      .get('/insights?limit=2')
142
      .set('Authorization', `Bearer ${token}`)
143

144
    expect(res.status).toBe(200)
145
    expect(res.body.insights).toHaveLength(2)
146
  })
147

148
  it('returns 400 for an unknown type', async () => {
149
    const user = await createTestUser(db)
150
    const token = generateTestToken(user.id, user.email)
151

152
    const res = await request(app)
153
      .get('/insights?type=NOPE')
154
      .set('Authorization', `Bearer ${token}`)
155

156
    expect(res.status).toBe(400)
157
    expect(res.body.fields).toBeTruthy()
158
  })
159

160
  it('returns 400 for a non-positive limit', async () => {
161
    const user = await createTestUser(db)
162
    const token = generateTestToken(user.id, user.email)
163

164
    const res = await request(app)
165
      .get('/insights?limit=0')
166
      .set('Authorization', `Bearer ${token}`)
167

168
    expect(res.status).toBe(400)
169
  })
170

171
  it('returns 401 without a token', async () => {
172
    const res = await request(app).get('/insights')
173
    expect(res.status).toBe(401)
174
  })
175

176
  it('resolves an unqualified daily request to today', async () => {
177
    const user = await createTestUser(db)
178
    const token = generateTestToken(user.id, user.email)
179
    const { start: today } = appDayWindow()
180
    const { start: yesterday } = appDayWindow(new Date(today.getTime() - 1))
181

182
    await seedInsight(user.id, {
183
      type: InsightType.DAILY_ENERGY,
184
      period: InsightPeriod.DAILY,
185
      periodStart: yesterday,
186
      title: 'energy-yesterday',
187
    })
188
    await seedInsight(user.id, {
189
      type: InsightType.DAILY_ENERGY,
190
      period: InsightPeriod.DAILY,
191
      periodStart: today,
192
      title: 'energy-today',
193
    })
194
    await seedInsight(user.id, {
195
      type: InsightType.DAILY_SLEEP,
196
      period: InsightPeriod.DAILY,
197
      periodStart: yesterday,
198
      title: 'sleep-yesterday',
199
    })
200
    await seedInsight(user.id, {
201
      type: InsightType.DAILY_SLEEP,
202
      period: InsightPeriod.DAILY,
203
      periodStart: today,
204
      title: 'sleep-today',
205
    })
206

207
    const energy = await request(app)
208
      .get('/insights?type=DAILY_ENERGY')
209
      .set('Authorization', `Bearer ${token}`)
210
    const sleep = await request(app)
211
      .get('/insights?type=DAILY_SLEEP')
212
      .set('Authorization', `Bearer ${token}`)
213

214
    expect(energy.status).toBe(200)
215
    expect(energy.body.insights.map((i: any) => i.title)).toEqual([
216
      'energy-today',
217
    ])
218
    expect(sleep.status).toBe(200)
219
    expect(sleep.body.insights.map((i: any) => i.title)).toEqual(['sleep-today'])
220
  })
221

222
  it('filters the insight to an explicit day', async () => {
223
    const user = await createTestUser(db)
224
    const token = generateTestToken(user.id, user.email)
225
    const { start: today } = appDayWindow()
226
    const { start: yesterday } = appDayWindow(new Date(today.getTime() - 1))
227

228
    await seedInsight(user.id, {
229
      type: InsightType.DAILY_ENERGY,
230
      period: InsightPeriod.DAILY,
231
      periodStart: yesterday,
232
      title: 'energy-yesterday',
233
    })
234
    await seedInsight(user.id, {
235
      type: InsightType.DAILY_ENERGY,
236
      period: InsightPeriod.DAILY,
237
      periodStart: today,
238
      title: 'energy-today',
239
    })
240

241
    const res = await request(app)
242
      .get(`/insights?type=DAILY_ENERGY&day=${appDayKey(yesterday)}`)
243
      .set('Authorization', `Bearer ${token}`)
244

245
    expect(res.status).toBe(200)
246
    expect(res.body.insights.map((i: any) => i.title)).toEqual([
247
      'energy-yesterday',
248
    ])
249
  })
250

251
  it('returns an empty list for a day with no generated insight', async () => {
252
    const user = await createTestUser(db)
253
    const token = generateTestToken(user.id, user.email)
254
    const { start: today } = appDayWindow()
255
    const { start: yesterday } = appDayWindow(new Date(today.getTime() - 1))
256

257
    await seedInsight(user.id, {
258
      type: InsightType.DAILY_ENERGY,
259
      period: InsightPeriod.DAILY,
260
      periodStart: today,
261
      title: 'energy-today',
262
    })
263

264
    const res = await request(app)
265
      .get(`/insights?type=DAILY_ENERGY&day=${appDayKey(yesterday)}`)
266
      .set('Authorization', `Bearer ${token}`)
267

268
    expect(res.status).toBe(200)
269
    expect(res.body.insights).toEqual([])
270
  })
271

272
  it('returns an empty list when today has no daily insight', async () => {
273
    const user = await createTestUser(db)
274
    const token = generateTestToken(user.id, user.email)
275
    const { start: today } = appDayWindow()
276
    const { start: yesterday } = appDayWindow(new Date(today.getTime() - 1))
277

278
    await seedInsight(user.id, {
279
      type: InsightType.DAILY_ENERGY,
280
      period: InsightPeriod.DAILY,
281
      periodStart: yesterday,
282
      title: 'energy-yesterday',
283
    })
284

285
    const res = await request(app)
286
      .get('/insights?type=DAILY_ENERGY')
287
      .set('Authorization', `Bearer ${token}`)
288

289
    expect(res.status).toBe(200)
290
    expect(res.body.insights).toEqual([])
291
  })
292

293
  it('returns 400 for a malformed day', async () => {
294
    const user = await createTestUser(db)
295
    const token = generateTestToken(user.id, user.email)
296

297
    const res = await request(app)
298
      .get('/insights?day=2026-13-40')
299
      .set('Authorization', `Bearer ${token}`)
300

301
    expect(res.status).toBe(400)
302
    expect(res.body.fields).toBeTruthy()
303
  })
304
})