test(codex): cover app-server Docker flows

This commit is contained in:
Peter Steinberger
2026-04-24 04:17:26 +01:00
parent 69566e43cb
commit e0d3256311
11 changed files with 863 additions and 78 deletions

View File

@@ -0,0 +1,109 @@
#!/usr/bin/env -S node --import tsx
import fs from "node:fs/promises";
import path from "node:path";
type CodexAuthJson = {
tokens?: {
account_id?: unknown;
id_token?: unknown;
};
};
type JwtParts = {
header: string;
payload: Record<string, unknown>;
signature: string;
};
function decodeBase64UrlJson(value: string): Record<string, unknown> {
const decoded = Buffer.from(value, "base64url").toString("utf-8");
const parsed: unknown = JSON.parse(decoded);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error("JWT payload is not a JSON object.");
}
return parsed as Record<string, unknown>;
}
function encodeBase64UrlJson(value: Record<string, unknown>): string {
return Buffer.from(JSON.stringify(value), "utf-8").toString("base64url");
}
function parseJwt(value: string): JwtParts {
const parts = value.split(".");
if (parts.length !== 3 || !parts[0] || !parts[1]) {
throw new Error("id_token is not a JWT.");
}
return {
header: parts[0],
payload: decodeBase64UrlJson(parts[1]),
signature: parts[2] ?? "",
};
}
function stringifyJwt(parts: JwtParts): string {
return [parts.header, encodeBase64UrlJson(parts.payload), parts.signature].join(".");
}
export function patchCodexAuthForCi(auth: CodexAuthJson): {
auth: CodexAuthJson;
changed: boolean;
} {
const tokens = auth.tokens;
if (!tokens) {
return { auth, changed: false };
}
const accountId = typeof tokens.account_id === "string" ? tokens.account_id.trim() : "";
const idToken = typeof tokens.id_token === "string" ? tokens.id_token.trim() : "";
if (!accountId || !idToken) {
return { auth, changed: false };
}
const jwt = parseJwt(idToken);
if (typeof jwt.payload.chatgpt_account_id === "string" && jwt.payload.chatgpt_account_id) {
return { auth, changed: false };
}
return {
auth: {
...auth,
tokens: {
...tokens,
// Newer Codex app-server builds read ChatGPT account metadata from
// id_token claims. Older local auth files can have the same value only
// at tokens.account_id, so patch the staged Docker copy for CI.
id_token: stringifyJwt({
...jwt,
payload: {
...jwt.payload,
chatgpt_account_id: accountId,
},
}),
},
},
changed: true,
};
}
export async function prepareCodexCiAuth(authPath: string): Promise<boolean> {
const raw = await fs.readFile(authPath, "utf-8");
const parsed = JSON.parse(raw) as CodexAuthJson;
const { auth, changed } = patchCodexAuthForCi(parsed);
if (!changed) {
return false;
}
const stat = await fs.stat(authPath);
await fs.writeFile(authPath, `${JSON.stringify(auth, null, 2)}\n`, "utf-8");
await fs.chmod(authPath, stat.mode);
return true;
}
if (path.basename(process.argv[1] ?? "") === "prepare-codex-ci-auth.ts") {
const authPath = process.argv[2];
if (!authPath) {
throw new Error("Usage: node --import tsx scripts/prepare-codex-ci-auth.ts <auth-json-path>");
}
const changed = await prepareCodexCiAuth(authPath);
if (changed) {
console.error("Prepared staged Codex auth metadata for CI.");
}
}

View File

