mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
- Store record-scoped scheduler events outside delivery attempts\n- Keep delivery diagnostics free of bookkeeping rows
367 lines
11 KiB
TypeScript
367 lines
11 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { getPrismaClient, truncateAllTables } from "./setup";
|
|
import { resolveNotificationDeliveryIdentity } from "@/lib/notifications/delivery-identity";
|
|
import {
|
|
hasReminderDedupAnchor,
|
|
writeReminderDedupAnchor,
|
|
} from "@/lib/notifications/reminder-dedup";
|
|
import { recordPushAttempt } from "@/lib/notifications/senders/push-attempt-record";
|
|
|
|
let sequence = 0;
|
|
|
|
async function createUser(label: string, managed = false) {
|
|
const suffix = sequence++;
|
|
return getPrismaClient().user.create({
|
|
data: {
|
|
username: `${label}-${suffix}`,
|
|
email: `${label}-${suffix}@example.test`,
|
|
managedProfileAt: managed ? new Date() : null,
|
|
},
|
|
});
|
|
}
|
|
|
|
async function createGuardianGrant(input: {
|
|
recordUserId: string;
|
|
recipientUserId: string;
|
|
access?: "READ" | "WRITE" | "MANAGE";
|
|
acceptedAt?: Date | null;
|
|
revokedAt?: Date | null;
|
|
expiresAt?: Date | null;
|
|
}) {
|
|
return getPrismaClient().accountGrant.create({
|
|
data: {
|
|
grantorId: input.recordUserId,
|
|
granteeId: input.recipientUserId,
|
|
access: input.access ?? "MANAGE",
|
|
acceptedAt: input.acceptedAt === undefined ? new Date() : input.acceptedAt,
|
|
revokedAt: input.revokedAt ?? null,
|
|
revokedBy: input.revokedAt ? "GRANTOR" : null,
|
|
expiresAt: input.expiresAt ?? null,
|
|
},
|
|
});
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
await truncateAllTables(getPrismaClient());
|
|
});
|
|
|
|
describe("notification attribution (real Postgres)", () => {
|
|
it("persistence retains distinct record and recipient principals", async () => {
|
|
const recordUser = await createUser("record", true);
|
|
const recipientUser = await createUser("recipient");
|
|
|
|
recordPushAttempt({
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: recipientUser.id,
|
|
channel: "NTFY",
|
|
eventType: "MEDICATION_REMINDER",
|
|
result: "ok",
|
|
});
|
|
|
|
await vi.waitFor(async () => {
|
|
const attempt = await getPrismaClient().pushAttempt.findFirstOrThrow({
|
|
where: {
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: recipientUser.id,
|
|
},
|
|
select: {
|
|
recordUserId: true,
|
|
recipientUserId: true,
|
|
channel: true,
|
|
eventType: true,
|
|
},
|
|
});
|
|
|
|
expect(attempt).toEqual({
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: recipientUser.id,
|
|
channel: "NTFY",
|
|
eventType: "MEDICATION_REMINDER",
|
|
});
|
|
});
|
|
});
|
|
|
|
it("persistence leaves explicit self attribution unchanged", async () => {
|
|
const self = await createUser("self");
|
|
|
|
const attempt = await getPrismaClient().pushAttempt.create({
|
|
data: {
|
|
userId: self.id,
|
|
recordUserId: self.id,
|
|
recipientUserId: self.id,
|
|
channel: "NTFY",
|
|
eventType: "MEDICATION_REMINDER",
|
|
result: "ok",
|
|
},
|
|
select: {
|
|
userId: true,
|
|
recordUserId: true,
|
|
recipientUserId: true,
|
|
},
|
|
});
|
|
|
|
expect(attempt).toEqual({
|
|
userId: self.id,
|
|
recordUserId: self.id,
|
|
recipientUserId: self.id,
|
|
});
|
|
});
|
|
|
|
it("persists a managed reminder anchor as a record event without a delivery attempt", async () => {
|
|
const recordUser = await createUser("managed-record", true);
|
|
const client = getPrismaClient();
|
|
const now = new Date();
|
|
|
|
await writeReminderDedupAnchor(client, {
|
|
userId: recordUser.id,
|
|
eventType: "MEDICATION_REMINDER",
|
|
reason: "med:slot",
|
|
});
|
|
|
|
await expect(
|
|
hasReminderDedupAnchor(client, {
|
|
userId: recordUser.id,
|
|
eventType: "MEDICATION_REMINDER",
|
|
reason: "med:slot",
|
|
now,
|
|
}),
|
|
).resolves.toBe(true);
|
|
expect(await client.pushAttempt.count()).toBe(0);
|
|
|
|
const event = await client.notificationEvent.findFirstOrThrow({
|
|
where: { recordUserId: recordUser.id, dedupKey: "med:slot" },
|
|
select: { id: true },
|
|
});
|
|
await client.user.delete({ where: { id: recordUser.id } });
|
|
expect(
|
|
await client.notificationEvent.findUnique({ where: { id: event.id } }),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("persistence maps an omitted pair from the legacy recipient", async () => {
|
|
const self = await createUser("legacy");
|
|
|
|
const attempt = await getPrismaClient().pushAttempt.create({
|
|
data: {
|
|
userId: self.id,
|
|
channel: "NTFY",
|
|
eventType: "MEDICATION_REMINDER",
|
|
result: "ok",
|
|
},
|
|
select: {
|
|
recordUserId: true,
|
|
recipientUserId: true,
|
|
},
|
|
});
|
|
|
|
expect(attempt).toEqual({
|
|
recordUserId: self.id,
|
|
recipientUserId: self.id,
|
|
});
|
|
});
|
|
|
|
it("persistence rejects a partial or contradictory principal pair atomically", async () => {
|
|
const recordUser = await createUser("record", true);
|
|
const recipientUser = await createUser("recipient");
|
|
const otherUser = await createUser("other");
|
|
|
|
await expect(
|
|
getPrismaClient().pushAttempt.create({
|
|
data: {
|
|
userId: recipientUser.id,
|
|
recordUserId: recordUser.id,
|
|
channel: "NTFY",
|
|
eventType: "MEDICATION_REMINDER",
|
|
result: "ok",
|
|
},
|
|
}),
|
|
).rejects.toThrow("push attempt attribution requires both principals");
|
|
|
|
await expect(
|
|
getPrismaClient().pushAttempt.create({
|
|
data: {
|
|
userId: otherUser.id,
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: recipientUser.id,
|
|
channel: "NTFY",
|
|
eventType: "MEDICATION_REMINDER",
|
|
result: "ok",
|
|
},
|
|
}),
|
|
).rejects.toThrow("push attempt recipient must match user_id");
|
|
|
|
expect(await getPrismaClient().pushAttempt.count()).toBe(0);
|
|
});
|
|
|
|
it("persistence refuses a managed recipient and cascades an attributed recipient deletion", async () => {
|
|
const recordUser = await createUser("record", true);
|
|
const recipientUser = await createUser("recipient");
|
|
const managedRecipient = await createUser("managed-recipient", true);
|
|
|
|
await expect(
|
|
getPrismaClient().pushAttempt.create({
|
|
data: {
|
|
userId: managedRecipient.id,
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: managedRecipient.id,
|
|
channel: "NTFY",
|
|
eventType: "MEDICATION_REMINDER",
|
|
result: "ok",
|
|
},
|
|
}),
|
|
).rejects.toThrow("managed profile cannot receive notification delivery");
|
|
|
|
const attempt = await getPrismaClient().pushAttempt.create({
|
|
data: {
|
|
userId: recipientUser.id,
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: recipientUser.id,
|
|
channel: "NTFY",
|
|
eventType: "MEDICATION_REMINDER",
|
|
result: "ok",
|
|
},
|
|
});
|
|
await getPrismaClient().user.delete({ where: { id: recipientUser.id } });
|
|
|
|
expect(
|
|
await getPrismaClient().pushAttempt.findUnique({
|
|
where: { id: attempt.id },
|
|
}),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("principal resolution requires an active MANAGE grant for managed delivery", async () => {
|
|
const recordUser = await createUser("record", true);
|
|
const recipientUser = await createUser("recipient");
|
|
const ordinaryRecord = await createUser("ordinary");
|
|
|
|
await expect(
|
|
resolveNotificationDeliveryIdentity({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId: recordUser.id,
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: recipientUser.id,
|
|
title: "Record content",
|
|
message: "Record schedule",
|
|
}),
|
|
).resolves.toBeNull();
|
|
|
|
await createGuardianGrant({
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: recipientUser.id,
|
|
});
|
|
|
|
await expect(
|
|
resolveNotificationDeliveryIdentity({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId: recordUser.id,
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: recipientUser.id,
|
|
title: "Record content",
|
|
message: "Record schedule",
|
|
}),
|
|
).resolves.toEqual({
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: recipientUser.id,
|
|
managed: true,
|
|
});
|
|
|
|
await expect(
|
|
resolveNotificationDeliveryIdentity({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId: recordUser.id,
|
|
title: "Record content",
|
|
message: "Record schedule",
|
|
}),
|
|
).resolves.toBeNull();
|
|
|
|
await expect(
|
|
resolveNotificationDeliveryIdentity({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId: ordinaryRecord.id,
|
|
recordUserId: ordinaryRecord.id,
|
|
recipientUserId: recipientUser.id,
|
|
title: "Record content",
|
|
message: "Record schedule",
|
|
}),
|
|
).resolves.toBeNull();
|
|
});
|
|
|
|
it("principal resolution rejects inactive, insufficient, wrong-record, and managed-recipient grants", async () => {
|
|
const recordUser = await createUser("record", true);
|
|
const otherRecord = await createUser("other-record", true);
|
|
const pendingRecipient = await createUser("pending-recipient");
|
|
const revokedRecipient = await createUser("revoked-recipient");
|
|
const expiredRecipient = await createUser("expired-recipient");
|
|
const writeRecipient = await createUser("write-recipient");
|
|
const wrongRecordRecipient = await createUser("wrong-record-recipient");
|
|
const managedRecipient = await createUser("managed-recipient", true);
|
|
|
|
await Promise.all([
|
|
createGuardianGrant({
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: pendingRecipient.id,
|
|
acceptedAt: null,
|
|
}),
|
|
createGuardianGrant({
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: revokedRecipient.id,
|
|
revokedAt: new Date(),
|
|
}),
|
|
createGuardianGrant({
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: expiredRecipient.id,
|
|
expiresAt: new Date(Date.now() - 1_000),
|
|
}),
|
|
createGuardianGrant({
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: writeRecipient.id,
|
|
access: "WRITE",
|
|
}),
|
|
createGuardianGrant({
|
|
recordUserId: otherRecord.id,
|
|
recipientUserId: wrongRecordRecipient.id,
|
|
}),
|
|
createGuardianGrant({
|
|
recordUserId: recordUser.id,
|
|
recipientUserId: managedRecipient.id,
|
|
}),
|
|
]);
|
|
|
|
for (const recipientUserId of [
|
|
pendingRecipient.id,
|
|
revokedRecipient.id,
|
|
expiredRecipient.id,
|
|
writeRecipient.id,
|
|
wrongRecordRecipient.id,
|
|
managedRecipient.id,
|
|
]) {
|
|
await expect(
|
|
resolveNotificationDeliveryIdentity({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId: recordUser.id,
|
|
recordUserId: recordUser.id,
|
|
recipientUserId,
|
|
title: "Record content",
|
|
message: "Record schedule",
|
|
}),
|
|
).resolves.toBeNull();
|
|
}
|
|
|
|
const ordinaryUser = await createUser("ordinary-self");
|
|
await expect(
|
|
resolveNotificationDeliveryIdentity({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId: ordinaryUser.id,
|
|
title: "Personal content",
|
|
message: "Personal schedule",
|
|
}),
|
|
).resolves.toEqual({
|
|
recordUserId: ordinaryUser.id,
|
|
recipientUserId: ordinaryUser.id,
|
|
managed: false,
|
|
});
|
|
});
|
|
});
|