The Live Trading control stored its mode in localStorage, so the shell could claim live money while the bot was in dry-run. Show the Tradingbot status when TRADING_BOT_URL is set, and an unknown state when it is not. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
97 lines
3.4 KiB
TypeScript
97 lines
3.4 KiB
TypeScript
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<boolean>;
|
|
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 });
|
|
});
|
|
});
|