|
1 | 1 | import { afterEach, describe, expect, test } from "bun:test"; |
2 | 2 |
|
3 | 3 | import { checkOAuthProviderScope } from "./oauth-scope-check.js"; |
| 4 | +import { XAI_BASE_URL } from "./xai/constants.js"; |
4 | 5 |
|
5 | 6 | const commandName = "test-cli"; |
6 | 7 |
|
@@ -119,6 +120,51 @@ describe("checkOAuthProviderScope", () => { |
119 | 120 | } |
120 | 121 | }); |
121 | 122 |
|
| 123 | + test("codex: empty account id omits the account header", async () => { |
| 124 | + stubFetch((_url, init) => { |
| 125 | + const headers = init?.headers as Record<string, string>; |
| 126 | + expect(headers.authorization).toBe("Bearer staged-codex-token"); |
| 127 | + expect("chatgpt-account-id" in headers).toBe(false); |
| 128 | + return new Response(JSON.stringify({ models: ["gpt-5"] }), { |
| 129 | + status: 200, |
| 130 | + }); |
| 131 | + }); |
| 132 | + const result = await checkOAuthProviderScope( |
| 133 | + "codex", |
| 134 | + { ...codexTokens, accountId: "" }, |
| 135 | + commandName, |
| 136 | + ); |
| 137 | + expect(result.status).toBe("ok"); |
| 138 | + }); |
| 139 | + |
| 140 | + test("codex: fail-closed on empty access (Bearer probe classifies blocked)", async () => { |
| 141 | + stubFetch((_url, init) => { |
| 142 | + const headers = init?.headers as Record<string, string>; |
| 143 | + expect(headers.authorization).toBe("Bearer "); |
| 144 | + return new Response("nope", { status: 401 }); |
| 145 | + }); |
| 146 | + const result = await checkOAuthProviderScope( |
| 147 | + "codex", |
| 148 | + { ...codexTokens, access: "" }, |
| 149 | + commandName, |
| 150 | + ); |
| 151 | + expect(result.status).toBe("blocked"); |
| 152 | + }); |
| 153 | + |
| 154 | + test("codex: fail-closed on garbage access (Bearer probe classifies blocked)", async () => { |
| 155 | + stubFetch((_url, init) => { |
| 156 | + const headers = init?.headers as Record<string, string>; |
| 157 | + expect(headers.authorization).toBe("Bearer !!!not-a-token!!!"); |
| 158 | + return new Response("forbidden", { status: 403 }); |
| 159 | + }); |
| 160 | + const result = await checkOAuthProviderScope( |
| 161 | + "codex", |
| 162 | + { ...codexTokens, access: "!!!not-a-token!!!" }, |
| 163 | + commandName, |
| 164 | + ); |
| 165 | + expect(result.status).toBe("blocked"); |
| 166 | + }); |
| 167 | + |
122 | 168 | test("codex: blocks a definitive 401", async () => { |
123 | 169 | stubFetch(() => new Response("nope", { status: 401 })); |
124 | 170 | const result = await checkOAuthProviderScope( |
@@ -220,6 +266,60 @@ describe("checkOAuthProviderScope", () => { |
220 | 266 | expect(result.status).toBe("blocked"); |
221 | 267 | }); |
222 | 268 |
|
| 269 | + test("xai: non-JWT token omits the user-id header, keeps authorization", async () => { |
| 270 | + stubFetch((_url, init) => { |
| 271 | + const headers = init?.headers as Record<string, string>; |
| 272 | + expect(headers.authorization).toBe("Bearer not-a-jwt"); |
| 273 | + expect("x-grok-user-id" in headers).toBe(false); |
| 274 | + return new Response(JSON.stringify({ data: [] }), { status: 200 }); |
| 275 | + }); |
| 276 | + const result = await checkOAuthProviderScope( |
| 277 | + "xai", |
| 278 | + { ...xaiTokens, access: "not-a-jwt" }, |
| 279 | + commandName, |
| 280 | + ); |
| 281 | + expect(result.status).toBe("ok"); |
| 282 | + }); |
| 283 | + |
| 284 | + test("xai: fail-closed on empty access (Bearer probe classifies blocked)", async () => { |
| 285 | + stubFetch((_url, init) => { |
| 286 | + const headers = init?.headers as Record<string, string>; |
| 287 | + expect(headers.authorization).toBe("Bearer "); |
| 288 | + return new Response("nope", { status: 401 }); |
| 289 | + }); |
| 290 | + const result = await checkOAuthProviderScope( |
| 291 | + "xai", |
| 292 | + { ...xaiTokens, access: "" }, |
| 293 | + commandName, |
| 294 | + ); |
| 295 | + expect(result.status).toBe("blocked"); |
| 296 | + }); |
| 297 | + |
| 298 | + test("xai: fail-closed on garbage access (Bearer probe classifies blocked)", async () => { |
| 299 | + stubFetch((_url, init) => { |
| 300 | + const headers = init?.headers as Record<string, string>; |
| 301 | + expect(headers.authorization).toBe("Bearer !!!not-a-token!!!"); |
| 302 | + return new Response("forbidden", { status: 403 }); |
| 303 | + }); |
| 304 | + const result = await checkOAuthProviderScope( |
| 305 | + "xai", |
| 306 | + { ...xaiTokens, access: "!!!not-a-token!!!" }, |
| 307 | + commandName, |
| 308 | + ); |
| 309 | + expect(result.status).toBe("blocked"); |
| 310 | + }); |
| 311 | + |
| 312 | + test("xai: pins the probe to the fixed models URL", async () => { |
| 313 | + const seen: string[] = []; |
| 314 | + stubFetch((url) => { |
| 315 | + seen.push(url); |
| 316 | + return new Response(JSON.stringify({ data: [] }), { status: 200 }); |
| 317 | + }); |
| 318 | + const result = await checkOAuthProviderScope("xai", xaiTokens, commandName); |
| 319 | + expect(result.status).toBe("ok"); |
| 320 | + expect(seen).toEqual([`${XAI_BASE_URL}/models`]); |
| 321 | + }); |
| 322 | + |
223 | 323 | test("xai: unavailable on a timeout-style abort", async () => { |
224 | 324 | stubFetch(() => { |
225 | 325 | throw new DOMException("The operation timed out.", "TimeoutError"); |
|
0 commit comments