test(api): cover notification broadcast reference errors
This commit is contained in:
@@ -82,6 +82,24 @@ describe("notification procedure support", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("rewrites broadcast source foreign-key errors to a not found TRPC error", () => {
|
||||
const error = {
|
||||
code: "P2003",
|
||||
meta: { field_name: "Notification_sourceId_fkey" },
|
||||
};
|
||||
|
||||
try {
|
||||
rethrowNotificationReferenceError(error);
|
||||
throw new Error("expected notification reference error");
|
||||
} catch (caught) {
|
||||
expect(caught).toBeInstanceOf(TRPCError);
|
||||
expect(caught).toMatchObject<Partial<TRPCError>>({
|
||||
code: "NOT_FOUND",
|
||||
message: "Notification broadcast not found",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("rethrows unrelated errors unchanged", () => {
|
||||
const error = new Error("boom");
|
||||
|
||||
|
||||
@@ -452,6 +452,66 @@ describe("notification.createBroadcast", () => {
|
||||
expect(outerCreateNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("maps broadcast source reference loss during recipient fan-out to not found errors", async () => {
|
||||
resolveRecipientsMock.mockResolvedValue(["user_a", "user_b"]);
|
||||
|
||||
const txCreateBroadcast = vi.fn().mockResolvedValue({
|
||||
id: "broadcast_tx_source_missing",
|
||||
title: "Ops update",
|
||||
createdAt: new Date("2026-03-30T10:00:00Z"),
|
||||
});
|
||||
const txUpdateBroadcast = vi.fn();
|
||||
const txCreateNotification = vi.fn().mockRejectedValue(
|
||||
Object.assign(new Error("Foreign key constraint failed"), {
|
||||
code: "P2003",
|
||||
meta: { field_name: "Notification_sourceId_fkey" },
|
||||
}),
|
||||
);
|
||||
const tx = {
|
||||
notificationBroadcast: {
|
||||
create: txCreateBroadcast,
|
||||
update: txUpdateBroadcast,
|
||||
},
|
||||
notification: {
|
||||
create: txCreateNotification,
|
||||
},
|
||||
};
|
||||
const outerCreateBroadcast = vi.fn();
|
||||
const outerUpdateBroadcast = vi.fn();
|
||||
const outerCreateNotification = vi.fn();
|
||||
const transaction = vi.fn(async (callback: (db: typeof tx) => Promise<unknown>) => callback(tx));
|
||||
const db = {
|
||||
$transaction: transaction,
|
||||
notificationBroadcast: {
|
||||
create: outerCreateBroadcast,
|
||||
update: outerUpdateBroadcast,
|
||||
},
|
||||
notification: {
|
||||
create: outerCreateNotification,
|
||||
},
|
||||
};
|
||||
|
||||
const caller = createManagerCaller(db);
|
||||
|
||||
await expect(caller.createBroadcast({
|
||||
title: "Ops update",
|
||||
targetType: "all",
|
||||
})).rejects.toMatchObject({
|
||||
code: "NOT_FOUND",
|
||||
message: "Notification broadcast not found",
|
||||
});
|
||||
|
||||
expect(transaction).toHaveBeenCalledTimes(1);
|
||||
expect(txCreateBroadcast).toHaveBeenCalledTimes(1);
|
||||
expect(txCreateNotification).toHaveBeenCalledTimes(1);
|
||||
expect(txUpdateBroadcast).not.toHaveBeenCalled();
|
||||
expect(outerCreateBroadcast).not.toHaveBeenCalled();
|
||||
expect(outerUpdateBroadcast).not.toHaveBeenCalled();
|
||||
expect(outerCreateNotification).not.toHaveBeenCalled();
|
||||
expect(emitNotificationCreated).not.toHaveBeenCalled();
|
||||
expect(emitTaskAssigned).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("emits recipient SSE only after an immediate broadcast commits", async () => {
|
||||
resolveRecipientsMock.mockResolvedValue(["user_a", "user_b"]);
|
||||
|
||||
@@ -688,6 +748,43 @@ describe("notification.createBroadcast", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("maps scheduled broadcast sender persistence failures to not found errors", async () => {
|
||||
resolveRecipientsMock.mockResolvedValue(["user_a", "user_b"]);
|
||||
|
||||
const create = vi.fn().mockRejectedValue(
|
||||
Object.assign(new Error("Foreign key constraint failed"), {
|
||||
code: "P2003",
|
||||
meta: { field_name: "NotificationBroadcast_senderId_fkey" },
|
||||
}),
|
||||
);
|
||||
const db = {
|
||||
notificationBroadcast: {
|
||||
create,
|
||||
update: vi.fn(),
|
||||
},
|
||||
notification: {
|
||||
create: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
const caller = createManagerCaller(db);
|
||||
|
||||
await expect(caller.createBroadcast({
|
||||
title: "Scheduled ops update",
|
||||
targetType: "all",
|
||||
scheduledAt: FUTURE_SCHEDULED_AT,
|
||||
})).rejects.toMatchObject({
|
||||
code: "NOT_FOUND",
|
||||
message: "Sender user not found",
|
||||
});
|
||||
|
||||
expect(create).toHaveBeenCalledTimes(1);
|
||||
expect(db.notificationBroadcast.update).not.toHaveBeenCalled();
|
||||
expect(db.notification.create).not.toHaveBeenCalled();
|
||||
expect(emitNotificationCreated).not.toHaveBeenCalled();
|
||||
expect(emitTaskAssigned).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rolls back an immediate broadcast when the final broadcast update fails", async () => {
|
||||
resolveRecipientsMock.mockResolvedValue(["user_a", "user_b"]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user