@appotter/nestjs-s3
Version:
NestJS provider to integrates with AWS S3
255 lines (204 loc) • 6.81 kB
text/typescript
import dotenv from 'dotenv';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { S3_CONFIGURATION } from './constants';
import { S3Service } from './s3.service';
import { Options, UploadedFile } from './interfaces';
import {
DeleteObjectCommand,
GetObjectCommand,
ListObjectsCommand,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3';
import { randomUUID } from 'crypto';
import { mockClient } from 'aws-sdk-client-mock';
import { Logger } from '@nestjs/common';
import { Readable } from 'stream';
import { sdkStreamMixin } from '@smithy/util-stream';
const configService = new ConfigService(dotenv.config());
const fakeS3Client = mockClient(S3Client);
const mockedFile = {
fieldname: 'file',
originalname: 'file.png',
encoding: '7bit',
mimetype: 'image/png',
buffer: Buffer.from('fake-file.png'),
size: 256,
} as UploadedFile;
describe('S3Service', () => {
let service: S3Service;
let accessKeyId: string;
let secretAccessKey: string;
let region: string;
let bucket: string;
let endpoint: string;
beforeEach(async () => {
accessKeyId = configService.get<string>('S3_ACCESS_KEY_ID');
secretAccessKey = configService.get<string>('S3_SECRET_ACCESS_KEY');
region = configService.get<string>('S3_REGION');
bucket = configService.get<string>('S3_BUCKET');
endpoint = configService.get<string>('S3_ENDPOINT');
fakeS3Client.reset();
Logger.error = jest.fn();
const module: TestingModule = await Test.createTestingModule({
providers: [
S3Service,
{
provide: S3_CONFIGURATION,
useValue: {
accessKeyId,
secretAccessKey,
region,
bucket,
endpoint,
} as Options,
},
],
}).compile();
service = module.get(S3Service);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('call getClient should be object', () => {
const client = service.getClient();
expect(client).toBeDefined();
expect(typeof client === 'object').toBeTruthy();
});
it('should be ok when put a file', async () => {
const { url, origin } = await service.put(mockedFile);
expect(url).toBeDefined();
expect(origin).toBeDefined();
expect(origin.Key).toBe(mockedFile.originalname);
});
it('should be ok when put a file with a path that has an extension', async () => {
const { url, origin } = await service.put(mockedFile, 'avatars/file.png');
expect(url).toBeDefined();
expect(origin).toBeDefined();
expect(origin.Key).toBe('avatars/file.png');
});
it('should be ok when put a file with a path that ending with slash', async () => {
const { url, origin } = await service.put(mockedFile, 'avatars/');
expect(url).toBeDefined();
expect(origin).toBeDefined();
expect(origin.Key).toBe('avatars/file.png');
});
it('should throws exception if cannot put a file', async () => {
fakeS3Client.on(PutObjectCommand).rejects(new Error('cannot put a file'));
try {
await service.put(mockedFile);
} catch (error) {
expect(error.message).toBe('cannot put a file');
}
expect(Logger.error).toHaveBeenCalled();
});
it('put a file as unique name', async () => {
const { url, origin } = await service.putAsUniqueName(mockedFile);
expect(url).toBeDefined();
expect(origin).toBeDefined();
expect(origin.Key).toBeDefined();
});
it('put a file as unique name with folder', async () => {
const { url, origin } = await service.putAsUniqueName(
mockedFile,
'avatars',
);
expect(url).toBeDefined();
expect(origin).toBeDefined();
expect(origin.Key).toContain('avatars');
});
it('list all files', async () => {
fakeS3Client.on(ListObjectsCommand).resolves({
Name: bucket,
Contents: [
{
Key: 'file1.png',
Size: 1024,
LastModified: new Date(),
},
{
Key: 'file2.jpg',
Size: 1024,
LastModified: new Date(),
},
],
});
const items = await service.lists();
expect(items.length).toBe(2);
expect(items[0].key).toBe('file1.png');
expect(items[1].key).toBe('file2.jpg');
});
it('should throws exception if cannot list all files', async () => {
fakeS3Client
.on(ListObjectsCommand)
.rejects(new Error('cannot list all files'));
try {
await service.lists();
} catch (error) {
expect(error.message).toBe('cannot list all files');
}
expect(Logger.error).toHaveBeenCalled();
});
it('get a file', async () => {
// create Stream from string
const stream = new Readable();
stream.push('hello world');
stream.push(null); // end of stream
// wrap the Stream with SDK mixin
const sdkStream = sdkStreamMixin(stream);
fakeS3Client.on(GetObjectCommand).resolves({
Body: sdkStream,
});
const { key, contentLength, contentType, body } =
await service.get('fake.png');
expect(key).toBe('fake.png');
expect(contentLength).toBeDefined();
expect(contentType).toBeDefined();
expect(body).toBeInstanceOf(Buffer);
});
it('should throws exception if cannot get a file', async () => {
fakeS3Client.on(GetObjectCommand).rejects(new Error('cannot get a file'));
try {
await service.get('fake.png');
} catch (error) {
expect(error.message).toBe('cannot get a file');
}
expect(Logger.error).toHaveBeenCalled();
});
it('delete a file', async () => {
fakeS3Client.on(DeleteObjectCommand).resolves({
DeleteMarker: true,
VersionId: randomUUID(),
});
const { status, origin } = await service.delete('fake.png');
expect(status).toBeDefined();
expect(status).toBeTruthy();
expect(origin).toBeDefined();
});
it('should throws exception if cannot delete a file', async () => {
fakeS3Client
.on(DeleteObjectCommand)
.rejects(new Error('cannot delete a file'));
try {
await service.delete('fake.png');
} catch (error) {
expect(error.message).toBe('cannot delete a file');
}
expect(Logger.error).toHaveBeenCalled();
});
it('signed url with 60 second', async () => {
const data = await service.signedUrl('fake.png', 60);
expect(data).toContain('X-Amz-Algorithm');
expect(data).toContain('X-Amz-Signature');
});
it('should throws exception if cannot signed url', async () => {
fakeS3Client.on(GetObjectCommand).rejects(new Error('cannot signed url'));
try {
await service.signedUrl('fake.png', 60);
} catch (error) {
console.log(error);
expect(error.message).toBe('cannot signed url');
}
});
});