feat: migrate account usage to CLIProxyAPI
Frontend / quality (push) Canceled after 0s

This commit is contained in:
2026-09-08 09:56:08 +08:00
parent 0b2f969c6d
commit 8997886eef
35 changed files with 2778 additions and 1651 deletions
+61 -38
View File
@@ -3,7 +3,7 @@ import { expect, test, type Page } from "@playwright/test";
const responses: Record<string, unknown> = {
"system/status": {
initialized: true,
appServer: true,
cpa: true,
version: "0.3.0-beta.1",
},
"auth/me": { username: "admin" },
@@ -20,6 +20,7 @@ const responses: Record<string, unknown> = {
{
id: 1,
displayName: "默认账号",
authIndex: "index-default",
email: "test@example.com",
planType: "plus",
expectedKind: "personal",
@@ -56,7 +57,7 @@ async function openSettings(page: Page, version = "0.3.0-beta.1") {
await new Promise((resolve) => setTimeout(resolve, 40));
if (key === "system/status") {
await route.fulfill({
json: { initialized: true, appServer: true, version },
json: { initialized: true, cpa: true, version },
});
return;
}
@@ -159,7 +160,7 @@ test("shows every account as a collapsible summary on the overview", async ({
const key = new URL(request.url()).pathname.replace("/api/v1/", "");
if (key === "system/status")
return route.fulfill({
json: { initialized: true, appServer: true, version: "test" },
json: { initialized: true, cpa: true, version: "test" },
});
if (key === "auth/me")
return route.fulfill({ json: { username: "admin" } });
@@ -247,7 +248,7 @@ test("shows a read-only overview before login", async ({ page }) => {
const key = new URL(route.request().url()).pathname.replace("/api/v1/", "");
if (key === "system/status")
return route.fulfill({
json: { initialized: true, appServer: true, version: "test" },
json: { initialized: true, cpa: true, version: "test" },
});
if (key === "auth/me")
return route.fulfill({ status: 401, json: { error: "未登录" } });
@@ -354,7 +355,7 @@ test("keeps the authenticated brand on one line with a release version", async (
test("shows the build version before setup", async ({ page }) => {
await page.route("**/api/v1/system/status", (route) =>
route.fulfill({
json: { initialized: false, appServer: false, version: "test" },
json: { initialized: false, cpa: false, version: "test" },
}),
);
await page.goto("/");
@@ -370,7 +371,7 @@ test("shows the build version before setup", async ({ page }) => {
test("shows the build version on login", async ({ page }) => {
await page.route("**/api/v1/system/status", (route) =>
route.fulfill({
json: { initialized: true, appServer: false, version: "test" },
json: { initialized: true, cpa: false, version: "test" },
}),
);
await page.route("**/api/v1/auth/me", (route) =>
@@ -633,56 +634,50 @@ test("programmatic tab changes do not clamp an existing scroll position", async
.toEqual(before.content);
});
test("deleting a newly added account clears its device authorization", async ({
test("deleting a newly added account keeps the CPA credential external", async ({
page,
}) => {
let accounts: Array<(typeof responses.accounts)[number]> = [];
let createdAuthIndex = "";
await page.route("**/api/v1/**", async (route) => {
const request = route.request();
const key = new URL(request.url()).pathname.replace("/api/v1/", "");
if (key === "accounts" && request.method() === "GET")
return route.fulfill({ json: accounts });
if (key === "accounts" && request.method() === "POST") {
const body = request.postDataJSON() as { authIndex: string };
createdAuthIndex = body.authIndex;
const account = {
...responses.accounts[0],
id: 2,
displayName: "账号 1",
email: "",
connected: false,
validationStatus: "pending" as const,
displayName: "CPA 账号",
authIndex: body.authIndex,
};
accounts = [account];
return route.fulfill({ json: account });
}
if (key === "accounts/2/login/device")
return route.fulfill({
json: {
verificationUrl: "https://auth.openai.com/codex/device",
userCode: "PLTJ-7M6I6",
},
});
if (key === "accounts/2" && request.method() === "DELETE") {
accounts = [];
return route.fulfill({ json: { ok: true } });
}
return route.fulfill({ json: responses[key] ?? {} });
});
page.on("dialog", (dialog) => dialog.accept());
page.on("dialog", (dialog) => {
expect(dialog.message()).toContain("不会删除 CLIProxyAPI 中的凭据");
void dialog.accept();
});
await page.goto("/settings");
await page.getByRole("tab", { name: "Codex" }).click();
await page.getByLabel("CPA authIndex", { exact: true }).fill("index-new");
await page.getByRole("button", { name: "添加账号" }).click();
await expect(page.getByText("PLTJ-7M6I6")).toBeVisible();
await expect(
page.getByRole("link", { name: "https://auth.openai.com/codex/device" }),
).toBeVisible();
await expect.poll(() => createdAuthIndex).toBe("index-new");
await expect(page.getByLabel("“CPA 账号”的 CPA authIndex")).toHaveValue(
"index-new",
);
await page.getByRole("button", { name: "删除“账号 1”" }).click();
await expect(page.getByText("PLTJ-7M6I6")).toHaveCount(0);
await page.getByRole("button", { name: "删除“CPA 账号”" }).click();
await expect(
page.getByRole("link", { name: "https://auth.openai.com/codex/device" }),
).toHaveCount(0);
await expect(
page.getByText("尚未添加 Codex 账号", { exact: false }),
page.getByText("尚未绑定 CPA Codex 账号", { exact: false }),
).toBeVisible();
});
@@ -690,7 +685,7 @@ test("sets public visibility when adding and editing an account", async ({
page,
}) => {
let accounts: Array<(typeof responses.accounts)[number]> = [];
let createdBody: { publicVisible?: boolean } | undefined;
let createdBody: { authIndex?: string; publicVisible?: boolean } | undefined;
let updatedBody: { publicVisible?: boolean } | undefined;
await page.route("**/api/v1/**", async (route) => {
const request = route.request();
@@ -698,11 +693,15 @@ test("sets public visibility when adding and editing an account", async ({
if (key === "accounts" && request.method() === "GET")
return route.fulfill({ json: accounts });
if (key === "accounts" && request.method() === "POST") {
createdBody = request.postDataJSON() as { publicVisible?: boolean };
createdBody = request.postDataJSON() as {
authIndex?: string;
publicVisible?: boolean;
};
const account = {
...responses.accounts[0],
id: 2,
displayName: "账号 1",
authIndex: createdBody.authIndex ?? "",
email: "",
connected: false,
validationStatus: "pending" as const,
@@ -711,13 +710,6 @@ test("sets public visibility when adding and editing an account", async ({
accounts = [account];
return route.fulfill({ json: account });
}
if (key === "accounts/2/login/device")
return route.fulfill({
json: {
verificationUrl: "https://auth.openai.com/codex/device",
userCode: "PLTJ-7M6I6",
},
});
if (key === "accounts/2" && request.method() === "PUT") {
updatedBody = request.postDataJSON() as { publicVisible?: boolean };
accounts = [
@@ -738,8 +730,10 @@ test("sets public visibility when adding and editing an account", async ({
);
await expect(addVisibility).not.toBeChecked();
await addVisibility.check();
await page.getByLabel("CPA authIndex", { exact: true }).fill("index-public");
await page.getByRole("button", { name: "添加账号" }).click();
await expect.poll(() => createdBody?.publicVisible).toBe(true);
expect(createdBody?.authIndex).toBe("index-public");
const accountCard = page.locator(".account-card");
const accountVisibility = accountCard.locator('input[type="checkbox"]');
@@ -751,6 +745,35 @@ test("sets public visibility when adding and editing an account", async ({
await expect(accountVisibility).not.toBeChecked();
});
test("changing authIndex confirms local history deletion", async ({ page }) => {
let account = { ...responses.accounts[0] };
let updatedBody: { authIndex?: string } | undefined;
await page.route("**/api/v1/**", async (route) => {
const request = route.request();
const key = new URL(request.url()).pathname.replace("/api/v1/", "");
if (key === "accounts" && request.method() === "GET")
return route.fulfill({ json: [account] });
if (key === "accounts/1" && request.method() === "PUT") {
updatedBody = request.postDataJSON() as { authIndex?: string };
account = { ...account, authIndex: updatedBody.authIndex ?? "" };
return route.fulfill({ json: { ok: true } });
}
return route.fulfill({ json: responses[key] ?? {} });
});
page.on("dialog", (dialog) => {
expect(dialog.message()).toContain("会清除该本地账号已有的 Token 历史");
void dialog.accept();
});
await page.goto("/settings");
await page.getByRole("tab", { name: "Codex" }).click();
const input = page.getByLabel("“默认账号”的 CPA authIndex");
await input.fill("index-rebound");
await input.blur();
await expect.poll(() => updatedBody?.authIndex).toBe("index-rebound");
await expect(input).toHaveValue("index-rebound");
});
test("Codex account cards fit within the viewport", async ({ page }) => {
await openSettings(page);
await page.getByRole("tab", { name: "Codex" }).click();