test(codex-lsp): cover lazy metadata fallback
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { PassThrough } from "node:stream";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveLazyLspIdleTimeoutMs } from "../src/lazy-lsp-mcp.js";
|
||||
import { resolveLazyLspIdleTimeoutMs, runLazyLspMcpServer } from "../src/lazy-lsp-mcp.js";
|
||||
import { DEFAULT_LAZY_MCP_IDLE_TIMEOUT_MS } from "../src/lazy-mcp-proxy.js";
|
||||
|
||||
describe("lazy LSP MCP config", () => {
|
||||
@@ -18,4 +19,84 @@ describe("lazy LSP MCP config", () => {
|
||||
expect(malformed.value).toBe(fallback);
|
||||
expect(malformed.warning).toContain("Ignoring malformed lazy MCP idle timeout");
|
||||
});
|
||||
|
||||
it("#given missing lazy backend config #when metadata requests are served #then the server stays usable without starting a backend", async () => {
|
||||
// given
|
||||
const input = new PassThrough();
|
||||
const output = new PassThrough();
|
||||
const chunks: string[] = [];
|
||||
const previousBackendConfig = process.env["CODEX_LSP_LAZY_BACKEND"];
|
||||
delete process.env["CODEX_LSP_LAZY_BACKEND"];
|
||||
output.on("data", (chunk: Buffer | string) => {
|
||||
chunks.push(chunk.toString());
|
||||
});
|
||||
|
||||
try {
|
||||
const server = runLazyLspMcpServer(input, output);
|
||||
|
||||
// when
|
||||
input.write(
|
||||
`${JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
method: "initialize",
|
||||
params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "test" } },
|
||||
})}\n`,
|
||||
);
|
||||
input.write(`${JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} })}\n`);
|
||||
input.end();
|
||||
await server;
|
||||
} finally {
|
||||
restoreEnv("CODEX_LSP_LAZY_BACKEND", previousBackendConfig);
|
||||
}
|
||||
|
||||
// then
|
||||
const responses = parseJsonRpcResponses(chunks.join(""));
|
||||
expect(responses).toHaveLength(2);
|
||||
expect(responses[0]?.result?.serverInfo).toEqual({ name: "lsp", version: "0.2.0" });
|
||||
expect(toolNames(responses[1])).toContain("status");
|
||||
});
|
||||
});
|
||||
|
||||
interface JsonRpcTestResponse {
|
||||
readonly result?: {
|
||||
readonly serverInfo?: unknown;
|
||||
readonly tools?: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
function parseJsonRpcResponses(output: string): readonly JsonRpcTestResponse[] {
|
||||
return output
|
||||
.trim()
|
||||
.split(/\n+/)
|
||||
.filter((line) => line.length > 0)
|
||||
.map((line) => {
|
||||
const parsed: unknown = JSON.parse(line);
|
||||
return isJsonRpcTestResponse(parsed) ? parsed : {};
|
||||
});
|
||||
}
|
||||
|
||||
function toolNames(response: JsonRpcTestResponse | undefined): readonly string[] {
|
||||
const tools = response?.result?.tools;
|
||||
if (!Array.isArray(tools)) return [];
|
||||
return tools.flatMap((tool) => {
|
||||
if (!isRecord(tool) || typeof tool["name"] !== "string") return [];
|
||||
return [tool["name"]];
|
||||
});
|
||||
}
|
||||
|
||||
function restoreEnv(name: string, value: string | undefined): void {
|
||||
if (value === undefined) {
|
||||
delete process.env[name];
|
||||
return;
|
||||
}
|
||||
process.env[name] = value;
|
||||
}
|
||||
|
||||
function isJsonRpcTestResponse(value: unknown): value is JsonRpcTestResponse {
|
||||
return isRecord(value);
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user