UNPKG

filecoin-pin

Version:

Bridge IPFS content to Filecoin Onchain Cloud using familiar tools

339 lines 16.8 kB
import * as synapseSdk from '@filoz/synapse-sdk'; import { CID } from 'multiformats/cid'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createConfig } from '../../config.js'; import { getSynapseService, resetSynapseService, setupSynapse, } from '../../core/synapse/index.js'; import { uploadToSynapse } from '../../core/upload/index.js'; import { createLogger } from '../../logger.js'; // Mock the Synapse SDK - vi.mock requires async import for ES modules vi.mock('@filoz/synapse-sdk', async () => await import('../mocks/synapse-sdk.js')); // Test CID for upload tests const TEST_CID = CID.parse('bafkreia5fn4rmshmb7cl7fufkpcw733b5anhuhydtqstnglpkzosqln5kq'); describe('synapse-service', () => { let config; let logger; beforeEach(() => { // Create test config with Synapse enabled config = { ...createConfig(), privateKey: '0x0000000000000000000000000000000000000000000000000000000000000001', // Fake test key rpcUrl: 'wss://wss.calibration.node.glif.io/apigw/lotus/rpc/v1', }; logger = createLogger({ logLevel: 'info' }); // Reset the service instances resetSynapseService(); vi.clearAllMocks(); }); afterEach(() => { vi.clearAllMocks(); }); describe('setupSynapse', () => { it('should throw error when no authentication is provided', async () => { // Create an invalid config with no authentication const invalidConfig = { rpcUrl: 'wss://wss.calibration.node.glif.io/apigw/lotus/rpc/v1', }; await expect(setupSynapse(invalidConfig, logger)).rejects.toThrow('Authentication required'); }); it('should initialize Synapse when private key is configured', async () => { const result = await setupSynapse(config, logger); expect(result).not.toBeNull(); expect(result?.synapse).toBeDefined(); expect(result?.storage).toBeDefined(); }); it('should log initialization events', async () => { const infoSpy = vi.spyOn(logger, 'info'); await setupSynapse(config, logger); // Check that initialization logs were called expect(infoSpy).toHaveBeenCalledWith(expect.objectContaining({ event: 'synapse.init', authMode: 'standard', rpcUrl: config.rpcUrl, }), 'Initializing Synapse SDK'); expect(infoSpy).toHaveBeenCalledWith(expect.objectContaining({ event: 'synapse.init.success' }), 'Synapse SDK initialized'); }); it('should call provider selection callback', async () => { const callbacks = []; const originalCreate = synapseSdk.Synapse.create; // Capture callbacks vi.mocked(originalCreate).mockImplementationOnce(async (options) => { const synapse = await originalCreate(options); const originalCreateContext = synapse.storage.createContext.bind(synapse.storage); synapse.storage.createContext = async (opts) => { if (opts?.callbacks?.onProviderSelected != null) { callbacks.push(opts.callbacks.onProviderSelected); } return await originalCreateContext(opts); }; return synapse; }); await setupSynapse(config, logger); expect(callbacks.length).toBeGreaterThan(0); }); }); describe('getSynapseService', () => { it('should return null when not initialized', () => { // Ensure service is reset resetSynapseService(); const result = getSynapseService(); expect(result).toBeNull(); }); it('should return service after initialization', async () => { await setupSynapse(config, logger); const result = getSynapseService(); expect(result).not.toBeNull(); expect(result?.synapse).toBeDefined(); expect(result?.storage).toBeDefined(); }); }); describe('uploadToSynapse', () => { let service; beforeEach(async () => { service = await setupSynapse(config, logger); }); it('should upload data successfully', async () => { const data = new Uint8Array([1, 2, 3]); const contextId = 'pin-123'; const result = await uploadToSynapse(service, data, TEST_CID, logger, { contextId }); expect(result).toHaveProperty('pieceCid'); expect(result).toHaveProperty('pieceId'); expect(result).toHaveProperty('dataSetId'); expect(result.pieceCid).toMatch(/^bafkzcib/); expect(result.dataSetId).toBe('123'); }); it('should log upload events', async () => { const infoSpy = vi.spyOn(logger, 'info'); const data = new Uint8Array([1, 2, 3]); const contextId = 'pin-456'; await uploadToSynapse(service, data, TEST_CID, logger, { contextId }); expect(infoSpy).toHaveBeenCalledWith(expect.objectContaining({ event: 'synapse.upload.piece_uploaded', contextId, }), 'Upload to PDP server complete'); expect(infoSpy).toHaveBeenCalledWith(expect.objectContaining({ event: 'synapse.upload.success', contextId, }), 'Successfully uploaded to Filecoin with Synapse'); }); it('should call upload callbacks', async () => { let uploadCompleteCallbackCalled = false; let pieceAddedCallbackCalled = false; const data = new Uint8Array([1, 2, 3]); await uploadToSynapse(service, data, TEST_CID, logger, { contextId: 'pin-789', onProgress(event) { switch (event.type) { case 'onUploadComplete': { uploadCompleteCallbackCalled = true; break; } case 'onPieceAdded': { pieceAddedCallbackCalled = true; break; } } }, }); expect(uploadCompleteCallbackCalled).toBe(true); expect(pieceAddedCallbackCalled).toBe(true); }); }); describe('Provider Information', () => { it('should capture provider info during initialization', async () => { const mockConfig = { privateKey: 'test-private-key', rpcUrl: 'wss://wss.calibration.node.glif.io/apigw/lotus/rpc/v1', }; const service = await setupSynapse(mockConfig, logger); // Check that provider info was captured expect(service.providerInfo).toBeDefined(); expect(service.providerInfo?.id).toBe(1); expect(service.providerInfo?.name).toBe('Mock Provider'); expect(service.providerInfo?.products?.PDP?.data?.serviceURL).toBe('http://localhost:8888/pdp'); }); it('should include provider info in upload result', async () => { // Ensure synapse is initialized with provider info const mockConfig = { privateKey: 'test-private-key', rpcUrl: 'wss://wss.calibration.node.glif.io/apigw/lotus/rpc/v1', }; const service = await setupSynapse(mockConfig, logger); // Now test the upload with synapse-upload.ts const data = new Uint8Array([1, 2, 3]); const result = await uploadToSynapse(service, data, TEST_CID, logger, { contextId: 'test-upload', }); // Verify provider info is included in result expect(result.providerInfo).toBeDefined(); expect(result.providerInfo?.id).toBe(1); expect(result.providerInfo?.name).toBe('Mock Provider'); expect(result.providerInfo?.products?.PDP?.data?.serviceURL).toBe('http://localhost:8888/pdp'); }); it('should always include provider info', async () => { // Initialize with provider info const mockConfig = { privateKey: 'test-private-key', rpcUrl: 'wss://wss.calibration.node.glif.io/apigw/lotus/rpc/v1', }; const service = await setupSynapse(mockConfig, logger); const data = new Uint8Array([1, 2, 3]); const result = await uploadToSynapse(service, data, TEST_CID, logger, { contextId: 'test-upload', }); // Verify upload includes provider info expect(result.pieceCid).toBeDefined(); expect(result.providerInfo).toBeDefined(); expect(result.providerInfo.id).toBe(1); expect(result.providerInfo.name).toBe('Mock Provider'); }); it('should handle provider without serviceURL gracefully', async () => { const mockConfig = { privateKey: 'test-private-key', rpcUrl: 'wss://wss.calibration.node.glif.io/apigw/lotus/rpc/v1', }; const service = await setupSynapse(mockConfig, logger); // Modify provider info to not have serviceURL if (service.providerInfo) { ; service.providerInfo.products = { PDP: { data: { // No serviceURL }, }, }; } const data = new Uint8Array([1, 2, 3]); const result = await uploadToSynapse(service, data, TEST_CID, logger, { contextId: 'test-upload', }); // Verify upload works with provider info (serviceURL is empty) expect(result.pieceCid).toBeDefined(); expect(result.providerInfo).toBeDefined(); expect(result.providerInfo.products?.PDP?.data?.serviceURL).toBeUndefined(); }); }); describe('Session Key Authentication', () => { it('should accept session key with valid ADD_PIECES permission even without CREATE_DATA_SET', async () => { const mockConfig = { walletAddress: '0x1234567890123456789012345678901234567890', sessionKey: '0x0000000000000000000000000000000000000000000000000000000000000001', rpcUrl: 'wss://wss.calibration.node.glif.io/apigw/lotus/rpc/v1', }; // Mock session key with no CREATE_DATA_SET permission (expiry = 0) const mockCreateSessionKey = vi.fn(() => { const now = Math.floor(Date.now() / 1000); const oneYear = 365 * 24 * 60 * 60; return { fetchExpiries: async (typehashes) => { const expiries = {}; for (const typehash of typehashes) { // CREATE_DATA_SET has no permission (0) // ADD_PIECES has valid future expiry if (typehash.includes('1234567890abcdef')) { // This is CREATE_DATA_SET_TYPEHASH expiries[typehash] = 0n; } else { // This is ADD_PIECES_TYPEHASH expiries[typehash] = BigInt(now + oneYear); } } return expiries; }, }; }); // Override the mock const originalCreate = synapseSdk.Synapse.create; vi.mocked(synapseSdk.Synapse.create).mockImplementationOnce(async (options) => { const synapse = await originalCreate(options); synapse.createSessionKey = mockCreateSessionKey; synapse.setSession = vi.fn(); return synapse; }); // Should not throw - session key with only ADD_PIECES is valid for reusing datasets const service = await setupSynapse(mockConfig, logger); expect(service).toBeDefined(); expect(mockCreateSessionKey).toHaveBeenCalled(); }); it('should reject session key with expired CREATE_DATA_SET permission', async () => { const mockConfig = { walletAddress: '0x1234567890123456789012345678901234567890', sessionKey: '0x0000000000000000000000000000000000000000000000000000000000000001', rpcUrl: 'wss://wss.calibration.node.glif.io/apigw/lotus/rpc/v1', }; // Mock session key with expired CREATE_DATA_SET permission const mockCreateSessionKey = vi.fn(() => { const now = Math.floor(Date.now() / 1000); const oneYear = 365 * 24 * 60 * 60; return { fetchExpiries: async (typehashes) => { const expiries = {}; for (const typehash of typehashes) { // CREATE_DATA_SET expired 1 hour ago // ADD_PIECES has valid future expiry if (typehash.includes('1234567890abcdef')) { // This is CREATE_DATA_SET_TYPEHASH - expired expiries[typehash] = BigInt(now - 3600); } else { // This is ADD_PIECES_TYPEHASH - valid expiries[typehash] = BigInt(now + oneYear); } } return expiries; }, }; }); // Override the mock const originalCreate = synapseSdk.Synapse.create; vi.mocked(synapseSdk.Synapse.create).mockImplementationOnce(async (options) => { const synapse = await originalCreate(options); synapse.createSessionKey = mockCreateSessionKey; return synapse; }); // Should throw - expired CREATE_DATA_SET permission await expect(setupSynapse(mockConfig, logger)).rejects.toThrow('Session key expired or expiring soon'); }); it('should reject session key with expired ADD_PIECES permission', async () => { const mockConfig = { walletAddress: '0x1234567890123456789012345678901234567890', sessionKey: '0x0000000000000000000000000000000000000000000000000000000000000001', rpcUrl: 'wss://wss.calibration.node.glif.io/apigw/lotus/rpc/v1', }; // Mock session key with expired ADD_PIECES permission const mockCreateSessionKey = vi.fn(() => { const now = Math.floor(Date.now() / 1000); const oneYear = 365 * 24 * 60 * 60; return { fetchExpiries: async (typehashes) => { const expiries = {}; for (const typehash of typehashes) { // CREATE_DATA_SET has valid permission // ADD_PIECES expired 1 hour ago if (typehash.includes('1234567890abcdef')) { // This is CREATE_DATA_SET_TYPEHASH - valid expiries[typehash] = BigInt(now + oneYear); } else { // This is ADD_PIECES_TYPEHASH - expired expiries[typehash] = BigInt(now - 3600); } } return expiries; }, }; }); // Override the mock const originalCreate = synapseSdk.Synapse.create; vi.mocked(synapseSdk.Synapse.create).mockImplementationOnce(async (options) => { const synapse = await originalCreate(options); synapse.createSessionKey = mockCreateSessionKey; return synapse; }); // Should throw - expired ADD_PIECES permission (always required) await expect(setupSynapse(mockConfig, logger)).rejects.toThrow('Session key expired or expiring soon'); }); }); }); //# sourceMappingURL=synapse-service.test.js.map