import { describe, expect, it, vi } from "vitest"; vi.mock("@rakazo/db", () => ({ appendEventInTransaction: vi.fn(), })); import { botProfileLabelsChanged, commitBotUpdate } from "./bot-update.js"; describe("botProfileLabelsChanged", () => { it("is true only when name, title, or description is present", () => { expect(botProfileLabelsChanged({})).toBe(false); expect(botProfileLabelsChanged({ name: "SEO" })).toBe(true); expect(botProfileLabelsChanged({ title: "Strategist" })).toBe(true); expect(botProfileLabelsChanged({ description: "Helps with SEO" })).toBe(true); }); }); describe("commitBotUpdate", () => { it("writes the bot row and bot.updated in one transaction when labels change", async () => { const updated = { id: "bot-1", name: "SEO Strategist", title: "SEO Strategist", description: "Helps with keyword research", }; const botUpdate = vi.fn().mockResolvedValue(updated); const tx = { bot: { update: botUpdate } }; const transaction = vi.fn(async (run: (client: typeof tx) => Promise) => run(tx)); const notify = vi.fn().mockResolvedValue(undefined); const appendEvent = vi.fn().mockResolvedValue({ seq: 9 }); const prisma = { $transaction: transaction, bot: { update: vi.fn() }, }; await expect( commitBotUpdate( { prisma: prisma as never, notify, spaceId: "space-1", threadId: "thread-1", botId: "bot-1", data: { name: "SEO Strategist", title: "SEO Strategist" }, emitBotUpdated: true, }, appendEvent, ), ).resolves.toEqual(updated); expect(transaction).toHaveBeenCalledOnce(); expect(botUpdate).toHaveBeenCalledOnce(); expect(appendEvent).toHaveBeenCalledWith(tx, { spaceId: "space-1", threadId: "thread-1", botId: "bot-1", type: "bot.updated", payload: { botId: "bot-1", name: "SEO Strategist", title: "SEO Strategist", description: "Helps with keyword research", }, }); expect(notify).toHaveBeenCalledWith("thread-1", 9); expect(prisma.bot.update).not.toHaveBeenCalled(); }); it("fails the whole update when the durable event write fails", async () => { const appendEvent = vi.fn().mockRejectedValue(new Error("event store unavailable")); const transaction = vi.fn(async (run: (client: unknown) => Promise) => run({ bot: { update: vi.fn().mockResolvedValue({ id: "bot-1", name: "SEO", title: "SEO", description: "", }), }, }), ); const notify = vi.fn(); const prisma = { $transaction: transaction, bot: { update: vi.fn() }, }; await expect( commitBotUpdate( { prisma: prisma as never, notify, spaceId: "space-1", threadId: "thread-1", botId: "bot-1", data: { name: "SEO" }, emitBotUpdated: true, }, appendEvent, ), ).rejects.toThrow("event store unavailable"); expect(notify).not.toHaveBeenCalled(); expect(prisma.bot.update).not.toHaveBeenCalled(); }); it("updates without an event when profile labels are unchanged", async () => { const update = vi.fn().mockResolvedValue({ id: "bot-1", name: "Chief", title: "Chief", description: "", }); const prisma = { $transaction: vi.fn(), bot: { update }, }; const notify = vi.fn(); const appendEvent = vi.fn(); await expect( commitBotUpdate( { prisma: prisma as never, notify, spaceId: "space-1", threadId: "thread-1", botId: "bot-1", data: { pinned: true }, emitBotUpdated: false, }, appendEvent, ), ).resolves.toMatchObject({ id: "bot-1" }); expect(prisma.$transaction).not.toHaveBeenCalled(); expect(notify).not.toHaveBeenCalled(); expect(appendEvent).not.toHaveBeenCalled(); expect(update).toHaveBeenCalledOnce(); }); });