filecoin-pin
Version:
Bridge IPFS content to Filecoin Onchain Cloud using familiar tools
268 lines • 9.68 kB
JavaScript
import { METADATA_KEYS } from '@filoz/synapse-sdk';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
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: {},
pieceList: [],
};
const mockWarmStorageInstance = {
getPDPVerifierAddress: () => '0xverifier',
getPieceMetadata: vi.fn(async () => ({ ...state.pieceMetadata })),
};
const mockWarmStorageCreate = vi.fn(async () => mockWarmStorageInstance);
class MockPDPVerifier {
async getDataSetLeafCount() {
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) => {
// 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;
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];
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];
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;
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();
});
});
//# sourceMappingURL=data-set.test.js.map