@@ -46,6 +46,7 @@ const exclusiveLanes = [
"OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:openai-web-search-minimal",
],
["live-codex-harness", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-codex-harness"],
["live-codex-bind", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-codex-bind"],
[
"live-cli-backend-codex",
"OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-cli-backend:codex",

View File

@@ -157,6 +157,9 @@ if [ "${OPENCLAW_LIVE_CODEX_HARNESS_AUTH:-codex-auth}" != "api-key" ] && [ ! -s
echo "ERROR: missing ~/.codex/auth.json for Codex harness live test." >&2
exit 1
fi
if [ "${OPENCLAW_LIVE_CODEX_HARNESS_AUTH:-codex-auth}" != "api-key" ]; then
node --import tsx /src/scripts/prepare-codex-ci-auth.ts "$HOME/.codex/auth.json"
fi
if [ ! -x "$NPM_CONFIG_PREFIX/bin/codex" ]; then
npm install -g @openai/codex
fi
@@ -181,7 +184,7 @@ cd "$tmp_dir"
if [ "${OPENCLAW_LIVE_CODEX_HARNESS_USE_CI_SAFE_CODEX_CONFIG:-1}" = "1" ]; then
node --import tsx /src/scripts/prepare-codex-ci-config.ts "$HOME/.codex/config.toml" "$tmp_dir"
fi
pnpm test:live src/gateway/gateway-codex-harness.live.test.ts
pnpm test:live ${OPENCLAW_LIVE_CODEX_TEST_FILES:-src/gateway/gateway-codex-harness.live.test.ts}
EOF
openclaw_live_codex_harness_append_build_extension codex
@@ -194,6 +197,7 @@ echo "==> MCP probe: ${OPENCLAW_LIVE_CODEX_HARNESS_MCP_PROBE:-1}"
echo "==> Guardian probe: ${OPENCLAW_LIVE_CODEX_HARNESS_GUARDIAN_PROBE:-1}"
echo "==> Auth mode: $CODEX_HARNESS_AUTH_MODE"
echo "==> CI-safe Codex config: ${OPENCLAW_LIVE_CODEX_HARNESS_USE_CI_SAFE_CODEX_CONFIG:-1}"
echo "==> Test files: ${OPENCLAW_LIVE_CODEX_TEST_FILES:-src/gateway/gateway-codex-harness.live.test.ts}"
echo "==> Harness fallback: none"
echo "==> Auth files: ${AUTH_FILES_CSV:-none}"
DOCKER_RUN_ARGS=(docker run --rm -t \
@@ -213,8 +217,12 @@ DOCKER_RUN_ARGS=(docker run --rm -t \
-e OPENCLAW_LIVE_CODEX_HARNESS_IMAGE_PROBE="${OPENCLAW_LIVE_CODEX_HARNESS_IMAGE_PROBE:-1}" \
-e OPENCLAW_LIVE_CODEX_HARNESS_MCP_PROBE="${OPENCLAW_LIVE_CODEX_HARNESS_MCP_PROBE:-1}" \
-e OPENCLAW_LIVE_CODEX_HARNESS_MODEL="${OPENCLAW_LIVE_CODEX_HARNESS_MODEL:-codex/gpt-5.4}" \
-e OPENCLAW_LIVE_CODEX_HARNESS_REQUIRE_GUARDIAN_EVENTS="${OPENCLAW_LIVE_CODEX_HARNESS_REQUIRE_GUARDIAN_EVENTS:-1}" \
-e OPENCLAW_LIVE_CODEX_HARNESS_REQUEST_TIMEOUT_MS="${OPENCLAW_LIVE_CODEX_HARNESS_REQUEST_TIMEOUT_MS:-}" \
-e OPENCLAW_LIVE_CODEX_HARNESS_USE_CI_SAFE_CODEX_CONFIG="${OPENCLAW_LIVE_CODEX_HARNESS_USE_CI_SAFE_CODEX_CONFIG:-1}" \
-e OPENCLAW_LIVE_CODEX_BIND="${OPENCLAW_LIVE_CODEX_BIND:-}" \
-e OPENCLAW_LIVE_CODEX_BIND_MODEL="${OPENCLAW_LIVE_CODEX_BIND_MODEL:-}" \
-e OPENCLAW_LIVE_CODEX_TEST_FILES="${OPENCLAW_LIVE_CODEX_TEST_FILES:-}" \
-e OPENCLAW_LIVE_TEST=1 \
-e OPENCLAW_VITEST_FS_MODULE_CACHE=0)
openclaw_live_append_array DOCKER_RUN_ARGS DOCKER_AUTH_ENV