eletrotupi / tcc/ commit / 269c105

api/insights: cover the daily window rules with tests

The daily energy and sleep processors had no coverage, which is how the
seven-day energy window and the timezone drift went unnoticed. The tests
pin the today-only window, the day boundary, and the empty case
Pedro Lucas Porcellis porcellis@eletrotupi.com · 12 days ago 269c1050a0fedbfadc33cb251e67d54cdc399f8f Browse files
Parents 6b26342
Changed files
1
Additions
+128
Deletions
0
/dev/null → api/tests/jobs/daily-insights.test.ts
1 +
import { describe, it, expect, afterAll, beforeEach } from 'vitest'
2 +
import { processDailyEnergy } from '@app/lib/queue/processors/insights/dailyEnergy'
3 +
import { processDailySleep } from '@app/lib/queue/processors/insights/dailySleep'
4 +
import { appDayWindow } from '@app/lib/queue/processors/insights/utils'
5 +
import { buildPrisma, cleanupTestDb, createTestUser } from '../test-helper'
6 +

7 +
const db = buildPrisma()
8 +

9 +
afterAll(() => db.$disconnect())
10 +
beforeEach(() => cleanupTestDb(db))
11 +

12 +
const HOUR = 60 * 60 * 1000
13 +

14 +
function createMood(userId: number, energyLevel: number, moment: Date) {
15 +
  return db.mood.create({
16 +
    data: {
17 +
      userId,
18 +
      selectedMood: 'GOOD',
19 +
      anxietyLevel: 2,
20 +
      stressLevel: 2,
21 +
      energyLevel,
22 +
      moment,
23 +
    },
24 +
  })
25 +
}
26 +

27 +
describe('processDailyEnergy', () => {
28 +
  it('averages only the mood entries logged inside today', async () => {
29 +
    const user = await createTestUser(db)
30 +
    const ref = new Date()
31 +
    const { start } = appDayWindow(ref)
32 +

33 +
    await createMood(user.id, 8, new Date(start.getTime() + HOUR))
34 +
    await createMood(user.id, 4, new Date(start.getTime() + 2 * HOUR))
35 +
    // one hour before the app day begins, so it belongs to yesterday
36 +
    await createMood(user.id, 10, new Date(start.getTime() - HOUR))
37 +

38 +
    await processDailyEnergy({
39 +
      userId: user.id,
40 +
      periodStart: ref.toISOString(),
41 +
      periodEnd: ref.toISOString(),
42 +
    })
43 +

44 +
    const insight = await db.insight.findFirst({
45 +
      where: { userId: user.id, type: 'DAILY_ENERGY' },
46 +
    })
47 +

48 +
    expect(insight).not.toBeNull()
49 +
    expect(insight!.periodStart).toEqual(start)
50 +
    expect(insight!.metadata).toMatchObject({ avgEnergy: 6, entryCount: 2 })
51 +
  })
52 +

53 +
  it('writes nothing when no mood entry falls inside today', async () => {
54 +
    const user = await createTestUser(db)
55 +
    const ref = new Date()
56 +
    const { start } = appDayWindow(ref)
57 +

58 +
    await createMood(user.id, 9, new Date(start.getTime() - HOUR))
59 +

60 +
    await processDailyEnergy({
61 +
      userId: user.id,
62 +
      periodStart: ref.toISOString(),
63 +
      periodEnd: ref.toISOString(),
64 +
    })
65 +

66 +
    const insight = await db.insight.findFirst({
67 +
      where: { userId: user.id, type: 'DAILY_ENERGY' },
68 +
    })
69 +

70 +
    expect(insight).toBeNull()
71 +
  })
72 +
})
73 +

74 +
describe('processDailySleep', () => {
75 +
  it('sums today and compares against yesterday', async () => {
76 +
    const user = await createTestUser(db)
77 +
    const ref = new Date()
78 +
    const { start: today } = appDayWindow(ref)
79 +
    const { start: yesterday } = appDayWindow(new Date(today.getTime() - 1))
80 +

81 +
    await db.sleepRecord.create({
82 +
      data: { userId: user.id, date: today, average: 8 },
83 +
    })
84 +
    await db.sleepRecord.create({
85 +
      data: { userId: user.id, date: yesterday, average: 6 },
86 +
    })
87 +

88 +
    await processDailySleep({
89 +
      userId: user.id,
90 +
      periodStart: ref.toISOString(),
91 +
      periodEnd: ref.toISOString(),
92 +
    })
93 +

94 +
    const insight = await db.insight.findFirst({
95 +
      where: { userId: user.id, type: 'DAILY_SLEEP' },
96 +
    })
97 +

98 +
    expect(insight).not.toBeNull()
99 +
    expect(insight!.periodStart).toEqual(today)
100 +
    expect(insight!.metadata).toMatchObject({
101 +
      totalHours: 8,
102 +
      diffMinutes: 120,
103 +
    })
104 +
  })
105 +

106 +
  it('writes nothing when no sleep record falls inside today', async () => {
107 +
    const user = await createTestUser(db)
108 +
    const ref = new Date()
109 +
    const { start: today } = appDayWindow(ref)
110 +
    const { start: yesterday } = appDayWindow(new Date(today.getTime() - 1))
111 +

112 +
    await db.sleepRecord.create({
113 +
      data: { userId: user.id, date: yesterday, average: 7 },
114 +
    })
115 +

116 +
    await processDailySleep({
117 +
      userId: user.id,
118 +
      periodStart: ref.toISOString(),
119 +
      periodEnd: ref.toISOString(),
120 +
    })
121 +

122 +
    const insight = await db.insight.findFirst({
123 +
      where: { userId: user.id, type: 'DAILY_SLEEP' },
124 +
    })
125 +

126 +
    expect(insight).toBeNull()
127 +
  })
128 +
})