fix(e2e): 修复API测试失败 - 错误响应格式适配 + 登录限流自动重试
Deploy / Deploy Staging (push) Failing after 125h45m35s
CI/CD Pipeline / Frontend Lint (push) Failing after 125h47m13s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 125h47m13s
Deploy / Production Browser E2E (push) Failing after 1784h20m49s
Deploy / Staging E2E Tests (push) Failing after 1784h20m51s
Deploy / Build Production Runtime Images (push) Failing after 1784h20m54s
Deploy / Deploy Production (push) Failing after 1784h52m17s
Deploy / Deploy Staging (push) Failing after 125h45m35s
CI/CD Pipeline / Frontend Lint (push) Failing after 125h47m13s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 125h47m13s
Deploy / Production Browser E2E (push) Failing after 1784h20m49s
Deploy / Staging E2E Tests (push) Failing after 1784h20m51s
Deploy / Build Production Runtime Images (push) Failing after 1784h20m54s
Deploy / Deploy Production (push) Failing after 1784h52m17s
1. 错误响应格式适配:后端使用自定义异常处理器返回
{ error: { code, message } } 格式,test_auth的错误消息断言
增加 extractErrorMessage 兼容新老格式
2. 登录限流自动重试:所有 E2E 测试文件增加 loginWithRetry 辅助函数,
遇到 429 RATE_LIMIT_EXCEEDED 自动等待 65s 后重试(最多2次),
解决 staging E2E 批量测试触发登录限流(10次/60s)导致的失败
This commit is contained in:
@@ -19,6 +19,26 @@ const routeBrowserApiToTestApi = async (
|
||||
});
|
||||
};
|
||||
|
||||
/** 登录操作,遇到 429 限流自动等待重试 */
|
||||
async function loginWithRetry(
|
||||
request: any,
|
||||
email: string,
|
||||
password: string,
|
||||
maxRetries = 2,
|
||||
) {
|
||||
for (let i = 0; i <= maxRetries; i++) {
|
||||
const response = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
if (response.status() !== 429) return response;
|
||||
console.log(`[login] 触发限流,等待 65s 后重试 (${i + 1}/${maxRetries})`);
|
||||
await new Promise((r) => setTimeout(r, 65000));
|
||||
}
|
||||
return request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
}
|
||||
|
||||
type ProjectResponse = { id: string };
|
||||
type LibraryResponse = { id: string };
|
||||
type AssetListResponse = {
|
||||
@@ -60,9 +80,7 @@ test.describe("Core generation flow", () => {
|
||||
const registerData = (await register.json()) as { user_id: string };
|
||||
|
||||
// Login
|
||||
const login = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password: PASSWORD },
|
||||
});
|
||||
const login = await loginWithRetry(request, email, PASSWORD);
|
||||
expect(login.status(), await login.text()).toBe(200);
|
||||
const loginData = (await login.json()) as { access_token: string };
|
||||
const headers = { Authorization: `Bearer ${loginData.access_token}` };
|
||||
@@ -226,9 +244,7 @@ test.describe("Core generation flow", () => {
|
||||
});
|
||||
expect(register.status()).toBe(201);
|
||||
|
||||
const login = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password: PASSWORD },
|
||||
});
|
||||
const login = await loginWithRetry(request, email, PASSWORD);
|
||||
expect(login.status()).toBe(200);
|
||||
const loginData = (await login.json()) as { access_token: string };
|
||||
const headers = { Authorization: `Bearer ${loginData.access_token}` };
|
||||
|
||||
@@ -19,6 +19,26 @@ const routeBrowserApiToTestApi = async (
|
||||
});
|
||||
};
|
||||
|
||||
/** 登录操作,遇到 429 限流自动等待重试 */
|
||||
async function loginWithRetry(
|
||||
request: any,
|
||||
email: string,
|
||||
password: string,
|
||||
maxRetries = 2,
|
||||
) {
|
||||
for (let i = 0; i <= maxRetries; i++) {
|
||||
const response = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
if (response.status() !== 429) return response;
|
||||
console.log(`[login] 触发限流,等待 65s 后重试 (${i + 1}/${maxRetries})`);
|
||||
await new Promise((r) => setTimeout(r, 65000));
|
||||
}
|
||||
return request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
}
|
||||
|
||||
test.describe("Title library flow", () => {
|
||||
test("loads title library page and displays titles", async ({
|
||||
page,
|
||||
@@ -36,9 +56,7 @@ test.describe("Title library flow", () => {
|
||||
|
||||
const registerData = (await register.json()) as { user_id: string };
|
||||
|
||||
const login = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password: PASSWORD },
|
||||
});
|
||||
const login = await loginWithRetry(request, email, PASSWORD);
|
||||
expect(login.status(), await login.text()).toBe(200);
|
||||
const loginData = (await login.json()) as { access_token: string };
|
||||
|
||||
|
||||
@@ -19,6 +19,26 @@ const routeBrowserApiToTestApi = async (
|
||||
});
|
||||
};
|
||||
|
||||
/** 登录操作,遇到 429 限流自动等待重试 */
|
||||
async function loginWithRetry(
|
||||
request: any,
|
||||
email: string,
|
||||
password: string,
|
||||
maxRetries = 2,
|
||||
) {
|
||||
for (let i = 0; i <= maxRetries; i++) {
|
||||
const response = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
if (response.status() !== 429) return response;
|
||||
console.log(`[login] 触发限流,等待 65s 后重试 (${i + 1}/${maxRetries})`);
|
||||
await new Promise((r) => setTimeout(r, 65000));
|
||||
}
|
||||
return request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
}
|
||||
|
||||
type ProjectResponse = { id: string };
|
||||
type LibraryResponse = { id: string };
|
||||
|
||||
@@ -46,9 +66,7 @@ test.describe("Core media upload flow", () => {
|
||||
|
||||
const registerData = (await register.json()) as { user_id: string };
|
||||
|
||||
const login = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password: PASSWORD },
|
||||
});
|
||||
const login = await loginWithRetry(request, email, PASSWORD);
|
||||
expect(login.status(), await login.text()).toBe(200);
|
||||
const loginData = (await login.json()) as { access_token: string };
|
||||
const headers = { Authorization: `Bearer ${loginData.access_token}` };
|
||||
|
||||
@@ -16,6 +16,26 @@ function uniqueUsername(prefix: string): string {
|
||||
return `${prefix}_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
||||
}
|
||||
|
||||
/** 登录操作,遇到 429 限流自动等待重试 */
|
||||
async function loginWithRetry(
|
||||
request: any,
|
||||
email: string,
|
||||
password: string,
|
||||
maxRetries = 2,
|
||||
) {
|
||||
for (let i = 0; i <= maxRetries; i++) {
|
||||
const response = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
if (response.status() !== 429) return response;
|
||||
console.log(`[login] 触发限流,等待 65s 后重试 (${i + 1}/${maxRetries})`);
|
||||
await new Promise((r) => setTimeout(r, 65000));
|
||||
}
|
||||
return request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
}
|
||||
|
||||
/** 注册并登录,返回 { headers, email, username, userId } */
|
||||
async function createAuthedUser(request: any, label: string) {
|
||||
const email = uniqueEmail(label);
|
||||
@@ -26,9 +46,7 @@ async function createAuthedUser(request: any, label: string) {
|
||||
});
|
||||
expect(reg.ok(), `注册应成功: ${await reg.text()}`).toBeTruthy();
|
||||
|
||||
const login = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password: PASSWORD },
|
||||
});
|
||||
const login = await loginWithRetry(request, email, PASSWORD);
|
||||
expect(login.ok(), `登录应成功: ${await login.text()}`).toBeTruthy();
|
||||
const loginData = await login.json();
|
||||
|
||||
|
||||
@@ -17,6 +17,26 @@ function uniqueUsername(prefix: string): string {
|
||||
return `${prefix}_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
||||
}
|
||||
|
||||
/** 登录操作,遇到 429 限流自动等待重试 */
|
||||
async function loginWithRetry(
|
||||
request: any,
|
||||
email: string,
|
||||
password: string,
|
||||
maxRetries = 2,
|
||||
) {
|
||||
for (let i = 0; i <= maxRetries; i++) {
|
||||
const response = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
if (response.status() !== 429) return response;
|
||||
console.log(`[login] 触发限流,等待 65s 后重试 (${i + 1}/${maxRetries})`);
|
||||
await new Promise((r) => setTimeout(r, 65000));
|
||||
}
|
||||
return request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
}
|
||||
|
||||
/** 注册并登录,返回 { headers, email, username, userId } */
|
||||
async function createAuthedUser(request: any, label: string) {
|
||||
const email = uniqueEmail(label);
|
||||
@@ -28,9 +48,7 @@ async function createAuthedUser(request: any, label: string) {
|
||||
expect(reg.ok(), `注册应成功: ${await reg.text()}`).toBeTruthy();
|
||||
const regData = await reg.json();
|
||||
|
||||
const login = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password: PASSWORD },
|
||||
});
|
||||
const login = await loginWithRetry(request, email, PASSWORD);
|
||||
expect(login.ok(), `登录应成功: ${await login.text()}`).toBeTruthy();
|
||||
const loginData = await login.json();
|
||||
|
||||
|
||||
@@ -17,6 +17,39 @@ function uniqueUsername(prefix: string): string {
|
||||
return `${prefix}_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
||||
}
|
||||
|
||||
/** 从错误响应中提取错误消息文本,兼容新老格式 */
|
||||
function extractErrorMessage(body: any): string {
|
||||
if (!body) return "";
|
||||
// 新格式: { error: { code: "...", message: "..." } }
|
||||
if (body.error && typeof body.error === "object" && body.error.message) {
|
||||
return String(body.error.message);
|
||||
}
|
||||
// 老格式: { detail: "..." } 或 { message: "..." } 或 { error: "..." }
|
||||
return String(body.detail || body.message || body.error || "");
|
||||
}
|
||||
|
||||
/** 登录操作,遇到 429 限流自动等待重试(最多等 65s) */
|
||||
async function loginWithRetry(
|
||||
request: any,
|
||||
email: string,
|
||||
password: string,
|
||||
maxRetries = 2,
|
||||
) {
|
||||
for (let i = 0; i <= maxRetries; i++) {
|
||||
const response = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
if (response.status() !== 429) return response;
|
||||
// 被限流了,等窗口重置
|
||||
console.log(`[login] 触发限流,等待 65s 后重试 (${i + 1}/${maxRetries})`);
|
||||
await new Promise((r) => setTimeout(r, 65000));
|
||||
}
|
||||
// 最后一次直接返回
|
||||
return request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
}
|
||||
|
||||
test.describe("认证流程", () => {
|
||||
// ─── 注册 ────────────────────────────────────────────
|
||||
|
||||
@@ -77,9 +110,7 @@ test.describe("认证流程", () => {
|
||||
|
||||
const body = await second.json();
|
||||
// 错误信息应包含"已注册"或"exists"相关提示
|
||||
const detail = (body.detail || body.message || body.error || "")
|
||||
.toString()
|
||||
.toLowerCase();
|
||||
const detail = extractErrorMessage(body).toLowerCase();
|
||||
expect(
|
||||
detail.includes("已") ||
|
||||
detail.includes("exist") ||
|
||||
@@ -128,10 +159,8 @@ test.describe("认证流程", () => {
|
||||
});
|
||||
expect(reg.ok(), "注册应成功").toBeTruthy();
|
||||
|
||||
// 登录
|
||||
const response = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password: PASSWORD },
|
||||
});
|
||||
// 登录(带限流重试)
|
||||
const response = await loginWithRetry(request, email, PASSWORD);
|
||||
|
||||
expect(
|
||||
response.ok(),
|
||||
@@ -153,10 +182,8 @@ test.describe("认证流程", () => {
|
||||
data: { email, password: PASSWORD, username, display_name: "Bad Login" },
|
||||
});
|
||||
|
||||
// 使用错误密码登录
|
||||
const response = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password: "WrongPassword999!" },
|
||||
});
|
||||
// 使用错误密码登录(带限流重试)
|
||||
const response = await loginWithRetry(request, email, "WrongPassword999!");
|
||||
|
||||
expect(response.status(), "错误密码应返回 401").toBe(401);
|
||||
});
|
||||
@@ -184,9 +211,7 @@ test.describe("认证流程", () => {
|
||||
display_name: "Logout Test",
|
||||
},
|
||||
});
|
||||
const login = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password: PASSWORD },
|
||||
});
|
||||
const login = await loginWithRetry(request, email, PASSWORD);
|
||||
const { access_token } = await login.json();
|
||||
const headers = { Authorization: `Bearer ${access_token}` };
|
||||
|
||||
@@ -214,9 +239,7 @@ test.describe("认证流程", () => {
|
||||
await request.post(`${apiBase}/auth/register`, {
|
||||
data: { email, password: PASSWORD, username, display_name: "Me Test" },
|
||||
});
|
||||
const login = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password: PASSWORD },
|
||||
});
|
||||
const login = await loginWithRetry(request, email, PASSWORD);
|
||||
const { access_token } = await login.json();
|
||||
|
||||
const response = await request.get(`${apiBase}/auth/me`, {
|
||||
|
||||
@@ -17,6 +17,26 @@ function uniqueUsername(prefix: string): string {
|
||||
return `${prefix}_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
||||
}
|
||||
|
||||
/** 登录操作,遇到 429 限流自动等待重试 */
|
||||
async function loginWithRetry(
|
||||
request: any,
|
||||
email: string,
|
||||
password: string,
|
||||
maxRetries = 2,
|
||||
) {
|
||||
for (let i = 0; i <= maxRetries; i++) {
|
||||
const response = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
if (response.status() !== 429) return response;
|
||||
console.log(`[login] 触发限流,等待 65s 后重试 (${i + 1}/${maxRetries})`);
|
||||
await new Promise((r) => setTimeout(r, 65000));
|
||||
}
|
||||
return request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password },
|
||||
});
|
||||
}
|
||||
|
||||
/** 注册并登录,返回 { headers, email, username, userId } */
|
||||
async function createAuthedUser(request: any, label: string) {
|
||||
const email = uniqueEmail(label);
|
||||
@@ -28,9 +48,7 @@ async function createAuthedUser(request: any, label: string) {
|
||||
expect(reg.ok(), `注册应成功: ${await reg.text()}`).toBeTruthy();
|
||||
const regData = await reg.json();
|
||||
|
||||
const login = await request.post(`${apiBase}/auth/login`, {
|
||||
data: { email, password: PASSWORD },
|
||||
});
|
||||
const login = await loginWithRetry(request, email, PASSWORD);
|
||||
expect(login.ok(), `登录应成功: ${await login.text()}`).toBeTruthy();
|
||||
const loginData = await login.json();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user