11import { spawn , spawnSync } from "node:child_process" ;
2- import { mkdtempSync , readFileSync , rmSync } from "node:fs" ;
2+ import { mkdtempSync , readFileSync , rmSync , writeFileSync } from "node:fs" ;
33import { createServer , type Server } from "node:http" ;
44import { tmpdir } from "node:os" ;
55import path from "node:path" ;
66import { describe , expect , it } from "vitest" ;
77
88const clientPath = path . resolve ( "scripts/e2e/lib/openai-chat-tools/client.mjs" ) ;
9+ const dockerRunnerPath = path . resolve ( "scripts/e2e/openai-chat-tools-docker.sh" ) ;
910const writeConfigPath = path . resolve ( "scripts/e2e/lib/openai-chat-tools/write-config.mjs" ) ;
1011
1112interface ClientResult {
@@ -96,6 +97,20 @@ function runWriteConfig(root: string, env: Record<string, string> = {}) {
9697 } ) ;
9798}
9899
100+ function runDockerRunnerAuthPreflight ( root : string , env : Record < string , string > = { } ) {
101+ return spawnSync ( "bash" , [ dockerRunnerPath ] , {
102+ encoding : "utf8" ,
103+ env : {
104+ ...process . env ,
105+ HOME : root ,
106+ OPENAI_API_KEY : "" ,
107+ OPENAI_BASE_URL : "" ,
108+ OPENCLAW_OPENAI_CHAT_TOOLS_PROFILE_FILE : path . join ( root , "missing.profile" ) ,
109+ ...env ,
110+ } ,
111+ } ) ;
112+ }
113+
99114function toolCallResponse ( ) {
100115 return {
101116 choices : [
@@ -118,6 +133,53 @@ function toolCallResponse() {
118133}
119134
120135describe ( "scripts/e2e/lib/openai-chat-tools/client.mjs" , ( ) => {
136+ it ( "keeps full profile exports out of the Docker build phase" , ( ) => {
137+ const runner = readFileSync ( dockerRunnerPath , "utf8" ) ;
138+ const preflightSourceIndex = runner . indexOf ( 'source "$profile_file"' ) ;
139+ const buildIndex = runner . indexOf ( "docker_e2e_build_or_reuse" ) ;
140+ const fullProfileSourceIndex = runner . indexOf ( 'source "$PROFILE_FILE"' , buildIndex ) ;
141+
142+ expect ( preflightSourceIndex ) . toBeGreaterThanOrEqual ( 0 ) ;
143+ expect ( buildIndex ) . toBeGreaterThan ( preflightSourceIndex ) ;
144+ expect ( fullProfileSourceIndex ) . toBeGreaterThan ( buildIndex ) ;
145+ } ) ;
146+
147+ it ( "fails auth preflight before Docker build work starts" , ( ) => {
148+ const root = mkdtempSync ( path . join ( tmpdir ( ) , "openclaw-openai-chat-tools-" ) ) ;
149+ try {
150+ const result = runDockerRunnerAuthPreflight ( root ) ;
151+ const output = `${ result . stdout } \n${ result . stderr } ` ;
152+
153+ expect ( result . status ) . toBe ( 1 ) ;
154+ expect ( output ) . toContain ( "OPENAI_API_KEY was not available" ) ;
155+ expect ( output ) . not . toContain ( "Building Docker image:" ) ;
156+ expect ( output ) . not . toContain ( "Reusing Docker image:" ) ;
157+ expect ( output ) . not . toContain ( "Running OpenAI Chat Completions tools Docker E2E" ) ;
158+ } finally {
159+ rmSync ( root , { force : true , recursive : true } ) ;
160+ }
161+ } ) ;
162+
163+ it ( "treats placeholder profile auth as missing before Docker build work starts" , ( ) => {
164+ const root = mkdtempSync ( path . join ( tmpdir ( ) , "openclaw-openai-chat-tools-" ) ) ;
165+ try {
166+ const profile = path . join ( root , "profile" ) ;
167+ writeFileSync ( profile , "OPENAI_API_KEY=undefined\n" ) ;
168+ const result = runDockerRunnerAuthPreflight ( root , {
169+ OPENCLAW_OPENAI_CHAT_TOOLS_PROFILE_FILE : profile ,
170+ } ) ;
171+ const output = `${ result . stdout } \n${ result . stderr } ` ;
172+
173+ expect ( result . status ) . toBe ( 1 ) ;
174+ expect ( output ) . toContain ( "OPENAI_API_KEY was not available" ) ;
175+ expect ( output ) . not . toContain ( "Building Docker image:" ) ;
176+ expect ( output ) . not . toContain ( "Reusing Docker image:" ) ;
177+ expect ( output ) . not . toContain ( "Running OpenAI Chat Completions tools Docker E2E" ) ;
178+ } finally {
179+ rmSync ( root , { force : true , recursive : true } ) ;
180+ }
181+ } ) ;
182+
121183 it ( "rejects loose timeout env values instead of parsing numeric prefixes" , async ( ) => {
122184 const result = await runClient ( 1 , {
123185 OPENCLAW_OPENAI_CHAT_TOOLS_TIMEOUT_SECONDS : "1e3" ,
0 commit comments