53 lines
2.7 KiB
TypeScript
53 lines
2.7 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { outboxEvents } from "@/db/schema";
|
|
|
|
const mocks = vi.hoisted(() => ({ previous: { isPublic: false, publishedAt: null as Date | null },
|
|
updated: [] as unknown[], current: [] as unknown[], inserts: [] as { table: unknown; values: unknown }[] }));
|
|
vi.mock("server-only", () => ({}));
|
|
vi.mock("@/lib/audit-log", () => ({ writeAuditLog: vi.fn() }));
|
|
vi.mock("@/db", () => ({ getDb: () => ({ transaction: async (callback: (tx: unknown) => unknown) => {
|
|
const tx = {
|
|
select: () => {
|
|
const query = { from: () => query, where: () => query,
|
|
for: async () => [mocks.previous], limit: async () => mocks.current };
|
|
return query;
|
|
},
|
|
update: () => { const query = { set: () => query, where: () => query, returning: async () => mocks.updated }; return query; },
|
|
insert: (table: unknown) => ({ values: async (values: unknown) => { mocks.inserts.push({ table, values }); } }),
|
|
};
|
|
return callback(tx);
|
|
} }) }));
|
|
|
|
import { GuideVersionConflictError, setGuideVisibility } from "./mutations";
|
|
|
|
const guide = { id: "guide-id", version: 2, name: "Amber", characterKey: "amber", publishedAt: new Date("2026-10-08T10:00:00Z") };
|
|
const context = { actor: { id: "admin", label: "Admin" } };
|
|
beforeEach(() => {
|
|
mocks.previous = { isPublic: false, publishedAt: null };
|
|
mocks.updated = [guide]; mocks.current = []; mocks.inserts.length = 0;
|
|
});
|
|
const publicationEvents = () => mocks.inserts.filter(row => row.table === outboxEvents && !Array.isArray(row.values));
|
|
|
|
describe("guide publication trigger", () => {
|
|
it("queues delivery in the publishing transaction", async () => {
|
|
await setGuideVisibility(guide.id, { isPublic: true, expectedVersion: 1 }, context);
|
|
expect(publicationEvents().map(row => row.values)).toEqual([{
|
|
topic: "notifications:guide", aggregateId: guide.id, eventType: "guide.published",
|
|
payload: { publishedAt: guide.publishedAt.toISOString() },
|
|
}]);
|
|
});
|
|
|
|
it.each(["unpublish", "republish", "already public"])("does not notify on %s", async (scenario) => {
|
|
if (scenario === "republish") mocks.previous.publishedAt = guide.publishedAt;
|
|
if (scenario === "already public") mocks.previous.isPublic = true;
|
|
await setGuideVisibility(guide.id, { isPublic: scenario !== "unpublish", expectedVersion: 1 }, context);
|
|
expect(publicationEvents()).toHaveLength(0);
|
|
});
|
|
|
|
it("preserves version conflicts without queuing notifications", async () => {
|
|
mocks.updated = []; mocks.current = [{ version: 5 }];
|
|
await expect(setGuideVisibility(guide.id, { isPublic: true, expectedVersion: 1 }, context)).rejects.toBeInstanceOf(GuideVersionConflictError);
|
|
expect(mocks.inserts).toHaveLength(0);
|
|
});
|
|
});
|