diff --git a/src/lib/notifications/senders/__tests__/push-attempt-record.test.ts b/src/lib/notifications/senders/__tests__/push-attempt-record.test.ts index e683aac24..85d048094 100644 --- a/src/lib/notifications/senders/__tests__/push-attempt-record.test.ts +++ b/src/lib/notifications/senders/__tests__/push-attempt-record.test.ts @@ -222,15 +222,11 @@ async function flushMicrotasks(): Promise { describe("recordPushAttemptForPayload — attribution boundary", () => { it("rejects a partial delivery identity without writing a ledger row", async () => { - recordPushAttemptForPayload( - { recordUserId: "record-1" }, - "recipient-1", - { - channel: "TELEGRAM", - eventType: "MEDICATION_REMINDER", - result: "ok", - }, - ); + recordPushAttemptForPayload({ recordUserId: "record-1" }, "recipient-1", { + channel: "TELEGRAM", + eventType: "MEDICATION_REMINDER", + result: "ok", + }); recordPushAttemptForPayload( { recipientUserId: "recipient-1" }, "recipient-1", diff --git a/src/lib/notifications/senders/__tests__/telegram.test.ts b/src/lib/notifications/senders/__tests__/telegram.test.ts index 74aaaaf58..292900143 100644 --- a/src/lib/notifications/senders/__tests__/telegram.test.ts +++ b/src/lib/notifications/senders/__tests__/telegram.test.ts @@ -208,7 +208,11 @@ describe("sendViaTelegram — per-slot delete scope (H2)", () => { reminderPayload(), ); - expect(deleteMessageMock).toHaveBeenCalledWith("bot-token", "old-chat", 123); + expect(deleteMessageMock).toHaveBeenCalledWith( + "bot-token", + "old-chat", + 123, + ); expect(upsertMock).toHaveBeenCalledWith( expect.objectContaining({ where: { diff --git a/src/lib/notifications/senders/telegram.ts b/src/lib/notifications/senders/telegram.ts index a98727338..8a2bf2479 100644 --- a/src/lib/notifications/senders/telegram.ts +++ b/src/lib/notifications/senders/telegram.ts @@ -90,7 +90,6 @@ async function deleteExistingReminders( } catch { // Best-effort: message may already be deleted } - } catch (err) { getEvent()?.addWarning(`Failed to delete existing reminders: ${err}`); } diff --git a/tests/integration/notification-attribution.test.ts b/tests/integration/notification-attribution.test.ts index ab7c91abb..25af08248 100644 --- a/tests/integration/notification-attribution.test.ts +++ b/tests/integration/notification-attribution.test.ts @@ -34,7 +34,8 @@ async function createGuardianGrant(input: { grantorId: input.recordUserId, granteeId: input.recipientUserId, access: input.access ?? "MANAGE", - acceptedAt: input.acceptedAt === undefined ? new Date() : input.acceptedAt, + acceptedAt: + input.acceptedAt === undefined ? new Date() : input.acceptedAt, revokedAt: input.revokedAt ?? null, revokedBy: input.revokedAt ? "GRANTOR" : null, expiresAt: input.expiresAt ?? null, @@ -139,6 +140,91 @@ describe("notification attribution (real Postgres)", () => { ).toBeNull(); }); + it("keeps Telegram reminder tracking independent for each recipient slot", async () => { + const recordUser = await createUser("telegram-record", true); + const firstRecipient = await createUser("telegram-recipient-a"); + const secondRecipient = await createUser("telegram-recipient-b"); + const client = getPrismaClient(); + const medication = await client.medication.create({ + data: { + userId: recordUser.id, + name: "Tracking medication", + dose: "1 tablet", + schedules: { + create: { windowStart: "08:00", windowEnd: "08:00" }, + }, + }, + include: { schedules: true }, + }); + const scheduleId = medication.schedules[0]!.id; + const slot = { + medicationId: medication.id, + scheduleId, + date: "2026-08-06", + phase: "YELLOW" as const, + timeOfDay: "08:00", + }; + + await client.telegramReminderMessage.createMany({ + data: [ + { + recipientUserId: firstRecipient.id, + ...slot, + chatId: "first-chat", + messageId: 101, + }, + { + recipientUserId: secondRecipient.id, + ...slot, + chatId: "second-chat", + messageId: 202, + }, + ], + }); + + await client.telegramReminderMessage.upsert({ + where: { + recipientUserId_medicationId_scheduleId_date_phase_timeOfDay: { + recipientUserId: firstRecipient.id, + ...slot, + }, + }, + create: { + recipientUserId: firstRecipient.id, + ...slot, + chatId: "unused-create-chat", + messageId: 303, + }, + update: { chatId: "first-chat-new", messageId: 303 }, + }); + + expect( + await client.telegramReminderMessage.findMany({ + where: { medicationId: medication.id }, + orderBy: { recipientUserId: "asc" }, + select: { recipientUserId: true, chatId: true, messageId: true }, + }), + ).toEqual([ + { + recipientUserId: firstRecipient.id, + chatId: "first-chat-new", + messageId: 303, + }, + { + recipientUserId: secondRecipient.id, + chatId: "second-chat", + messageId: 202, + }, + ]); + + await client.user.delete({ where: { id: firstRecipient.id } }); + expect( + await client.telegramReminderMessage.count({ + where: { medicationId: medication.id }, + }), + ).toBe(1); + }); + it("persistence maps an omitted pair from the legacy recipient", async () => { const self = await createUser("legacy");