mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
425 lines
14 KiB
TypeScript
425 lines
14 KiB
TypeScript
/**
|
|
* Integration tests for IPFS/Helia with Docker IPFS nodes
|
|
*
|
|
* These tests require Docker and docker-compose to be running with IPFS nodes.
|
|
* Run with: docker-compose up -d ipfs
|
|
*/
|
|
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import { IPFSClient, createIPFSClient } from '../../src/client/services/ipfs';
|
|
// TODO: Fix imports - vault/enclave and client-ipfs paths are incorrect
|
|
// import { EnclaveIPFSManager } from '../../src/plugins/vault/enclave';
|
|
import { EnclaveIPFSManager } from '../../src/plugin/enclave';
|
|
import { IPFSCache } from '../../src/client/services/ipfs-cache';
|
|
// import { VaultClientWithIPFS } from '../../src/plugins/vault/client-ipfs';
|
|
import { VaultClientWithIPFS } from '../../src/plugin/client-ipfs';
|
|
// import type { EnclaveDataWithCID } from '../../src/plugins/vault/enclave';
|
|
import type { EnclaveDataWithCID } from '../../src/plugin/enclave';
|
|
|
|
// Skip these tests in CI environment or when IPFS is not available
|
|
const skipIntegrationTests = process.env.CI === 'true' || process.env.SKIP_INTEGRATION === 'true';
|
|
|
|
describe.skipIf(skipIntegrationTests)('IPFS Integration Tests', () => {
|
|
let ipfsClient: IPFSClient;
|
|
let enclaveManager: EnclaveIPFSManager;
|
|
let cache: IPFSCache;
|
|
|
|
beforeAll(async () => {
|
|
// Initialize IPFS client with local node
|
|
ipfsClient = await createIPFSClient({
|
|
gateways: ['http://localhost:8080', 'http://localhost:5001'],
|
|
enablePersistence: true,
|
|
});
|
|
|
|
// Create enclave manager
|
|
enclaveManager = new EnclaveIPFSManager(ipfsClient, {
|
|
encryptionRequired: false, // Simplified for testing
|
|
pinningEnabled: true,
|
|
maxRetries: 3,
|
|
});
|
|
|
|
// Initialize cache
|
|
cache = new IPFSCache({
|
|
maxSize: 100,
|
|
ttl: 300000,
|
|
enablePersistence: true,
|
|
});
|
|
}, 30000); // Allow 30 seconds for initialization
|
|
|
|
afterAll(async () => {
|
|
await ipfsClient.cleanup();
|
|
await cache.destroy();
|
|
});
|
|
|
|
describe('Basic IPFS Operations', () => {
|
|
it('should connect to IPFS node and get status', async () => {
|
|
const status = await ipfsClient.getNodeStatus();
|
|
|
|
expect(status.peerId).toBeDefined();
|
|
expect(status.isOnline).toBe(true);
|
|
console.log('Connected to IPFS node:', status.peerId);
|
|
});
|
|
|
|
it('should store and retrieve data', async () => {
|
|
const testData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
|
|
|
|
const { cid } = await ipfsClient.addEnclaveData(testData);
|
|
expect(cid).toBeDefined();
|
|
|
|
const retrieved = await ipfsClient.getEnclaveData(cid);
|
|
expect(retrieved).toEqual(testData);
|
|
});
|
|
|
|
it('should store and retrieve strings', async () => {
|
|
const testString = 'Hello IPFS from Sonr integration test!';
|
|
|
|
const cid = await ipfsClient.addString(testString);
|
|
expect(cid).toBeDefined();
|
|
|
|
const retrieved = await ipfsClient.getString(cid);
|
|
expect(retrieved).toBe(testString);
|
|
});
|
|
});
|
|
|
|
describe('Large File Handling', () => {
|
|
it('should handle large enclave data (1MB)', async () => {
|
|
const largeData = new Uint8Array(1024 * 1024); // 1MB
|
|
// Fill with random data
|
|
for (let i = 0; i < largeData.length; i++) {
|
|
largeData[i] = Math.floor(Math.random() * 256);
|
|
}
|
|
|
|
const { cid, size } = await ipfsClient.addEnclaveData(largeData);
|
|
expect(cid).toBeDefined();
|
|
expect(size).toBe(largeData.length);
|
|
|
|
const retrieved = await ipfsClient.getEnclaveData(cid);
|
|
expect(retrieved.length).toBe(largeData.length);
|
|
expect(retrieved).toEqual(largeData);
|
|
}, 60000); // Allow 60 seconds for large file
|
|
|
|
it('should handle chunked data retrieval', async () => {
|
|
// Create data that will be chunked
|
|
const chunkSize = 256 * 1024; // 256KB chunks
|
|
const totalSize = chunkSize * 3; // 768KB
|
|
const chunkedData = new Uint8Array(totalSize);
|
|
|
|
// Fill with pattern for verification
|
|
for (let i = 0; i < chunkedData.length; i++) {
|
|
chunkedData[i] = i % 256;
|
|
}
|
|
|
|
const { cid } = await ipfsClient.addEnclaveData(chunkedData);
|
|
const retrieved = await ipfsClient.getEnclaveData(cid);
|
|
|
|
// Verify data integrity
|
|
expect(retrieved.length).toBe(totalSize);
|
|
for (let i = 0; i < 100; i++) {
|
|
const idx = Math.floor(Math.random() * totalSize);
|
|
expect(retrieved[idx]).toBe(idx % 256);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('MPC Enclave Storage', () => {
|
|
it('should store and retrieve MPC enclave data', async () => {
|
|
const enclaveData: EnclaveDataWithCID = {
|
|
publicKey: 'test-public-key-12345',
|
|
privateKeyShares: ['share1', 'share2', 'share3'],
|
|
threshold: 2,
|
|
parties: 3,
|
|
encryptionMetadata: {
|
|
algorithm: 'AES-256-GCM',
|
|
keyVersion: 1,
|
|
consensusHeight: 1000,
|
|
nonce: 'test-nonce-123',
|
|
},
|
|
};
|
|
|
|
const payload = new TextEncoder().encode(JSON.stringify(enclaveData));
|
|
|
|
const result = await enclaveManager.storeEnclaveData(enclaveData, payload);
|
|
expect(result.cid).toBeDefined();
|
|
expect(result.isPinned).toBe(true);
|
|
|
|
const retrieved = await enclaveManager.retrieveEnclaveData(result.cid);
|
|
const decoded = JSON.parse(new TextDecoder().decode(retrieved));
|
|
expect(decoded.publicKey).toBe(enclaveData.publicKey);
|
|
});
|
|
|
|
it('should verify enclave data integrity', async () => {
|
|
const testData = new Uint8Array([10, 20, 30, 40, 50]);
|
|
const { cid } = await ipfsClient.addEnclaveData(testData);
|
|
|
|
const enclaveData: EnclaveDataWithCID = {
|
|
publicKey: 'integrity-test-key',
|
|
privateKeyShares: ['share1'],
|
|
threshold: 1,
|
|
parties: 1,
|
|
};
|
|
|
|
const isValid = await enclaveManager.verifyEnclaveDataIntegrity(cid, testData);
|
|
expect(isValid).toBe(true);
|
|
|
|
const tamperedData = new Uint8Array([10, 20, 30, 40, 51]); // Changed last byte
|
|
const isInvalid = await enclaveManager.verifyEnclaveDataIntegrity(cid, tamperedData);
|
|
expect(isInvalid).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Pinning Operations', () => {
|
|
it('should pin and unpin content', async () => {
|
|
const testData = new Uint8Array([100, 200, 50]);
|
|
const { cid } = await ipfsClient.addEnclaveData(testData);
|
|
|
|
await ipfsClient.pin(cid);
|
|
let isPinned = await ipfsClient.isPinned(cid);
|
|
expect(isPinned).toBe(true);
|
|
|
|
await ipfsClient.unpin(cid);
|
|
isPinned = await ipfsClient.isPinned(cid);
|
|
expect(isPinned).toBe(false);
|
|
});
|
|
|
|
it('should list all pinned CIDs', async () => {
|
|
const data1 = new Uint8Array([1, 1, 1]);
|
|
const data2 = new Uint8Array([2, 2, 2]);
|
|
|
|
const { cid: cid1 } = await ipfsClient.addEnclaveData(data1);
|
|
const { cid: cid2 } = await ipfsClient.addEnclaveData(data2);
|
|
|
|
await ipfsClient.pin(cid1);
|
|
await ipfsClient.pin(cid2);
|
|
|
|
const pins = await ipfsClient.listPins();
|
|
expect(pins).toContain(cid1);
|
|
expect(pins).toContain(cid2);
|
|
});
|
|
});
|
|
|
|
describe('Caching Integration', () => {
|
|
it('should cache retrieved data', async () => {
|
|
const testData = new Uint8Array([111, 222, 333]);
|
|
const { cid } = await ipfsClient.addEnclaveData(testData);
|
|
|
|
// First retrieval - from IPFS
|
|
const start1 = Date.now();
|
|
await cache.set(cid, testData);
|
|
const time1 = Date.now() - start1;
|
|
|
|
// Second retrieval - from cache
|
|
const start2 = Date.now();
|
|
const cached = await cache.get(cid);
|
|
const time2 = Date.now() - start2;
|
|
|
|
expect(cached).toEqual(testData);
|
|
expect(time2).toBeLessThan(time1); // Cache should be faster
|
|
|
|
const stats = cache.getStats();
|
|
expect(stats.hits).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should preload multiple CIDs into cache', async () => {
|
|
const cids: string[] = [];
|
|
const dataMap = new Map<string, Uint8Array>();
|
|
|
|
// Store multiple items
|
|
for (let i = 0; i < 5; i++) {
|
|
const data = new Uint8Array([i, i, i]);
|
|
const { cid } = await ipfsClient.addEnclaveData(data);
|
|
cids.push(cid);
|
|
dataMap.set(cid, data);
|
|
}
|
|
|
|
// Preload into cache
|
|
await cache.preload(cids, async (cid) => {
|
|
return await ipfsClient.getEnclaveData(cid);
|
|
});
|
|
|
|
// Verify all are cached
|
|
for (const cid of cids) {
|
|
expect(await cache.has(cid)).toBe(true);
|
|
const cached = await cache.get(cid);
|
|
expect(cached).toEqual(dataMap.get(cid));
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Network Failure Recovery', () => {
|
|
it('should retry on transient failures', async () => {
|
|
// This test simulates retry logic
|
|
const enclaveData: EnclaveDataWithCID = {
|
|
publicKey: 'retry-test-key',
|
|
privateKeyShares: ['share1'],
|
|
threshold: 1,
|
|
parties: 1,
|
|
};
|
|
|
|
const payload = new Uint8Array([99, 99, 99]);
|
|
|
|
// Store data (should succeed even with retries configured)
|
|
const result = await enclaveManager.storeEnclaveData(enclaveData, payload);
|
|
expect(result.cid).toBeDefined();
|
|
|
|
// Retrieve with retry logic
|
|
const retrieved = await enclaveManager.retrieveEnclaveData(result.cid);
|
|
expect(retrieved).toEqual(payload);
|
|
});
|
|
|
|
it('should use gateway fallback when direct connection fails', async () => {
|
|
const testData = new Uint8Array([77, 88, 99]);
|
|
const { cid } = await ipfsClient.addEnclaveData(testData);
|
|
|
|
// Test verified fetch with fallback
|
|
const response = await ipfsClient.verifiedFetch(cid);
|
|
expect(response.ok).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Batch Operations', () => {
|
|
it('should batch store multiple enclaves', async () => {
|
|
const enclaves = [];
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
const enclaveData: EnclaveDataWithCID = {
|
|
publicKey: `batch-key-${i}`,
|
|
privateKeyShares: [`share-${i}`],
|
|
threshold: 1,
|
|
parties: 1,
|
|
};
|
|
|
|
const payload = new Uint8Array([i, i, i]);
|
|
enclaves.push({ data: enclaveData, payload });
|
|
}
|
|
|
|
const results = await enclaveManager.batchStoreEnclaves(enclaves);
|
|
|
|
expect(results).toHaveLength(3);
|
|
for (const result of results) {
|
|
expect(result.cid).toBeDefined();
|
|
expect(result.isPinned).toBe(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('VaultClient IPFS Integration', () => {
|
|
it('should initialize VaultClient with IPFS', async () => {
|
|
const vaultClient = new VaultClientWithIPFS({
|
|
chainId: 'test-chain',
|
|
enableIPFSPersistence: true,
|
|
ipfsGateways: ['http://localhost:8080'],
|
|
});
|
|
|
|
// Initialize with IPFS (skip WASM for integration test)
|
|
try {
|
|
await vaultClient.initializeWithIPFS();
|
|
|
|
const status = await vaultClient.getIPFSStatus();
|
|
expect(status.peerId).toBeDefined();
|
|
expect(status.isOnline).toBe(true);
|
|
|
|
await vaultClient.cleanup();
|
|
} catch (error) {
|
|
// WASM initialization might fail in test environment
|
|
// But IPFS should still be initialized
|
|
if (error.message.includes('WASM')) {
|
|
console.log('WASM initialization skipped in test');
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Data Persistence', () => {
|
|
it('should persist data across client reconnections', async () => {
|
|
const testData = new Uint8Array([50, 60, 70, 80, 90]);
|
|
const { cid } = await ipfsClient.addEnclaveData(testData);
|
|
|
|
// Clean up current client
|
|
await ipfsClient.cleanup();
|
|
|
|
// Create new client
|
|
const newClient = await createIPFSClient({
|
|
gateways: ['http://localhost:8080'],
|
|
});
|
|
|
|
// Should still be able to retrieve the data
|
|
const retrieved = await newClient.getEnclaveData(cid);
|
|
expect(retrieved).toEqual(testData);
|
|
|
|
await newClient.cleanup();
|
|
});
|
|
});
|
|
|
|
describe('CID Validation', () => {
|
|
it('should validate CID format', async () => {
|
|
const invalidCids = [
|
|
'invalid-cid',
|
|
'12345',
|
|
'',
|
|
'Qm', // Too short
|
|
];
|
|
|
|
for (const invalidCid of invalidCids) {
|
|
await expect(ipfsClient.getEnclaveData(invalidCid)).rejects.toThrow();
|
|
}
|
|
});
|
|
|
|
it('should handle non-existent CIDs gracefully', async () => {
|
|
const nonExistentCid = 'QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG';
|
|
|
|
// This should timeout or throw after retries
|
|
await expect(
|
|
Promise.race([
|
|
ipfsClient.getEnclaveData(nonExistentCid),
|
|
new Promise((_, reject) =>
|
|
setTimeout(() => reject(new Error('Timeout')), 5000)
|
|
)
|
|
])
|
|
).rejects.toThrow();
|
|
});
|
|
});
|
|
});
|
|
|
|
// Performance benchmarks (optional, run with --bench flag)
|
|
describe.skipIf(skipIntegrationTests)('IPFS Performance Benchmarks', () => {
|
|
let ipfsClient: IPFSClient;
|
|
|
|
beforeAll(async () => {
|
|
ipfsClient = await createIPFSClient({
|
|
gateways: ['http://localhost:8080'],
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await ipfsClient.cleanup();
|
|
});
|
|
|
|
it('should measure storage performance', async () => {
|
|
const sizes = [1024, 10240, 102400]; // 1KB, 10KB, 100KB
|
|
const results: any[] = [];
|
|
|
|
for (const size of sizes) {
|
|
const data = new Uint8Array(size);
|
|
crypto.getRandomValues(data);
|
|
|
|
const start = performance.now();
|
|
const { cid } = await ipfsClient.addEnclaveData(data);
|
|
const storeTime = performance.now() - start;
|
|
|
|
const retrieveStart = performance.now();
|
|
await ipfsClient.getEnclaveData(cid);
|
|
const retrieveTime = performance.now() - retrieveStart;
|
|
|
|
results.push({
|
|
size: `${size / 1024}KB`,
|
|
storeTime: `${storeTime.toFixed(2)}ms`,
|
|
retrieveTime: `${retrieveTime.toFixed(2)}ms`,
|
|
throughput: `${(size / storeTime * 1000 / 1024).toFixed(2)}KB/s`,
|
|
});
|
|
}
|
|
|
|
console.table(results);
|
|
});
|
|
}); |