1// @effect-diagnostics nodeBuiltinImport:off globalFetch:off - Tests exercise the real native loopback listener without an OpenAI account.
2import * as NodeHttp from "node:http";
3import { describe, expect, it } from "vite-plus/test";
4import { codexAuthDeliveryUrl, readCodexAuthDelivery } from "@t3tools/shared/codexAuthHandoff";
5import { EnvironmentId, ProviderInstanceId } from "@t3tools/contracts";
6import { receiveCodexAuthCallback, cancelCodexAuthCallback } from "./CodexAuthCallback.ts";
7
8async function freePort() {
9 const server = NodeHttp.createServer();
10 await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
11 const address = server.address();
12 if (!address || typeof address === "string") throw new Error("address");
13 await new Promise<void>((resolve) => server.close(() => resolve()));
14 return address.port;
15}
16function request(port: number, state = "a".repeat(43)) {
17 const url = new URL("https://auth.openai.com/api/accounts/authorize");
18 url.search = new URLSearchParams({
19 client_id: "dynamic_agent_client",
20 response_type: "code",
21 redirect_uri: `http://127.0.0.1:${port}/auth/callback`,
22 state,
23 code_challenge_method: "S256",
24 code_challenge: "b".repeat(43),
25 }).toString();
26 return url.toString();
27}
28function callback(authorizationUrl: string) {
29 const request = new URL(authorizationUrl);
30 const url = new URL(request.searchParams.get("redirect_uri")!);
31 url.search = new URLSearchParams({
32 state: request.searchParams.get("state")!,
33 code: "test-code",
34 client_id: "oaiapp_test",
35 }).toString();
36 return url.toString();
37}
38
39describe("desktop Codex callback helper", () => {
40 it("binds before opening sign-in, ignores a foreign response, and returns only the code callback", async () => {
41 const authorizationUrl = request(await freePort());
42 const expected = callback(authorizationUrl);
43 const received = await receiveCodexAuthCallback(authorizationUrl, async () => {
44 const invalid = new URL(expected);
45 invalid.searchParams.set("state", "foreign");
46 expect((await fetch(invalid)).status).toBe(400);
47 const response = await fetch(expected);
48 expect(response.headers.get("cache-control")).toBe("no-store");
49 expect(await response.text()).not.toContain("test-code");
50 return true;
51 });
52 expect(received).toBe(expected);
53 });
54 it("returns hosted web to the exact instance and environment without putting the code in its query", async () => {
55 const authorizationUrl = request(await freePort());
56 const expected = callback(authorizationUrl);
57 const input = {
58 authorizationUrl,
59 returnUrl: "https://app.t3.codes/settings/providers?environmentId=remote-one&instanceId=work",
60 environmentId: EnvironmentId.make("remote-one"),
61 instanceId: ProviderInstanceId.make("work"),
62 flowId: "flow-one",
63 };
64 await receiveCodexAuthCallback(
65 authorizationUrl,
66 async () => {
67 const response = await fetch(expected, { redirect: "manual" });
68 expect(response.status).toBe(303);
69 const delivery = response.headers.get("location")!;
70 expect(new URL(delivery).searchParams.has("code")).toBe(false);
71 expect(readCodexAuthDelivery(delivery)?.callbackUrl).toBe(expected);
72 expect(readCodexAuthDelivery(delivery)?.returnUrl).toBe(input.returnUrl);
73 return true;
74 },
75 (url) => codexAuthDeliveryUrl(input, url),
76 );
77 });
78 it("cancels and releases its listener so exact-port reauthorization can run again", async () => {
79 const authorizationUrl = request(await freePort());
80 await expect(
81 receiveCodexAuthCallback(authorizationUrl, async () => {
82 cancelCodexAuthCallback(authorizationUrl);
83 return true;
84 }),
85 ).rejects.toThrow("cancelled");
86 expect(
87 await receiveCodexAuthCallback(authorizationUrl, async () => {
88 await fetch(callback(authorizationUrl));
89 return true;
90 }),
91 ).toBe(callback(authorizationUrl));
92 });
93 it("allows two accounts to complete independently", async () => {
94 const a = request(await freePort(), "a".repeat(43));
95 const b = request(await freePort(), "c".repeat(43));
96 const openedA = Promise.withResolvers<void>();
97 const openedB = Promise.withResolvers<void>();
98 const receiveA = receiveCodexAuthCallback(a, async () => {
99 openedA.resolve();
100 await openedB.promise;
101 await fetch(callback(a));
102 return true;
103 });
104 const receiveB = receiveCodexAuthCallback(b, async () => {
105 openedB.resolve();
106 await openedA.promise;
107 await fetch(callback(b));
108 return true;
109 });
110 expect(await Promise.all([receiveA, receiveB])).toEqual([callback(a), callback(b)]);
111 });
112});