Skip to content

Commit 95cf428

Browse files
fix(files): create empty file
1 parent 6413359 commit 95cf428

File tree

15 files changed

+310
-30
lines changed

15 files changed

+310
-30
lines changed
Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1+
import { RemoteConfig } from '@lomray/microservice-helpers';
12
import { TypeormMock } from '@lomray/microservice-helpers/mocks';
23
import { endpointOptions, waitResult } from '@lomray/microservice-helpers/test-helpers';
34
import { expect } from 'chai';
45
import rewiremock from 'rewiremock';
56
import sinon from 'sinon';
7+
import StorageStub from '@__mocks__/storage-stub';
68
import FileType from '@constants/file-type';
9+
import File from '@entities/file';
710
import OriginalEndpointCreateEmpty from '@methods/file/create-empty';
11+
import Factory from '@services/file/factory';
12+
import StorageFactory from '@services/storage/factory';
813

9-
const { default: Create } = rewiremock.proxy<{
14+
const { default: CreateEmpty } = rewiremock.proxy<{
1015
default: typeof OriginalEndpointCreateEmpty;
1116
}>(() => require('@methods/file/create-empty'), {
1217
typeorm: TypeormMock.mock,
@@ -21,22 +26,52 @@ describe('methods/file/create-empty', () => {
2126

2227
it('should throw error: validation failed', async () => {
2328
// @ts-ignore
24-
const res = Create({}, endpointOptions);
29+
const res = CreateEmpty({}, endpointOptions);
2530

26-
expect(await waitResult(res)).to.throw('Validation failed for one or more entities.');
31+
expect(await waitResult(res)).to.throw('Invalid request params');
2732
});
2833

2934
it('should correctly create file', async () => {
30-
const fields = {
35+
let serviceParams: Parameters<typeof Factory.create> | undefined;
36+
let saveStub;
37+
38+
sandbox.stub(RemoteConfig, 'get').resolves({});
39+
40+
const methodParams = {
41+
fileName: 'file.mp4',
3142
userId: 'user_id',
32-
type: FileType.image,
33-
url: '/',
43+
alt: 'alt',
44+
type: FileType.video,
3445
};
46+
const file = {
47+
id: 'file_id',
48+
url: 'url',
49+
userId: methodParams.userId,
50+
alt: methodParams.alt,
51+
};
52+
53+
const serviceStub = sandbox.stub(Factory, 'create').callsFake(async (...args) => {
54+
serviceStub.restore();
55+
56+
sandbox.stub(StorageFactory, 'create').resolves(StorageStub);
57+
58+
const service = await Factory.create(...args);
59+
60+
serviceParams = args;
61+
saveStub = sandbox.stub(service, 'save').resolves(file as File);
3562

36-
TypeormMock.entityManager.save.resolves([fields]);
63+
return service;
64+
});
3765

38-
const res = await Create({ fields }, endpointOptions);
66+
const res = await CreateEmpty(methodParams, endpointOptions);
3967

40-
expect(res?.entity).to.deep.equal(fields);
68+
expect(res).to.deep.equal({ entity: file });
69+
expect(serviceParams?.[0]).to.be.equal(FileType.video);
70+
expect(serviceParams?.[1]).to.deep.equal(TypeormMock.entityManager);
71+
expect(saveStub).to.calledOnceWith(
72+
methodParams.fileName,
73+
methodParams.userId,
74+
methodParams.alt,
75+
);
4176
});
4277
});

microservices/files/__tests__/services/file/any-file-test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ describe('services/file/any-file', () => {
9898
});
9999

100100
sandbox.stub(service, <any>'uploadFile');
101-
102101
sandbox.stub(storage, 'delete');
103102

104103
const entity = await service.update(fileData, 'file');
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { RemoteConfig } from '@lomray/microservice-helpers';
2+
import { TypeormMock } from '@lomray/microservice-helpers/mocks';
3+
import { expect } from 'chai';
4+
import sinon from 'sinon';
5+
import { getRepository } from 'typeorm';
6+
import { bucketNameMock } from '@__mocks__/common';
7+
import FileType from '@constants/file-type';
8+
import CONST from '@constants/index';
9+
import File from '@entities/file';
10+
import EmptyFile from '@services/file/empty-file';
11+
import StorageFactory from '@services/storage/factory';
12+
13+
describe('services/file/empty-file', () => {
14+
const sandbox = sinon.createSandbox();
15+
const fileData = getRepository(File).create({
16+
id: 'file_id',
17+
});
18+
const resultFile = {
19+
id: 'file_id',
20+
type: FileType.video,
21+
meta: { mime: 'video/mp4' },
22+
};
23+
24+
/**
25+
* Create service
26+
*/
27+
const getService = async (): Promise<EmptyFile> => {
28+
sandbox.stub(RemoteConfig, 'get').resolves({ s3: { bucketName: bucketNameMock } });
29+
30+
const storage = await StorageFactory.create();
31+
32+
return new EmptyFile(FileType.video, TypeormMock.entityManager, storage, {
33+
storagePathPrefix: CONST.STORAGE_PATH_PREFIX,
34+
});
35+
};
36+
37+
beforeEach(() => {
38+
TypeormMock.sandbox.reset();
39+
});
40+
41+
afterEach(() => {
42+
sandbox.restore();
43+
});
44+
45+
it('should successfully save file', async () => {
46+
// first call - create empty file
47+
TypeormMock.entityManager.save.onFirstCall().resolves(fileData);
48+
// second call - return from transaction
49+
TypeormMock.entityManager.save.onSecondCall().callsFake((_, entity) => entity);
50+
51+
const service = await getService();
52+
const entity = await service.save('file.mp4', 'user_id');
53+
54+
const [, file] = TypeormMock.entityManager.save.secondCall.args;
55+
56+
const fileUrl = entity.url;
57+
58+
expect(fileUrl.startsWith(`/${fileData.id}`)).to.true;
59+
expect(fileUrl.endsWith('.mp4')).to.true;
60+
expect(entity).to.deep.equal({
61+
...resultFile,
62+
url: fileUrl,
63+
});
64+
expect(file).to.deep.equal({
65+
...resultFile,
66+
url: fileUrl,
67+
});
68+
});
69+
70+
it('should throw error remove file', async () => {
71+
const service = await getService();
72+
73+
expect(() => service.remove()).to.throw('Use files.file.remove instead.');
74+
});
75+
76+
it('should throw update file', async () => {
77+
const service = await getService();
78+
79+
expect(() => service.remove()).to.throw('Use files.file.remove instead.');
80+
});
81+
});

microservices/files/__tests__/services/file/factory-test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { RemoteConfig } from '@lomray/microservice-helpers';
22
import { TypeormMock } from '@lomray/microservice-helpers/mocks';
3+
import { waitResult } from '@lomray/microservice-helpers/test-helpers';
34
import { expect } from 'chai';
45
import sinon from 'sinon';
6+
import { bucketNameMock } from '@__mocks__/common';
57
import FileType from '@constants/file-type';
68
import AnyFile from '@services/file/any-file';
9+
import EmptyFile from '@services/file/empty-file';
710
import Factory from '@services/file/factory';
811
import Image from '@services/file/image';
912
import StorageFactory from '@services/storage/factory';
@@ -41,4 +44,21 @@ describe('services/file/factory', () => {
4144

4245
expect(service).instanceof(AnyFile);
4346
});
47+
48+
it('should successful create EmptyFile instance', async () => {
49+
sandbox.stub(RemoteConfig, 'get').resolves({});
50+
sandbox.stub(StorageFactory, 'create');
51+
52+
const service = await Factory.create(FileType.video, TypeormMock.entityManager, true);
53+
54+
expect(service).instanceof(EmptyFile);
55+
});
56+
57+
it('should throw error: unknown type', async () => {
58+
sandbox.stub(RemoteConfig, 'get').resolves({ s3: { bucketName: bucketNameMock } });
59+
// @ts-ignore
60+
const service = Factory.create('unknown', TypeormMock.entityManager);
61+
62+
expect(await waitResult(service)).to.throw('Unknown file type.');
63+
});
4464
});

microservices/files/package-lock.json

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

microservices/files/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,14 @@
4747
"class-validator-jsonschema": "^5.0.0",
4848
"file-type": "^16.5.4",
4949
"mime": "^3.0.0",
50+
"mime-types": "^2.1.35",
5051
"sharp": "^0.32.1",
5152
"typeorm": "0.2.41",
5253
"uuid": "^9.0.0"
5354
},
5455
"devDependencies": {
5556
"@types/mime": "^3.0.1",
57+
"@types/mime-types": "^2.1.1",
5658
"@types/node": "^18.13.0",
5759
"@types/uuid": "^9.0.1"
5860
},
Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,51 @@
1-
import { Endpoint } from '@lomray/microservice-helpers';
2-
import { getRepository } from 'typeorm';
1+
import { Endpoint, IsUndefinable } from '@lomray/microservice-helpers';
2+
import { Type } from 'class-transformer';
3+
import { IsEnum, IsObject, Length } from 'class-validator';
4+
import { getManager } from 'typeorm';
5+
import FileType from '@constants/file-type';
36
import File from '@entities/file';
7+
import Factory from '@services/file/factory';
8+
import FilePostProcess from '@services/file-post-process';
9+
10+
class FileCreateEmptyInput {
11+
@IsEnum(FileType)
12+
type: FileType;
13+
14+
@Length(3)
15+
fileName: string;
16+
17+
@Length(1, 36)
18+
@IsUndefinable()
19+
userId: string;
20+
21+
@Length(1, 150)
22+
@IsUndefinable()
23+
alt: string;
24+
}
25+
26+
class FileCreateEmptyOutput {
27+
@IsObject()
28+
@Type(() => File)
29+
entity: File;
30+
}
431

532
/**
633
* Create empty file
734
*/
8-
const create = Endpoint.create(() => ({
9-
repository: getRepository(File),
10-
}));
35+
const createEmpty = Endpoint.custom(
36+
() => ({
37+
input: FileCreateEmptyInput,
38+
output: FileCreateEmptyOutput,
39+
description: 'Create empty file',
40+
}),
41+
async ({ type, fileName, alt, userId, payload }) => {
42+
const service = await Factory.create(type, getManager(), true);
43+
const file = await service.save(fileName, userId, alt);
44+
45+
return {
46+
entity: await FilePostProcess.handle(file, payload),
47+
};
48+
},
49+
);
1150

12-
export default create;
51+
export default createEmpty;

microservices/files/src/methods/file/create.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ const create = Endpoint.custom(
4040
}),
4141
async ({ type, file, userId, alt, payload }) => {
4242
const service = await Factory.create(type, getManager());
43+
const entity = await service.save(file, userId, alt);
4344

4445
return {
45-
entity: await FilePostProcess.handle(await service.save(file, userId, alt), payload),
46+
entity: await FilePostProcess.handle(entity, payload),
4647
};
4748
},
4849
);

microservices/files/src/methods/file/update.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ const update = Endpoint.custom(
4949
}
5050

5151
const service = await Factory.create(fileEntity.type, manager);
52+
const entity = await service.update(fileEntity, file, alt);
5253

5354
return {
54-
entity: await FilePostProcess.handle(await service.update(fileEntity, file, alt), payload),
55+
entity: await FilePostProcess.handle(entity, payload),
5556
};
5657
},
5758
);

microservices/files/src/services/file/any-file.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class AnyFile extends Abstract {
2323
const fileRepository = transactionManager.getRepository(File);
2424
const fileEntity = await fileRepository.save(
2525
fileRepository.create({ userId, alt, url: '/', type: this.type }),
26+
{ listeners: false },
2627
);
2728

2829
const { fileData, fileBuffer } = await this.composeData(file, fileEntity.id);

0 commit comments

Comments
 (0)