feat: implement storage provider with local and S3 adapters
This commit is contained in:
143
tests/lib/storage/storage.s3.test.ts
Normal file
143
tests/lib/storage/storage.s3.test.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
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<typeof mockClient>;
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user