test: harden docker live readiness

This commit is contained in:
Peter Steinberger
2026-04-24 09:28:57 +01:00
parent f03252aaf9
commit a79c40a789
5 changed files with 79 additions and 55 deletions

View File

@@ -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]}

View File

@@ -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]}

View File

@@ -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

View File

@@ -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`, {

View File

@@ -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[] {