fix(canvas): 画布删除 - 跳过关联素材预加载与合同校验

This commit is contained in:
ddCat
2026-09-19 08:43:19 +08:00
parent 4f75f2734a
commit 6b40fe12df
3 changed files with 57 additions and 5 deletions

View File

@@ -4,7 +4,7 @@ import { apiClient } from "../src/services/api/request";
import { canvasContentHash } from "../src/lib/canvas/canvas-content";
import { rebaseCanvasProjects, parseCanvasStorageDocument } from "../src/lib/canvas/canvas-storage-revision";
import { readCanvasSyncDrafts } from "../src/services/canvas-sync-drafts";
import { applyAgentCanvasPatches, refreshCanvasAfterAgent, initializeRemoteUserDataSession, installRemoteUserDataAutoSync, loadCanvasProjectForEditing, resetRemoteUserDataSync, saveRemoteUserDataNow, syncRemoteUserData } from "../src/services/user-data-sync";
import { applyAgentCanvasPatches, deleteCanvasProjectsWithRemoteSync, refreshCanvasAfterAgent, initializeRemoteUserDataSession, installRemoteUserDataAutoSync, loadCanvasProjectForEditing, resetRemoteUserDataSync, saveRemoteUserDataNow, syncRemoteUserData } from "../src/services/user-data-sync";
import { createAgentCanvasSync } from "../src/services/agent-canvas-sync";
import { flushCanvasStorePersistence, useCanvasStore, type CanvasProject } from "../src/stores/canvas/use-canvas-store";
import { flushAssetStorePersistence, useAssetStore } from "../src/stores/use-asset-store";
@@ -22,6 +22,7 @@ let autoSave: (() => void) | undefined;
let remote = new Map<string, CanvasProject>();
let requests: Array<{ method: string; id: string; project?: CanvasProject }> = [];
let beforePut: (() => Promise<void>) | undefined;
let deleteFailureId: string | undefined;
function canvas(id = "canvas"): CanvasProject {
return {
@@ -52,6 +53,7 @@ beforeEach(async () => {
requests = [];
autoSave = undefined;
beforePut = undefined;
deleteFailureId = undefined;
remote = new Map([
["canvas", canvas()],
["other", canvas("other")],
@@ -82,7 +84,19 @@ beforeEach(async () => {
let data: unknown;
let status = 200;
if (id === "snapshot") data = { projects: structuredClone([...remote.values()]), assets: [] };
else if (id === "restore" && method === "post") {
else if (method === "delete") {
if (id === deleteFailureId) status = 403;
else {
remote.delete(id);
data = { id };
}
} else if (id === "batch" && method === "post") {
data = { assets: [{
id: "invalid-mime-asset", kind: "image", title: "历史素材", coverUrl: "", tags: [],
createdAt: "2026-09-01", updatedAt: "2026-09-01",
data: { dataUrl: "https://example.com/image.png", width: 100, height: 100, bytes: 1, mimeType: "image/*" },
}] };
} else if (id === "restore" && method === "post") {
const current = remote.get("canvas")!;
if (body.revision !== current.revision) status = 409;
else {
@@ -119,6 +133,39 @@ afterEach(async () => {
else Object.defineProperty(globalThis, "window", { configurable: true, value: originalWindow });
});
test("deletion skips editing and invalid MIME assets, including an uncached canvas", async () => {
const project = canvas();
project.nodes[0].metadata = { assetId: "invalid-mime-asset" };
remote.set(project.id, project);
await initializeRemoteUserDataSession(scope);
await expect(loadCanvasProjectForEditing(project.id)).rejects.toThrow("具体 MIME");
useCanvasStore.getState().deleteProjects(["other"]);
await flushCanvasStorePersistence();
requests = [];
await deleteCanvasProjectsWithRemoteSync([" canvas ", "canvas", "other"]);
expect(requests.map(({ method, id }) => ({ method, id }))).toEqual([
{ method: "delete", id: "canvas" }, { method: "delete", id: "other" },
]);
expect(remote.size).toBe(0);
expect(useCanvasStore.getState().projects).toEqual([]);
await useCanvasStore.persist.rehydrate();
expect(useCanvasStore.getState().projects).toEqual([]);
});
test("batch deletion retains failed canvases and persists successful deletions", async () => {
await initializeRemoteUserDataSession(scope);
deleteFailureId = "other";
await expect(deleteCanvasProjectsWithRemoteSync(["canvas", "other"])).rejects.toThrow();
expect([...remote.keys()]).toEqual(["other"]);
expect(useCanvasStore.getState().projects.map((project) => project.id)).toEqual(["other"]);
await useCanvasStore.persist.rehydrate();
expect(useCanvasStore.getState().projects.map((project) => project.id)).toEqual(["other"]);
});
test("stale viewport, no-op restore and open do not submit old content", async () => {
const old = useCanvasStore.getState().openProject("canvas")!;
remote.set("canvas", { ...addNode(canvas(), "afternoon-video"), revision: 2 });