126 lines
3.4 KiB
TypeScript
126 lines
3.4 KiB
TypeScript
import { assertEquals, assertRejects } from "jsr:@std/assert@1";
|
|
import {
|
|
codexAuthentication,
|
|
persistCodexAuth,
|
|
retryInvalidToken,
|
|
stageCodexAuth,
|
|
} from "./auth.ts";
|
|
|
|
Deno.test("retries once after an invalid refresh token", async () => {
|
|
let attempts = 0;
|
|
let relogins = 0;
|
|
const result = await retryInvalidToken(
|
|
() =>
|
|
Promise.resolve({
|
|
status: { success: false },
|
|
error: attempts++ === 0 ? "invalid_refresh_token" : "still failed",
|
|
}),
|
|
() => {
|
|
relogins++;
|
|
return Promise.resolve();
|
|
},
|
|
);
|
|
|
|
assertEquals({ attempts, relogins, error: result.error }, {
|
|
attempts: 2,
|
|
relogins: 1,
|
|
error: "still failed",
|
|
});
|
|
});
|
|
|
|
Deno.test("does not retry an unrelated failure", async () => {
|
|
let attempts = 0;
|
|
let relogins = 0;
|
|
await retryInvalidToken(
|
|
() => {
|
|
attempts++;
|
|
return Promise.resolve({
|
|
status: { success: false },
|
|
error: "rate limited",
|
|
});
|
|
},
|
|
() => {
|
|
relogins++;
|
|
return Promise.resolve();
|
|
},
|
|
);
|
|
|
|
assertEquals({ attempts, relogins }, { attempts: 1, relogins: 0 });
|
|
});
|
|
|
|
Deno.test("selects API-key login only for a configured Codex token", () => {
|
|
assertEquals(codexAuthentication("selected-key"), {
|
|
kind: "api-key",
|
|
token: "selected-key",
|
|
});
|
|
assertEquals(codexAuthentication(undefined), { kind: "persistent" });
|
|
});
|
|
|
|
const AUTH = JSON.stringify({
|
|
auth_mode: "chatgpt",
|
|
tokens: {
|
|
access_token: "access",
|
|
refresh_token: "refresh",
|
|
id_token: "id",
|
|
account_id: "account",
|
|
},
|
|
});
|
|
|
|
Deno.test("stages and persists only a regular private Codex login", async () => {
|
|
if (Deno.build.os === "windows") return;
|
|
const root = await Deno.makeTempDir();
|
|
try {
|
|
const canonical = `${root}/persistent/auth.json`;
|
|
await Deno.mkdir(`${root}/persistent`);
|
|
await Deno.writeTextFile(canonical, AUTH, { mode: 0o600 });
|
|
const home = `${root}/profile`;
|
|
await Deno.mkdir(home);
|
|
|
|
const staged = await stageCodexAuth(
|
|
canonical,
|
|
home,
|
|
Deno.uid()!,
|
|
Deno.gid()!,
|
|
);
|
|
assertEquals(await Deno.readTextFile(staged), AUTH);
|
|
assertEquals((await Deno.stat(staged)).mode! & 0o777, 0o600);
|
|
|
|
const refreshed = JSON.stringify({
|
|
...JSON.parse(AUTH),
|
|
tokens: { ...JSON.parse(AUTH).tokens, access_token: "refreshed" },
|
|
});
|
|
await Deno.writeTextFile(staged, refreshed);
|
|
await persistCodexAuth(staged, canonical);
|
|
assertEquals(await Deno.readTextFile(canonical), refreshed);
|
|
assertEquals((await Deno.stat(canonical)).mode! & 0o777, 0o600);
|
|
} finally {
|
|
await Deno.remove(root, { recursive: true });
|
|
}
|
|
});
|
|
|
|
Deno.test("rejects a symlink or malformed Codex login", async () => {
|
|
if (Deno.build.os === "windows") return;
|
|
const root = await Deno.makeTempDir();
|
|
try {
|
|
const canonical = `${root}/auth.json`;
|
|
const malformed = `${root}/malformed.json`;
|
|
const linked = `${root}/linked.json`;
|
|
await Deno.writeTextFile(canonical, AUTH, { mode: 0o600 });
|
|
await Deno.writeTextFile(malformed, '{"tokens":{}}', { mode: 0o600 });
|
|
await Deno.symlink(canonical, linked);
|
|
|
|
await assertRejects(
|
|
() => persistCodexAuth(linked, canonical),
|
|
Error,
|
|
"regular file",
|
|
);
|
|
await assertRejects(
|
|
() => persistCodexAuth(malformed, canonical),
|
|
Error,
|
|
"invalid Codex login",
|
|
);
|
|
} finally {
|
|
await Deno.remove(root, { recursive: true });
|
|
}
|
|
});
|