UNPKG

@profullstack/storage-service

Version:

Flexible storage service abstraction for file storage, retrieval, and metadata management

368 lines (299 loc) 11.8 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { StorageService, MemoryAdapter } from '../src/index.js'; describe('StorageService', () => { let storageService; beforeEach(() => { storageService = new StorageService(); }); describe('constructor', () => { it('should create a storage service with default options', () => { expect(storageService).toBeInstanceOf(StorageService); expect(storageService.adapter).toBeInstanceOf(MemoryAdapter); expect(storageService.defaultBucket).toBe('default'); expect(storageService.generateUniqueFilenames).toBe(true); }); it('should create a storage service with custom options', () => { const customAdapter = new MemoryAdapter(); const customService = new StorageService({ adapter: customAdapter, defaultBucket: 'custom-bucket', generateUniqueFilenames: false }); expect(customService.adapter).toBe(customAdapter); expect(customService.defaultBucket).toBe('custom-bucket'); expect(customService.generateUniqueFilenames).toBe(false); }); }); describe('bucket operations', () => { it('should create a bucket', async () => { const result = await storageService.createBucket('test-bucket'); expect(result).toHaveProperty('name', 'test-bucket'); expect(result).toHaveProperty('createdAt'); const buckets = await storageService.listBuckets(); expect(buckets).toHaveLength(1); expect(buckets[0].name).toBe('test-bucket'); }); it('should list buckets', async () => { await storageService.createBucket('bucket1'); await storageService.createBucket('bucket2'); const buckets = await storageService.listBuckets(); expect(buckets).toHaveLength(2); expect(buckets[0].name).toBe('bucket1'); expect(buckets[1].name).toBe('bucket2'); }); it('should delete a bucket', async () => { await storageService.createBucket('bucket-to-delete'); const result = await storageService.deleteBucket('bucket-to-delete'); expect(result).toBe(true); const buckets = await storageService.listBuckets(); expect(buckets).toHaveLength(0); }); it('should throw an error when deleting a non-empty bucket without force option', async () => { await storageService.createBucket('non-empty-bucket'); await storageService.uploadFile({ bucketName: 'non-empty-bucket', path: 'test-file.txt', data: 'test content' }); await expect(storageService.deleteBucket('non-empty-bucket')) .rejects.toThrow('Bucket "non-empty-bucket" is not empty'); // Should succeed with force option const result = await storageService.deleteBucket('non-empty-bucket', { force: true }); expect(result).toBe(true); }); }); describe('file operations', () => { beforeEach(async () => { await storageService.createBucket('test-bucket'); }); it('should upload a file', async () => { const result = await storageService.uploadFile({ bucketName: 'test-bucket', path: 'test-file.txt', data: 'test content', contentType: 'text/plain' }); expect(result).toHaveProperty('path'); expect(result.path).toMatch(/test-file_.*\.txt/); // Should have timestamp in name expect(result).toHaveProperty('contentType', 'text/plain'); expect(result).toHaveProperty('metadata'); }); it('should upload a file with custom path when generateUniqueFilenames is false', async () => { const customService = new StorageService({ generateUniqueFilenames: false }); await customService.createBucket('test-bucket'); const result = await customService.uploadFile({ bucketName: 'test-bucket', path: 'custom-path.txt', data: 'test content' }); expect(result.path).toBe('custom-path.txt'); }); it('should download a file', async () => { // Upload a file first const uploadResult = await storageService.uploadFile({ bucketName: 'test-bucket', path: 'download-test.txt', data: 'content to download', contentType: 'text/plain', metadata: { testKey: 'testValue' } }); // Download the file const downloadResult = await storageService.downloadFile({ bucketName: 'test-bucket', path: uploadResult.path, responseType: 'text' }); expect(downloadResult).toHaveProperty('data', 'content to download'); expect(downloadResult).toHaveProperty('contentType', 'text/plain'); expect(downloadResult).toHaveProperty('metadata.testKey', 'testValue'); }); it('should get file info', async () => { // Upload a file first const uploadResult = await storageService.uploadFile({ bucketName: 'test-bucket', path: 'info-test.txt', data: 'test content', metadata: { testKey: 'testValue' } }); // Get file info const fileInfo = await storageService.getFileInfo({ bucketName: 'test-bucket', path: uploadResult.path }); expect(fileInfo).toHaveProperty('path', uploadResult.path); expect(fileInfo).toHaveProperty('size', 12); // 'test content' length expect(fileInfo).toHaveProperty('metadata.testKey', 'testValue'); }); it('should list files', async () => { // Upload some files await storageService.uploadFile({ bucketName: 'test-bucket', path: 'file1.txt', data: 'content 1' }); await storageService.uploadFile({ bucketName: 'test-bucket', path: 'file2.txt', data: 'content 2' }); await storageService.uploadFile({ bucketName: 'test-bucket', path: 'subdir/file3.txt', data: 'content 3' }); // List all files const allFiles = await storageService.listFiles({ bucketName: 'test-bucket' }); expect(allFiles.files).toHaveLength(3); // List files with prefix const subdirFiles = await storageService.listFiles({ bucketName: 'test-bucket', prefix: 'subdir/' }); expect(subdirFiles.files).toHaveLength(1); expect(subdirFiles.files[0].path).toMatch(/subdir\/file3/); }); it('should delete a file', async () => { // Upload a file first const uploadResult = await storageService.uploadFile({ bucketName: 'test-bucket', path: 'delete-test.txt', data: 'content to delete' }); // Delete the file const deleteResult = await storageService.deleteFile({ bucketName: 'test-bucket', path: uploadResult.path }); expect(deleteResult).toBe(true); // Verify file is deleted await expect(storageService.getFileInfo({ bucketName: 'test-bucket', path: uploadResult.path })).rejects.toThrow(); }); it('should copy a file', async () => { // Upload a file first const uploadResult = await storageService.uploadFile({ bucketName: 'test-bucket', path: 'source-file.txt', data: 'content to copy', metadata: { source: true } }); // Copy the file const copyResult = await storageService.copyFile({ sourceBucket: 'test-bucket', sourcePath: uploadResult.path, destinationBucket: 'test-bucket', destinationPath: 'destination-file.txt' }); expect(copyResult).toHaveProperty('path', 'destination-file.txt'); // Verify both files exist const sourceInfo = await storageService.getFileInfo({ bucketName: 'test-bucket', path: uploadResult.path }); const destInfo = await storageService.getFileInfo({ bucketName: 'test-bucket', path: 'destination-file.txt' }); expect(sourceInfo).toHaveProperty('metadata.source', true); expect(destInfo).toHaveProperty('metadata.source', true); }); it('should move a file', async () => { // Upload a file first const uploadResult = await storageService.uploadFile({ bucketName: 'test-bucket', path: 'move-source.txt', data: 'content to move' }); // Move the file const moveResult = await storageService.moveFile({ sourceBucket: 'test-bucket', sourcePath: uploadResult.path, destinationBucket: 'test-bucket', destinationPath: 'move-destination.txt' }); expect(moveResult).toHaveProperty('path', 'move-destination.txt'); // Verify source is gone and destination exists await expect(storageService.getFileInfo({ bucketName: 'test-bucket', path: uploadResult.path })).rejects.toThrow(); const destInfo = await storageService.getFileInfo({ bucketName: 'test-bucket', path: 'move-destination.txt' }); expect(destInfo).toHaveProperty('path', 'move-destination.txt'); }); it('should update metadata', async () => { // Upload a file first const uploadResult = await storageService.uploadFile({ bucketName: 'test-bucket', path: 'metadata-test.txt', data: 'test content', metadata: { initial: 'value' } }); // Update metadata const updateResult = await storageService.updateMetadata({ bucketName: 'test-bucket', path: uploadResult.path, metadata: { updated: true, initial: 'new-value' } }); expect(updateResult).toHaveProperty('updated', true); expect(updateResult).toHaveProperty('initial', 'new-value'); // Verify metadata was updated const fileInfo = await storageService.getFileInfo({ bucketName: 'test-bucket', path: uploadResult.path }); expect(fileInfo.metadata).toHaveProperty('updated', true); expect(fileInfo.metadata).toHaveProperty('initial', 'new-value'); }); it('should search files by metadata', async () => { // Upload files with different metadata await storageService.uploadFile({ bucketName: 'test-bucket', path: 'search1.txt', data: 'content 1', metadata: { category: 'A', tag: 'test' } }); await storageService.uploadFile({ bucketName: 'test-bucket', path: 'search2.txt', data: 'content 2', metadata: { category: 'B', tag: 'test' } }); await storageService.uploadFile({ bucketName: 'test-bucket', path: 'search3.txt', data: 'content 3', metadata: { category: 'A', tag: 'production' } }); // Search by category const categoryResults = await storageService.searchFiles({ bucketName: 'test-bucket', metadata: { category: 'A' } }); expect(categoryResults.files).toHaveLength(2); // Search by tag const tagResults = await storageService.searchFiles({ bucketName: 'test-bucket', metadata: { tag: 'test' } }); expect(tagResults.files).toHaveLength(2); // Search by multiple criteria const multiResults = await storageService.searchFiles({ bucketName: 'test-bucket', metadata: { category: 'A', tag: 'test' } }); expect(multiResults.files).toHaveLength(1); }); }); });