Files
gunshiz 72e9f525cc
CI / Verify (push) Successful in 1m42s
CI / Build immutable images and deploy (push) Successful in 3m3s
feat(notifications) : notify guide publications and admin hearts; simplify Stygian navigation
2026-10-08 04:04:02 +07:00

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);
});
});