eletrotupi / tcc/ commit / dafa6f8

api: record notifications before pushing them

Both reminder processors now write the notification first and let the
push be a gated side-effect, so a muted or tokenless user still gets a
record and a delivery failure can't lose it. Each row carries the time
its occurrence was due, computed in the app timezone, so its window
cannot slip onto the following day
Pedro Lucas Porcellis porcellis@eletrotupi.com · 11 days ago dafa6f8a8e3b0e51605fc953a979d50ff6085b26 Browse files
Parents f1edb15
Changed files
9
Additions
+351
Deletions
-78
/dev/null → api/src/lib/appTime.ts
1 +
import { tz } from "@date-fns/tz";
2 +
import { set } from "date-fns";
3 +

4 +
export const APP_TIMEZONE = tz("America/Sao_Paulo");
5 +

6 +
// The instant, on the app-timezone day containing `reference`, at "HH:MM"
7 +
export function appTimeToday(time: string, reference: Date = new Date()): Date {
8 +
  const [hours, minutes] = time.split(":").map(Number);
9 +

10 +
  return set(reference, { hours, minutes, seconds: 0, milliseconds: 0 }, {
11 +
    in: APP_TIMEZONE,
12 +
  });
13 +
}
api/src/lib/queue/processors/dailyReminders.ts
1 1
import { Job } from "bullmq";
2 -
import { prisma } from "@app/lib/prisma";
3 -
import { sendPushNotification } from "@app/lib/sendPushNotification";
2 +
import { NotificationKind } from "@prisma/client";
3 +

4 +
import { createNotification } from "@app/services/notification.service";
4 5
import { DailyReminderPayload } from "@app/lib/queue/types";
6 +
import { appTimeToday } from "@app/lib/appTime";
5 7
import { logger } from "@app/lib/logger";
6 8

