import { beforeEach, describe, expect, test, vi } from "vitest"; vi.mock("@/core/api/fetcher", () => ({ fetch: vi.fn(), })); vi.mock("@/core/config", () => ({ getBackendBaseURL: () => "/backend", })); import { fetch as fetcher } from "@/core/api/fetcher"; import { connectChannelProvider, disconnectChannelConnection, listChannelConnections, listChannelProviders, } from "@/core/channels/api"; const mockedFetch = vi.mocked(fetcher); function jsonResponse(status: number, body: unknown): Response { return new Response(JSON.stringify(body), { status, statusText: status >= 400 ? "Bad Request" : "OK", headers: { "Content-Type": "application/json" }, }); } beforeEach(() => { mockedFetch.mockReset(); }); describe("channels api", () => { test("loads provider catalog", async () => { mockedFetch.mockResolvedValueOnce( jsonResponse(200, { enabled: true, providers: [ { provider: "telegram", display_name: "Telegram", enabled: true, configured: true, auth_mode: "deep_link", connection_status: "not_connected", }, ], }), ); await expect(listChannelProviders()).resolves.toMatchObject({ enabled: true, providers: [{ provider: "telegram", display_name: "Telegram" }], }); expect(mockedFetch).toHaveBeenCalledWith("/backend/api/channels/providers"); }); test("loads current user's connections", async () => { mockedFetch.mockResolvedValueOnce( jsonResponse(200, { connections: [ { id: "connection-1", provider: "telegram", status: "connected", external_account_name: "Alice", scopes: [], metadata: {}, }, ], }), ); await expect(listChannelConnections()).resolves.toMatchObject([ { id: "connection-1", provider: "telegram", status: "connected" }, ]); expect(mockedFetch).toHaveBeenCalledWith( "/backend/api/channels/connections", ); }); test("starts a provider connection flow", async () => { mockedFetch.mockResolvedValueOnce( jsonResponse(200, { provider: "telegram", mode: "deep_link", url: "https://t.me/deerflow_bot?start=state", code: "state", instruction: "Send /start state to the DeerFlow Telegram bot.", expires_in: 600, }), ); await expect(connectChannelProvider("telegram")).resolves.toMatchObject({ provider: "telegram", url: "https://t.me/deerflow_bot?start=state", instruction: "Send /start state to the DeerFlow Telegram bot.", }); expect(mockedFetch).toHaveBeenCalledWith( "/backend/api/channels/telegram/connect", { method: "POST" }, ); }); test("starts a binding-code connection flow", async () => { mockedFetch.mockResolvedValueOnce( jsonResponse(200, { provider: "slack", mode: "binding_code", url: null, code: "abc123", instruction: "Send /connect abc123 to the DeerFlow Slack bot.", expires_in: 600, }), ); await expect(connectChannelProvider("slack")).resolves.toMatchObject({ provider: "slack", url: null, code: "abc123", instruction: "Send /connect abc123 to the DeerFlow Slack bot.", }); }); test("disconnects a channel connection", async () => { mockedFetch.mockResolvedValueOnce(new Response(null, { status: 204 })); await expect( disconnectChannelConnection("connection-1"), ).resolves.toBeUndefined(); expect(mockedFetch).toHaveBeenCalledWith( "/backend/api/channels/connections/connection-1", { method: "DELETE" }, ); }); test("uses backend detail for failed requests", async () => { mockedFetch.mockResolvedValueOnce( jsonResponse(400, { detail: "Channel provider is not configured" }), ); await expect(connectChannelProvider("slack")).rejects.toThrow( "Channel provider is not configured", ); }); });