diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-02-01 12:15:37 +0100 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-02-01 12:15:37 +0100 |
| commit | 2759d7ed31587efa994be20ee3f2df1a90bca0bd (patch) | |
| tree | 1ca72dc05408c507f86ab9e17801a2ea033195a3 /apps/web/src/services/storage/migrations/__tests__ | |
| parent | 03c2b16064833c85ca8ff74bff7510947728f07b (diff) | |
| download | opencut-2759d7ed31587efa994be20ee3f2df1a90bca0bd.tar.gz | |
add tests for storage migrations
Diffstat (limited to 'apps/web/src/services/storage/migrations/__tests__')
8 files changed, 823 insertions, 0 deletions
diff --git a/apps/web/src/services/storage/migrations/__tests__/fixtures/index.ts b/apps/web/src/services/storage/migrations/__tests__/fixtures/index.ts new file mode 100644 index 00000000..ee1485ba --- /dev/null +++ b/apps/web/src/services/storage/migrations/__tests__/fixtures/index.ts @@ -0,0 +1,9 @@ +/** + * Test fixtures for storage migrations. + * These represent real project data shapes from each version. + */ + +export * from "./v0"; +export * from "./v1"; +export * from "./v2"; +export * from "./v3"; diff --git a/apps/web/src/services/storage/migrations/__tests__/fixtures/v0.ts b/apps/web/src/services/storage/migrations/__tests__/fixtures/v0.ts new file mode 100644 index 00000000..24428cfe --- /dev/null +++ b/apps/web/src/services/storage/migrations/__tests__/fixtures/v0.ts @@ -0,0 +1,71 @@ +export const v0Project = { + id: "project-v0-123", + name: "My V0 Project", + createdAt: "2024-01-15T10:00:00.000Z", + updatedAt: "2024-01-15T12:00:00.000Z", + fps: 30, + canvasSize: { width: 1920, height: 1080 }, + backgroundColor: "#000000", + backgroundType: "color", + tracks: [ + { + id: "track-1", + type: "video", + name: "Video Track", + elements: [ + { + id: "element-1", + type: "video", + mediaId: "media-1", + startTime: 0, + duration: 5, + trimStart: 0, + trimEnd: 0, + }, + ], + }, + ], + bookmarks: [1.5, 3.0], +}; + +export const v0ProjectWithMetadata = { + id: "project-v0-456", + metadata: { + id: "project-v0-456", + name: "V0 With Metadata", + createdAt: "2024-02-01T08:00:00.000Z", + updatedAt: "2024-02-01T09:00:00.000Z", + }, + fps: 24, + canvasSize: { width: 1280, height: 720 }, + backgroundType: "blur", + blurIntensity: 20, +}; + +export const v0ProjectEmpty = { + id: "project-empty", + name: "Empty Project", + createdAt: "2024-03-01T00:00:00.000Z", + updatedAt: "2024-03-01T00:00:00.000Z", +}; + +// Edge cases +export const projectWithNoId = { + name: "No ID Project", + version: 1, + scenes: [], +}; + +export const projectWithNullValues = { + id: "project-nulls", + version: 1, + name: null, + metadata: null, + scenes: null, + settings: null, +}; + +export const projectMalformed = { + id: "project-malformed", + // Missing almost everything +}; diff --git a/apps/web/src/services/storage/migrations/__tests__/fixtures/v1.ts b/apps/web/src/services/storage/migrations/__tests__/fixtures/v1.ts new file mode 100644 index 00000000..93904947 --- /dev/null +++ b/apps/web/src/services/storage/migrations/__tests__/fixtures/v1.ts @@ -0,0 +1,124 @@ +export const v1Project = { + id: "project-v1-123", + version: 1, + name: "My V1 Project", + createdAt: "2024-01-15T10:00:00.000Z", + updatedAt: "2024-01-15T12:00:00.000Z", + fps: 30, + canvasSize: { width: 1920, height: 1080 }, + backgroundColor: "#1a1a1a", + backgroundType: "color", + currentSceneId: "scene-main", + bookmarks: [2.0, 4.5, 7.0], + scenes: [ + { + id: "scene-main", + name: "Main scene", + isMain: true, + tracks: [ + { + id: "track-1", + type: "video", + name: "Video Track", + isMain: true, + elements: [ + { + id: "element-1", + type: "video", + mediaId: "media-1", + startTime: 0, + duration: 10, + trimStart: 0, + trimEnd: 0, + }, + ], + }, + { + id: "track-2", + type: "audio", + name: "Audio Track", + elements: [ + { + id: "element-2", + type: "audio", + sourceType: "upload", + mediaId: "media-2", + startTime: 0, + duration: 10, + trimStart: 0, + trimEnd: 0, + }, + ], + }, + ], + bookmarks: [], + createdAt: "2024-01-15T10:00:00.000Z", + updatedAt: "2024-01-15T12:00:00.000Z", + }, + ], +}; + +export const v1ProjectWithMultipleScenes = { + id: "project-v1-multi", + version: 1, + metadata: { + id: "project-v1-multi", + name: "Multi-Scene Project", + createdAt: "2024-02-20T14:00:00.000Z", + updatedAt: "2024-02-20T16:00:00.000Z", + }, + currentSceneId: "scene-1", + fps: 60, + canvasSize: { width: 3840, height: 2160 }, + background: { type: "blur", blurIntensity: 15 }, + scenes: [ + { + id: "scene-1", + name: "Intro", + isMain: true, + tracks: [ + { + id: "track-1", + type: "video", + isMain: true, + elements: [ + { + id: "el-1", + type: "video", + mediaId: "m1", + startTime: 0, + duration: 5, + }, + ], + }, + ], + bookmarks: [1.0], + createdAt: "2024-02-20T14:00:00.000Z", + updatedAt: "2024-02-20T16:00:00.000Z", + }, + { + id: "scene-2", + name: "Content", + isMain: false, + tracks: [ + { + id: "track-2", + type: "video", + isMain: true, + elements: [ + { + id: "el-2", + type: "video", + mediaId: "m2", + startTime: 0, + duration: 20, + }, + ], + }, + ], + bookmarks: [], + createdAt: "2024-02-20T14:30:00.000Z", + updatedAt: "2024-02-20T16:00:00.000Z", + }, + ], +};
\ No newline at end of file diff --git a/apps/web/src/services/storage/migrations/__tests__/fixtures/v2.ts b/apps/web/src/services/storage/migrations/__tests__/fixtures/v2.ts new file mode 100644 index 00000000..58813a9e --- /dev/null +++ b/apps/web/src/services/storage/migrations/__tests__/fixtures/v2.ts @@ -0,0 +1,121 @@ +export const v2Project = { + id: "project-v2-123", + version: 2, + metadata: { + id: "project-v2-123", + name: "My V2 Project", + thumbnail: "data:image/png;base64,abc123", + createdAt: "2024-03-01T10:00:00.000Z", + updatedAt: "2024-03-01T14:00:00.000Z", + }, + settings: { + fps: 30, + canvasSize: { width: 1920, height: 1080 }, + background: { type: "color", color: "#000000" }, + }, + currentSceneId: "scene-main", + scenes: [ + { + id: "scene-main", + name: "Main scene", + isMain: true, + tracks: [ + { + id: "track-1", + type: "video", + name: "Video Track", + isMain: true, + elements: [ + { + id: "element-1", + type: "video", + mediaId: "media-1", + startTime: 0, + duration: 15.5, + trimStart: 0, + trimEnd: 0, + }, + ], + }, + { + id: "track-2", + type: "text", + name: "Text Track", + elements: [ + { + id: "element-2", + type: "text", + content: "Hello World", + startTime: 2, + duration: 5, + }, + ], + }, + ], + bookmarks: [5.0, 10.0], + createdAt: "2024-03-01T10:00:00.000Z", + updatedAt: "2024-03-01T14:00:00.000Z", + }, + ], +}; + +export const v2ProjectWithBlurBackground = { + id: "project-v2-blur", + version: 2, + metadata: { + id: "project-v2-blur", + name: "Blur Background Project", + createdAt: "2024-03-15T08:00:00.000Z", + updatedAt: "2024-03-15T10:00:00.000Z", + }, + settings: { + fps: 24, + canvasSize: { width: 1080, height: 1920 }, + background: { type: "blur", blurIntensity: 25 }, + }, + currentSceneId: "scene-1", + scenes: [ + { + id: "scene-1", + name: "Main scene", + isMain: true, + tracks: [ + { + id: "track-1", + type: "video", + isMain: true, + elements: [ + { + id: "el-1", + type: "video", + mediaId: "m1", + startTime: 0, + duration: 30, + }, + ], + }, + ], + bookmarks: [], + createdAt: "2024-03-15T08:00:00.000Z", + updatedAt: "2024-03-15T10:00:00.000Z", + }, + ], +}; + +export const v2ProjectEmptyScenes = { + id: "project-v2-empty", + version: 2, + metadata: { + id: "project-v2-empty", + name: "Empty Scenes Project", + createdAt: "2024-04-01T00:00:00.000Z", + updatedAt: "2024-04-01T00:00:00.000Z", + }, + settings: { + fps: 30, + canvasSize: { width: 1920, height: 1080 }, + background: { type: "color", color: "#ffffff" }, + }, + currentSceneId: "", + scenes: [], +}; diff --git a/apps/web/src/services/storage/migrations/__tests__/fixtures/v3.ts b/apps/web/src/services/storage/migrations/__tests__/fixtures/v3.ts new file mode 100644 index 00000000..1d46e362 --- /dev/null +++ b/apps/web/src/services/storage/migrations/__tests__/fixtures/v3.ts @@ -0,0 +1,47 @@ +export const v3Project = { + id: "project-v3-123", + version: 3, + metadata: { + id: "project-v3-123", + name: "My V3 Project", + thumbnail: "data:image/png;base64,xyz789", + duration: 25.5, + createdAt: "2024-05-01T10:00:00.000Z", + updatedAt: "2024-05-01T14:00:00.000Z", + }, + settings: { + fps: 30, + canvasSize: { width: 1920, height: 1080 }, + background: { type: "color", color: "#000000" }, + }, + currentSceneId: "scene-main", + scenes: [ + { + id: "scene-main", + name: "Main scene", + isMain: true, + tracks: [ + { + id: "track-1", + type: "video", + name: "Video Track", + isMain: true, + elements: [ + { + id: "element-1", + type: "video", + mediaId: "media-1", + startTime: 0, + duration: 25.5, + trimStart: 0, + trimEnd: 0, + }, + ], + }, + ], + bookmarks: [], + createdAt: "2024-05-01T10:00:00.000Z", + updatedAt: "2024-05-01T14:00:00.000Z", + }, + ], +}; diff --git a/apps/web/src/services/storage/migrations/__tests__/v0-to-v1.test.ts b/apps/web/src/services/storage/migrations/__tests__/v0-to-v1.test.ts new file mode 100644 index 00000000..2cd67da8 --- /dev/null +++ b/apps/web/src/services/storage/migrations/__tests__/v0-to-v1.test.ts @@ -0,0 +1,111 @@ +import { describe, expect, test } from "bun:test"; +import { getProjectId, transformProjectV0ToV1 } from "../transformers/v0-to-v1"; +import { + projectMalformed, + projectWithNoId, + v0Project, + v0ProjectEmpty, + v0ProjectWithMetadata, + v1Project, +} from "./fixtures"; + +describe("V0 to V1 Migration", () => { + const fixedDate = new Date("2024-06-01T12:00:00.000Z"); + + describe("transformProjectV0ToV1", () => { + test("adds scenes array to v0 project", () => { + const result = transformProjectV0ToV1({ + project: v0Project, + options: { now: fixedDate }, + }); + + expect(result.skipped).toBe(false); + expect(result.project.version).toBe(1); + expect(Array.isArray(result.project.scenes)).toBe(true); + expect((result.project.scenes as unknown[]).length).toBe(1); + expect(result.project.currentSceneId).toBeDefined(); + }); + + test("creates main scene with correct structure", () => { + const result = transformProjectV0ToV1({ + project: v0Project, + options: { now: fixedDate }, + }); + + const scenes = result.project.scenes as Array<Record<string, unknown>>; + const mainScene = scenes[0]; + + expect(mainScene.isMain).toBe(true); + expect(mainScene.name).toBe("Main scene"); + expect(typeof mainScene.id).toBe("string"); + expect(Array.isArray(mainScene.tracks)).toBe(true); + expect(Array.isArray(mainScene.bookmarks)).toBe(true); + }); + + test("updates metadata.updatedAt when metadata exists", () => { + const result = transformProjectV0ToV1({ + project: v0ProjectWithMetadata, + options: { now: fixedDate }, + }); + + const metadata = result.project.metadata as Record<string, unknown>; + expect(metadata.updatedAt).toBe(fixedDate.toISOString()); + }); + + test("updates root updatedAt when no metadata", () => { + const result = transformProjectV0ToV1({ + project: v0ProjectEmpty, + options: { now: fixedDate }, + }); + + expect(result.project.updatedAt).toBe(fixedDate.toISOString()); + }); + + test("skips project that already has scenes", () => { + const result = transformProjectV0ToV1({ + project: v1Project, + options: { now: fixedDate }, + }); + + expect(result.skipped).toBe(true); + expect(result.reason).toBe("already has scenes"); + }); + + test("preserves original project properties", () => { + const result = transformProjectV0ToV1({ + project: v0Project, + options: { now: fixedDate }, + }); + + expect(result.project.id).toBe(v0Project.id); + expect(result.project.name).toBe(v0Project.name); + expect(result.project.fps).toBe(v0Project.fps); + expect(result.project.canvasSize).toEqual(v0Project.canvasSize); + }); + }); + + describe("getProjectId", () => { + test("returns id from root level", () => { + const id = getProjectId({ project: v0Project }); + expect(id).toBe("project-v0-123"); + }); + + test("returns id from metadata when root id missing", () => { + const project = { + metadata: { id: "from-metadata" }, + }; + const id = getProjectId({ project }); + expect(id).toBe("from-metadata"); + }); + + test("returns null when no id found", () => { + const id = getProjectId({ project: projectWithNoId }); + expect(id).toBe(null); + }); + + test("returns null for malformed project", () => { + const id = getProjectId({ project: projectMalformed }); + expect(id).toBe("project-malformed"); + }); + }); +}); diff --git a/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts b/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts new file mode 100644 index 00000000..ff6a31c2 --- /dev/null +++ b/apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts @@ -0,0 +1,165 @@ +import { describe, expect, test } from "bun:test"; +import { + DEFAULT_BLUR_INTENSITY, + DEFAULT_CANVAS_SIZE, + DEFAULT_COLOR, + DEFAULT_FPS, +} from "@/constants/project-constants"; +import { getProjectId, transformProjectV1ToV2 } from "../transformers/v1-to-v2"; +import { + projectWithNoId, + projectWithNullValues, + v1Project, + v1ProjectWithMultipleScenes, + v2Project, +} from "./fixtures"; + +describe("V1 to V2 Migration", () => { + describe("transformProjectV1ToV2", () => { + test("creates metadata object from flat properties", () => { + const result = transformProjectV1ToV2({ project: v1Project }); + + expect(result.skipped).toBe(false); + expect(result.project.version).toBe(2); + + const metadata = result.project.metadata as Record<string, unknown>; + expect(metadata.id).toBe(v1Project.id); + expect(metadata.name).toBe(v1Project.name); + expect(typeof metadata.createdAt).toBe("string"); + expect(typeof metadata.updatedAt).toBe("string"); + }); + + test("creates settings object from flat properties", () => { + const result = transformProjectV1ToV2({ project: v1Project }); + + const settings = result.project.settings as Record<string, unknown>; + expect(settings.fps).toBe(v1Project.fps); + expect(settings.canvasSize).toEqual(v1Project.canvasSize); + }); + + test("converts color background correctly", () => { + const result = transformProjectV1ToV2({ project: v1Project }); + + const settings = result.project.settings as Record<string, unknown>; + const background = settings.background as Record<string, unknown>; + expect(background.type).toBe("color"); + expect(background.color).toBe(v1Project.backgroundColor); + }); + + test("converts blur background correctly", () => { + const projectWithBlur = { + ...v1Project, + backgroundType: "blur", + blurIntensity: 30, + }; + const result = transformProjectV1ToV2({ project: projectWithBlur }); + + const settings = result.project.settings as Record<string, unknown>; + const background = settings.background as Record<string, unknown>; + expect(background.type).toBe("blur"); + expect(background.blurIntensity).toBe(30); + }); + + test("applies legacy bookmarks to main scene", () => { + const result = transformProjectV1ToV2({ project: v1Project }); + + const scenes = result.project.scenes as Array<Record<string, unknown>>; + const mainScene = scenes.find((s) => s.isMain === true); + expect(mainScene?.bookmarks).toEqual(v1Project.bookmarks); + }); + + test("preserves existing scene bookmarks", () => { + const result = transformProjectV1ToV2({ + project: v1ProjectWithMultipleScenes, + }); + + const scenes = result.project.scenes as Array<Record<string, unknown>>; + const introScene = scenes.find((s) => s.name === "Intro"); + expect(introScene?.bookmarks).toEqual([1.0]); + }); + + test("skips project that already has v2 structure", () => { + const result = transformProjectV1ToV2({ project: v2Project }); + + expect(result.skipped).toBe(true); + expect(result.reason).toBe("already v2"); + }); + + test("skips project with no id", () => { + const result = transformProjectV1ToV2({ project: projectWithNoId }); + + expect(result.skipped).toBe(true); + expect(result.reason).toBe("no project id"); + }); + + test("handles null values gracefully", () => { + const result = transformProjectV1ToV2({ project: projectWithNullValues }); + + expect(result.skipped).toBe(false); + const settings = result.project.settings as Record<string, unknown>; + expect(settings.fps).toBe(DEFAULT_FPS); + expect(settings.canvasSize).toEqual(DEFAULT_CANVAS_SIZE); + }); + + test("uses default values for missing properties", () => { + const minimalProject = { + id: "minimal", + version: 1, + scenes: [], + }; + const result = transformProjectV1ToV2({ project: minimalProject }); + + const settings = result.project.settings as Record<string, unknown>; + expect(settings.fps).toBe(DEFAULT_FPS); + expect(settings.canvasSize).toEqual(DEFAULT_CANVAS_SIZE); + + const background = settings.background as Record<string, unknown>; + expect(background.type).toBe("color"); + expect(background.color).toBe(DEFAULT_COLOR); + }); + + test("uses default blur intensity when missing", () => { + const projectWithBlurNoIntensity = { + id: "blur-no-intensity", + version: 1, + backgroundType: "blur", + scenes: [], + }; + const result = transformProjectV1ToV2({ + project: projectWithBlurNoIntensity, + }); + + const settings = result.project.settings as Record<string, unknown>; + const background = settings.background as Record<string, unknown>; + expect(background.blurIntensity).toBe(DEFAULT_BLUR_INTENSITY); + }); + + test("preserves currentSceneId", () => { + const result = transformProjectV1ToV2({ project: v1Project }); + expect(result.project.currentSceneId).toBe(v1Project.currentSceneId); + }); + + test("finds main scene id when currentSceneId missing", () => { + const projectWithoutCurrentScene = { + ...v1Project, + currentSceneId: undefined, + }; + const result = transformProjectV1ToV2({ + project: projectWithoutCurrentScene, + }); + expect(result.project.currentSceneId).toBe("scene-main"); + }); + }); + + describe("getProjectId", () => { + test("returns id from root level", () => { + const id = getProjectId({ project: v1Project }); + expect(id).toBe("project-v1-123"); + }); + + test("returns id from metadata", () => { + const id = getProjectId({ project: v1ProjectWithMultipleScenes }); + expect(id).toBe("project-v1-multi"); + }); + }); +}); diff --git a/apps/web/src/services/storage/migrations/__tests__/v2-to-v3.test.ts b/apps/web/src/services/storage/migrations/__tests__/v2-to-v3.test.ts new file mode 100644 index 00000000..6e2991d0 --- /dev/null +++ b/apps/web/src/services/storage/migrations/__tests__/v2-to-v3.test.ts @@ -0,0 +1,175 @@ +import { describe, expect, test } from "bun:test"; +import { getProjectId, transformProjectV2ToV3 } from "../transformers/v2-to-v3"; +import { + projectWithNoId, + v2Project, + v2ProjectEmptyScenes, + v2ProjectWithBlurBackground, + v3Project, +} from "./fixtures"; + +describe("V2 to V3 Migration", () => { + describe("transformProjectV2ToV3", () => { + test("adds duration to metadata", () => { + const result = transformProjectV2ToV3({ project: v2Project }); + + expect(result.skipped).toBe(false); + expect(result.project.version).toBe(3); + + const metadata = result.project.metadata as Record<string, unknown>; + expect(typeof metadata.duration).toBe("number"); + }); + + test("calculates duration from scene tracks", () => { + const result = transformProjectV2ToV3({ project: v2Project }); + + const metadata = result.project.metadata as Record<string, unknown>; + // v2Project has a video element with duration 15.5 and a text element at startTime 2 with duration 5 + // Total duration should be max(15.5, 2+5) = 15.5 + expect(metadata.duration).toBe(15.5); + }); + + test("handles project with blur background", () => { + const result = transformProjectV2ToV3({ + project: v2ProjectWithBlurBackground, + }); + + expect(result.skipped).toBe(false); + const metadata = result.project.metadata as Record<string, unknown>; + // v2ProjectWithBlurBackground has a video with duration 30 + expect(metadata.duration).toBe(30); + }); + + test("handles empty scenes with zero duration", () => { + const result = transformProjectV2ToV3({ project: v2ProjectEmptyScenes }); + + expect(result.skipped).toBe(false); + const metadata = result.project.metadata as Record<string, unknown>; + expect(metadata.duration).toBe(0); + }); + + test("skips project that already has v3 structure", () => { + const result = transformProjectV2ToV3({ project: v3Project }); + + expect(result.skipped).toBe(true); + expect(result.reason).toBe("already v3"); + }); + + test("skips project that has duration in metadata", () => { + const projectWithDuration = { + ...v2Project, + version: 2, + metadata: { + ...v2Project.metadata, + duration: 10, + }, + }; + const result = transformProjectV2ToV3({ project: projectWithDuration }); + + expect(result.skipped).toBe(true); + expect(result.reason).toBe("already v3"); + }); + + test("skips project with no id", () => { + const result = transformProjectV2ToV3({ project: projectWithNoId }); + + expect(result.skipped).toBe(true); + expect(result.reason).toBe("no project id"); + }); + + test("preserves existing metadata fields", () => { + const result = transformProjectV2ToV3({ project: v2Project }); + + const metadata = result.project.metadata as Record<string, unknown>; + expect(metadata.id).toBe(v2Project.metadata.id); + expect(metadata.name).toBe(v2Project.metadata.name); + expect(metadata.thumbnail).toBe(v2Project.metadata.thumbnail); + expect(metadata.createdAt).toBe(v2Project.metadata.createdAt); + expect(metadata.updatedAt).toBe(v2Project.metadata.updatedAt); + }); + + test("preserves settings object", () => { + const result = transformProjectV2ToV3({ project: v2Project }); + + expect(result.project.settings).toEqual(v2Project.settings); + }); + + test("preserves scenes array", () => { + const result = transformProjectV2ToV3({ project: v2Project }); + + expect(result.project.scenes).toEqual(v2Project.scenes); + }); + + test("handles project without metadata object", () => { + const projectWithoutMetadata = { + id: "no-metadata", + version: 2, + scenes: [], + }; + const result = transformProjectV2ToV3({ + project: projectWithoutMetadata, + }); + + expect(result.skipped).toBe(false); + const metadata = result.project.metadata as Record<string, unknown>; + expect(metadata.duration).toBe(0); + }); + + test("calculates duration from main scene only", () => { + const multiSceneProject = { + id: "multi-scene", + version: 2, + metadata: { id: "multi-scene", name: "Multi" }, + scenes: [ + { + id: "scene-1", + isMain: true, + tracks: [ + { + type: "video", + elements: [{ startTime: 0, duration: 10 }], + }, + ], + }, + { + id: "scene-2", + isMain: false, + tracks: [ + { + type: "video", + elements: [{ startTime: 0, duration: 20 }], + }, + ], + }, + ], + }; + const result = transformProjectV2ToV3({ project: multiSceneProject }); + + const metadata = result.project.metadata as Record<string, unknown>; + // Duration is from main scene only, not sum of all scenes + expect(metadata.duration).toBe(10); + }); + }); + + describe("getProjectId", () => { + test("returns id from root level", () => { + const projectWithRootId = { id: "root-id", metadata: {} }; + const id = getProjectId({ project: projectWithRootId }); + expect(id).toBe("root-id"); + }); + + test("returns id from metadata when root id missing", () => { + const id = getProjectId({ project: v2Project }); + expect(id).toBe("project-v2-123"); + }); + + test("prefers root id over metadata id", () => { + const projectWithBothIds = { + id: "root-id", + metadata: { id: "metadata-id" }, + }; + const id = getProjectId({ project: projectWithBothIds }); + expect(id).toBe("root-id"); + }); + }); +}); |
