import { readFileSync, rmSync } from "node:fs"; import { describe, expect, test } from "bun:test"; import { parseAgentRunClientConfigYaml, resolveAgentRunAuth, runAgentRunCommand, stripAgentRunResourceWrapperArgs } from "./agentrun"; const agentRunClientYaml = [ "manager:", " baseUrl: https://agentrun.127-0-0-1.nip.io/", " timeoutMs: 15000", "publicExposure:", " enabled: true", " proxyName: agentrun-v01-frpc", " remotePort: 22880", " publicBaseUrl: https://agentrun.127-0-0-1.nip.io/", " masterBaseUrl: http://127.0.0.1:22880", " masterFrps:", " configPath: /opt/hwlab-frp/frps.dev.toml", " containerName: hwlab-frps-dev", " masterCaddy:", " enabled: true", " domain: agentrun.127-0-0-1.nip.io", " configPath: /etc/caddy/Caddyfile", " serviceName: caddy", " upstreamBaseUrl: http://127.0.0.1:22880", " responseHeaderTimeoutSeconds: 60", "auth:", " env: HWLAB_API_KEY", " file: /tmp/hwlab-api-key.env", " header: Authorization", " scheme: Bearer", "client:", " role: render-only", " transport: direct-http", ].join("\n"); const agentRunMinimalClientYaml = [ "manager:", " baseUrl: http://agentrun.example.local:8080", " timeoutMs: 15000", "auth:", " env: HWLAB_API_KEY", " file: /tmp/hwlab-api-key.env", " header: Authorization", " scheme: Bearer", "client:", " role: render-only", " transport: direct-http", ].join("\n"); describe("AgentRun resource bridge argv", () => { test("does not forward UniDesk output flags to create task prompt argv", () => { expect(stripAgentRunResourceWrapperArgs([ "--aipod", "Artificer", "--prompt-stdin", "-o", "json", "--idempotency-key", "case-262", "--dry-run", ])).toEqual([ "--aipod", "Artificer", "--prompt-stdin", "--idempotency-key", "case-262", "--dry-run", ]); }); test("strips wrapper display flags while preserving official apply arguments", () => { expect(stripAgentRunResourceWrapperArgs([ "-f", "-", "--output=yaml", "--raw", "--full", "--full-text", "--dry-run", ])).toEqual([ "-f", "-", "--dry-run", ]); }); }); describe("AgentRun render-only REST client config", () => { test("parses explicit YAML config for direct REST render-only client", () => { const config = parseAgentRunClientConfigYaml(agentRunClientYaml, "config/agentrun.yaml"); expect(config.manager.baseUrl).toBe("https://agentrun.127-0-0-1.nip.io/"); expect(config.publicExposure?.publicBaseUrl).toBe("https://agentrun.127-0-0-1.nip.io/"); expect(config.publicExposure?.masterCaddy.domain).toBe("agentrun.127-0-0-1.nip.io"); expect(config.auth.env).toBe("HWLAB_API_KEY"); expect(config.auth.header).toBe("Authorization"); expect(config.auth.scheme).toBe("Bearer"); expect(config.client.role).toBe("render-only"); expect(config.client.transport).toBe("direct-http"); }); test("uses env auth before configured file auth without exposing the key", () => { const config = parseAgentRunClientConfigYaml(agentRunClientYaml, "config/agentrun.yaml"); const auth = resolveAgentRunAuth(config, { HWLAB_API_KEY: "secret-value" }); expect(auth.source).toBe("env"); expect(auth.value).toBe("secret-value"); }); test("requires explicit render-only direct-http client contract", () => { expect(() => parseAgentRunClientConfigYaml(agentRunClientYaml.replace(" transport: direct-http", " transport: ssh-bridge"), "config/agentrun.yaml")).toThrow("client.transport must be direct-http"); expect(() => parseAgentRunClientConfigYaml(agentRunClientYaml.replace(" role: render-only", " role: proxy"), "config/agentrun.yaml")).toThrow("client.role must be render-only"); }); }); describe("AgentRun default transport contract", () => { test("server-facing compatibility groups use REST dispatcher, not official SSH CLI wrapper", () => { const source = readFileSync(new URL("./agentrun.ts", import.meta.url), "utf8"); const commandRouter = source.slice(source.indexOf("export async function runAgentRunCommand"), source.indexOf("function isAgentRunRestCompatGroup")); expect(commandRouter).toContain("runAgentRunRestCompatCommand"); expect(commandRouter).not.toContain("runOfficialAgentRunCli"); expect(commandRouter).not.toContain("runPreparedOfficialAgentRunCli"); }); test("resource verbs can use direct REST without UniDesk SSH config", async () => { const server = Bun.serve({ port: 0, fetch(request) { const url = new URL(request.url); expect(request.headers.get("authorization")).toBe("Bearer secret-value"); expect(url.pathname).toBe("/api/v1/queue/tasks"); expect(url.searchParams.get("queue")).toBe("commander"); expect(url.searchParams.get("state")).toBe("running"); return Response.json({ ok: true, data: { items: [ { id: "qt_1", state: "queued", queue: "commander" }, ], }, }); }, }); const previousConfig = process.env.AGENTRUN_CLIENT_CONFIG; const previousKey = process.env.HWLAB_API_KEY; process.env.HWLAB_API_KEY = "secret-value"; const tempConfigPath = `/tmp/unidesk-agentrun-test-${process.pid}-${Date.now()}.yaml`; try { process.env.AGENTRUN_CLIENT_CONFIG = tempConfigPath; await Bun.write(process.env.AGENTRUN_CLIENT_CONFIG, agentRunMinimalClientYaml.replace("http://agentrun.example.local:8080", server.url.href.replace(/\/$/u, ""))); const result = await runAgentRunCommand(null, ["get", "tasks", "--queue", "commander", "-o", "json"]); expect(result.ok).toBe(true); expect("renderedText" in result).toBe(true); if ("renderedText" in result) { expect(result.renderedText).toContain("\"kind\": \"TaskList\""); expect(result.renderedText).toContain("\"name\": \"qt_1\""); } } finally { server.stop(true); if (previousConfig === undefined) delete process.env.AGENTRUN_CLIENT_CONFIG; else process.env.AGENTRUN_CLIENT_CONFIG = previousConfig; if (previousKey === undefined) delete process.env.HWLAB_API_KEY; else process.env.HWLAB_API_KEY = previousKey; rmSync(tempConfigPath, { force: true }); } }); });