agent-orchestrator/frontend/src/shared/daemon-discovery.test.ts

116 lines
4.5 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { createListenPortScanner, defaultRunFilePath, parseDaemonListenPort, parseRunFile } from "./daemon-discovery";
// Real shape emitted by slog's TextHandler in backend/internal/httpd/server.go.
const LISTEN_LINE = 'time=2026-06-10T09:15:04.221-07:00 level=INFO msg="daemon listening" addr=127.0.0.1:3001 pid=4242';
describe("parseDaemonListenPort", () => {
it("extracts the bound port from the slog listen line", () => {
expect(parseDaemonListenPort(LISTEN_LINE)).toBe(3001);
});
it("reports a fallback port that differs from the configured one", () => {
expect(parseDaemonListenPort(LISTEN_LINE.replace("3001", "3037"))).toBe(3037);
});
it("handles IPv6 listen addresses", () => {
expect(parseDaemonListenPort('level=INFO msg="daemon listening" addr=[::1]:4317 pid=1')).toBe(4317);
});
it("ignores other log lines, even ones carrying an addr attribute", () => {
expect(parseDaemonListenPort('level=INFO msg="proxy dial" addr=127.0.0.1:9999')).toBeNull();
expect(parseDaemonListenPort("plain stdout chatter")).toBeNull();
});
it("returns null when the listen line has no usable addr", () => {
expect(parseDaemonListenPort('level=INFO msg="daemon listening" pid=4242')).toBeNull();
expect(parseDaemonListenPort('level=INFO msg="daemon listening" addr=garbage')).toBeNull();
expect(parseDaemonListenPort('level=INFO msg="daemon listening" addr=127.0.0.1:99999')).toBeNull();
});
});
describe("createListenPortScanner", () => {
it("reassembles a line split across chunks", () => {
const onPort = vi.fn();
const scan = createListenPortScanner(onPort);
scan('level=INFO msg="daemon lis');
expect(onPort).not.toHaveBeenCalled();
scan('tening" addr=127.0.0.1:3001 pid=4242\n');
expect(onPort).toHaveBeenCalledWith(3001);
});
it("reports only the first announcement", () => {
const onPort = vi.fn();
const scan = createListenPortScanner(onPort);
scan(`noise line\n${LISTEN_LINE}\n${LISTEN_LINE.replace("3001", "4000")}\n`);
scan(`${LISTEN_LINE.replace("3001", "5000")}\n`);
expect(onPort).toHaveBeenCalledTimes(1);
expect(onPort).toHaveBeenCalledWith(3001);
});
it("stays quiet on streams without the announcement", () => {
const onPort = vi.fn();
const scan = createListenPortScanner(onPort);
scan("starting up\nmigrations applied\n");
expect(onPort).not.toHaveBeenCalled();
});
});
describe("parseRunFile", () => {
const valid = JSON.stringify({ pid: 4242, port: 3037, startedAt: "2026-06-10T16:15:04Z" });
it("parses a valid handshake", () => {
expect(parseRunFile(valid)).toEqual({
pid: 4242,
port: 3037,
startedAtMs: Date.parse("2026-06-10T16:15:04Z"),
});
});
it("returns null for malformed JSON", () => {
expect(parseRunFile("{not json")).toBeNull();
expect(parseRunFile("")).toBeNull();
expect(parseRunFile("null")).toBeNull();
});
it("returns null for a missing or invalid port", () => {
expect(parseRunFile(JSON.stringify({ pid: 1, startedAt: "2026-06-10T16:15:04Z" }))).toBeNull();
expect(parseRunFile(JSON.stringify({ pid: 1, port: 0 }))).toBeNull();
expect(parseRunFile(JSON.stringify({ pid: 1, port: 70000 }))).toBeNull();
expect(parseRunFile(JSON.stringify({ pid: 1, port: "3001" }))).toBeNull();
});
it("treats a missing or unparseable startedAt as epoch zero", () => {
expect(parseRunFile(JSON.stringify({ pid: 1, port: 3001 }))?.startedAtMs).toBe(0);
expect(parseRunFile(JSON.stringify({ pid: 1, port: 3001, startedAt: "not-a-date" }))?.startedAtMs).toBe(0);
});
});
describe("defaultRunFilePath", () => {
it("matches Go's canonical AO home default on macOS", () => {
expect(defaultRunFilePath("darwin", {}, "/Users/me")).toBe("/Users/me/.ao/running.json");
});
it("ignores XDG_CONFIG_HOME on linux", () => {
expect(defaultRunFilePath("linux", { XDG_CONFIG_HOME: "/xdg" }, "/home/me")).toBe("/home/me/.ao/running.json");
expect(defaultRunFilePath("linux", {}, "/home/me")).toBe("/home/me/.ao/running.json");
});
it("ignores APPDATA on windows", () => {
expect(defaultRunFilePath("win32", { APPDATA: "C:\\Users\\me\\AppData\\Roaming" }, "C:\\Users\\me")).toBe(
"C:\\Users\\me/.ao/running.json",
);
expect(defaultRunFilePath("win32", {}, "C:\\Users\\me")).toBe("C:\\Users\\me/.ao/running.json");
});
it("returns null when no home directory can be resolved", () => {
expect(defaultRunFilePath("linux", {}, "")).toBeNull();
expect(defaultRunFilePath("darwin", {}, "")).toBeNull();
expect(defaultRunFilePath("win32", {}, "")).toBeNull();
});
});