murashit_codex-panel/tests/app-server/short-lived-client.test.ts

87 lines
3 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import type { AppServerClientHandlers, AppServerClientOptions } from "../../src/app-server/connection/client";
import { withShortLivedAppServerClient } from "../../src/app-server/connection/short-lived-client";
const clientMocks = vi.hoisted(() => ({
connect: vi.fn<() => Promise<unknown>>(),
disconnect: vi.fn<() => void>(),
options: [] as AppServerClientOptions[],
}));
vi.mock("../../src/app-server/connection/client", () => ({
AppServerClient: class {
constructor(options: AppServerClientOptions) {
clientMocks.options.push(options);
}
connect = clientMocks.connect;
disconnect = clientMocks.disconnect;
},
}));
describe("withShortLivedAppServerClient", () => {
beforeEach(() => {
clientMocks.connect.mockReset().mockResolvedValue({});
clientMocks.disconnect.mockReset();
clientMocks.options.length = 0;
});
it("connects, runs the operation, and disconnects in lifecycle order", async () => {
const lifecycle: string[] = [];
clientMocks.connect.mockImplementation(async () => {
lifecycle.push("connect");
});
clientMocks.disconnect.mockImplementation(() => {
lifecycle.push("disconnect");
});
await expect(
withShortLivedAppServerClient("/opt/codex", "/vault", async (client) => {
lifecycle.push("operation");
return client;
}),
).resolves.toBeDefined();
expect(lifecycle).toEqual(["connect", "operation", "disconnect"]);
expect(clientMocks.options[0]).toMatchObject({ codexPath: "/opt/codex", cwd: "/vault" });
});
it.each([
["connect", () => clientMocks.connect.mockRejectedValue(new Error("connect failed"))],
["operation", () => undefined],
])("disconnects when %s fails", async (failure, arrange) => {
arrange();
const operation = vi.fn(async () => {
throw new Error("operation failed");
});
await expect(withShortLivedAppServerClient("/opt/codex", "/vault", failure === "connect" ? vi.fn() : operation)).rejects.toThrow(
`${failure} failed`,
);
expect(clientMocks.disconnect).toHaveBeenCalledOnce();
expect(operation).toHaveBeenCalledTimes(failure === "connect" ? 0 : 1);
});
it.each([
[undefined, "This Codex Panel view does not handle server requests."],
[
{ serverRequests: { kind: "reject" as const, message: "Background operation cannot answer." } },
"Background operation cannot answer.",
],
])("rejects server requests with the configured policy message", async (options, expectedMessage) => {
await withShortLivedAppServerClient("/opt/codex", "/vault", async () => undefined, options);
const reject = vi.fn();
clientHandlers().onServerRequest({} as never, { respond: vi.fn(), reject });
expect(reject).toHaveBeenCalledWith(-32601, expectedMessage);
});
});
function clientHandlers(): AppServerClientHandlers {
const handlers = clientMocks.options[0]?.handlers;
if (!handlers) throw new Error("Expected a short-lived client to be constructed.");
return handlers;
}