mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:30:43 +00:00
test: harden docker live readiness
This commit is contained in:
@@ -52,28 +52,19 @@ docker run --rm \
|
||||
}
|
||||
trap cleanup_inner EXIT
|
||||
trap dump_gateway_log_on_error ERR
|
||||
for _ in \$(seq 1 80); do
|
||||
if node --input-type=module -e '
|
||||
import net from \"node:net\";
|
||||
const socket = net.createConnection({ host: \"127.0.0.1\", port: $PORT });
|
||||
const timeout = setTimeout(() => {
|
||||
socket.destroy();
|
||||
process.exit(1);
|
||||
}, 400);
|
||||
socket.on(\"connect\", () => {
|
||||
clearTimeout(timeout);
|
||||
socket.end();
|
||||
process.exit(0);
|
||||
});
|
||||
socket.on(\"error\", () => {
|
||||
clearTimeout(timeout);
|
||||
process.exit(1);
|
||||
});
|
||||
' >/dev/null 2>&1; then
|
||||
gateway_ready=0
|
||||
for _ in \$(seq 1 160); do
|
||||
if grep -q '\[gateway\] ready' /tmp/cron-mcp-cleanup-gateway.log 2>/dev/null; then
|
||||
gateway_ready=1
|
||||
break
|
||||
fi
|
||||
sleep 0.25
|
||||
done
|
||||
if [ \"\$gateway_ready\" -ne 1 ]; then
|
||||
echo \"Gateway did not become ready\"
|
||||
tail -n 120 /tmp/cron-mcp-cleanup-gateway.log 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
node --import tsx scripts/e2e/cron-mcp-cleanup-docker-client.ts
|
||||
" >"$CLIENT_LOG" 2>&1
|
||||
status=${PIPESTATUS[0]}
|
||||
|
||||
@@ -52,28 +52,19 @@ docker run --rm \
|
||||
}
|
||||
trap cleanup_inner EXIT
|
||||
trap dump_gateway_log_on_error ERR
|
||||
for _ in \$(seq 1 80); do
|
||||
if node --input-type=module -e '
|
||||
import net from \"node:net\";
|
||||
const socket = net.createConnection({ host: \"127.0.0.1\", port: $PORT });
|
||||
const timeout = setTimeout(() => {
|
||||
socket.destroy();
|
||||
process.exit(1);
|
||||
}, 400);
|
||||
socket.on(\"connect\", () => {
|
||||
clearTimeout(timeout);
|
||||
socket.end();
|
||||
process.exit(0);
|
||||
});
|
||||
socket.on(\"error\", () => {
|
||||
clearTimeout(timeout);
|
||||
process.exit(1);
|
||||
});
|
||||
' >/dev/null 2>&1; then
|
||||
gateway_ready=0
|
||||
for _ in \$(seq 1 160); do
|
||||
if grep -q '\[gateway\] ready' /tmp/mcp-channels-gateway.log 2>/dev/null; then
|
||||
gateway_ready=1
|
||||
break
|
||||
fi
|
||||
sleep 0.25
|
||||
done
|
||||
if [ \"\$gateway_ready\" -ne 1 ]; then
|
||||
echo \"Gateway did not become ready\"
|
||||
tail -n 120 /tmp/mcp-channels-gateway.log 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
node --import tsx scripts/e2e/mcp-channels-docker-client.ts
|
||||
" >"$CLIENT_LOG" 2>&1
|
||||
status=${PIPESTATUS[0]}
|
||||
|
||||
@@ -54,6 +54,10 @@ docker run -d \
|
||||
--network "$NET_NAME" \
|
||||
-e "OPENCLAW_GATEWAY_TOKEN=$TOKEN" \
|
||||
-e "OPENCLAW_OPENWEBUI_MODEL=$MODEL" \
|
||||
-e "OPENCLAW_SKIP_CHANNELS=1" \
|
||||
-e "OPENCLAW_SKIP_GMAIL_WATCHER=1" \
|
||||
-e "OPENCLAW_SKIP_CRON=1" \
|
||||
-e "OPENCLAW_SKIP_CANVAS_HOST=1" \
|
||||
-e OPENAI_API_KEY \
|
||||
${OPENAI_BASE_URL_VALUE:+-e OPENAI_BASE_URL} \
|
||||
"$IMAGE_NAME" \
|
||||
@@ -110,7 +114,7 @@ EOF
|
||||
|
||||
echo "Waiting for gateway HTTP surface..."
|
||||
gateway_ready=0
|
||||
for _ in $(seq 1 60); do
|
||||
for _ in $(seq 1 240); do
|
||||
if [ "$(docker inspect -f '{{.State.Running}}' "$GW_NAME" 2>/dev/null || echo false)" != "true" ]; then
|
||||
break
|
||||
fi
|
||||
@@ -128,6 +132,10 @@ done
|
||||
|
||||
if [ "$gateway_ready" -ne 1 ]; then
|
||||
echo "Gateway failed to start"
|
||||
docker inspect "$GW_NAME" --format '{{json .State}}' 2>/dev/null || true
|
||||
if [ "$(docker inspect -f '{{.State.Running}}' "$GW_NAME" 2>/dev/null || echo false)" = "true" ]; then
|
||||
docker exec "$GW_NAME" bash -lc 'tail -n 200 /tmp/openwebui-gateway.log' || true
|
||||
fi
|
||||
docker logs "$GW_NAME" 2>&1 | tail -n 200 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -31,6 +31,25 @@ function buildAuthHeaders(token, cookie) {
|
||||
return headers;
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function extractModelIds(modelsJson) {
|
||||
const models = Array.isArray(modelsJson)
|
||||
? modelsJson
|
||||
: Array.isArray(modelsJson?.data)
|
||||
? modelsJson.data
|
||||
: Array.isArray(modelsJson?.models)
|
||||
? modelsJson.models
|
||||
: [];
|
||||
return models
|
||||
.map((entry) => entry?.id ?? entry?.model ?? entry?.name)
|
||||
.filter((value) => typeof value === "string");
|
||||
}
|
||||
|
||||
const signinRes = await fetch(`${baseUrl}/api/v1/auths/signin`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
@@ -50,25 +69,34 @@ const authHeaders = {
|
||||
accept: "application/json",
|
||||
};
|
||||
|
||||
const modelsRes = await fetch(`${baseUrl}/api/models`, { headers: authHeaders });
|
||||
if (!modelsRes.ok) {
|
||||
throw new Error(`/api/models failed: HTTP ${modelsRes.status} ${await modelsRes.text()}`);
|
||||
let modelIds = [];
|
||||
let targetModel = "";
|
||||
let lastModelsError = "";
|
||||
for (let attempt = 1; attempt <= 24; attempt += 1) {
|
||||
const modelsRes = await fetch(`${baseUrl}/api/models`, { headers: authHeaders }).catch(
|
||||
(error) => {
|
||||
lastModelsError = error instanceof Error ? error.message : String(error);
|
||||
return undefined;
|
||||
},
|
||||
);
|
||||
if (modelsRes?.ok) {
|
||||
const modelsJson = await modelsRes.json();
|
||||
modelIds = extractModelIds(modelsJson);
|
||||
targetModel =
|
||||
modelIds.find((id) => id === "openclaw/default") ?? modelIds.find((id) => id === "openclaw");
|
||||
if (targetModel) {
|
||||
break;
|
||||
}
|
||||
lastModelsError = `missing openclaw model: ${JSON.stringify(modelIds)}`;
|
||||
} else if (modelsRes) {
|
||||
lastModelsError = `HTTP ${modelsRes.status} ${await modelsRes.text()}`;
|
||||
}
|
||||
await sleep(5_000);
|
||||
}
|
||||
const modelsJson = await modelsRes.json();
|
||||
const models = Array.isArray(modelsJson)
|
||||
? modelsJson
|
||||
: Array.isArray(modelsJson?.data)
|
||||
? modelsJson.data
|
||||
: Array.isArray(modelsJson?.models)
|
||||
? modelsJson.models
|
||||
: [];
|
||||
const modelIds = models
|
||||
.map((entry) => entry?.id ?? entry?.model ?? entry?.name)
|
||||
.filter((value) => typeof value === "string");
|
||||
const targetModel =
|
||||
modelIds.find((id) => id === "openclaw/default") ?? modelIds.find((id) => id === "openclaw");
|
||||
if (!targetModel) {
|
||||
throw new Error(`openclaw model missing from Open WebUI model list: ${JSON.stringify(modelIds)}`);
|
||||
throw new Error(
|
||||
`openclaw model missing from Open WebUI model list after retry: ${JSON.stringify(modelIds)} (${lastModelsError})`,
|
||||
);
|
||||
}
|
||||
|
||||
const chatRes = await fetch(`${baseUrl}/api/chat/completions`, {
|
||||
|
||||
@@ -136,7 +136,13 @@ export function shouldRunCliModelSwitchProbe(providerId: string, modelRef: strin
|
||||
export function matchesCliBackendReply(text: string, expected: string): boolean {
|
||||
const normalized = text.trim();
|
||||
const target = expected.trim();
|
||||
return normalized === target || normalized === target.slice(0, -1);
|
||||
const targetWithoutPeriod = target.slice(0, -1);
|
||||
return (
|
||||
normalized === target ||
|
||||
normalized === targetWithoutPeriod ||
|
||||
normalized.includes(target) ||
|
||||
normalized.includes(targetWithoutPeriod)
|
||||
);
|
||||
}
|
||||
|
||||
export function withClaudeMcpConfigOverrides(args: string[], mcpConfigPath: string): string[] {
|
||||
|
||||
Reference in New Issue
Block a user