test: cover stale canvas autosave conflicts
This commit is contained in:
parent
084a6225e7
commit
7f3ce72468
@ -4,6 +4,10 @@ import { db } from '@/lib/db';
|
||||
import { canvasProject } from '@/lib/db/schema';
|
||||
import { syncCanvasNodesForProject } from '@/lib/db/queries';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import {
|
||||
buildStaleCanvasSaveConflictPayload,
|
||||
isStaleCanvasSaveBaseRevision,
|
||||
} from '@/lib/canvas/canvas-save-conflict';
|
||||
|
||||
function resolveWorkspaceType(input: unknown) {
|
||||
return input === "agent" || input === "video" ? input : "canvas";
|
||||
@ -13,19 +17,6 @@ function getDefaultWorkspaceTitle(workspaceType: ReturnType<typeof resolveWorksp
|
||||
return workspaceType === "video" ? "未命名视频画布" : "未命名画布";
|
||||
}
|
||||
|
||||
function parseRevisionDate(value: unknown) {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw) return null;
|
||||
const parsed = new Date(raw);
|
||||
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
||||
}
|
||||
|
||||
function isStaleBaseRevision(baseRevision: unknown, currentUpdatedAt: Date) {
|
||||
const baseDate = parseRevisionDate(baseRevision);
|
||||
if (!baseDate) return false;
|
||||
return baseDate.getTime() + 1000 < currentUpdatedAt.getTime();
|
||||
}
|
||||
|
||||
// 获取单个画布
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
@ -95,15 +86,14 @@ export async function PUT(
|
||||
if (
|
||||
canvasData !== undefined &&
|
||||
baseCanvasRevision &&
|
||||
isStaleBaseRevision(baseCanvasRevision, existingProject.updatedAt)
|
||||
isStaleCanvasSaveBaseRevision({
|
||||
baseCanvasRevision: body?.baseCanvasRevision,
|
||||
baseCanvasUpdatedAt: body?.baseCanvasUpdatedAt,
|
||||
currentUpdatedAt: existingProject.updatedAt,
|
||||
})
|
||||
) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: "画布已更新,请刷新后再保存",
|
||||
code: "STALE_CANVAS_REVISION",
|
||||
canvasUpdatedAt: existingProject.updatedAt.toISOString(),
|
||||
},
|
||||
buildStaleCanvasSaveConflictPayload(existingProject.updatedAt),
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
|
||||
29
lib/canvas/canvas-save-conflict.ts
Normal file
29
lib/canvas/canvas-save-conflict.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export const STALE_CANVAS_REVISION_CODE = "STALE_CANVAS_REVISION";
|
||||
|
||||
export function parseCanvasRevisionDate(value: unknown) {
|
||||
const raw = String(value || "").trim();
|
||||
if (!raw) return null;
|
||||
const parsed = new Date(raw);
|
||||
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
||||
}
|
||||
|
||||
export function isStaleCanvasSaveBaseRevision(params: {
|
||||
baseCanvasRevision?: unknown;
|
||||
baseCanvasUpdatedAt?: unknown;
|
||||
currentUpdatedAt: Date;
|
||||
}) {
|
||||
const baseDate = parseCanvasRevisionDate(
|
||||
params.baseCanvasUpdatedAt || params.baseCanvasRevision
|
||||
);
|
||||
if (!baseDate) return false;
|
||||
return baseDate.getTime() + 1000 < params.currentUpdatedAt.getTime();
|
||||
}
|
||||
|
||||
export function buildStaleCanvasSaveConflictPayload(currentUpdatedAt: Date) {
|
||||
return {
|
||||
success: false,
|
||||
error: "画布已更新,请刷新后再保存",
|
||||
code: STALE_CANVAS_REVISION_CODE,
|
||||
canvasUpdatedAt: currentUpdatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
@ -64,6 +64,11 @@ import {
|
||||
getCanvasAgentOperationErrorMessage,
|
||||
normalizeCanvasAgentOperationErrorCode,
|
||||
} from "@/lib/canvas/agent-operation-errors";
|
||||
import {
|
||||
buildStaleCanvasSaveConflictPayload,
|
||||
isStaleCanvasSaveBaseRevision,
|
||||
parseCanvasRevisionDate,
|
||||
} from "@/lib/canvas/canvas-save-conflict";
|
||||
import {
|
||||
applyCanvasOperationPlanToCanvasData,
|
||||
CanvasAgentMutationError,
|
||||
@ -125,6 +130,34 @@ import type { CanvasAgentContext } from "@/types/canvas/agent-protocol";
|
||||
import type { ChatMessage } from "@/lib/types";
|
||||
|
||||
test.describe("canvas chat pure helpers", () => {
|
||||
test("rejects stale autosave bases without blocking fresh or missing revisions", () => {
|
||||
const currentUpdatedAt = new Date("2026-06-22T00:00:05.500Z");
|
||||
|
||||
expect(parseCanvasRevisionDate("not-a-date")).toBeNull();
|
||||
expect(
|
||||
isStaleCanvasSaveBaseRevision({
|
||||
baseCanvasRevision: "2026-06-22T00:00:00.000Z",
|
||||
currentUpdatedAt,
|
||||
})
|
||||
).toBe(true);
|
||||
expect(
|
||||
isStaleCanvasSaveBaseRevision({
|
||||
baseCanvasUpdatedAt: "2026-06-22T00:00:05.000Z",
|
||||
currentUpdatedAt,
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
isStaleCanvasSaveBaseRevision({
|
||||
currentUpdatedAt,
|
||||
})
|
||||
).toBe(false);
|
||||
expect(buildStaleCanvasSaveConflictPayload(currentUpdatedAt)).toMatchObject({
|
||||
success: false,
|
||||
code: "STALE_CANVAS_REVISION",
|
||||
canvasUpdatedAt: "2026-06-22T00:00:05.500Z",
|
||||
});
|
||||
});
|
||||
|
||||
const plannerContext: CanvasAgentContext = {
|
||||
version: 1,
|
||||
source: "canvas",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user