From e1a98171417c6d1a143b3c1765795e4420eb4c19 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 31 May 2026 05:20:33 +0200 Subject: [PATCH] fix(e2e): preflight openai chat tools auth --- scripts/e2e/openai-chat-tools-docker.sh | 30 ++++++++- test/scripts/openai-chat-tools-client.test.ts | 64 ++++++++++++++++++- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/scripts/e2e/openai-chat-tools-docker.sh b/scripts/e2e/openai-chat-tools-docker.sh index 5541a4b17ca..53b686f859a 100644 --- a/scripts/e2e/openai-chat-tools-docker.sh +++ b/scripts/e2e/openai-chat-tools-docker.sh @@ -13,11 +13,39 @@ if [ ! -f "$PROFILE_FILE" ] && [ -f "$HOME/.profile" ]; then PROFILE_FILE="$HOME/.profile" fi +read_profile_openai_api_key() { + local profile_file="$1" + ( + set +u + set -a + # shellcheck disable=SC1090 + source "$profile_file" >/dev/null + set +a + printf '%s' "${OPENAI_API_KEY:-}" + ) +} + +PROFILE_STATUS="none" +if [ -f "$PROFILE_FILE" ] && [ -r "$PROFILE_FILE" ]; then + PROFILE_STATUS="$PROFILE_FILE" +fi + +OPENAI_API_KEY_VALUE="${OPENAI_API_KEY:-}" +if [ "$PROFILE_STATUS" != "none" ]; then + OPENAI_API_KEY_VALUE="$(read_profile_openai_api_key "$PROFILE_FILE")" +fi +if [[ "$OPENAI_API_KEY_VALUE" == "undefined" || "$OPENAI_API_KEY_VALUE" == "null" ]]; then + OPENAI_API_KEY_VALUE="" +fi +if [ -z "$OPENAI_API_KEY_VALUE" ]; then + echo "ERROR: OPENAI_API_KEY was not available after sourcing $PROFILE_STATUS." >&2 + exit 1 +fi + docker_e2e_build_or_reuse "$IMAGE_NAME" openai-chat-tools "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "" "$SKIP_BUILD" OPENCLAW_TEST_STATE_SCRIPT_B64="$(docker_e2e_test_state_shell_b64 openai-chat-tools empty)" PROFILE_MOUNT=() -PROFILE_STATUS="none" if [ -f "$PROFILE_FILE" ] && [ -r "$PROFILE_FILE" ]; then set -a # shellcheck disable=SC1090 diff --git a/test/scripts/openai-chat-tools-client.test.ts b/test/scripts/openai-chat-tools-client.test.ts index 7a04f8d19aa..0c65d9aa627 100644 --- a/test/scripts/openai-chat-tools-client.test.ts +++ b/test/scripts/openai-chat-tools-client.test.ts @@ -1,11 +1,12 @@ import { spawn, spawnSync } from "node:child_process"; -import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { createServer, type Server } from "node:http"; import { tmpdir } from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; const clientPath = path.resolve("scripts/e2e/lib/openai-chat-tools/client.mjs"); +const dockerRunnerPath = path.resolve("scripts/e2e/openai-chat-tools-docker.sh"); const writeConfigPath = path.resolve("scripts/e2e/lib/openai-chat-tools/write-config.mjs"); interface ClientResult { @@ -96,6 +97,20 @@ function runWriteConfig(root: string, env: Record = {}) { }); } +function runDockerRunnerAuthPreflight(root: string, env: Record = {}) { + return spawnSync("bash", [dockerRunnerPath], { + encoding: "utf8", + env: { + ...process.env, + HOME: root, + OPENAI_API_KEY: "", + OPENAI_BASE_URL: "", + OPENCLAW_OPENAI_CHAT_TOOLS_PROFILE_FILE: path.join(root, "missing.profile"), + ...env, + }, + }); +} + function toolCallResponse() { return { choices: [ @@ -118,6 +133,53 @@ function toolCallResponse() { } describe("scripts/e2e/lib/openai-chat-tools/client.mjs", () => { + it("keeps full profile exports out of the Docker build phase", () => { + const runner = readFileSync(dockerRunnerPath, "utf8"); + const preflightSourceIndex = runner.indexOf('source "$profile_file"'); + const buildIndex = runner.indexOf("docker_e2e_build_or_reuse"); + const fullProfileSourceIndex = runner.indexOf('source "$PROFILE_FILE"', buildIndex); + + expect(preflightSourceIndex).toBeGreaterThanOrEqual(0); + expect(buildIndex).toBeGreaterThan(preflightSourceIndex); + expect(fullProfileSourceIndex).toBeGreaterThan(buildIndex); + }); + + it("fails auth preflight before Docker build work starts", () => { + const root = mkdtempSync(path.join(tmpdir(), "openclaw-openai-chat-tools-")); + try { + const result = runDockerRunnerAuthPreflight(root); + const output = `${result.stdout}\n${result.stderr}`; + + expect(result.status).toBe(1); + expect(output).toContain("OPENAI_API_KEY was not available"); + expect(output).not.toContain("Building Docker image:"); + expect(output).not.toContain("Reusing Docker image:"); + expect(output).not.toContain("Running OpenAI Chat Completions tools Docker E2E"); + } finally { + rmSync(root, { force: true, recursive: true }); + } + }); + + it("treats placeholder profile auth as missing before Docker build work starts", () => { + const root = mkdtempSync(path.join(tmpdir(), "openclaw-openai-chat-tools-")); + try { + const profile = path.join(root, "profile"); + writeFileSync(profile, "OPENAI_API_KEY=undefined\n"); + const result = runDockerRunnerAuthPreflight(root, { + OPENCLAW_OPENAI_CHAT_TOOLS_PROFILE_FILE: profile, + }); + const output = `${result.stdout}\n${result.stderr}`; + + expect(result.status).toBe(1); + expect(output).toContain("OPENAI_API_KEY was not available"); + expect(output).not.toContain("Building Docker image:"); + expect(output).not.toContain("Reusing Docker image:"); + expect(output).not.toContain("Running OpenAI Chat Completions tools Docker E2E"); + } finally { + rmSync(root, { force: true, recursive: true }); + } + }); + it("rejects loose timeout env values instead of parsing numeric prefixes", async () => { const result = await runClient(1, { OPENCLAW_OPENAI_CHAT_TOOLS_TIMEOUT_SECONDS: "1e3",