Clarify server delete API errors
This commit is contained in:
@@ -810,6 +810,38 @@ describe("PlatformApiClient AI providers", () => {
|
||||
expect(onAuthFailure).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("surfaces safe validation details for delete denials", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({
|
||||
code: "validation_failed",
|
||||
message: "validation failed",
|
||||
details: [
|
||||
"running or installing server instances must be stopped before delete",
|
||||
"config path /Users/operator/private/server.ini is unavailable"
|
||||
]
|
||||
}), { status: 400, headers: { "Content-Type": "application/json" } })));
|
||||
|
||||
const client = new PlatformApiClient("/api/v1", () => "owner-session");
|
||||
await expect(client.deleteServerInstance("running-server", { password: "secret-password" })).rejects.toMatchObject({
|
||||
status: 400,
|
||||
code: "validation_failed",
|
||||
message: "运行中或安装中的服务器必须先停止再删除。;config path [host-path] is unavailable"
|
||||
});
|
||||
});
|
||||
|
||||
it("surfaces password confirmation denials without exposing generic forbidden text", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({
|
||||
code: "forbidden",
|
||||
message: "password confirmation failed"
|
||||
}), { status: 403, headers: { "Content-Type": "application/json" } })));
|
||||
|
||||
const client = new PlatformApiClient("/api/v1", () => "owner-session");
|
||||
await expect(client.deleteServerInstance("server-1", { password: "wrong-password" })).rejects.toMatchObject({
|
||||
status: 403,
|
||||
code: "forbidden",
|
||||
message: "当前登录密码不正确,请重新输入。"
|
||||
});
|
||||
});
|
||||
|
||||
it("surfaces allow-listed plugin capability denials", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn(async () => new Response(JSON.stringify({
|
||||
code: "forbidden",
|
||||
|
||||
@@ -752,7 +752,9 @@ async function responseError(response: Response): Promise<PlatformApiError> {
|
||||
const apiError = await safeReadError(response);
|
||||
const message = response.status === 401
|
||||
? "会话已失效,请重新登录。"
|
||||
: response.status === 403
|
||||
: apiError?.code === "validation_failed"
|
||||
? safeValidationMessage(apiError)
|
||||
: response.status === 403
|
||||
? safeForbiddenMessage(apiError?.message)
|
||||
: apiError?.message ?? `request failed: ${response.status}`;
|
||||
const error = new PlatformApiError(response.status, apiError?.code ?? "request_failed", message);
|
||||
@@ -763,11 +765,37 @@ async function responseError(response: Response): Promise<PlatformApiError> {
|
||||
return error;
|
||||
}
|
||||
|
||||
function safeValidationMessage(apiError?: ApiErrorResponse | null): string {
|
||||
const details = apiError?.details
|
||||
?.map((detail) => safeValidationDetail(detail))
|
||||
.filter((detail): detail is string => Boolean(detail));
|
||||
if (details?.length) {
|
||||
return details.slice(0, 3).join(";");
|
||||
}
|
||||
const sanitized = safeDiagnosticText(apiError?.message, "")?.trim();
|
||||
return sanitized || "请求校验失败。";
|
||||
}
|
||||
|
||||
function safeValidationDetail(detail: string): string | undefined {
|
||||
const sanitized = safeDiagnosticText(detail, "")?.trim();
|
||||
switch (sanitized) {
|
||||
case "password is required":
|
||||
return "请输入当前登录密码。";
|
||||
case "running or installing server instances must be stopped before delete":
|
||||
return "运行中或安装中的服务器必须先停止再删除。";
|
||||
default:
|
||||
return sanitized || undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function safeForbiddenMessage(apiMessage?: string): string {
|
||||
const sanitized = safeDiagnosticText(apiMessage, "")?.trim();
|
||||
if (!sanitized || sanitized === "account is not allowed to access this resource") {
|
||||
return "没有权限访问该资源。";
|
||||
}
|
||||
if (sanitized === "password confirmation failed") {
|
||||
return "当前登录密码不正确,请重新输入。";
|
||||
}
|
||||
const missingPermission = sanitized.match(/^plugin does not declare required permission:\s*([a-z0-9._-]+)$/i);
|
||||
if (missingPermission) {
|
||||
return `插件未声明所需权限:${missingPermission[1]}`;
|
||||
|
||||
Reference in New Issue
Block a user