UNPKG

filecoin-pin

Version:

Bridge IPFS content to Filecoin Onchain Cloud using familiar tools

225 lines 9.39 kB
/** * Unit tests for add command functionality * * Tests the add command's ability to: * - Create UnixFS CAR files from regular files * - Clean up temporary files * - Handle errors properly * - Integrate with Synapse upload flow */ import { randomBytes } from 'node:crypto'; import { mkdir, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { runAdd } from '../../add/add.js'; // Mock the external dependencies at module level vi.mock('../../common/upload-flow.js', () => ({ validatePaymentSetup: vi.fn(), performUpload: vi.fn().mockResolvedValue({ pieceCid: 'bafkzcibtest1234567890', pieceId: 789, dataSetId: '456', network: 'calibration', transactionHash: '0xabc123', providerInfo: { id: 1, name: 'Test Provider', serviceURL: 'http://test.provider', }, }), displayUploadResults: vi.fn(), })); vi.mock('../../core/synapse/index.js', () => ({ initializeSynapse: vi.fn().mockImplementation(async (config) => { // Validate auth config (mirrors validateAuthConfig in actual code) const hasStandardAuth = config.privateKey != null; const hasSessionKeyAuth = config.walletAddress != null && config.sessionKey != null; if (!hasStandardAuth && !hasSessionKeyAuth) { throw new Error('Authentication required: provide either a privateKey or walletAddress + sessionKey'); } return { getNetwork: () => 'calibration', }; }), createStorageContext: vi.fn().mockResolvedValue({ storage: {}, providerInfo: { id: 1, name: 'Test Provider', serviceURL: 'http://test.provider', }, }), cleanupSynapseService: vi.fn(), })); vi.mock('../../core/unixfs/index.js', () => ({ createCarFromPath: vi.fn((_filePath, options) => { const bare = options?.bare || false; // Different CIDs for bare vs directory mode const cid = bare ? 'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi' : 'bafybeihw4ytkqxrq7q7e3p2l5s5di7zjzkhxdmfwvqfylkdamdg3xybpbq'; return Promise.resolve({ carPath: '/tmp/test.car', rootCid: { toString: () => cid, }, }); }), cleanupTempCar: vi.fn(), })); vi.mock('../../utils/cli-helpers.js', () => ({ intro: vi.fn(), outro: vi.fn(), cancel: vi.fn(), createSpinner: vi.fn(() => ({ start: vi.fn(), stop: vi.fn(), message: vi.fn(), })), formatFileSize: vi.fn((size) => `${size} bytes`), })); // We need to partially mock fs/promises to keep real file operations for test setup // but mock readFile for the CAR reading part vi.mock('node:fs/promises', async () => { const actual = await vi.importActual('node:fs/promises'); return { ...actual, readFile: vi.fn((path) => { // If it's reading the temp CAR, return mock data if (path === '/tmp/test.car') { return Promise.resolve(Buffer.from('mock-car-data')); } // Otherwise use real readFile return actual.readFile(path); }), }; }); // Test CID constants (defined after vi.mock calls due to hoisting) const TEST_BARE_CID = 'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi'; const TEST_DIR_WRAPPED_CID = 'bafybeihw4ytkqxrq7q7e3p2l5s5di7zjzkhxdmfwvqfylkdamdg3xybpbq'; const TEST_PIECE_CID = 'bafkzcibtest1234567890'; describe('Add Command', () => { const testDir = join(process.cwd(), 'test-add-files'); const testFile = join(testDir, 'test.bin'); // Use random bytes to avoid deduplication and ensure multi-block CAR (>1MiB) const testContent = randomBytes(1024 * 1024 * 1.5); // 1.5MB of random data beforeEach(async () => { // Create test directory and file await mkdir(testDir, { recursive: true }); await writeFile(testFile, testContent); }); afterEach(async () => { // Clean up test directory await rm(testDir, { recursive: true, force: true }); vi.clearAllMocks(); }); describe('runAdd command', () => { it('should successfully add a file with directory wrapper by default', async () => { const result = await runAdd({ filePath: testFile, privateKey: 'test-private-key', rpcUrl: 'wss://test.rpc.url', }); // Verify the result structure (should use directory wrapper CID by default) expect(result).toMatchObject({ filePath: testFile, fileSize: expect.any(Number), rootCid: TEST_DIR_WRAPPED_CID, // Directory wrapper CID pieceCid: TEST_PIECE_CID, pieceId: 789, dataSetId: '456', transactionHash: '0xabc123', providerInfo: { id: 1, name: 'Test Provider', }, }); // Verify createCarFromPath was called without bare flag const { createCarFromPath } = await import('../../core/unixfs/index.js'); expect(vi.mocked(createCarFromPath)).toHaveBeenCalledWith(testFile, expect.objectContaining({ logger: expect.any(Object), // bare is not passed when undefined, due to spread operator })); }); it('should successfully add a file in bare mode when specified', async () => { const result = await runAdd({ filePath: testFile, privateKey: 'test-private-key', rpcUrl: 'wss://test.rpc.url', bare: true, }); // Verify the result structure (should use bare mode CID) expect(result).toMatchObject({ filePath: testFile, fileSize: expect.any(Number), rootCid: TEST_BARE_CID, // Bare mode CID pieceCid: TEST_PIECE_CID, pieceId: 789, dataSetId: '456', transactionHash: '0xabc123', providerInfo: { id: 1, name: 'Test Provider', }, }); // Verify createCarFromPath was called with bare flag const { createCarFromPath } = await import('../../core/unixfs/index.js'); expect(vi.mocked(createCarFromPath)).toHaveBeenCalledWith(testFile, expect.objectContaining({ logger: expect.any(Object), bare: true, })); }); it('passes metadata options through to upload and storage context', async () => { await runAdd({ filePath: testFile, privateKey: 'test-private-key', rpcUrl: 'wss://test.rpc.url', pieceMetadata: { region: 'us-west', note: '' }, dataSetMetadata: { purpose: 'erc8004' }, }); const { createStorageContext, initializeSynapse } = await import('../../core/synapse/index.js'); expect(vi.mocked(initializeSynapse)).toHaveBeenCalledWith(expect.objectContaining({ dataSetMetadata: { purpose: 'erc8004' }, }), expect.anything()); expect(vi.mocked(createStorageContext)).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ dataset: { metadata: { purpose: 'erc8004' }, }, logger: expect.anything(), })); const { performUpload } = await import('../../common/upload-flow.js'); expect(vi.mocked(performUpload)).toHaveBeenCalledWith(expect.anything(), expect.anything(), expect.anything(), expect.objectContaining({ metadata: { region: 'us-west', note: '' }, })); }); it('should reject when file does not exist', async () => { const mockExit = vi.spyOn(process, 'exit'); await expect(runAdd({ filePath: '/non/existent/file.txt', privateKey: 'test-key', })).rejects.toThrow('Path not found'); expect(mockExit).not.toHaveBeenCalled(); mockExit.mockRestore(); }); it('should reject when private key is missing', async () => { const mockExit = vi.spyOn(process, 'exit'); await expect(runAdd({ filePath: testFile, // No private key })).rejects.toThrow(); expect(mockExit).not.toHaveBeenCalled(); mockExit.mockRestore(); }); it('should reject --bare flag with directories', async () => { const mockExit = vi.spyOn(process, 'exit'); await expect(runAdd({ filePath: testDir, // Directory privateKey: 'test-key', bare: true, // --bare flag should not work with directories })).rejects.toThrow('--bare flag is not supported for directories'); expect(mockExit).not.toHaveBeenCalled(); mockExit.mockRestore(); }); }); }); //# sourceMappingURL=add.test.js.map