7 9
export async function dailyReminderProcessor(job: Job): Promise<void> {
8 -
  const { userId } = job.data as DailyReminderPayload;
10 +
  const { userId, scheduledTime } = job.data as DailyReminderPayload;
9 11

10 12
  logger.info({ jobId: job.id, userId }, "[daily-reminder] job started");
11 13

12 -
  const user = await prisma.user.findUnique({
13 -
    where: { id: userId },
14 -
    select: { pushToken: true, notificationsEnabled: true },
15 -
  });
16 -

17 -
  if (!user) {
18 -
    logger.warn({ userId }, "[daily-reminder] user not found, skipping");
19 -

20 -
    return;
21 -
  }
22 -

23 -
  if (!user.notificationsEnabled) {
24 -
    logger.warn({ userId }, "[daily-reminder] notifications disabled, skipping");
25 -

26 -
    return;
27 -
  }
28 -

29 -
  if (!user.pushToken) {
30 -
    logger.warn({ userId }, "[daily-reminder] no pushToken, skipping");
31 -

32 -
    return;
33 -
  }
34 -

35 -
  // TODO: These should be a side-effect from a new entry on the notifications
36 -
  // table
37 -
  await sendPushNotification({
38 -
    token: user.pushToken,
14 +
  await createNotification({
15 +
    userId,
16 +
    kind: NotificationKind.DAILY_REMINDER,
39 17
    title: "Como você está?",
40 18
    body: "Registre seu humor para acompanhar sua saúde.",
41 -
    data: { screen: "moods" },
19 +
    scheduledFor: appTimeToday(scheduledTime),
42 20
  });
43 21

44 -
  logger.info({ userId }, "[daily-reminder] push sent");
22 +
  logger.info({ userId }, "[daily-reminder] notification stored");
45 23
}
api/src/lib/queue/processors/medicineReminders.ts
1 1
import { Job } from "bullmq";
2 -
import { prisma } from "@app/lib/prisma";
3 -
import { sendPushNotification } from "@app/lib/sendPushNotification";
2 +
import { NotificationKind } from "@prisma/client";
3 +

4 +
import { createNotification } from "@app/services/notification.service";
4 5
import { MedicineReminderPayload } from "@app/lib/queue/types";
6 +
import { appTimeToday } from "@app/lib/appTime";
5 7
import { logger } from "@app/lib/logger";
6 8

7 9
export async function medicineReminderProcessor(job: Job): Promise<void> {
8 -
  const { userId, regimenId, medicineName, dosage } =
10 +
  const { userId, regimenId, medicineName, dosage, scheduledTime } =
9 11
    job.data as MedicineReminderPayload;
10 12

11 13
  logger.info(
12 14
    { jobId: job.id, userId, regimenId, medicine: medicineName },
13 15
    "[medicine-reminder] job started",
14 16
  );
15 17

16 -
  const user = await prisma.user.findUnique({
17 -
    where: { id: userId },
18 -
    select: { pushToken: true },
19 -
  });
20 -

21 -
  if (!user) {
22 -
    logger.warn({ userId }, "[medicine-reminder] user not found, skipping");
23 -
    return;
24 -
  }
25 -
  if (!user.pushToken) {
26 -
    logger.warn({ userId }, "[medicine-reminder] no pushToken, skipping");
27 -
    return;
28 -
  }
29 -

30 -
  await sendPushNotification({
31 -
    token: user.pushToken,
18 +
  await createNotification({
19 +
    userId,
20 +
    kind: NotificationKind.MEDICINE_REMINDER,
32 21
    title: `Hora de tomar ${medicineName}`,
33 22
    body: dosage,
34 -
    data: { screen: "medicine-regimens", regimenId },
23 +
    scheduledFor: appTimeToday(scheduledTime),
24 +
    regimenId,
35 25
  });
36 26

37 -
  logger.info({ userId }, "[medicine-reminder] push sent");
27 +
  logger.info({ userId, regimenId }, "[medicine-reminder] notification stored");
38 28
}
api/src/lib/queue/types.ts
1 1
// Mail queue
2 2
export enum MailJobName {
3 3
  WelcomeEmail = "mail:welcome",
4 4
  PasswordReset = "mail:password-reset",
5 5
  ActivateAccountEmail = "mail:activate-account",
61 61
  Send = "daily-reminder:send",
62 62
}
63 63

64 64
export type DailyReminderPayload = {
65 65
  userId: number;
66 +
  scheduledTime: string;
66 67
};
67 68

68 69
// Export queue
69 70
export enum ExportJobName {
70 71
  Data = "export:data",
76 77

77 78
export type ExportJobData = {
78 79
  name: ExportJobName.Data;
79 80
  data: ExportPayload;
80 81
};
api/src/services/dailyReminder.sync.ts
1 1
import { User } from "@prisma/client";
2 2
import { getQueue } from "@app/lib/queue/QueueRegistry";
3 3
import { DailyReminderJobName } from "@app/lib/queue/types";
4 4
import { logger } from "@app/lib/logger";
5 5

36 36

37 37
  const cron = timeToCron(user.dailyReminderTime);
38 38

39 39
  await queue.add(
40 40
    DailyReminderJobName.Send,
41 -
    { userId: user.id },
41 +
    { userId: user.id, scheduledTime: user.dailyReminderTime },
42 42
    {
43 43
      repeat: { pattern: cron, tz: "America/Sao_Paulo" },
44 44
      jobId: `daily-reminder-user-${user.id}`,
45 45
    }
46 46
  );
48 48
  logger.info(
49 49
    { userId: user.id, time: user.dailyReminderTime, cron },
50 50
    "[daily-sync] scheduled daily reminder",
51 51
  );
52 52
}
/dev/null → api/src/services/notification.service.ts
1 +
import { Notification, NotificationKind } from "@prisma/client";
2 +
import { endOfDay } from "date-fns";
3 +

4 +
import { prisma } from "@app/lib/prisma";
5 +
import { APP_TIMEZONE } from "@app/lib/appTime";
6 +
import { logger } from "@app/lib/logger";
7 +
import { sendPushNotification } from "@app/lib/sendPushNotification";
8 +

9 +
export type CreateNotificationInput = {
10 +
  userId: number;
11 +
  kind: NotificationKind;
12 +
  title: string;
13 +
  body: string;
14 +
  scheduledFor: Date;
15 +
  regimenId?: number | null;
16 +
};
17 +

18 +
const endOfAppDay = (scheduledFor: Date): Date =>
19 +
  endOfDay(scheduledFor, { in: APP_TIMEZONE });
20 +

21 +
// The only place a kind's behaviour is described: how long it stays relevant,
22 +
// and where tapping it should lead
23 +
type NotificationRule = {
24 +
  expiresAt: (scheduledFor: Date) => Date | null;
25 +
  screen?: string;
26 +
};
27 +

28 +
const notificationRules: Record<NotificationKind, NotificationRule> = {
29 +
  [NotificationKind.DAILY_REMINDER]: {
30 +
    expiresAt: endOfAppDay,
31 +
    screen: "moods",
32 +
  },
33 +
  [NotificationKind.MEDICINE_REMINDER]: {
34 +
    expiresAt: endOfAppDay,
35 +
    screen: "medicine-regimens",
36 +
  },
37 +
  [NotificationKind.INSIGHT_ALERT]: {
38 +
    expiresAt: () => null,
39 +
  },
40 +
};
41 +

42 +
export const createNotification = async (
43 +
  input: CreateNotificationInput
44 +
): Promise<Notification | null> => {
45 +
  const { userId, kind, title, body, scheduledFor, regimenId = null } = input;
46 +

47 +
  const user = await prisma.user.findUnique({
48 +
    where: { id: userId },
49 +
    select: { pushToken: true, notificationsEnabled: true },
50 +
  });
51 +

52 +
  if (!user) {
53 +
    logger.warn({ userId, kind }, "[notification] user not found, skipping");
54 +

55 +
    return null;
56 +
  }
57 +

58 +
  const rule = notificationRules[kind];
59 +

60 +
  const notification = await prisma.notification.create({
61 +
    data: {
62 +
      userId,
63 +
      kind,
64 +
      title,
65 +
      body,
66 +
      scheduledFor,
67 +
      regimenId,
68 +
      expiresAt: rule.expiresAt(scheduledFor),
69 +
    },
70 +
  });
71 +

72 +
  if (!user.notificationsEnabled || !user.pushToken) {
73 +
    logger.info(
74 +
      { notificationId: notification.id, userId, kind },
75 +
      "[notification] stored without push"
76 +
    );
77 +

78 +
    return notification;
79 +
  }
80 +

81 +
  const data = {
82 +
    ...(rule.screen ? { screen: rule.screen } : {}),
83 +
    ...(regimenId !== null ? { regimenId } : {}),
84 +
  };
85 +

86 +
  try {
87 +
    await sendPushNotification({
88 +
      token: user.pushToken,
89 +
      title,
90 +
      body,
91 +
      data,
92 +
    });
93 +
  } catch (error) {
94 +
    logger.error(
95 +
      { notificationId: notification.id, userId, err: error },
96 +
      "[notification] push failed"
97 +
    );
98 +

99 +
    return notification;
100 +
  }
101 +

102 +
  logger.info(
103 +
    { notificationId: notification.id, userId, kind },
104 +
    "[notification] push sent"
105 +
  );
106 +

107 +
  return prisma.notification.update({
108 +
    where: { id: notification.id },
109 +
    data: { pushedAt: new Date() },
110 +
  });
111 +
};
api/tests/jobs/push-notifications.test.ts
1 1
import { describe, it, expect, afterAll, beforeEach, vi } from 'vitest'
2 2
import { medicineReminderProcessor } from '@app/lib/queue/processors/medicineReminders'
3 3
import { dailyReminderProcessor } from '@app/lib/queue/processors/dailyReminders'
4 4
import { buildPrisma, cleanupTestDb, createTestUser } from '../test-helper'
5 5

18 18
  await cleanupTestDb(db)
19 19
  mockedSend.mockClear()
20 20
})
21 21

22 22
describe('medicineReminderProcessor', () => {
23 -
  it('sends a push notification to a user with a push token', async () => {
24 -
    const user = await createTestUser(db, { pushToken: 'ExponentPushToken[abc123]' })
23 +
  it('stores a medicine notification for the dose time and pushes it', async () => {
24 +
    const user = await createTestUser(db, {
25 +
      pushToken: 'ExponentPushToken[abc123]',
26 +
    })
27 +
    const regimen = await db.medicineRegimen.create({
28 +
      data: {
29 +
        name: 'Sertralina',
30 +
        dosage: '50mg',
31 +
        periodicity: 'DAILY',
32 +
        scheduledAt: ['08:00'],
33 +
        userId: user.id,
34 +
      },
35 +
    })
25 36

26 37
    await medicineReminderProcessor({
27 38
      data: {
28 39
        userId: user.id,
29 -
        regimenId: 1,
40 +
        regimenId: regimen.id,
30 41
        medicineName: 'Sertralina',
31 42
        dosage: '50mg',
32 43
        scheduledTime: '08:00',
33 44
      },
34 45
    } as any)
35 46

47 +
    const row = await db.notification.findFirst()
48 +
    expect(row).toMatchObject({
49 +
      userId: user.id,
50 +
      kind: 'MEDICINE_REMINDER',
51 +
      title: 'Hora de tomar Sertralina',
52 +
      body: '50mg',
53 +
      regimenId: regimen.id,
54 +
    })
55 +
    // 08:00 in São Paulo (UTC-3)
56 +
    expect(row?.scheduledFor.getUTCHours()).toBe(11)
57 +
    expect(row?.scheduledFor.getUTCMinutes()).toBe(0)
58 +
    expect(row?.pushedAt).not.toBeNull()
59 +

36 60
    expect(mockedSend).toHaveBeenCalledOnce()
37 61
    expect(mockedSend).toHaveBeenCalledWith(
38 62
      expect.objectContaining({
39 63
        token: 'ExponentPushToken[abc123]',
40 64
        title: 'Hora de tomar Sertralina',
41 65
        body: '50mg',
42 -
        data: expect.objectContaining({ screen: 'medicine-regimens' }),
66 +
        data: expect.objectContaining({
67 +
          screen: 'medicine-regimens',
68 +
          regimenId: regimen.id,
69 +
        }),
43 70
      })
44 71
    )
45 72
  })
46 73

47 -
  it('skips when the user has no push token', async () => {
74 +
  it('stores the notification without pushing when the user has no token', async () => {
48 75
    const user = await createTestUser(db) // no pushToken
76 +
    const regimen = await db.medicineRegimen.create({
77 +
      data: {
78 +
        name: 'Ritalina',
79 +
        dosage: '10mg',
80 +
        periodicity: 'DAILY',
81 +
        scheduledAt: ['12:00'],
82 +
        userId: user.id,
83 +
      },
84 +
    })
49 85

50 86
    await medicineReminderProcessor({
51 87
      data: {
52 88
        userId: user.id,
53 -
        regimenId: 1,
89 +
        regimenId: regimen.id,
54 90
        medicineName: 'Ritalina',
55 91
        dosage: '10mg',
56 -
        scheduledTime: '12:00'
92 +
        scheduledTime: '12:00',
57 93
      },
58 94
    } as any)
59 95

60 96
    expect(mockedSend).not.toHaveBeenCalled()
97 +
    const row = await db.notification.findFirst()
98 +
    expect(row?.kind).toBe('MEDICINE_REMINDER')
99 +
    expect(row?.pushedAt).toBeNull()
61 100
  })
62 101

63 -
  it('skips when the user does not exist', async () => {
102 +
  it('stores nothing when the user does not exist', async () => {
64 103
    await medicineReminderProcessor({
65 104
      data: {
66 105
        userId: 999999,
67 106
        regimenId: 1,
68 107
        medicineName: 'X',
69 108
        dosage: 'Y',
70 -
        scheduledTime: '08:00'
109 +
        scheduledTime: '08:00',
71 110
      },
72 111
    } as any)
73 112

74 113
    expect(mockedSend).not.toHaveBeenCalled()
114 +
    expect(await db.notification.count()).toBe(0)
75 115
  })
76 116
})
77 117

78 118
describe('dailyReminderProcessor', () => {
79 -
  it('sends a notification when user allows it and token is present', async () => {
119 +
  it('stores a daily notification for the reminder time and pushes it', async () => {
80 120
    const user = await createTestUser(db, {
81 121
      pushToken: 'ExponentPushToken[daily123]',
82 122
      notificationsEnabled: true,
83 123
    })
84 124

85 -
    await dailyReminderProcessor({ data: { userId: user.id } } as any)
125 +
    await dailyReminderProcessor({
126 +
      data: { userId: user.id, scheduledTime: '20:00' },
127 +
    } as any)
128 +

129 +
    const row = await db.notification.findFirst()
130 +
    expect(row).toMatchObject({
131 +
      userId: user.id,
132 +
      kind: 'DAILY_REMINDER',
133 +
      title: 'Como você está?',
134 +
      body: 'Registre seu humor para acompanhar sua saúde.',
135 +
    })
136 +
    // 20:00 in São Paulo (UTC-3)
137 +
    expect(row?.scheduledFor.getUTCHours()).toBe(23)
138 +
    expect(row?.scheduledFor.getUTCMinutes()).toBe(0)
139 +
    expect(row?.pushedAt).not.toBeNull()
86 140

87 141
    expect(mockedSend).toHaveBeenCalledOnce()
88 142
    expect(mockedSend).toHaveBeenCalledWith(
89 143
      expect.objectContaining({
90 144
        token: 'ExponentPushToken[daily123]',
92 146
        data: expect.objectContaining({ screen: 'moods' }),
93 147
      })
94 148
    )
95 149
  })
96 150

97 -
  it('skips when notifications are disabled', async () => {
151 +
  it('stores the notification without pushing when notifications are disabled', async () => {
98 152
    const user = await createTestUser(db, {
99 153
      pushToken: 'ExponentPushToken[disabled]',
100 154
      notificationsEnabled: false,
101 155
    })
102 156

103 -
    await dailyReminderProcessor({ data: { userId: user.id } } as any)
157 +
    await dailyReminderProcessor({
158 +
      data: { userId: user.id, scheduledTime: '20:00' },
159 +
    } as any)
104 160

105 161
    expect(mockedSend).not.toHaveBeenCalled()
162 +
    const row = await db.notification.findFirst()
163 +
    expect(row?.kind).toBe('DAILY_REMINDER')
164 +
    expect(row?.pushedAt).toBeNull()
106 165
  })
107 166

108 -
  it('skips when the user has no push token', async () => {
109 -
    const user = await createTestUser(db, { notificationsEnabled: true })
110 -

111 -
    await dailyReminderProcessor({ data: { userId: user.id } } as any)
112 -

113 -
    expect(mockedSend).not.toHaveBeenCalled()
114 -
  })
115 -

116 -
  it('skips when the user does not exist', async () => {
117 -
    await dailyReminderProcessor({ data: { userId: 999999 } } as any)
167 +
  it('stores nothing when the user does not exist', async () => {
168 +
    await dailyReminderProcessor({
169 +
      data: { userId: 999999, scheduledTime: '20:00' },
170 +
    } as any)
118 171

119 172
    expect(mockedSend).not.toHaveBeenCalled()
173 +
    expect(await db.notification.count()).toBe(0)
120 174
  })
121 175
})
/dev/null → api/tests/notifications/create.test.ts
1 +
import { describe, it, expect, afterAll, beforeEach, vi } from 'vitest'
2 +
import { NotificationKind } from '@prisma/client'
3 +
import { buildPrisma, cleanupTestDb, createTestUser } from '../test-helper'
4 +

5 +
vi.mock('@app/lib/sendPushNotification', () => ({
6 +
  sendPushNotification: vi.fn().mockResolvedValue(undefined),
7 +
}))
8 +

9 +
import { sendPushNotification } from '@app/lib/sendPushNotification'
10 +
import { createNotification } from '@app/services/notification.service'
11 +

12 +
const mockedSend = vi.mocked(sendPushNotification)
13 +

14 +
const db = buildPrisma()
15 +

16 +
afterAll(() => db.$disconnect())
17 +
beforeEach(async () => {
18 +
  await cleanupTestDb(db)
19 +
  mockedSend.mockClear()
20 +
  mockedSend.mockResolvedValue(undefined)
21 +
})
22 +

23 +
// São Paulo is UTC-3 year round, so this instant is midday in the app timezone
24 +
const midday = new Date('2026-03-10T12:00:00-03:00')
25 +

26 +
const dailyInput = (userId: number) => ({
27 +
  userId,
28 +
  kind: NotificationKind.DAILY_REMINDER,
29 +
  title: 'Como você está?',
30 +
  body: 'Registre seu humor para acompanhar sua saúde.',
31 +
  scheduledFor: midday,
32 +
})
33 +

34 +
describe('createNotification', () => {
35 +
  it('stores a notification whose window ends at the end of the app day', async () => {
36 +
    const user = await createTestUser(db)
37 +

38 +
    await createNotification(dailyInput(user.id))
39 +

40 +
    const row = await db.notification.findFirst()
41 +
    expect(row).toMatchObject({
42 +
      userId: user.id,
43 +
      kind: 'DAILY_REMINDER',
44 +
      title: 'Como você está?',
45 +
      regimenId: null,
46 +
    })
47 +
    expect(row?.scheduledFor.toISOString()).toBe(midday.toISOString())
48 +
    expect(row?.expiresAt?.toISOString()).toBe('2026-03-11T02:59:59.999Z')
49 +
  })
50 +

51 +
  it('stores a timeless notification without a window', async () => {
52 +
    const user = await createTestUser(db)
53 +

54 +
    await createNotification({
55 +
      userId: user.id,
56 +
      kind: NotificationKind.INSIGHT_ALERT,
57 +
      title: 'Trabalho aparece muito',
58 +
      body: 'Trabalho tem aparecido bastante nos seus gatilhos.',
59 +
      scheduledFor: midday,
60 +
    })
61 +

62 +
    const row = await db.notification.findFirst()
63 +
    expect(row?.kind).toBe('INSIGHT_ALERT')
64 +
    expect(row?.expiresAt).toBeNull()
65 +
  })
66 +

67 +
  it('records when a push was dispatched', async () => {
68 +
    const user = await createTestUser(db, {
69 +
      pushToken: 'ExponentPushToken[abc123]',
70 +
      notificationsEnabled: true,
71 +
    })
72 +

73 +
    await createNotification(dailyInput(user.id))
74 +

75 +
    expect(mockedSend).toHaveBeenCalledOnce()
76 +
    const row = await db.notification.findFirst()
77 +
    expect(row?.pushedAt).not.toBeNull()
78 +
  })
79 +

80 +
  it('stores the notification but does not push when notifications are disabled', async () => {
81 +
    const user = await createTestUser(db, {
82 +
      pushToken: 'ExponentPushToken[disabled]',
83 +
      notificationsEnabled: false,
84 +
    })
85 +

86 +
    await createNotification(dailyInput(user.id))
87 +

88 +
    expect(mockedSend).not.toHaveBeenCalled()
89 +
    const row = await db.notification.findFirst()
90 +
    expect(row).not.toBeNull()
91 +
    expect(row?.pushedAt).toBeNull()
92 +
  })
93 +

94 +
  it('stores the notification but does not push without a push token', async () => {
95 +
    const user = await createTestUser(db, { notificationsEnabled: true })
96 +

97 +
    await createNotification(dailyInput(user.id))
98 +

99 +
    expect(mockedSend).not.toHaveBeenCalled()
100 +
    const row = await db.notification.findFirst()
101 +
    expect(row).not.toBeNull()
102 +
    expect(row?.pushedAt).toBeNull()
103 +
  })
104 +

105 +
  it('keeps an unpushed record when the push fails', async () => {
106 +
    const user = await createTestUser(db, {
107 +
      pushToken: 'ExponentPushToken[boom]',
108 +
      notificationsEnabled: true,
109 +
    })
110 +
    mockedSend.mockRejectedValueOnce(new Error('Expo push API returned 500'))
111 +

112 +
    await expect(createNotification(dailyInput(user.id))).resolves.toBeDefined()
113 +

114 +
    const row = await db.notification.findFirst()
115 +
    expect(row).not.toBeNull()
116 +
    expect(row?.pushedAt).toBeNull()
117 +
  })
118 +

119 +
  it('stores nothing when the user does not exist', async () => {
120 +
    const result = await createNotification(dailyInput(999999))
121 +

122 +
    expect(result).toBeNull()
123 +
    expect(await db.notification.count()).toBe(0)
124 +
    expect(mockedSend).not.toHaveBeenCalled()
125 +
  })
126 +
})
api/tests/users/preferences.test.ts
1 1
import { describe, it, expect, afterAll, beforeEach } from 'vitest'
2 2
import request from 'supertest'
3 3
import { createApp } from '@app/createApp'
4 4
import { DailyReminderJobName } from '@app/lib/queue/types'
5 5
import {
42 42

43 43
    expect(recordedJobs).toContainEqual(
44 44
      expect.objectContaining({
45 45
        queue: 'daily-reminders',
46 46
        name: DailyReminderJobName.Send,
47 -
        data: { userId: user.id },
47 +
        data: { userId: user.id, scheduledTime: '08:30' },
48 48
      })
49 49
    )
50 50
  })
51 51

52 52
  it('updates the weekly resume preference', async () => {
123 123
      .send({ user: { notificationsEnabled: false } })
124 124

125 125
    expect(res.status).toBe(401)
126 126
  })
127 127
})