import { S3StorageAdapter } from '@/lib/storage/storage.adapter'; import { DeleteObjectCommand, PutObjectCommand, S3Client, } from '@aws-sdk/client-s3'; import { mockClient } from 'aws-sdk-client-mock'; describe('S3StorageAdapter', () => { let s3Mock: ReturnType; let mockS3Client: S3Client; let adapter: S3StorageAdapter; beforeEach(() => { s3Mock = mockClient(S3Client); mockS3Client = new S3Client({ region: 'us-east-1' }); adapter = new S3StorageAdapter( 'test-bucket', 'us-east-1', mockS3Client ); }); afterEach(() => { s3Mock.restore(); }); describe('put', () => { it('should upload file to S3 with correct parameters', async () => { const key = 'test-image.jpg'; const fileContent = 'test file content'; const file = Buffer.from(fileContent); s3Mock.on(PutObjectCommand).resolves({}); await adapter.put(key, file, 'image/jpeg'); expect(s3Mock.call(0).args[0]).toBeInstanceOf(PutObjectCommand); const command = s3Mock.call(0).args[0] as PutObjectCommand; expect(command.input.Bucket).toBe('test-bucket'); expect(command.input.Key).toBe(key); expect(command.input.ContentType).toBe('image/jpeg'); }); it('should return StorageResult with s3 type', async () => { const key = 'test-image.jpg'; const file = Buffer.from('test file content'); s3Mock.on(PutObjectCommand).resolves({}); const result = await adapter.put(key, file, 'image/jpeg'); expect(result).toEqual({ type: 's3', provider: 'aws-s3', key, publicUrl: `https://test-bucket.s3.us-east-1.amazonaws.com/${key}`, }); }); it('should return correct public URL with different regions', async () => { const adapter2 = new S3StorageAdapter( 'my-bucket', 'eu-west-1', mockS3Client ); const key = 'my-image.png'; const file = Buffer.from('test'); s3Mock.on(PutObjectCommand).resolves({}); const result = await adapter2.put(key, file, 'image/png'); expect(result.publicUrl).toBe( `https://my-bucket.s3.eu-west-1.amazonaws.com/${key}` ); }); it('should handle Blob input', async () => { const key = 'test-blob.jpg'; const fileContent = 'blob content'; const blob = new Blob([fileContent], { type: 'image/jpeg' }); s3Mock.on(PutObjectCommand).resolves({}); const result = await adapter.put(key, blob, 'image/jpeg'); expect(result.type).toBe('s3'); expect(result.key).toBe(key); }); it('should handle nested keys', async () => { const key = 'articles/2026/04/image.jpg'; const file = Buffer.from('test'); s3Mock.on(PutObjectCommand).resolves({}); const result = await adapter.put(key, file, 'image/jpeg'); expect(result.key).toBe(key); expect(result.publicUrl).toBe( `https://test-bucket.s3.us-east-1.amazonaws.com/${key}` ); }); }); describe('delete', () => { it('should delete object from S3 with correct parameters', async () => { const key = 'test-image.jpg'; s3Mock.on(DeleteObjectCommand).resolves({}); await adapter.delete(key); expect(s3Mock.call(0).args[0]).toBeInstanceOf(DeleteObjectCommand); const command = s3Mock.call(0).args[0] as DeleteObjectCommand; expect(command.input.Bucket).toBe('test-bucket'); expect(command.input.Key).toBe(key); }); it('should not throw error if deletion fails', async () => { const key = 'test-image.jpg'; s3Mock.on(DeleteObjectCommand).rejects(new Error('S3 error')); // Should not throw expect(async () => { await adapter.delete(key); }).not.toThrow(); }); it('should handle nested keys', async () => { const key = 'articles/2026/04/image.jpg'; s3Mock.on(DeleteObjectCommand).resolves({}); await adapter.delete(key); const command = s3Mock.call(0).args[0] as DeleteObjectCommand; expect(command.input.Key).toBe(key); }); }); });