mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
test(02-11): cover guardian notification channel safety
- Cover non-interactive Guardian payloads across delivery channels\n- Reject Telegram callbacks tied to managed records
This commit is contained in:
1 parent
59de92b62e
commit
accb98e223
6 files changed
+136
No files matched your search
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { isManagedGuardianDelivery } from "@/lib/notifications/managed-delivery";
|
||||
|
||||
describe("managed Guardian notification payload safety", () => {
|
||||
it("recognises only cross-account record delivery as managed Guardian delivery", () => {
|
||||
expect(
|
||||
isManagedGuardianDelivery({
|
||||
eventType: "MEDICATION_REMINDER",
|
||||
userId: "record-1",
|
||||
recordUserId: "record-1",
|
||||
recipientUserId: "guardian-1",
|
||||
title: "t",
|
||||
message: "m",
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isManagedGuardianDelivery({
|
||||
eventType: "MEDICATION_REMINDER",
|
||||
userId: "user-1",
|
||||
recordUserId: "user-1",
|
||||
recipientUserId: "user-1",
|
||||
title: "t",
|
||||
message: "m",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -287,6 +287,25 @@ describe("Telegram webhook — rate limit", () => {
|
||||
});
|
||||
|
||||
describe("Telegram webhook — callback dispatch", () => {
|
||||
it("rejects a callback tied to a managed Guardian reminder without mutating the record", async () => {
|
||||
vi.mocked(prisma.telegramReminderMessage.findFirst).mockResolvedValueOnce({
|
||||
medication: { userId: "managed-record" },
|
||||
} as never);
|
||||
|
||||
const res = await POST(tgRequest(callbackUpdate("taken:med-1")));
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(prisma.medication.findFirst).not.toHaveBeenCalled();
|
||||
expect(prisma.medicationIntakeEvent.create).not.toHaveBeenCalled();
|
||||
expect(prisma.medication.update).not.toHaveBeenCalled();
|
||||
expect(deleteMessage).not.toHaveBeenCalled();
|
||||
expect(answerTelegramCallbackQuery).toHaveBeenCalledWith(
|
||||
"decrypted:ENC(token-blob)",
|
||||
"cb-1",
|
||||
"Invalid action.",
|
||||
);
|
||||
});
|
||||
|
||||
it("'taken:<medId>' creates a MedicationIntakeEvent + clears snoozedUntil + acks the callback", async () => {
|
||||
const res = await POST(tgRequest(callbackUpdate("taken:med-1")));
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
@@ -164,6 +164,30 @@ describe("web-push reminder push — stable tag + badge", () => {
|
||||
);
|
||||
expect(body.tag).toBe("REMINDER");
|
||||
});
|
||||
|
||||
it("uses an ordinary landing route without a persistent prompt for a managed Guardian", async () => {
|
||||
findManyMock.mockResolvedValue([SUB]);
|
||||
vi.spyOn(global, "fetch").mockResolvedValue(
|
||||
new Response(null, { status: 201 }),
|
||||
);
|
||||
|
||||
await sendViaWebPush("guardian-1", {
|
||||
eventType: "MEDICATION_REMINDER",
|
||||
userId: "record-1",
|
||||
recordUserId: "record-1",
|
||||
recipientUserId: "guardian-1",
|
||||
title: "Time for your dose",
|
||||
message: "Ramipril 5mg",
|
||||
urgent: true,
|
||||
metadata: { url: "/medications/med-1" },
|
||||
});
|
||||
|
||||
const body = JSON.parse(
|
||||
generateRequestDetailsMock.mock.calls[0][1] as string,
|
||||
);
|
||||
expect(body.url).toBe("/");
|
||||
expect(body.requireInteraction).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("clear-on-taken push", () => {
|
||||
|
||||
@@ -610,6 +610,32 @@ describe("sendViaApns — dispatcher fan-out", () => {
|
||||
expect(note.payload.eventType).toBe("MEDICATION_REMINDER");
|
||||
});
|
||||
|
||||
it("omits action metadata and categories for a managed Guardian", async () => {
|
||||
vi.mocked(prisma.device.findMany).mockResolvedValueOnce([
|
||||
{ id: "d1", apnsToken: "tok-a", apnsEnvironment: "sandbox" },
|
||||
] as never);
|
||||
sendMock.mockResolvedValueOnce({ sent: [{ device: "tok-a" }], failed: [] });
|
||||
|
||||
await sendViaApns("guardian-1", {
|
||||
title: "t",
|
||||
message: "m",
|
||||
eventType: "MEDICATION_REMINDER",
|
||||
userId: "record-1",
|
||||
recordUserId: "record-1",
|
||||
recipientUserId: "guardian-1",
|
||||
metadata: {
|
||||
medicationId: "med-1",
|
||||
scheduleId: "sched-1",
|
||||
reminderId: "rem-1",
|
||||
url: "/medications/med-1",
|
||||
},
|
||||
});
|
||||
|
||||
const note = sendMock.mock.calls[0][0];
|
||||
expect(note.category).toBeUndefined();
|
||||
expect(note.payload).toEqual({ eventType: "MEDICATION_REMINDER" });
|
||||
});
|
||||
|
||||
it("forwards reminderId on MEASUREMENT_REMINDER and strips unlisted keys", async () => {
|
||||
// The iOS "Erledigt" action POSTs the reminder completion directly
|
||||
// from a foreground / handled notification, so the reminder's id must
|
||||
|
||||
@@ -200,6 +200,26 @@ describe("sendViaTelegram — per-slot delete scope (H2)", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("omits a record's inline actions for a managed Guardian", async () => {
|
||||
await sendViaTelegram(
|
||||
{ botToken: "bot-token", chatId: "guardian-chat" },
|
||||
{
|
||||
...reminderPayload({
|
||||
replyMarkup: {
|
||||
inline_keyboard: [[{ text: "Take", callback_data: "taken:med-1" }]],
|
||||
},
|
||||
}),
|
||||
userId: "managed-record",
|
||||
recordUserId: "managed-record",
|
||||
recipientUserId: "guardian-1",
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
sendTelegramMessageMock.mock.calls[0][3].replyMarkup,
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("updates one recipient slot when its Telegram chat changes", async () => {
|
||||
findUniqueMock.mockResolvedValue({ chatId: "old-chat", messageId: 123 });
|
||||
|
||||
|
||||
@@ -146,6 +146,24 @@ describe("ntfy urgent mapping", () => {
|
||||
expect(headers.Priority).toBe("5");
|
||||
expect(headers.Tags).toBe("reminder");
|
||||
});
|
||||
|
||||
it("does not send ntfy action headers to a managed Guardian", async () => {
|
||||
safeFetchMock.mockResolvedValue({ ok: true, status: 200 });
|
||||
await sendViaNtfy(
|
||||
{ serverUrl: "https://ntfy.example.com", topic: "t" },
|
||||
payload({
|
||||
userId: "record-1",
|
||||
recordUserId: "record-1",
|
||||
recipientUserId: "guardian-1",
|
||||
}),
|
||||
);
|
||||
const headers = (
|
||||
safeFetchMock.mock.calls[0][1] as {
|
||||
headers: Record<string, string>;
|
||||
}
|
||||
).headers;
|
||||
expect(headers.Actions).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("webhook urgent mapping", () => {
|
||||
|
||||
Reference in new issue
Block a user