import { afterEach, describe, expect, it, vi } from "vitest"; import { createFabricNotificationService, type FabricFetch } from "../plugin/notifications/fabric.js"; import { validateYonexusServerConfig } from "../plugin/core/config.js"; const request = { identifier: "client-a", pairingCode: "PAIR-1234-CODE", expiresAt: 1_710_000_300, ttlSeconds: 300, createdAt: 1_710_000_000 }; const fabricConfig = { centerApiBase: "http://center.local/api", apiKey: "fak_test", guildNodeId: "guild-node-1", channelId: "chan-1" }; afterEach(() => { vi.restoreAllMocks(); }); describe("Fabric notification service", () => { it("logs in, resolves the guild token, and posts the pairing message", async () => { const fetcher = vi .fn() .mockResolvedValueOnce({ ok: true, status: 200, json: async () => ({ user: { id: "u-1" }, guilds: [{ nodeId: "guild-node-1", endpoint: "http://guild.local" }], guildAccessTokens: [{ guildNodeId: "guild-node-1", token: "gtok" }] }) }) .mockResolvedValueOnce({ ok: true, status: 200, json: async () => ({}) }); const svc = createFabricNotificationService(fabricConfig, { fetcher }); const ok = await svc.sendPairingNotification(request); expect(ok).toBe(true); expect(fetcher).toHaveBeenCalledTimes(2); expect(fetcher.mock.calls[0][0]).toBe("http://center.local/api/auth/agent/login"); expect(fetcher.mock.calls[1][0]).toBe( "http://guild.local/api/channels/chan-1/messages" ); const msgInit = fetcher.mock.calls[1][1]!; expect(msgInit.headers?.Authorization).toBe("Bearer gtok"); expect(msgInit.body).toContain("PAIR-1234-CODE"); }); it("returns false when the notifier is not a member of the guild", async () => { const fetcher = vi.fn().mockResolvedValueOnce({ ok: true, status: 200, json: async () => ({ user: { id: "u-1" }, guilds: [], guildAccessTokens: [] }) }); const svc = createFabricNotificationService(fabricConfig, { fetcher }); expect(await svc.sendPairingNotification(request)).toBe(false); }); it("returns false when config is incomplete", async () => { const fetcher = vi.fn(); const svc = createFabricNotificationService( { ...fabricConfig, apiKey: "" }, { fetcher } ); expect(await svc.sendPairingNotification(request)).toBe(false); expect(fetcher).not.toHaveBeenCalled(); }); }); describe("notifyProvider config selection", () => { it("defaults to discord and still requires discord fields", () => { expect(() => validateYonexusServerConfig({ listenPort: 8080 }) ).toThrowError(/notifyBotToken is required/); }); it("does not require discord fields when provider is fabric", () => { const cfg = validateYonexusServerConfig({ followerIdentifiers: [], listenPort: 8080, notifyProvider: "fabric", fabric: fabricConfig }); expect(cfg.notifyProvider).toBe("fabric"); expect(cfg.fabric).toEqual(fabricConfig); expect(cfg.notifyBotToken).toBeUndefined(); }); it("requires fabric fields when provider is fabric", () => { expect(() => validateYonexusServerConfig({ listenPort: 8080, notifyProvider: "fabric" }) ).toThrowError(/fabric\.centerApiBase is required/); }); it("rejects an unknown provider", () => { expect(() => validateYonexusServerConfig({ listenPort: 8080, notifyProvider: "sms" }) ).toThrowError(/notifyProvider must be/); }); });