UNPKG

@julesl23/s5js

Version:

Enhanced TypeScript SDK for S5 decentralized storage with path-based API, media processing, and directory utilities

217 lines 8.65 kB
import { describe, it, expect, beforeEach } from 'vitest'; import { BatchOperations } from '../../../src/fs/utils/batch.js'; import { DirectoryWalker } from '../../../src/fs/utils/walker.js'; // Simple mock FS5 for testing class MockFS5 { files = new Map(); directories = new Set(); constructor() { // Initialize root directories this.directories.add('/'); this.directories.add('home'); } async put(path, data, options) { // Ensure parent directories exist const parts = path.split('/').filter(p => p); let currentPath = ''; for (let i = 0; i < parts.length - 1; i++) { currentPath += (currentPath ? '/' : '') + parts[i]; this.directories.add(currentPath); } const fullPath = parts.join('/'); const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data; this.files.set(fullPath, bytes); } async get(path) { const data = this.files.get(path); if (!data) throw new Error(`File not found: ${path}`); return new TextDecoder().decode(data); } async delete(path) { if (this.files.has(path)) { this.files.delete(path); } else if (this.directories.has(path)) { // Check if directory is empty const hasChildren = Array.from(this.files.keys()).some(f => f.startsWith(path + '/')) || Array.from(this.directories).some(d => d !== path && d.startsWith(path + '/')); if (hasChildren) { throw new Error(`Directory ${path} is not empty`); } this.directories.delete(path); } else { throw new Error(`Path not found: ${path}`); } } async createDirectory(path) { const parts = path.split('/').filter(p => p); let currentPath = ''; for (const part of parts) { currentPath += (currentPath ? '/' : '') + part; this.directories.add(currentPath); } } async getMetadata(path) { if (this.files.has(path)) { return { type: 'file', path }; } else if (this.directories.has(path)) { return { type: 'directory', path }; } return null; } async *list(path, options) { const prefix = path === '/' ? '' : path + '/'; const yielded = new Set(); // List files for (const [filePath, data] of this.files.entries()) { if (filePath.startsWith(prefix)) { const relativePath = filePath.substring(prefix.length); const parts = relativePath.split('/'); if (parts.length === 1) { // Direct child file yield { name: parts[0], value: { hash: new Uint8Array(32), size: data.length } }; } else { // Subdirectory const dirName = parts[0]; if (!yielded.has(dirName)) { yielded.add(dirName); yield { name: dirName, value: { link: { type: 'fixed_hash_blake3', hash: new Uint8Array(32) } } }; } } } } // List directories for (const dir of this.directories) { if (dir.startsWith(prefix) && dir !== path) { const relativePath = dir.substring(prefix.length); const parts = relativePath.split('/'); if (parts.length === 1 && !yielded.has(parts[0])) { yield { name: parts[0], value: { link: { type: 'fixed_hash_blake3', hash: new Uint8Array(32) } } }; } } } } // Mock API for compatibility api = { downloadBlobAsBytes: async (hash) => { // Find file by hash (mock - just return first matching file) for (const data of this.files.values()) { return data; } throw new Error('Blob not found'); } }; } describe('BatchOperations Simple Tests', () => { let fs; let batch; beforeEach(async () => { fs = new MockFS5(); batch = new BatchOperations(fs); // Create test directory structure await fs.put('home/source/file1.txt', 'content1'); await fs.put('home/source/file2.txt', 'content2'); await fs.put('home/source/subdir/file3.txt', 'content3'); await fs.put('home/source/subdir/deep/file4.txt', 'content4'); }); describe('copyDirectory', () => { it('should copy entire directory structure', async () => { // First verify source files exist const sourceFile1 = await fs.get('home/source/file1.txt'); expect(sourceFile1).toBe('content1'); // Debug: list source directory console.log('Source directory contents:'); for await (const item of fs.list('home/source')) { console.log('- ', item.name, 'link' in item.value ? 'DIR' : 'FILE'); } // Test walker directly const walker = new DirectoryWalker(fs, 'home/source'); console.log('Walker test:'); for await (const item of walker.walk()) { console.log('Walked:', item.path, item.type); } const result = await batch.copyDirectory('home/source', 'home/destination'); console.log('Copy result:', result); expect(result.success).toBeGreaterThanOrEqual(4); // All files expect(result.failed).toBe(0); // Verify files were copied const file1 = await fs.get('home/destination/file1.txt'); expect(file1).toBe('content1'); const file4 = await fs.get('home/destination/subdir/deep/file4.txt'); expect(file4).toBe('content4'); }); it('should handle non-existent source directory', async () => { try { await batch.copyDirectory('home/non-existent', 'home/destination'); expect.fail('Should throw error'); } catch (error) { expect(error).toBeDefined(); } }); it('should support progress callback', async () => { const progress = []; await batch.copyDirectory('home/source', 'home/destination', { onProgress: (p) => { progress.push({ processed: p.processed }); } }); expect(progress.length).toBeGreaterThan(0); expect(progress[progress.length - 1].processed).toBeGreaterThanOrEqual(4); }); }); describe('deleteDirectory', () => { it('should delete empty directory non-recursively', async () => { await fs.createDirectory('home/empty-dir'); const result = await batch.deleteDirectory('home/empty-dir', { recursive: false }); expect(result.success).toBe(1); expect(result.failed).toBe(0); }); it('should delete directory recursively', async () => { const result = await batch.deleteDirectory('home/source', { recursive: true }); expect(result.success).toBeGreaterThanOrEqual(4); // All files and directories expect(result.failed).toBe(0); // Verify files are gone try { await fs.get('home/source/file1.txt'); expect.fail('File should be deleted'); } catch (error) { expect(error).toBeDefined(); } }); it('should fail on non-empty directory without recursive', async () => { const result = await batch.deleteDirectory('home/source', { recursive: false }); expect(result.success).toBe(0); expect(result.failed).toBe(1); }); }); describe('_ensureDirectory', () => { it('should create nested directory structure', async () => { await batch._ensureDirectory('home/a/b/c/d/e'); const meta = await fs.getMetadata('home/a/b/c'); expect(meta).toBeDefined(); expect(meta.type).toBe('directory'); }); }); }); //# sourceMappingURL=batch-simple.test.js.map