diff --git a/src/core/ai/recipes/azure-openai.ts b/src/core/ai/recipes/azure-openai.ts index 46b810c9c..d4302b7b8 100644 --- a/src/core/ai/recipes/azure-openai.ts +++ b/src/core/ai/recipes/azure-openai.ts @@ -45,9 +45,14 @@ export function __setEntraTokenForTests(token: string | null): void { _entraToken = token === null ? null : { token, fetchedAt: Date.now() }; } -/** Entra/keyless mode: explicit opt-in, or no api-key present (disableLocalAuth). */ +/** Entra/keyless mode: EXPLICIT opt-in only (AZURE_OPENAI_USE_ENTRA=1 / + * config azure_openai_use_entra). A missing api-key must NOT silently shell + * out to `az` — that surprises CI boxes and every non-Azure-CLI environment, + * and it broke the cross-recipe auth iron-rule test. Keyless subscriptions + * (disableLocalAuth) set the flag; missing key without the flag keeps the + * original loud AIConfigError. */ function isEntraMode(env: Record): boolean { - return env.AZURE_OPENAI_USE_ENTRA === '1' || !env.AZURE_OPENAI_API_KEY; + return env.AZURE_OPENAI_USE_ENTRA === '1'; } /** diff --git a/test/ai/recipe-azure-openai.test.ts b/test/ai/recipe-azure-openai.test.ts index 6ef77cfcc..84cf5e04d 100644 --- a/test/ai/recipe-azure-openai.test.ts +++ b/test/ai/recipe-azure-openai.test.ts @@ -74,7 +74,7 @@ describe('recipe: azure-openai', () => { expect(auth.headerName).toBe('api-key'); }); - test('resolveAuth Entra mode (no key) returns Authorization Bearer from the token cache', async () => { + test('resolveAuth Entra mode (explicit opt-in, no key) returns Authorization Bearer from the token cache', async () => { const { __setEntraTokenForTests } = await import('../../src/core/ai/recipes/azure-openai.ts'); __setEntraTokenForTests('fake-aad-token'); try { @@ -82,6 +82,7 @@ describe('recipe: azure-openai', () => { const auth = r.resolveAuth!({ AZURE_OPENAI_ENDPOINT: FULL_ENV.AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT: FULL_ENV.AZURE_OPENAI_DEPLOYMENT, + AZURE_OPENAI_USE_ENTRA: '1', }); expect(auth.headerName).toBe('Authorization'); expect(auth.token).toBe('Bearer fake-aad-token'); @@ -110,7 +111,8 @@ describe('recipe: azure-openai', () => { const cfg = r.resolveOpenAICompatConfig!({ AZURE_OPENAI_ENDPOINT: FULL_ENV.AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT: FULL_ENV.AZURE_OPENAI_DEPLOYMENT, - }); // no key → Entra mode + AZURE_OPENAI_USE_ENTRA: '1', + }); // explicit Entra opt-in (no key) const capturedAuth: (string | null)[] = []; const realFetch = globalThis.fetch; globalThis.fetch = ((_input: any, init?: any) => {