| import { describe, expect, test, mock, afterEach } from "bun:test"; |
| import { buildVersionedUrl } from "@/utils/versionUtils"; |
|
|
| |
| |
| |
| describe("buildVersionedUrl", () => { |
| test("builds URL for v2.0 dataset data path", () => { |
| const url = buildVersionedUrl( |
| "rabhishek100/so100_train_dataset", |
| "v2.0", |
| "data/000/episode_000000.parquet", |
| ); |
| expect(url).toBe( |
| "https://huggingface.co/datasets/rabhishek100/so100_train_dataset/resolve/main/data/000/episode_000000.parquet", |
| ); |
| }); |
|
|
| test("builds URL for v2.1 dataset video path", () => { |
| const url = buildVersionedUrl( |
| "youliangtan/so101-table-cleanup", |
| "v2.1", |
| "videos/observation.images.top/chunk-000/episode_000007.mp4", |
| ); |
| expect(url).toBe( |
| "https://huggingface.co/datasets/youliangtan/so101-table-cleanup/resolve/main/videos/observation.images.top/chunk-000/episode_000007.mp4", |
| ); |
| }); |
|
|
| test("builds URL for v3.0 episode metadata", () => { |
| const url = buildVersionedUrl( |
| "lerobot-data-collection/level12_rac_2_2026-02-07", |
| "v3.0", |
| "meta/episodes/chunk-000/file-000.parquet", |
| ); |
| expect(url).toBe( |
| "https://huggingface.co/datasets/lerobot-data-collection/level12_rac_2_2026-02-07/resolve/main/meta/episodes/chunk-000/file-000.parquet", |
| ); |
| }); |
|
|
| test("builds URL for v3.0 data chunk", () => { |
| const url = buildVersionedUrl( |
| "lerobot-data-collection/level12_rac_2_2026-02-07", |
| "v3.0", |
| "data/chunk-001/file-003.parquet", |
| ); |
| expect(url).toBe( |
| "https://huggingface.co/datasets/lerobot-data-collection/level12_rac_2_2026-02-07/resolve/main/data/chunk-001/file-003.parquet", |
| ); |
| }); |
|
|
| test("builds URL for meta/info.json", () => { |
| const url = buildVersionedUrl("myorg/mydataset", "v3.0", "meta/info.json"); |
| expect(url).toBe( |
| "https://huggingface.co/datasets/myorg/mydataset/resolve/main/meta/info.json", |
| ); |
| }); |
| }); |
|
|
| |
| |
| |
| describe("getDatasetVersionAndInfo", () => { |
| const originalFetch = globalThis.fetch; |
|
|
| afterEach(() => { |
| globalThis.fetch = originalFetch; |
| }); |
|
|
| test("accepts v2.0 codebase_version", async () => { |
| const infoV20 = { |
| codebase_version: "v2.0", |
| robot_type: "so100", |
| total_episodes: 50, |
| total_frames: 5000, |
| total_tasks: 1, |
| chunks_size: 1000, |
| data_files_size_in_mb: 10, |
| video_files_size_in_mb: 500, |
| fps: 30, |
| splits: { train: "0:50" }, |
| data_path: "data/{episode_chunk:03d}/episode_{episode_index:06d}.parquet", |
| video_path: |
| "videos/{video_key}/chunk-{episode_chunk:03d}/episode_{episode_index:06d}.mp4", |
| features: { |
| "observation.images.top": { |
| dtype: "video", |
| shape: [480, 640, 3], |
| names: null, |
| }, |
| "observation.state": { |
| dtype: "float32", |
| shape: [1, 6], |
| names: ["j0", "j1", "j2", "j3", "j4", "j5"], |
| }, |
| action: { |
| dtype: "float32", |
| shape: [1, 6], |
| names: ["j0", "j1", "j2", "j3", "j4", "j5"], |
| }, |
| }, |
| }; |
|
|
| globalThis.fetch = mock(() => |
| Promise.resolve(new Response(JSON.stringify(infoV20), { status: 200 })), |
| ) as unknown as typeof fetch; |
|
|
| const { getDatasetVersionAndInfo } = await import("@/utils/versionUtils"); |
| const result = await getDatasetVersionAndInfo( |
| "rabhishek100/so100_train_dataset", |
| ); |
| expect(result.version).toBe("v2.0"); |
| expect(result.info.total_episodes).toBe(50); |
| }); |
|
|
| test("accepts v2.1 codebase_version", async () => { |
| const infoV21 = { |
| codebase_version: "v2.1", |
| robot_type: "so101", |
| total_episodes: 100, |
| total_frames: 10000, |
| total_tasks: 1, |
| chunks_size: 1000, |
| data_files_size_in_mb: 20, |
| video_files_size_in_mb: 1000, |
| fps: 30, |
| splits: { train: "0:100" }, |
| data_path: "data/{episode_chunk:03d}/episode_{episode_index:06d}.parquet", |
| video_path: |
| "videos/{video_key}/chunk-{episode_chunk:03d}/episode_{episode_index:06d}.mp4", |
| features: { |
| "observation.images.top": { |
| dtype: "video", |
| shape: [480, 640, 3], |
| names: null, |
| }, |
| "observation.state": { dtype: "float32", shape: [1, 6], names: null }, |
| action: { dtype: "float32", shape: [1, 6], names: null }, |
| }, |
| }; |
|
|
| globalThis.fetch = mock(() => |
| Promise.resolve(new Response(JSON.stringify(infoV21), { status: 200 })), |
| ) as unknown as typeof fetch; |
|
|
| |
| const { getDatasetVersionAndInfo } = await import("@/utils/versionUtils"); |
| const result = await getDatasetVersionAndInfo( |
| "youliangtan/so101-table-cleanup", |
| ); |
| expect(result.version).toBe("v2.1"); |
| }); |
|
|
| test("accepts v3.0 codebase_version", async () => { |
| const infoV30 = { |
| codebase_version: "v3.0", |
| robot_type: "openarm", |
| total_episodes: 200, |
| total_frames: 40000, |
| total_tasks: 1, |
| chunks_size: 100, |
| data_files_size_in_mb: 50, |
| video_files_size_in_mb: 2000, |
| fps: 50, |
| splits: { train: "0:200" }, |
| data_path: null, |
| video_path: null, |
| features: { |
| "observation.images.top": { |
| dtype: "video", |
| shape: [480, 640, 3], |
| names: null, |
| }, |
| "observation.state": { dtype: "float32", shape: [1, 14], names: null }, |
| action: { dtype: "float32", shape: [1, 14], names: null }, |
| }, |
| }; |
|
|
| globalThis.fetch = mock(() => |
| Promise.resolve(new Response(JSON.stringify(infoV30), { status: 200 })), |
| ) as unknown as typeof fetch; |
|
|
| const { getDatasetVersionAndInfo } = await import("@/utils/versionUtils"); |
| const result = await getDatasetVersionAndInfo( |
| "lerobot-data-collection/level12_rac_2_2026-02-07", |
| ); |
| expect(result.version).toBe("v3.0"); |
| expect(result.info.total_episodes).toBe(200); |
| }); |
|
|
| test("throws for unsupported version", async () => { |
| const infoUnsupported = { |
| codebase_version: "v1.0", |
| features: { dummy: { dtype: "float32", shape: [1], names: null } }, |
| }; |
|
|
| globalThis.fetch = mock(() => |
| Promise.resolve( |
| new Response(JSON.stringify(infoUnsupported), { status: 200 }), |
| ), |
| ) as unknown as typeof fetch; |
|
|
| const { getDatasetVersionAndInfo } = await import("@/utils/versionUtils"); |
| await expect(getDatasetVersionAndInfo("old/dataset")).rejects.toThrow( |
| "not supported", |
| ); |
| }); |
|
|
| test("throws when info.json has no features field", async () => { |
| globalThis.fetch = mock(() => |
| Promise.resolve( |
| new Response(JSON.stringify({ codebase_version: "v3.0" }), { |
| status: 200, |
| }), |
| ), |
| ) as unknown as typeof fetch; |
|
|
| const { getDatasetVersionAndInfo } = await import("@/utils/versionUtils"); |
| await expect(getDatasetVersionAndInfo("broken/dataset")).rejects.toThrow(); |
| }); |
|
|
| test("throws when fetch fails (network error)", async () => { |
| globalThis.fetch = mock(() => |
| Promise.resolve(new Response("Not Found", { status: 404 })), |
| ) as unknown as typeof fetch; |
|
|
| const { getDatasetVersionAndInfo } = await import("@/utils/versionUtils"); |
| await expect( |
| getDatasetVersionAndInfo("nonexistent/repo"), |
| ).rejects.toThrow(); |
| }); |
| }); |
|
|