From 7f3ce7246851d26140274af26439d131470fcc33 Mon Sep 17 00:00:00 2001 From: 134 Date: Tue, 23 Jun 2026 13:21:31 +0800 Subject: [PATCH] test: cover stale canvas autosave conflicts --- app/api/canvas/[id]/route.ts | 30 +++++++-------------- lib/canvas/canvas-save-conflict.ts | 29 +++++++++++++++++++++ tests/routes/canvas-chat-helpers.test.ts | 33 ++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 lib/canvas/canvas-save-conflict.ts diff --git a/app/api/canvas/[id]/route.ts b/app/api/canvas/[id]/route.ts index 88af0bd0d..5b8f76aa8 100644 --- a/app/api/canvas/[id]/route.ts +++ b/app/api/canvas/[id]/route.ts @@ -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 { + 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",