mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
The eight journeys were enumerated and one of them was wired. The other
seven existed as claims in a fixture file, which is the gap the enumeration
was written to close: a browser suite reports what it ran, never what it did
not, and a journey nobody wired reads exactly like a journey that passed.
All eight are wired now, and the list is checked in both directions — an
enumerated journey missing from the wiring fails, not just a wiring entry
naming a journey nobody declared. Before this, a subset was legal, so a
dropped journey and a not-yet-written one looked the same.
A journey names more than one file where more than one file carries it. J1
claims a grant runs through invite, accept, switch, expiry, revoke and a
direct URL; the browser proves the switch, what each level opens and the
direct-URL refusal, and the transitions either side are database facts
proved against real Postgres. Folding that into one file would mean a
browser spec driving expiry through the clock or an integration file
asserting what a page renders, and both are worse tests than the two that
exist.
Three journeys ride Playwright and five ride Vitest, so a green run of this
guard means all eight are claimed and here is which have executed in the
ordinary gate — never that all eight passed. The runner is derived from the
path rather than trusted, so it cannot become a label set to whichever value
made the split read better.
The extractor now reads `it(` as well as `test(`, and the parameterised
`it.each(table)("title")` form alongside the single-call one. It saw neither
before: five of the journeys are proved by Vitest files, every per-file leg
is over a matched set, and an empty match set satisfied them all. The
synthetic spec that proves the parse grew a Vitest half, and it is what
caught the parameterised form being invisible. What the matcher still cannot
see is written into its own doc comment.
648 lines
20 KiB
TypeScript
648 lines
20 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const sent = vi.hoisted(() => ({
|
|
deliveries: [] as Array<{
|
|
channel: "NTFY" | "APNS" | "WEB_PUSH" | "TELEGRAM";
|
|
recordUserId: string;
|
|
recipientUserId: string;
|
|
}>,
|
|
ntfy: [] as Array<{
|
|
recipientUserId: string;
|
|
title: string;
|
|
message: string;
|
|
}>,
|
|
apns: [] as Array<{ recipientUserId: string; recordUserId?: string }>,
|
|
}));
|
|
|
|
const ntfyEgress = vi.hoisted(() => ({
|
|
block: null as Promise<void> | null,
|
|
onStart: null as (() => void) | null,
|
|
error: null as Error | null,
|
|
outcome: null as { ok: false; reason: string } | null,
|
|
}));
|
|
|
|
vi.mock("@/lib/notifications/senders/ntfy", async () => {
|
|
const { recordPushAttemptForPayload } = await vi.importActual<
|
|
typeof import("@/lib/notifications/senders/push-attempt-record")
|
|
>("@/lib/notifications/senders/push-attempt-record");
|
|
return {
|
|
sendViaNtfy: async (
|
|
_config: unknown,
|
|
payload: {
|
|
userId: string;
|
|
recordUserId?: string;
|
|
recipientUserId?: string;
|
|
eventType: string;
|
|
title: string;
|
|
message: string;
|
|
},
|
|
) => {
|
|
ntfyEgress.onStart?.();
|
|
if (ntfyEgress.block) await ntfyEgress.block;
|
|
if (ntfyEgress.error) throw ntfyEgress.error;
|
|
if (ntfyEgress.outcome) return ntfyEgress.outcome;
|
|
const recipientUserId = payload.recipientUserId ?? payload.userId;
|
|
sent.ntfy.push({
|
|
recipientUserId,
|
|
title: payload.title,
|
|
message: payload.message,
|
|
});
|
|
sent.deliveries.push({
|
|
channel: "NTFY",
|
|
recordUserId: payload.recordUserId ?? payload.userId,
|
|
recipientUserId,
|
|
});
|
|
recordPushAttemptForPayload(payload, recipientUserId, {
|
|
userId: payload.userId,
|
|
channel: "NTFY",
|
|
eventType: payload.eventType,
|
|
result: "ok",
|
|
});
|
|
return { ok: true };
|
|
},
|
|
};
|
|
});
|
|
|
|
vi.mock("@/lib/notifications/senders/apns", async () => {
|
|
const { recordPushAttemptForPayload } = await vi.importActual<
|
|
typeof import("@/lib/notifications/senders/push-attempt-record")
|
|
>("@/lib/notifications/senders/push-attempt-record");
|
|
return {
|
|
sendViaApns: async (
|
|
recipientUserId: string,
|
|
payload: { userId: string; recordUserId?: string; eventType: string },
|
|
) => {
|
|
sent.apns.push({ recipientUserId, recordUserId: payload.recordUserId });
|
|
sent.deliveries.push({
|
|
channel: "APNS",
|
|
recordUserId: payload.recordUserId ?? payload.userId,
|
|
recipientUserId,
|
|
});
|
|
recordPushAttemptForPayload(payload, recipientUserId, {
|
|
userId: payload.userId,
|
|
channel: "APNS",
|
|
eventType: payload.eventType,
|
|
result: "ok",
|
|
});
|
|
return { ok: true };
|
|
},
|
|
};
|
|
});
|
|
|
|
vi.mock("@/lib/notifications/senders/web-push", async () => {
|
|
const { recordPushAttemptForPayload } = await vi.importActual<
|
|
typeof import("@/lib/notifications/senders/push-attempt-record")
|
|
>("@/lib/notifications/senders/push-attempt-record");
|
|
return {
|
|
sendViaWebPush: async (
|
|
recipientUserId: string,
|
|
payload: { userId: string; recordUserId?: string; eventType: string },
|
|
) => {
|
|
sent.deliveries.push({
|
|
channel: "WEB_PUSH",
|
|
recordUserId: payload.recordUserId ?? payload.userId,
|
|
recipientUserId,
|
|
});
|
|
recordPushAttemptForPayload(payload, recipientUserId, {
|
|
userId: payload.userId,
|
|
channel: "WEB_PUSH",
|
|
eventType: payload.eventType,
|
|
result: "ok",
|
|
});
|
|
return { ok: true };
|
|
},
|
|
};
|
|
});
|
|
|
|
vi.mock("@/lib/notifications/senders/telegram", async () => {
|
|
const { recordPushAttemptForPayload } = await vi.importActual<
|
|
typeof import("@/lib/notifications/senders/push-attempt-record")
|
|
>("@/lib/notifications/senders/push-attempt-record");
|
|
return {
|
|
sendViaTelegram: async (
|
|
_config: unknown,
|
|
payload: {
|
|
userId: string;
|
|
recordUserId?: string;
|
|
recipientUserId?: string;
|
|
eventType: string;
|
|
},
|
|
) => {
|
|
const recipientUserId = payload.recipientUserId ?? payload.userId;
|
|
sent.deliveries.push({
|
|
channel: "TELEGRAM",
|
|
recordUserId: payload.recordUserId ?? payload.userId,
|
|
recipientUserId,
|
|
});
|
|
recordPushAttemptForPayload(payload, recipientUserId, {
|
|
userId: payload.userId,
|
|
channel: "TELEGRAM",
|
|
eventType: payload.eventType,
|
|
result: "ok",
|
|
});
|
|
return { ok: true };
|
|
},
|
|
};
|
|
});
|
|
|
|
import { encrypt } from "@/lib/crypto";
|
|
import type { Locale } from "@/lib/i18n/config";
|
|
import { withManagedProfileLock } from "@/lib/managed-profiles/lifecycle";
|
|
import { dispatchNotification } from "@/lib/notifications/dispatcher";
|
|
import { revokeGrant } from "@/lib/sharing/grants";
|
|
import { getPrismaClient, truncateAllTables } from "./setup";
|
|
|
|
let sequence = 0;
|
|
|
|
async function createUser(input: {
|
|
label: string;
|
|
locale: "en" | "de";
|
|
managed?: boolean;
|
|
clientManaged?: boolean;
|
|
}) {
|
|
const suffix = sequence++;
|
|
return getPrismaClient().user.create({
|
|
data: {
|
|
username: `${input.label}-${suffix}`,
|
|
email: `${input.label}-${suffix}@example.test`,
|
|
locale: input.locale,
|
|
managedProfileAt: input.managed ? new Date() : null,
|
|
notificationPrefs: input.clientManaged
|
|
? { medication: { clientManaged: true } }
|
|
: undefined,
|
|
},
|
|
});
|
|
}
|
|
|
|
async function addGuardian(recordUserId: string, recipientUserId: string) {
|
|
await getPrismaClient().accountGrant.create({
|
|
data: {
|
|
grantorId: recordUserId,
|
|
granteeId: recipientUserId,
|
|
access: "MANAGE",
|
|
acceptedAt: new Date(),
|
|
},
|
|
});
|
|
}
|
|
|
|
async function addChannel(
|
|
userId: string,
|
|
type: "NTFY" | "APNS" | "WEB_PUSH" | "TELEGRAM",
|
|
enabled = true,
|
|
) {
|
|
await getPrismaClient().notificationChannel.create({
|
|
data: {
|
|
userId,
|
|
type,
|
|
enabled,
|
|
config: encrypt("{}"),
|
|
},
|
|
});
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
ntfyEgress.block = null;
|
|
ntfyEgress.onStart = null;
|
|
ntfyEgress.error = null;
|
|
ntfyEgress.outcome = null;
|
|
sent.deliveries.length = 0;
|
|
sent.ntfy.length = 0;
|
|
sent.apns.length = 0;
|
|
await truncateAllTables(getPrismaClient());
|
|
});
|
|
|
|
describe("Guardian fan-out (real Postgres)", () => {
|
|
it("[J4-guardian-notification-matrix] isolates recipient locale and global preference across two Guardians and two records", async () => {
|
|
const [recordA, recordB, guardianEn, guardianDe] = await Promise.all([
|
|
createUser({ label: "record-a", locale: "en", managed: true }),
|
|
createUser({ label: "record-b", locale: "de", managed: true }),
|
|
createUser({ label: "guardian-en", locale: "en" }),
|
|
createUser({ label: "guardian-de", locale: "de" }),
|
|
]);
|
|
await Promise.all([
|
|
addGuardian(recordA.id, guardianEn.id),
|
|
addGuardian(recordA.id, guardianDe.id),
|
|
addGuardian(recordB.id, guardianEn.id),
|
|
addGuardian(recordB.id, guardianDe.id),
|
|
addChannel(guardianEn.id, "NTFY"),
|
|
addChannel(guardianDe.id, "NTFY"),
|
|
]);
|
|
|
|
const renderForRecipient = (locale: Locale) => ({
|
|
title: `title-${locale}`,
|
|
message: `message-${locale}`,
|
|
});
|
|
await dispatchNotification({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId: recordA.id,
|
|
title: "record-a-title",
|
|
message: "record-a-message",
|
|
renderForRecipient,
|
|
});
|
|
|
|
expect(sent.ntfy).toEqual(
|
|
expect.arrayContaining([
|
|
{
|
|
recipientUserId: guardianEn.id,
|
|
title: "title-en",
|
|
message: "message-en",
|
|
},
|
|
{
|
|
recipientUserId: guardianDe.id,
|
|
title: "title-de",
|
|
message: "message-de",
|
|
},
|
|
]),
|
|
);
|
|
|
|
await getPrismaClient().notificationPreference.create({
|
|
data: {
|
|
channel: {
|
|
connect: { userId_type: { userId: guardianEn.id, type: "NTFY" } },
|
|
},
|
|
eventType: "MEDICATION_REMINDER",
|
|
enabled: false,
|
|
},
|
|
});
|
|
sent.ntfy.length = 0;
|
|
|
|
await dispatchNotification({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId: recordB.id,
|
|
title: "record-b-title",
|
|
message: "record-b-message",
|
|
renderForRecipient,
|
|
});
|
|
|
|
expect(sent.ntfy).toEqual([
|
|
{
|
|
recipientUserId: guardianDe.id,
|
|
title: "title-de",
|
|
message: "message-de",
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("bypasses client-managed suppression for each Guardian but not self delivery", async () => {
|
|
const [record, guardianEn, guardianDe] = await Promise.all([
|
|
createUser({ label: "record", locale: "en", managed: true }),
|
|
createUser({
|
|
label: "guardian-en",
|
|
locale: "en",
|
|
clientManaged: true,
|
|
}),
|
|
createUser({
|
|
label: "guardian-de",
|
|
locale: "de",
|
|
clientManaged: true,
|
|
}),
|
|
]);
|
|
await Promise.all([
|
|
addGuardian(record.id, guardianEn.id),
|
|
addGuardian(record.id, guardianDe.id),
|
|
addChannel(guardianEn.id, "APNS"),
|
|
addChannel(guardianDe.id, "APNS"),
|
|
]);
|
|
|
|
await dispatchNotification({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId: record.id,
|
|
title: "record-title",
|
|
message: "record-message",
|
|
});
|
|
|
|
expect(sent.apns).toEqual(
|
|
expect.arrayContaining([
|
|
{ recipientUserId: guardianEn.id, recordUserId: record.id },
|
|
{ recipientUserId: guardianDe.id, recordUserId: record.id },
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("[J4-guardian-notification-matrix] writes the complete two-record, two-Guardian, four-channel attribution matrix", async () => {
|
|
const [recordA, recordB, guardianOne, guardianTwo, ordinaryRecord] =
|
|
await Promise.all([
|
|
createUser({ label: "record-a", locale: "en", managed: true }),
|
|
createUser({ label: "record-b", locale: "de", managed: true }),
|
|
createUser({ label: "guardian-one", locale: "en" }),
|
|
createUser({ label: "guardian-two", locale: "de" }),
|
|
createUser({ label: "ordinary-record", locale: "en" }),
|
|
]);
|
|
const guardianIds = [guardianOne.id, guardianTwo.id];
|
|
const recordIds = [recordA.id, recordB.id];
|
|
const channelTypes = ["NTFY", "APNS", "WEB_PUSH", "TELEGRAM"] as const;
|
|
|
|
await Promise.all([
|
|
...recordIds.flatMap((recordUserId) =>
|
|
guardianIds.map((recipientUserId) =>
|
|
addGuardian(recordUserId, recipientUserId),
|
|
),
|
|
),
|
|
// An ordinary adult's MANAGE grant must never cause fan-out.
|
|
addGuardian(ordinaryRecord.id, guardianOne.id),
|
|
...guardianIds.flatMap((userId) =>
|
|
channelTypes.map((type) => addChannel(userId, type)),
|
|
),
|
|
]);
|
|
|
|
await Promise.all(
|
|
recordIds.map((userId) =>
|
|
dispatchNotification({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId,
|
|
title: "record reminder",
|
|
message: "record message",
|
|
}),
|
|
),
|
|
);
|
|
|
|
const expected = recordIds.flatMap((recordUserId) =>
|
|
guardianIds.flatMap((recipientUserId) =>
|
|
channelTypes.map((channel) => ({
|
|
channel,
|
|
recordUserId,
|
|
recipientUserId,
|
|
})),
|
|
),
|
|
);
|
|
expect(sent.deliveries).toHaveLength(expected.length);
|
|
expect(sent.deliveries).toEqual(expect.arrayContaining(expected));
|
|
expect(
|
|
sent.deliveries.some((delivery) =>
|
|
recordIds.includes(delivery.recipientUserId),
|
|
),
|
|
).toBe(false);
|
|
|
|
await vi.waitFor(async () => {
|
|
const attempts = await getPrismaClient().pushAttempt.findMany({
|
|
where: { eventType: "MEDICATION_REMINDER" },
|
|
select: { channel: true, recordUserId: true, recipientUserId: true },
|
|
});
|
|
expect(attempts).toHaveLength(expected.length);
|
|
expect(attempts).toEqual(expect.arrayContaining(expected));
|
|
});
|
|
const claims =
|
|
await getPrismaClient().notificationEgressAuthorization.findMany({
|
|
where: { eventType: "MEDICATION_REMINDER" },
|
|
select: {
|
|
channel: true,
|
|
recordUserId: true,
|
|
recipientUserId: true,
|
|
eventType: true,
|
|
},
|
|
});
|
|
const expectedClaims = expected.map((claim) => ({
|
|
...claim,
|
|
eventType: "MEDICATION_REMINDER",
|
|
}));
|
|
expect(claims).toHaveLength(expectedClaims.length);
|
|
expect(claims).toEqual(expect.arrayContaining(expectedClaims));
|
|
|
|
await dispatchNotification({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId: ordinaryRecord.id,
|
|
title: "ordinary reminder",
|
|
message: "ordinary message",
|
|
});
|
|
|
|
expect(sent.deliveries).toHaveLength(expected.length);
|
|
await expect(
|
|
getPrismaClient().pushAttempt.count({
|
|
where: { eventType: "MEDICATION_REMINDER" },
|
|
}),
|
|
).resolves.toBe(expected.length);
|
|
});
|
|
|
|
it("allows Guardian revocation to commit while a claimed channel egress is in flight", async () => {
|
|
const [record, recipient, reserveGuardian] = await Promise.all([
|
|
createUser({ label: "record", locale: "en", managed: true }),
|
|
createUser({ label: "recipient", locale: "en" }),
|
|
createUser({ label: "reserve-guardian", locale: "de" }),
|
|
]);
|
|
await Promise.all([
|
|
addGuardian(record.id, recipient.id),
|
|
addGuardian(record.id, reserveGuardian.id),
|
|
addChannel(recipient.id, "NTFY"),
|
|
]);
|
|
const recipientGrant =
|
|
await getPrismaClient().accountGrant.findFirstOrThrow({
|
|
where: { grantorId: record.id, granteeId: recipient.id },
|
|
select: { id: true },
|
|
});
|
|
let releaseEgress!: () => void;
|
|
let markEgressStarted!: () => void;
|
|
ntfyEgress.block = new Promise<void>((resolve) => {
|
|
releaseEgress = resolve;
|
|
});
|
|
const egressStarted = new Promise<void>((resolve) => {
|
|
markEgressStarted = resolve;
|
|
});
|
|
ntfyEgress.onStart = markEgressStarted;
|
|
|
|
const delivery = dispatchNotification({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId: record.id,
|
|
title: "record reminder",
|
|
message: "record message",
|
|
});
|
|
await egressStarted;
|
|
await expect(
|
|
getPrismaClient().notificationEgressAuthorization.count({
|
|
where: {
|
|
recordUserId: record.id,
|
|
recipientUserId: recipient.id,
|
|
channel: "NTFY",
|
|
},
|
|
}),
|
|
).resolves.toBe(1);
|
|
|
|
const revocation = revokeGrant({
|
|
grantId: recipientGrant.id,
|
|
grantorId: record.id,
|
|
});
|
|
await expectPromiseToSettle(revocation);
|
|
|
|
releaseEgress();
|
|
await delivery;
|
|
});
|
|
|
|
it("allows Guardian expiry to commit while a claimed channel egress is in flight", async () => {
|
|
const [record, recipient, reserveGuardian] = await Promise.all([
|
|
createUser({ label: "record", locale: "en", managed: true }),
|
|
createUser({ label: "recipient", locale: "en" }),
|
|
createUser({ label: "reserve-guardian", locale: "de" }),
|
|
]);
|
|
await Promise.all([
|
|
addGuardian(record.id, recipient.id),
|
|
addGuardian(record.id, reserveGuardian.id),
|
|
addChannel(recipient.id, "NTFY"),
|
|
]);
|
|
const recipientGrant =
|
|
await getPrismaClient().accountGrant.findFirstOrThrow({
|
|
where: { grantorId: record.id, granteeId: recipient.id },
|
|
select: { id: true },
|
|
});
|
|
let releaseEgress!: () => void;
|
|
let markEgressStarted!: () => void;
|
|
ntfyEgress.block = new Promise<void>((resolve) => {
|
|
releaseEgress = resolve;
|
|
});
|
|
const egressStarted = new Promise<void>((resolve) => {
|
|
markEgressStarted = resolve;
|
|
});
|
|
ntfyEgress.onStart = markEgressStarted;
|
|
|
|
const delivery = dispatchNotification({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId: record.id,
|
|
title: "record reminder",
|
|
message: "record message",
|
|
});
|
|
await egressStarted;
|
|
await expect(
|
|
getPrismaClient().notificationEgressAuthorization.count({
|
|
where: {
|
|
recordUserId: record.id,
|
|
recipientUserId: recipient.id,
|
|
channel: "NTFY",
|
|
},
|
|
}),
|
|
).resolves.toBe(1);
|
|
|
|
const expiry = getPrismaClient().$transaction((tx) =>
|
|
withManagedProfileLock(tx, record.id, () =>
|
|
tx.accountGrant.update({
|
|
where: { id: recipientGrant.id },
|
|
data: { expiresAt: new Date() },
|
|
}),
|
|
),
|
|
);
|
|
await expectPromiseToSettle(expiry);
|
|
|
|
releaseEgress();
|
|
await delivery;
|
|
});
|
|
|
|
it("records a timed-out provider after the authorization claim has committed", async () => {
|
|
const [record, recipient, reserveGuardian] = await Promise.all([
|
|
createUser({ label: "record", locale: "en", managed: true }),
|
|
createUser({ label: "recipient", locale: "en" }),
|
|
createUser({ label: "reserve-guardian", locale: "de" }),
|
|
]);
|
|
await Promise.all([
|
|
addGuardian(record.id, recipient.id),
|
|
addGuardian(record.id, reserveGuardian.id),
|
|
addChannel(recipient.id, "NTFY"),
|
|
]);
|
|
ntfyEgress.error = new Error("provider timeout");
|
|
|
|
await expect(
|
|
dispatchNotification({
|
|
eventType: "MEDICATION_REMINDER",
|
|
userId: record.id,
|
|
title: "record reminder",
|
|
message: "record message",
|
|
}),
|
|
).resolves.toEqual({
|
|
dispatched: false,
|
|
channelsAttempted: 1,
|
|
channelsSucceeded: 0,
|
|
});
|
|
|
|
await expect(
|
|
getPrismaClient().notificationEgressAuthorization.findFirstOrThrow({
|
|
where: {
|
|
recordUserId: record.id,
|
|
recipientUserId: recipient.id,
|
|
channel: "NTFY",
|
|
},
|
|
select: { eventType: true, outcome: true, completedAt: true },
|
|
}),
|
|
).resolves.toEqual({
|
|
eventType: "MEDICATION_REMINDER",
|
|
outcome: "sender_threw",
|
|
completedAt: expect.any(Date),
|
|
});
|
|
});
|
|
|
|
it("creates a new claim for each retry and stops after Guardian revocation", async () => {
|
|
const [record, recipient, reserveGuardian] = await Promise.all([
|
|
createUser({ label: "record", locale: "en", managed: true }),
|
|
createUser({ label: "recipient", locale: "en" }),
|
|
createUser({ label: "reserve-guardian", locale: "de" }),
|
|
]);
|
|
await Promise.all([
|
|
addGuardian(record.id, recipient.id),
|
|
addGuardian(record.id, reserveGuardian.id),
|
|
addChannel(recipient.id, "NTFY"),
|
|
]);
|
|
const grant = await getPrismaClient().accountGrant.findFirstOrThrow({
|
|
where: { grantorId: record.id, granteeId: recipient.id },
|
|
select: { id: true },
|
|
});
|
|
const payload = {
|
|
eventType: "MEDICATION_REMINDER" as const,
|
|
userId: record.id,
|
|
recordUserId: record.id,
|
|
recipientUserId: recipient.id,
|
|
title: "record reminder",
|
|
message: "record message",
|
|
};
|
|
|
|
ntfyEgress.error = new Error("provider timeout");
|
|
await dispatchNotification(payload);
|
|
await getPrismaClient().notificationChannel.update({
|
|
where: { userId_type: { userId: recipient.id, type: "NTFY" } },
|
|
data: { consecutiveFailures: 0, nextRetryAt: null },
|
|
});
|
|
ntfyEgress.error = null;
|
|
ntfyEgress.outcome = { ok: false, reason: "provider_unavailable" };
|
|
await dispatchNotification(payload);
|
|
|
|
const beforeRevocation =
|
|
await getPrismaClient().notificationEgressAuthorization.findMany({
|
|
where: {
|
|
recordUserId: record.id,
|
|
recipientUserId: recipient.id,
|
|
channel: "NTFY",
|
|
},
|
|
select: { id: true, outcome: true },
|
|
orderBy: { authorizedAt: "asc" },
|
|
});
|
|
expect(beforeRevocation).toEqual([
|
|
{ id: expect.any(String), outcome: "sender_threw" },
|
|
{ id: expect.any(String), outcome: "transient_failure" },
|
|
]);
|
|
expect(beforeRevocation[0]!.id).not.toBe(beforeRevocation[1]!.id);
|
|
|
|
await revokeGrant({ grantId: grant.id, grantorId: record.id });
|
|
ntfyEgress.outcome = null;
|
|
await expect(dispatchNotification(payload)).resolves.toEqual({
|
|
dispatched: false,
|
|
channelsAttempted: 0,
|
|
channelsSucceeded: 0,
|
|
});
|
|
expect(sent.ntfy).toEqual([]);
|
|
await expect(
|
|
getPrismaClient().notificationEgressAuthorization.count({
|
|
where: {
|
|
recordUserId: record.id,
|
|
recipientUserId: recipient.id,
|
|
channel: "NTFY",
|
|
},
|
|
}),
|
|
).resolves.toBe(2);
|
|
});
|
|
});
|
|
|
|
async function expectPromiseToSettle(promise: Promise<unknown>) {
|
|
const settled = await Promise.race([
|
|
promise.then(
|
|
() => true,
|
|
() => true,
|
|
),
|
|
new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 250)),
|
|
]);
|
|
expect(settled).toBe(true);
|
|
}
|