

























@@ -1327,6 +1327,192 @@ describe("codex command", () => {
13271327);
13281328});
132913291330+it("respects explicit Codex auth order over stale lastGood after OAuth re-login", async () => {
1331+const config = {};
1332+const now = Date.now();
1333+installAuthProfileStore(
1334+{
1335+version: 1,
1336+profiles: {
1337+"openai-codex:default": {
1338+type: "oauth",
1339+provider: "openai-codex",
1340+access: "stale-access-token",
1341+refresh: "stale-refresh-token",
1342+expires: now + 2 * 24 * 60 * 60 * 1000,
1343+email: "previous@example.com",
1344+},
1345+"openai-codex:fresh-email@example.com": {
1346+type: "oauth",
1347+provider: "openai-codex",
1348+access: "fresh-access-token",
1349+refresh: "fresh-refresh-token",
1350+expires: now + 9 * 24 * 60 * 60 * 1000,
1351+email: "fresh-email@example.com",
1352+},
1353+},
1354+order: {
1355+"openai-codex": ["openai-codex:fresh-email@example.com", "openai-codex:default"],
1356+},
1357+lastGood: {
1358+"openai-codex": "openai-codex:default",
1359+},
1360+},
1361+config,
1362+);
1363+1364+const safeCodexControlRequest = vi
1365+.fn()
1366+.mockResolvedValueOnce({
1367+ok: true,
1368+value: { account: { type: "unknown" }, requiresOpenaiAuth: true },
1369+})
1370+.mockResolvedValueOnce({
1371+ok: true,
1372+value: codexRateLimitPayload({
1373+primaryUsedPercent: 5,
1374+secondaryUsedPercent: 10,
1375+primaryResetSeconds: Math.ceil(now / 1000) + 60 * 60,
1376+secondaryResetSeconds: Math.ceil(now / 1000) + 6 * 60 * 60,
1377+}),
1378+});
1379+1380+const result = await handleCodexCommand(createContext("account", undefined, { config }), {
1381+deps: createDeps({ safeCodexControlRequest }),
1382+});
1383+1384+expect(result.text).toContain(
1385+"\n 1. fresh-email@example.com ChatGPT subscription — active now",
1386+);
1387+expect(result.text).toContain(
1388+"\n 2. previous@example.com ChatGPT subscription — available if needed",
1389+);
1390+expect(result.text).not.toContain("previous@example.com ChatGPT subscription — active now");
1391+expect(result.text).not.toContain("openai-codex:");
1392+expect(safeCodexControlRequest).toHaveBeenCalledTimes(2);
1393+});
1394+1395+it("respects openai-alias explicit order over stale lastGood for API key profiles", async () => {
1396+const config = {};
1397+const now = Date.now();
1398+installAuthProfileStore(
1399+{
1400+version: 1,
1401+profiles: {
1402+"openai:fresh-key": {
1403+type: "api_key",
1404+provider: "openai",
1405+key: "sk-fresh-111",
1406+},
1407+"openai:stale-key": {
1408+type: "api_key",
1409+provider: "openai",
1410+key: "sk-stale-222",
1411+},
1412+},
1413+order: {
1414+openai: ["openai:fresh-key", "openai:stale-key"],
1415+},
1416+lastGood: {
1417+openai: "openai:stale-key",
1418+},
1419+},
1420+config,
1421+);
1422+1423+const safeCodexControlRequest = vi
1424+.fn()
1425+.mockResolvedValueOnce({
1426+ok: true,
1427+value: { account: { type: "unknown" }, requiresOpenaiAuth: false },
1428+})
1429+.mockResolvedValueOnce({
1430+ok: false,
1431+error: "usage data unavailable",
1432+});
1433+1434+const result = await handleCodexCommand(createContext("account", undefined, { config }), {
1435+deps: createDeps({ safeCodexControlRequest }),
1436+});
1437+1438+expect(result.text).toContain("\n 1. fresh-key API key — active now");
1439+expect(result.text).not.toContain("stale-key API key — active now");
1440+expect(safeCodexControlRequest).toHaveBeenCalledTimes(2);
1441+});
1442+1443+it("does not mark any profile active when all explicit-order token credentials are expired", async () => {
1444+// Both profiles use type:"token" with expired expiry, so resolveAuthProfileEligibility
1445+// returns eligible=false for both. resolveActiveProfileId must return undefined rather
1446+// than marking an ineligible profile as active; the display shows "no working credential".
1447+const config = {};
1448+const now = Date.now();
1449+installAuthProfileStore(
1450+{
1451+version: 1,
1452+profiles: {
1453+"openai-codex:fresh@example.com": {
1454+type: "token",
1455+provider: "openai-codex",
1456+token: "fresh-token",
1457+expires: now - 1000,
1458+email: "fresh@example.com",
1459+},
1460+"openai-codex:stale@example.com": {
1461+type: "token",
1462+provider: "openai-codex",
1463+token: "stale-token",
1464+expires: now - 2000,
1465+email: "stale@example.com",
1466+},
1467+},
1468+order: {
1469+"openai-codex": ["openai-codex:fresh@example.com", "openai-codex:stale@example.com"],
1470+},
1471+lastGood: {
1472+"openai-codex": "openai-codex:stale@example.com",
1473+},
1474+},
1475+config,
1476+);
1477+1478+const safeCodexControlRequest = vi
1479+.fn()
1480+// call 1: account info for the active/first profile
1481+.mockResolvedValueOnce({
1482+ok: true,
1483+value: { account: { type: "unknown" }, requiresOpenaiAuth: true },
1484+})
1485+// call 2: rate limits for the active profile
1486+.mockResolvedValueOnce({
1487+ok: false,
1488+error: "rate limits unavailable",
1489+})
1490+// call 3: readSubscriptionUsage — no activeProfileId means the subscription
1491+// profile (fresh, type:"token") is fetched separately
1492+.mockResolvedValueOnce({
1493+ok: false,
1494+error: "subscription limits unavailable",
1495+});
1496+1497+const result = await handleCodexCommand(createContext("account", undefined, { config }), {
1498+deps: createDeps({ safeCodexControlRequest }),
1499+});
1500+1501+// With all credentials expired, no profile is active — the display shows
1502+// "no working credential" and both profiles are labelled "sign-in expired".
1503+// lastGood (stale) must not override the stated operator rank, and the
1504+// first explicit-order entry must not be falsely marked active when ineligible.
1505+expect(result.text).toContain("no working credential");
1506+expect(result.text).toContain(
1507+"\n 1. fresh@example.com ChatGPT subscription — sign-in expired",
1508+);
1509+expect(result.text).toContain(
1510+"\n 2. stale@example.com ChatGPT subscription — sign-in expired",
1511+);
1512+expect(result.text).not.toContain("active now");
1513+expect(safeCodexControlRequest).toHaveBeenCalledTimes(3);
1514+});
1515+13301516it("escapes successful Codex account fallback summaries before chat display", async () => {
13311517const unsafe = "<@U123> [trusted](https://evil) @here";
13321518const safeCodexControlRequest = vi
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。