import { TRADING_BOT_PROXY_PATH } from "@rakazo/core"; import { Hono } from "hono"; import { describe, expect, it, vi } from "vitest"; import { mountTradingBotStatus } from "./trading-bot-proxy.js"; function proxyApp(options: { baseUrl?: string; authorize?: (request: Request) => Promise; fetchImpl?: typeof fetch; }) { const app = new Hono(); mountTradingBotStatus(app, { baseUrl: options.baseUrl, authorize: options.authorize ?? (async () => true), fetchImpl: options.fetchImpl, }); return app; } describe("trading bot status proxy", () => { it("stays idle when TRADING_BOT_URL is unset and does not call upstream", async () => { const fetchImpl = vi.fn(); const response = await proxyApp({ fetchImpl }).request(TRADING_BOT_PROXY_PATH); expect(response.status).toBe(200); expect(await response.json()).toEqual({ configured: false }); expect(fetchImpl).not.toHaveBeenCalled(); }); it("requires a session before it proxies a configured origin", async () => { const fetchImpl = vi.fn(); const response = await proxyApp({ baseUrl: "http://bot.test:8000", authorize: async () => false, fetchImpl, }).request(TRADING_BOT_PROXY_PATH, { headers: { cookie: "session=secret" } }); expect(response.status).toBe(401); expect(fetchImpl).not.toHaveBeenCalled(); }); it("returns the mapped status and does not forward browser credentials", async () => { const fetchImpl = vi.fn( async () => new Response( JSON.stringify({ mode: "LIVE", adapter_count: 2, last_error: "adapter timeout" }), { status: 200, headers: { "content-type": "application/json" } }, ), ); const response = await proxyApp({ baseUrl: "http://bot.test:8000", fetchImpl, }).request(TRADING_BOT_PROXY_PATH, { headers: { cookie: "session=secret", authorization: "Bearer browser-token" }, }); expect(response.status).toBe(200); expect(await response.json()).toEqual({ configured: true, reachable: true, status: { mode: "live", kind: "live", adapterCount: 2, lastError: "adapter timeout", }, }); expect(fetchImpl).toHaveBeenCalledTimes(1); const [url, init] = fetchImpl.mock.calls[0] as [string, RequestInit]; expect(url).toBe("http://bot.test:8000/bot/api/status"); const headers = new Headers(init.headers); expect(headers.get("cookie")).toBeNull(); expect(headers.get("authorization")).toBeNull(); expect(init.redirect).toBe("error"); }); it("hides an unmapped body and an upstream failure instead of claiming live", async () => { const unmapped = vi.fn( async () => new Response(JSON.stringify({ status: "ok" }), { status: 200, headers: { "content-type": "application/json" }, }), ); const unknown = await proxyApp({ baseUrl: "http://bot.test:8000", fetchImpl: unmapped, }).request(TRADING_BOT_PROXY_PATH); expect(await unknown.json()).toEqual({ configured: true, reachable: true, status: null }); const down = vi.fn(async () => new Response("nope", { status: 500 })); const offline = await proxyApp({ baseUrl: "http://bot.test:8000", fetchImpl: down, }).request(TRADING_BOT_PROXY_PATH); expect(offline.status).toBe(502); expect(await offline.json()).toEqual({ configured: true, reachable: false }); }); });