UNPKG

filecoin-pin

Version:

Bridge IPFS content to Filecoin Onchain Cloud using familiar tools

244 lines (192 loc) 9.16 kB
/** * Test to verify browser CAR builder generates same CID and CAR bytes as Node.js implementation */ import { readFile, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import { createCarFromFile, createCarFromFileList, createCarFromFiles } from '../../core/unixfs/browser-car-builder.js' import { cleanupTempCar, createCarFromPath } from '../../core/unixfs/car-builder.js' describe('Browser CAR Builder', () => { const testFiles: string[] = [] afterEach(async () => { // Clean up temporary files for (const file of testFiles) { try { await rm(file) } catch { // Ignore cleanup errors } } testFiles.length = 0 }) describe('Node.js Compatibility', () => { it('should generate same root CID as Node.js createCarFromPath', async () => { // Create test data const testContent = 'Hello, IPFS! This is a test file.' const testData = new TextEncoder().encode(testContent) const fileName = 'test.txt' // Use consistent filename for both implementations // Write to temp file for Node.js version const tempPath = join(tmpdir(), fileName) await writeFile(tempPath, testData) testFiles.push(tempPath) // Node.js implementation const nodeResult = await createCarFromPath(tempPath) testFiles.push(nodeResult.carPath) const nodeCarBytes = await readFile(nodeResult.carPath) // Browser implementation with same data and same filename const file = new File([testData], fileName, { type: 'text/plain' }) const browserResult = await createCarFromFile(file) // Compare CIDs - they should be identical! expect(browserResult.rootCid.toString()).toBe(nodeResult.rootCid.toString()) // Compare CAR sizes expect(browserResult.carBytes.length).toBe(nodeCarBytes.length) // Compare actual bytes expect(browserResult.carBytes.length).toBe(nodeCarBytes.length) expect(Buffer.from(browserResult.carBytes).equals(nodeCarBytes)).toBe(true) // Cleanup await cleanupTempCar(nodeResult.carPath) }) it('should generate same root CID with bare mode as Node.js', async () => { const testContent = 'Bare mode test content' const testData = new TextEncoder().encode(testContent) const fileName = 'bare-test.txt' // Use consistent filename // Write to temp file for Node.js version const tempPath = join(tmpdir(), fileName) await writeFile(tempPath, testData) testFiles.push(tempPath) // Node.js version with bare mode const nodeResult = await createCarFromPath(tempPath, { bare: true }) testFiles.push(nodeResult.carPath) const nodeCarBytes = await readFile(nodeResult.carPath) // Browser version with bare mode and same filename const file = new File([testData], fileName, { type: 'text/plain' }) const browserResult = await createCarFromFile(file, { bare: true }) // Compare CIDs - they should be identical! expect(browserResult.rootCid.toString()).toBe(nodeResult.rootCid.toString()) // Compare CAR sizes expect(browserResult.carBytes.length).toBe(nodeCarBytes.length) // Compare actual bytes expect(Buffer.from(browserResult.carBytes).equals(nodeCarBytes)).toBe(true) // Cleanup await cleanupTempCar(nodeResult.carPath) }) it('should produce valid CAR that can be read back', async () => { const testContent = 'CAR validation test' const testData = new TextEncoder().encode(testContent) // Browser version const file = new File([testData], 'test.txt') const browserResult = await createCarFromFile(file) // Verify the CAR is valid by reading it back const { CarBlockIterator } = await import('@ipld/car') const carReader = await CarBlockIterator.fromBytes(browserResult.carBytes) // Check roots const roots = await carReader.getRoots() expect(roots.length).toBe(1) expect(roots[0]?.toString()).toBe(browserResult.rootCid.toString()) // Verify we can read blocks const blocks = [] for await (const { cid, bytes } of carReader) { blocks.push({ cid, bytes }) } expect(blocks.length).toBeGreaterThan(0) }) it('should handle multiple files with createCarFromFiles', async () => { const file1 = new File([new TextEncoder().encode('File 1 content')], 'file1.txt') const file2 = new File([new TextEncoder().encode('File 2 content')], 'file2.txt') const result = await createCarFromFiles([file1, file2]) expect(result.rootCid).toBeDefined() expect(result.carBytes).toBeInstanceOf(Uint8Array) expect(result.carBytes.length).toBeGreaterThan(0) }) it('should handle FileList with createCarFromFileList', async () => { const file1 = new File([new TextEncoder().encode('A')], 'a.txt') const file2 = new File([new TextEncoder().encode('B')], 'b.txt') // Test with array const result = await createCarFromFileList([file1, file2]) expect(result.rootCid).toBeDefined() expect(result.carBytes).toBeInstanceOf(Uint8Array) }) it('should handle directory structure with webkitRelativePath same as Node.js directory', async () => { const { mkdir } = await import('node:fs/promises') // Create test files with data const file1Data = new TextEncoder().encode('File 1 content') const file2Data = new TextEncoder().encode('File 2 content') // Create actual directory structure for Node.js const testDirName = `test-dir-${Date.now()}` const testDirPath = join(tmpdir(), testDirName) await mkdir(testDirPath, { recursive: true }) const file1Path = join(testDirPath, 'file1.txt') const file2Path = join(testDirPath, 'file2.txt') await writeFile(file1Path, file1Data) await writeFile(file2Path, file2Data) testFiles.push(file1Path, file2Path, testDirPath) // Node.js directory processing const nodeResult = await createCarFromPath(testDirPath, { isDirectory: true }) testFiles.push(nodeResult.carPath) const nodeCarBytes = await readFile(nodeResult.carPath) // Browser version with webkitRelativePath const browserFile1 = new File([file1Data], 'file1.txt') const browserFile2 = new File([file2Data], 'file2.txt') // Add webkitRelativePath to simulate directory upload Object.defineProperty(browserFile1, 'webkitRelativePath', { value: `${testDirName}/file1.txt`, writable: false, }) Object.defineProperty(browserFile2, 'webkitRelativePath', { value: `${testDirName}/file2.txt`, writable: false, }) const browserResult = await createCarFromFileList([browserFile1, browserFile2]) // Compare CIDs - they should be identical (most important!) expect(browserResult.rootCid.toString()).toBe(nodeResult.rootCid.toString()) /** * Note: CAR file sizes differ due to an extra block in the nodejs car * @see https://github.com/filecoin-project/filecoin-pin/pull/83#discussion_r2415372437 */ expect(browserResult.carBytes).toBeInstanceOf(Uint8Array) expect(browserResult.carBytes.length).toBeLessThanOrEqual(nodeCarBytes.length) // Cleanup await cleanupTempCar(nodeResult.carPath) await rm(testDirPath, { recursive: true }) }) }) describe('Progress Callback', () => { it('should call progress callback during file processing', async () => { const testData = new Uint8Array(1024 * 256) // 256KB const file = new File([testData], 'large.bin') let progressCalled = false let lastProgress = 0 const result = await createCarFromFile(file, { onProgress: (processed, total) => { progressCalled = true expect(processed).toBeGreaterThanOrEqual(0) expect(processed).toBeLessThanOrEqual(total) expect(total).toBe(file.size) lastProgress = processed }, }) expect(progressCalled).toBe(true) expect(lastProgress).toBe(file.size) expect(result.rootCid).toBeDefined() }) }) describe('Error Handling', () => { it('should handle empty file', async () => { const emptyFile = new File([], 'empty.txt') const result = await createCarFromFile(emptyFile) expect(result.rootCid).toBeDefined() expect(result.carBytes).toBeInstanceOf(Uint8Array) }) it('should reject when no files provided to createCarFromFiles', async () => { await expect(createCarFromFiles([])).rejects.toThrow('At least one file is required') }) it('should reject bare mode with multiple files', async () => { const file1 = new File([new TextEncoder().encode('A')], 'a.txt') const file2 = new File([new TextEncoder().encode('B')], 'b.txt') await expect(createCarFromFiles([file1, file2], { bare: true })).rejects.toThrow( '--bare flag is not supported for multiple files' ) }) }) })