UNPKG

filecoin-pin

Version:

Bridge IPFS content to Filecoin Onchain Cloud using familiar tools

318 lines (282 loc) 8.81 kB
import { METADATA_KEYS } from '@filoz/synapse-sdk' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { DataSetSummary } from '../../core/data-set/types.js' import { runDataSetDetailsCommand, runDataSetListCommand } from '../../data-set/run.js' const { displayDataSetListMock, cleanupSynapseServiceMock, spinnerMock, cancelMock, mockFindDataSets, mockGetStorageInfo, mockGetAddress, mockWarmStorageCreate, mockWarmStorageInstance, mockSynapseCreate, MockPDPServer, MockPDPVerifier, state, } = vi.hoisted(() => { const displayDataSetListMock = vi.fn() const cleanupSynapseServiceMock = vi.fn() const cancelMock = vi.fn() const spinnerMock = { start: vi.fn(), stop: vi.fn(), message: vi.fn(), } const mockFindDataSets = vi.fn() const mockGetStorageInfo = vi.fn() const mockGetAddress = vi.fn() const state = { leafCount: 0, pieceMetadata: {} as Record<string, string>, pieceList: [] as Array<{ pieceId: number; pieceCid: string }>, } const mockWarmStorageInstance = { getPDPVerifierAddress: () => '0xverifier', getPieceMetadata: vi.fn(async () => ({ ...state.pieceMetadata })), } const mockWarmStorageCreate = vi.fn(async () => mockWarmStorageInstance) class MockPDPVerifier { async getDataSetLeafCount(): Promise<number> { return state.leafCount } } class MockPDPServer { async getDataSet() { return { pieces: state.pieceList.map((piece) => ({ pieceId: piece.pieceId, pieceCid: { toString: () => piece.pieceCid, }, })), } } } const mockStorageContext = { dataSetId: 158, getPieces: async function* () { for (const piece of state.pieceList) { yield { pieceId: piece.pieceId, pieceCid: { toString: () => piece.pieceCid, }, } } }, } const mockCreateContext = vi.fn(async () => mockStorageContext) // TODO: we should not need to mock synapseCreate, and should use mocks/synapse-sdk.ts instead const mockSynapseCreate = vi.fn(async (config: any) => { // Validate auth like the real initializeSynapse does 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', getSigner: () => ({ getAddress: mockGetAddress, }), getClient: () => ({ getAddress: mockGetAddress, }), storage: { findDataSets: mockFindDataSets, getStorageInfo: mockGetStorageInfo, createContext: mockCreateContext, }, getProvider: () => ({}), getWarmStorageAddress: () => '0xwarm', } }) return { displayDataSetListMock, cleanupSynapseServiceMock, cancelMock, spinnerMock, mockFindDataSets, mockGetStorageInfo, mockGetAddress, mockWarmStorageCreate, mockWarmStorageInstance, mockSynapseCreate, mockCreateContext, mockStorageContext, MockPDPServer, MockPDPVerifier, state, } }) vi.mock('../../data-set/display.js', () => ({ displayDataSets: displayDataSetListMock, })) vi.mock('../../core/synapse/index.js', () => ({ initializeSynapse: mockSynapseCreate, cleanupSynapseService: cleanupSynapseServiceMock, })) vi.mock('../../utils/cli-helpers.js', () => ({ intro: vi.fn(), outro: vi.fn(), cancel: cancelMock, createSpinner: () => spinnerMock, })) vi.mock('../../utils/cli-logger.js', () => ({ log: { line: vi.fn(), indent: vi.fn(), flush: vi.fn(), }, })) // Use shared SDK mock with custom extensions for dataset command testing vi.mock('@filoz/synapse-sdk', async () => { const sharedMock = await import('../mocks/synapse-sdk.js') return { ...sharedMock, WarmStorageService: { create: mockWarmStorageCreate }, PDPVerifier: MockPDPVerifier, PDPServer: MockPDPServer, } }) // Mock piece size calculation vi.mock('@filoz/synapse-core/piece', () => ({ MAX_UPLOAD_SIZE: 1048576, getSizeFromPieceCID: vi.fn(() => { // Return a realistic piece size (1 MiB = 1048576 bytes) return 1048576 }), })) describe('runDataSetCommand', () => { const summaryDataSet = { pdpVerifierDataSetId: 158, providerId: 2, isManaged: true, withCDN: false, currentPieceCount: 3, nextPieceId: 3, clientDataSetId: 1, pdpRailId: 327, cdnRailId: 0, cacheMissRailId: 0, payer: '0x123', payee: '0x456', serviceProvider: '0xservice', commissionBps: 100, pdpEndEpoch: 0, cdnEndEpoch: 0, metadata: { [METADATA_KEYS.WITH_IPFS_INDEXING]: '', source: 'filecoin-pin', note: 'demo', }, } const provider = { id: 2, name: 'Test Provider', serviceProvider: '0xservice', description: 'demo provider', payee: '0x456', active: true, products: { PDP: { type: 'PDP', isActive: true, capabilities: {}, data: { serviceURL: 'https://pdp.local' }, }, }, } beforeEach(() => { vi.clearAllMocks() state.leafCount = 0 state.pieceMetadata = {} state.pieceList = [] mockFindDataSets.mockResolvedValue([summaryDataSet]) mockGetStorageInfo.mockResolvedValue({ providers: [provider] }) mockGetAddress.mockResolvedValue('0xabc') mockWarmStorageInstance.getPieceMetadata.mockResolvedValue({}) }) afterEach(() => { delete process.env.PRIVATE_KEY process.exitCode = 0 }) it('lists datasets without fetching details when no id is provided', async () => { await runDataSetListCommand({ privateKey: 'test-key', rpcUrl: 'wss://sample', }) expect(displayDataSetListMock).toHaveBeenCalledTimes(1) const firstCall = displayDataSetListMock.mock.calls[0] expect(firstCall).toBeDefined() const [context] = firstCall as [DataSetSummary[]] expect(context).toHaveLength(1) const summary = context[0] expect(summary).toBeDefined() expect(summary?.dataSetId).toBe(158) expect(summary?.createdWithFilecoinPin).toBe(true) }) it('filters datasets by metadata entries', async () => { await runDataSetListCommand({ privateKey: 'test-key', rpcUrl: 'wss://sample', dataSetMetadata: { note: 'demo' }, }) const [dataSets] = displayDataSetListMock.mock.calls[0] as [DataSetSummary[]] expect(dataSets).toHaveLength(1) expect(dataSets[0]?.dataSetId).toBe(158) }) it('excludes datasets that do not match metadata filters', async () => { await runDataSetListCommand({ privateKey: 'test-key', rpcUrl: 'wss://sample', dataSetMetadata: { note: 'unknown' }, }) const [dataSets] = displayDataSetListMock.mock.calls[0] as [DataSetSummary[]] expect(dataSets).toHaveLength(0) }) it('loads detailed information when a dataset id is provided', async () => { state.pieceList = [{ pieceId: 0, pieceCid: 'bafkpiece0' }] const pieceMetadata = { [METADATA_KEYS.IPFS_ROOT_CID]: 'bafyroot0', custom: 'value', } state.pieceMetadata = pieceMetadata mockWarmStorageInstance.getPieceMetadata.mockResolvedValue(pieceMetadata) await runDataSetDetailsCommand(158, { privateKey: 'test-key', rpcUrl: 'wss://sample', }) expect(displayDataSetListMock).toHaveBeenCalledTimes(1) const statusCall = displayDataSetListMock.mock.calls[0] expect(statusCall).toBeDefined() const [dataSets] = statusCall as [DataSetSummary[]] expect(dataSets).toHaveLength(1) const dataSet = dataSets[0] expect(dataSet).toBeDefined() expect(dataSet?.totalSizeBytes).toBe(BigInt(1048576)) expect(dataSet?.pieces).toBeDefined() expect(dataSet?.pieces).toHaveLength(1) expect(dataSet?.pieces?.[0]?.size).toBe(1048576) expect(dataSet?.pieces?.[0]?.metadata).toMatchObject({ [METADATA_KEYS.IPFS_ROOT_CID]: 'bafyroot0', custom: 'value', }) }) it('exits when no private key is provided', async () => { await runDataSetListCommand({ rpcUrl: 'wss://sample', }) // Should call cancel with failure message expect(cancelMock).toHaveBeenCalledWith('Listing failed') // Should stop spinner with error message expect(spinnerMock.stop).toHaveBeenCalledWith(expect.stringContaining('Failed to list data sets')) // Should set exitCode to 1 due to authentication error expect(process.exitCode).toBe(1) // Should not call display function since it failed early expect(displayDataSetListMock).not.toHaveBeenCalled() }) })