server/bot-automations.test.ts
Rama main · réplica de lectura del repositorio original.
import { describe, expect, it, vi } from "vitest";
import { tieAutomationsToBots } from "./bot-automations.ts";
function fixture(bots: string[], owners: { routines?: string[]; runs?: string[]; webhooks?: string[]; apps?: string[]; waits?: string[] }) {
let listener: ((change: { type: string; botId?: string }) => void) | undefined;
const store = {
bots: bots.map((id) => ({ id })),
bot: (id: string) => bots.includes(id) ? { id } : undefined,
onChange: (next: typeof listener) => { listener = next; },
};
const automations = {
routines: {
removeForBot: vi.fn(), listRoutines: () => (owners.routines ?? []).map((botId) => ({ botId })), listRuns: () => (owners.runs ?? []).map((botId) => ({ botId })),
},
webhooks: { removeForBot: vi.fn(), list: () => (owners.webhooks ?? []).map((botId) => ({ botId })) },
apps: { remove: vi.fn(async (_id: string) => true), list: () => (owners.apps ?? []).map((botId, index) => ({ id: `app-${index}`, botId })) },
waits: { removeForBot: vi.fn(), owners: () => owners.waits ?? [] },
};
tieAutomationsToBots(store, automations as never);
return { automations, emit: (change: { type: string; botId?: string }) => listener!(change) };
}
describe("a bot's automations live and die with it", () => {
it("removes, at start, the automations of each bot that no longer exists, once per bot", () => {
const { automations } = fixture(["scout"], { routines: ["scout", "gone"], runs: ["gone", "older"], webhooks: ["gone"] });
expect(automations.routines.removeForBot.mock.calls).toEqual([["gone"], ["older"]]);
expect(automations.webhooks.removeForBot.mock.calls).toEqual([["gone"], ["older"]]);
});
it("removes nothing at start when no bot loaded at all, so an unreadable bot list never wipes every automation", () => {
const { automations } = fixture([], { routines: ["scout"], webhooks: ["scout"] });
expect(automations.routines.removeForBot).not.toHaveBeenCalled();
expect(automations.webhooks.removeForBot).not.toHaveBeenCalled();
});
it("removes a bot's automations whenever the store deletes it, and one failed removal never blocks the other", () => {
const { automations, emit } = fixture(["scout"], {});
automations.routines.removeForBot.mockImplementation(() => { throw new Error("disk full"); });
const error = vi.spyOn(console, "error").mockImplementation(() => {});
emit({ type: "bot", botId: "scout" });
emit({ type: "bot.deleted", botId: "scout" });
expect(automations.routines.removeForBot).toHaveBeenCalledWith("scout");
expect(automations.webhooks.removeForBot).toHaveBeenCalledWith("scout");
expect(error).toHaveBeenCalledTimes(1);
error.mockRestore();
});
it("removes a deleted bot's app events too, at start and on delete, and a failed removal is only logged", async () => {
const { automations, emit } = fixture(["scout", "writer"], { apps: ["gone", "scout", "writer", "scout"] });
expect(automations.apps.remove.mock.calls).toEqual([["app-0"]]);
automations.apps.remove.mockClear();
automations.apps.remove.mockRejectedValueOnce(new Error("unreadable app-triggers.json"));
const error = vi.spyOn(console, "error").mockImplementation(() => {});
emit({ type: "bot.deleted", botId: "scout" });
expect(automations.apps.remove.mock.calls).toEqual([["app-1"], ["app-3"]]);
await vi.waitFor(() => expect(error).toHaveBeenCalledTimes(1));
error.mockRestore();
});
it("removes a deleted bot's waits, at start and when it is deleted", () => {
const { automations, emit } = fixture(["scout"], { waits: ["gone", "scout"] });
emit({ type: "bot.deleted", botId: "scout" });
expect(automations.waits.removeForBot.mock.calls).toEqual([["gone"], ["scout"]]);
});
});