Files
ttstd-infinite-canvas/web/test/resource-upload-boundary.test.ts
T

82 lines
3.5 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import axios from "axios";
import { apiClient } from "@/services/api/request";
import { ResourceUploadError, uploadResourceFile } from "@/services/api/resources";
type AdapterResult = { status: number; body: unknown };
// 直传失败的分类是这条边界的全部价值:permanent 决定调用方是当场报错还是退回本机等待重传。
async function uploadWith(result: AdapterResult | Error) {
const previous = apiClient.defaults.adapter;
apiClient.defaults.adapter = async (config) => {
if (result instanceof Error) throw result;
if (result.status >= 400) {
throw new axios.AxiosError(`Request failed with status code ${result.status}`, "ERR_BAD_REQUEST", config, undefined, {
data: result.body,
status: result.status,
statusText: "Error",
headers: {},
config,
});
}
return { data: result.body, status: result.status, statusText: "OK", headers: {}, config };
};
try {
return await uploadResourceFile(new Blob(["x"], { type: "image/png" }), "image", { fileName: "x.png" });
} finally {
apiClient.defaults.adapter = previous;
}
}
async function uploadFailure(result: AdapterResult | Error) {
try {
await uploadWith(result);
} catch (error) {
return error;
}
throw new Error("上传本应失败");
}
describe("资源直传失败分类", () => {
test("鉴权、越权与体积超限属于重试不会自愈的永久失败", async () => {
for (const status of [400, 401, 403, 404, 413, 415]) {
const error = await uploadFailure({ status, body: { code: status, data: null, msg: "" } });
expect(error).toBeInstanceOf(ResourceUploadError);
expect((error as ResourceUploadError).permanent).toBe(true);
expect((error as ResourceUploadError).status).toBe(status);
}
});
test("限流与服务端故障属于瞬时失败,允许退回本机后重传", async () => {
for (const status of [429, 500, 502, 503]) {
const error = await uploadFailure({ status, body: { code: status, data: null, msg: "" } });
expect(error).toBeInstanceOf(ResourceUploadError);
expect((error as ResourceUploadError).permanent).toBe(false);
}
});
test("断网等非 HTTP 失败按瞬时处理", async () => {
const error = await uploadFailure(new Error("Network Error"));
expect(error).toBeInstanceOf(ResourceUploadError);
expect((error as ResourceUploadError).permanent).toBe(false);
});
test("保留后端可读文案,并把 multipart 超限翻译成中文", async () => {
const quota = await uploadFailure({ status: 403, body: { code: 403, data: null, msg: "存储配额不足" } });
expect((quota as ResourceUploadError).message).toBe("存储配额不足");
const oversize = await uploadFailure({ status: 400, body: { code: 400, data: null, msg: "http: request body too large" } });
expect((oversize as ResourceUploadError).message).toContain("文件过大");
expect((oversize as ResourceUploadError).permanent).toBe(true);
});
test("成功响应仍返回后端资源本身", async () => {
const resource = await uploadWith({
status: 200,
body: { code: 0, msg: "", data: { resource: { id: "res-1", kind: "image", status: "ready", publicUrl: "", size: 1 } } },
});
expect(resource.id).toBe("res-1");
});
});