mirror of
https://github.com/musistudio/claude-code-router.git
synced 2026-08-29 03:12:10 +08:00
Merge pull request #1547 from camjac251/test/loopback-listener-readiness
test(gateway): wait for loopback listener readiness
This commit is contained in:
@@ -6,6 +6,7 @@ import path from "node:path";
|
||||
import test from "node:test";
|
||||
import { createDefaultAppConfig } from "@ccr/core/config/default-config.ts";
|
||||
import { gatewayService } from "@ccr/core/gateway/service.ts";
|
||||
import { waitForTcpListener } from "../../support/loopback-listener.mjs";
|
||||
|
||||
test("gateway treats downstream client aborts as expected stream cleanup", async (t) => {
|
||||
const dir = mkdtempSync(path.join(tmpdir(), "ccr-gateway-client-abort-test-"));
|
||||
@@ -62,6 +63,7 @@ test("gateway treats downstream client aborts as expected stream cleanup", async
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
await waitForTcpListener(upstream);
|
||||
const upstreamPort = serverPort(upstream);
|
||||
const config = createDefaultAppConfig({ generatedConfigFile: path.join(dir, "gateway.config.json") });
|
||||
config.APIKEY = "test-api-key";
|
||||
@@ -73,6 +75,7 @@ test("gateway treats downstream client aborts as expected stream cleanup", async
|
||||
gatewayService.coreAuthToken = "test-core-auth-token";
|
||||
|
||||
await listen(gateway);
|
||||
await waitForTcpListener(gateway);
|
||||
const gatewayUrl = `http://127.0.0.1:${serverPort(gateway)}/v1/responses`;
|
||||
const controller = new AbortController();
|
||||
const response = await fetch(gatewayUrl, {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { GatewayMediaExecutor } from "@ccr/core/media/executors.ts";
|
||||
import { MediaService, mediaServiceForTest, resolveProviderMediaTarget } from "@ccr/core/media/service.ts";
|
||||
import { mediaMcpToolDefinition } from "@ccr/core/media/tools.ts";
|
||||
import { MEDIA_ARTIFACT_PATH_PREFIX, handleMediaArtifactRequest, handleMediaToolsMcpRequest } from "@ccr/core/mcp/grok-media-mcp.ts";
|
||||
import { waitForTcpListener } from "../../support/loopback-listener.mjs";
|
||||
|
||||
const png = Buffer.concat([
|
||||
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
|
||||
@@ -91,6 +92,7 @@ test("media gateway errors expose the concrete failed provider attempt", async (
|
||||
}));
|
||||
});
|
||||
if (!await listenOrSkip(t, server)) return;
|
||||
await waitForTcpListener(server);
|
||||
t.after(() => server.close());
|
||||
|
||||
const executor = new GatewayMediaExecutor({
|
||||
@@ -220,6 +222,7 @@ test("provider image jobs use the internal media gateway, persist artifacts, and
|
||||
response.writeHead(404).end();
|
||||
});
|
||||
if (!await listenOrSkip(t, server)) return;
|
||||
await waitForTcpListener(server);
|
||||
t.after(() => server.close());
|
||||
|
||||
const root = mkdtempSync(path.join(os.tmpdir(), "ccr-grok-media-image-"));
|
||||
@@ -332,6 +335,7 @@ test("provider video jobs return immediately and finish through asynchronous pol
|
||||
response.writeHead(404).end();
|
||||
});
|
||||
if (!await listenOrSkip(t, server)) return;
|
||||
await waitForTcpListener(server);
|
||||
t.after(() => server.close());
|
||||
|
||||
const root = mkdtempSync(path.join(os.tmpdir(), "ccr-grok-media-video-"));
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { connect } from "node:net";
|
||||
|
||||
export async function waitForTcpListener(server, timeoutMs = 1000) {
|
||||
const address = server.address();
|
||||
if (!address || typeof address !== "object") {
|
||||
throw new Error("TCP listener does not have a bound address");
|
||||
}
|
||||
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
let lastError;
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const socket = connect(address.port, "127.0.0.1");
|
||||
const timer = setTimeout(() => {
|
||||
socket.destroy();
|
||||
reject(new Error(`Timed out connecting to TCP listener on port ${address.port}`));
|
||||
}, Math.max(1, deadline - Date.now()));
|
||||
socket.once("connect", () => {
|
||||
clearTimeout(timer);
|
||||
socket.end();
|
||||
resolve();
|
||||
});
|
||||
socket.once("error", (error) => {
|
||||
clearTimeout(timer);
|
||||
socket.destroy();
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
}
|
||||
}
|
||||
throw lastError ?? new Error(`Timed out waiting for TCP listener on port ${address.port}`);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
prepareCodexAppCdpUserDataDir,
|
||||
shouldEnableCodexMediaPreviewBridge
|
||||
} from "@ccr/core/agents/codex/media-preview-bridge.ts";
|
||||
import { waitForTcpListener } from "../../support/loopback-listener.mjs";
|
||||
|
||||
const token = "A".repeat(32);
|
||||
const imageId = "123e4567-e89b-42d3-a456-426614174000";
|
||||
@@ -118,6 +119,7 @@ test("Codex media loader accepts signed image and video bytes and rejects redire
|
||||
server.once("error", reject);
|
||||
server.listen(0, "127.0.0.1", resolve);
|
||||
});
|
||||
await waitForTcpListener(server);
|
||||
t.after(() => server.close());
|
||||
const address = server.address();
|
||||
assert.ok(address && typeof address === "object");
|
||||
|
||||
Reference in New Issue
Block a user