Files
2026-07-13 13:39:12 +08:00

246 lines
8.4 KiB
TypeScript

/**
* Unit tests for the Codex bulk-import normalizer.
*
* Pure helpers — no I/O, no network. Synthetic id_tokens are crafted by
* base64url-encoding a header/payload/signature triple (only the payload is
* decoded; we never verify the signature).
*
* Ported from decolua/9router#1257 (beaaan).
*/
import test, { describe } from "node:test";
import assert from "node:assert/strict";
import {
normalizeCodexImportRecord,
flattenCodexImportPayload,
} from "../../src/lib/oauth/services/codexImport.ts";
// ── Helpers ───────────────────────────────────────────────────────────────────
function b64url(obj: unknown): string {
return Buffer.from(JSON.stringify(obj))
.toString("base64")
.replace(/=+$/, "")
.replace(/\+/g, "-")
.replace(/\//g, "_");
}
function makeIdToken(payload: Record<string, unknown>): string {
const header = b64url({ alg: "RS256", typ: "JWT" });
const body = b64url(payload);
return `${header}.${body}.signature`;
}
const FULL_RECORD = {
id_token: makeIdToken({
email: "test-jwt@example.com",
"https://api.openai.com/auth": {
chatgpt_account_id: "acct-from-jwt",
chatgpt_plan_type: "plus",
},
}),
access_token: "access-xyz",
refresh_token: "refresh-xyz",
account_id: "acct-from-record",
email: "test-record@example.com",
type: "codex",
expired: "2026-05-22T10:34:04+07:00",
};
// ── normalizeCodexImportRecord ────────────────────────────────────────────────
describe("normalizeCodexImportRecord", () => {
test("normalizes a full Codex export record", () => {
const result = normalizeCodexImportRecord(FULL_RECORD);
assert.equal(result.ok, true);
if (!result.ok) return;
const { payload } = result;
assert.equal(payload.provider, "codex");
assert.equal(payload.authType, "oauth");
assert.equal(payload.accessToken, "access-xyz");
assert.equal(payload.refreshToken, "refresh-xyz");
assert.equal(payload.idToken, FULL_RECORD.id_token);
assert.equal(payload.testStatus, "active");
// JWT email wins over the record-level email.
assert.equal(payload.email, "test-jwt@example.com");
assert.deepEqual(payload.providerSpecificData, {
chatgptAccountId: "acct-from-jwt",
chatgptPlanType: "plus",
});
// ISO-formatted, parses back to the right instant.
const expiresMs = Date.parse(payload.expiresAt);
assert.ok(Number.isFinite(expiresMs));
assert.equal(expiresMs, Date.parse(FULL_RECORD.expired));
});
test("falls back to record email when id_token has none", () => {
const idToken = makeIdToken({
"https://api.openai.com/auth": { chatgpt_account_id: "x" },
});
const result = normalizeCodexImportRecord({
access_token: "a",
refresh_token: "r",
email: "fallback@example.com",
id_token: idToken,
});
assert.equal(result.ok, true);
if (!result.ok) return;
assert.equal(result.payload.email, "fallback@example.com");
assert.equal(result.payload.providerSpecificData?.chatgptAccountId, "x");
});
test("falls back to account_id when id_token is missing", () => {
const result = normalizeCodexImportRecord({
access_token: "a",
refresh_token: "r",
email: "no-jwt@example.com",
account_id: "acct-top-level",
});
assert.equal(result.ok, true);
if (!result.ok) return;
assert.equal(result.payload.providerSpecificData?.chatgptAccountId, "acct-top-level");
assert.equal(result.payload.idToken, undefined);
});
test("synthesizes expiresAt when `expired` is missing", () => {
const before = Date.now();
const result = normalizeCodexImportRecord({
access_token: "a",
refresh_token: "r",
email: "x@example.com",
});
const after = Date.now();
assert.equal(result.ok, true);
if (!result.ok) return;
const expiresMs = Date.parse(result.payload.expiresAt);
assert.ok(expiresMs > before, "expiresAt should be in the future");
// Default lifetime is 10 days; allow a generous upper bound.
assert.ok(
expiresMs <= after + 11 * 24 * 60 * 60 * 1000,
"expiresAt should be within ~11 days",
);
});
test("rejects invalid `expired` strings by falling back to default", () => {
const result = normalizeCodexImportRecord({
access_token: "a",
refresh_token: "r",
email: "x@example.com",
expired: "not-a-date",
});
assert.equal(result.ok, true);
if (!result.ok) return;
assert.ok(Number.isFinite(Date.parse(result.payload.expiresAt)));
});
test("rejects records missing required fields", () => {
assert.equal(normalizeCodexImportRecord({}).ok, false);
assert.equal(
normalizeCodexImportRecord({ access_token: "a", email: "x@y.z" }).ok,
false,
);
assert.equal(
normalizeCodexImportRecord({ access_token: "a", refresh_token: "r" }).ok,
false, // no email anywhere
);
});
test("rejects records with non-codex `type`", () => {
const result = normalizeCodexImportRecord({
access_token: "a",
refresh_token: "r",
email: "x@example.com",
type: "claude",
});
assert.equal(result.ok, false);
if (result.ok) return;
assert.match(result.error, /Unsupported type/);
});
test("rejects non-objects", () => {
assert.equal(normalizeCodexImportRecord(null).ok, false);
assert.equal(normalizeCodexImportRecord("foo").ok, false);
assert.equal(normalizeCodexImportRecord([]).ok, false);
});
test("omits providerSpecificData when no account info is present", () => {
const result = normalizeCodexImportRecord({
access_token: "a",
refresh_token: "r",
email: "x@example.com",
});
assert.equal(result.ok, true);
if (!result.ok) return;
assert.equal(result.payload.providerSpecificData, undefined);
});
test("unwraps the Codex CLI auth.json shape", () => {
const idToken = makeIdToken({
email: "cli@example.com",
"https://api.openai.com/auth": {
chatgpt_account_id: "acct-cli",
chatgpt_plan_type: "free",
},
});
// Access token's `exp` is the only expiry signal in auth.json.
const expEpoch = Math.floor(Date.now() / 1000) + 3600;
const accessToken = makeIdToken({ exp: expEpoch });
const authJson = {
auth_mode: "chatgpt",
OPENAI_API_KEY: null,
tokens: {
id_token: idToken,
access_token: accessToken,
refresh_token: "rt-cli",
account_id: "acct-cli",
},
last_refresh: "2026-05-16T11:35:58.322795500Z",
};
const result = normalizeCodexImportRecord(authJson);
assert.equal(result.ok, true);
if (!result.ok) return;
assert.equal(result.payload.accessToken, accessToken);
assert.equal(result.payload.refreshToken, "rt-cli");
assert.equal(result.payload.idToken, idToken);
assert.equal(result.payload.email, "cli@example.com");
assert.deepEqual(result.payload.providerSpecificData, {
chatgptAccountId: "acct-cli",
chatgptPlanType: "free",
});
// Falls back to the access_token's `exp` claim when no `expired` field exists.
assert.equal(Date.parse(result.payload.expiresAt), expEpoch * 1000);
});
test("rejects auth.json when nested tokens are incomplete", () => {
const result = normalizeCodexImportRecord({
auth_mode: "chatgpt",
tokens: { id_token: "x" }, // missing access_token + refresh_token
});
assert.equal(result.ok, false);
});
});
// ── flattenCodexImportPayload ─────────────────────────────────────────────────
describe("flattenCodexImportPayload", () => {
test("wraps a single object", () => {
const result = flattenCodexImportPayload({ a: 1 });
assert.equal(result.ok, true);
if (!result.ok) return;
assert.deepEqual(result.records, [{ a: 1 }]);
});
test("passes arrays through", () => {
const result = flattenCodexImportPayload([{ a: 1 }, { b: 2 }]);
assert.equal(result.ok, true);
if (!result.ok) return;
assert.deepEqual(result.records, [{ a: 1 }, { b: 2 }]);
});
test("rejects scalars", () => {
assert.equal(flattenCodexImportPayload("nope").ok, false);
assert.equal(flattenCodexImportPayload(42).ok, false);
assert.equal(flattenCodexImportPayload(null).ok, false);
});
});