Skip to content

Commit eeeeae6

Browse files
author
arthosofteq
authored
Merge pull request #1813 from RedisInsight/be/feature/RI-4186-Upload_custom_tutorials
Be/feature/ri 4186 upload custom tutorials
2 parents 2cc0fba + 1722642 commit eeeeae6

17 files changed

+679
-64
lines changed

redisinsight/api/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"format": "prettier --write \"src/**/*.ts\"",
2222
"lint": "eslint --ext .ts .",
2323
"start": "nest start",
24-
"start:dev": "cross-env NODE_ENV=development SERVER_STATIC_CONTENT=1 nest start --watch --preserveWatchOutput",
24+
"start:dev": "cross-env NODE_ENV=development SERVER_STATIC_CONTENT=1 nest start --watch",
2525
"start:debug": "nest start --debug --watch",
2626
"start:stage": "cross-env NODE_ENV=staging SERVER_STATIC_CONTENT=true node dist/src/main",
2727
"start:prod": "cross-env NODE_ENV=production node dist/src/main",
@@ -84,6 +84,7 @@
8484
"@nestjs/cli": "^9.1.2",
8585
"@nestjs/schematics": "^9.0.3",
8686
"@nestjs/testing": "^9.0.11",
87+
"@types/adm-zip": "^0.5.0",
8788
"@types/axios": "^0.14.0",
8889
"@types/express": "^4.17.3",
8990
"@types/jest": "^26.0.15",

redisinsight/api/src/__mocks__/custom-tutorial.ts

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ import { CustomTutorialEntity } from 'src/modules/custom-tutorial/entities/custo
33
import { CustomTutorialManifestType } from 'src/modules/custom-tutorial/models/custom-tutorial.manifest';
44
import { MemoryStoredFile } from 'nestjs-form-data';
55
import { UploadCustomTutorialDto } from 'src/modules/custom-tutorial/dto/upload.custom-tutorial.dto';
6+
import AdmZip from 'adm-zip';
67

78
export const mockCustomTutorialId = 'a77b23c1-7816-4ea4-b61f-d37795a0f805-ct-id';
89

910
export const mockCustomTutorialId2 = 'a77b23c1-7816-4ea4-b61f-d37795a0f805-ct-id-2';
1011

1112
export const mockCustomTutorialTmpPath = '/tmp/path';
1213

14+
export const mockCustomTutorialsHttpLink = 'https://somesime.com/archive.zip';
15+
1316
export const mockCustomTutorial = Object.assign(new CustomTutorial(), {
1417
id: mockCustomTutorialId,
1518
name: 'custom tutorial',
@@ -23,6 +26,7 @@ export const mockCustomTutorialEntity = Object.assign(new CustomTutorialEntity()
2326
export const mockCustomTutorial2 = Object.assign(new CustomTutorial(), {
2427
id: mockCustomTutorialId2,
2528
name: 'custom tutorial 2',
29+
link: mockCustomTutorialsHttpLink,
2630
createdAt: new Date(),
2731
});
2832

@@ -31,77 +35,94 @@ export const mockCustomTutorialZipFile = Object.assign(new MemoryStoredFile(), {
3135
buffer: Buffer.from('zip-content', 'utf8'),
3236
});
3337

38+
export const mockCustomTutorialZipFileAxiosResponse = {
39+
data: mockCustomTutorialZipFile.buffer,
40+
};
41+
42+
export const mockCustomTutorialAdmZipEntry = {
43+
entryName: 'somefolder/info.md',
44+
} as AdmZip.IZipEntry;
45+
46+
export const mockCustomTutorialMacosxAdmZipEntry = {
47+
entryName: '__MACOSX/info.md',
48+
} as AdmZip.IZipEntry;
49+
3450
export const mockUploadCustomTutorialDto = Object.assign(new UploadCustomTutorialDto(), {
3551
name: mockCustomTutorial.name,
3652
file: mockCustomTutorialZipFile,
3753
});
3854

39-
export const mockCustomTutorialManifestManifestJson = {
40-
'ct-folder-1': {
55+
export const mockUploadCustomTutorialExternalLinkDto = Object.assign(new UploadCustomTutorialDto(), {
56+
name: mockCustomTutorial.name,
57+
link: mockCustomTutorialsHttpLink,
58+
});
59+
60+
export const mockCustomTutorialManifestManifestJson = [
61+
{
4162
type: 'group',
4263
id: 'ct-folder-1',
4364
label: 'ct-folder-1',
4465
// args: {
4566
// withBorder: true,
4667
// initialIsOpen: true,
4768
// },
48-
children: {
49-
'ct-sub-folder-1': {
69+
children: [
70+
{
5071
type: CustomTutorialManifestType.Group,
5172
id: 'ct-sub-folder-1',
5273
label: 'ct-sub-folder-1',
5374
// args: {
5475
// initialIsOpen: false,
5576
// },
56-
children: {
57-
introduction: {
77+
children: [
78+
{
5879
type: CustomTutorialManifestType.InternalLink,
5980
id: 'introduction',
6081
label: 'introduction',
6182
args: {
6283
path: '/ct-folder-1/ct-sub-folder-1/introduction.md',
6384
},
6485
},
65-
'working-with-hashes': {
86+
{
6687
type: CustomTutorialManifestType.InternalLink,
6788
id: 'working-with-hashes',
6889
label: 'working-with-hashes',
6990
args: {
7091
path: '/ct-folder-1/ct-sub-folder-1/working-with-hashes.md',
7192
},
7293
},
73-
},
94+
],
7495
},
75-
'ct-sub-folder-2': {
96+
{
7697
type: CustomTutorialManifestType.Group,
7798
id: 'ct-sub-folder-2',
7899
label: 'ct-sub-folder-2',
79100
// args: {
80101
// withBorder: true,
81102
// initialIsOpen: false,
82103
// },
83-
children: {
84-
introduction: {
104+
children: [
105+
{
85106
type: CustomTutorialManifestType.InternalLink,
86107
id: 'introduction',
87108
label: 'introduction',
88109
args: {
89110
path: '/ct-folder-1/ct-sub-folder-2/introduction.md',
90111
},
91112
},
92-
'working-with-graphs': {
113+
{
93114
type: CustomTutorialManifestType.InternalLink,
94115
id: 'working-with-graphs',
95116
label: 'working-with-graphs',
96117
args: {
97118
path: '/ct-folder-1/ct-sub-folder-2/working-with-graphs.md',
98119
},
99120
},
100-
},
121+
],
101122
},
102-
},
123+
],
103124
},
104-
};
125+
];
105126

106127
export const mockCustomTutorialManifestManifest = {
107128
type: CustomTutorialManifestType.Group,
@@ -121,24 +142,26 @@ export const mockCustomTutorialManifestManifest2 = {
121142
children: mockCustomTutorialManifestManifestJson,
122143
};
123144

124-
export const globalCustomTutorialManifest = {
125-
'custom-tutorials': {
145+
export const globalCustomTutorialManifest = [
146+
{
126147
type: CustomTutorialManifestType.Group,
127148
id: 'custom-tutorials',
128-
label: 'My Tutorials',
149+
label: 'MY TUTORIALS',
129150
_actions: [CustomTutorialActions.CREATE],
130151
args: {
131152
withBorder: true,
132153
initialIsOpen: true,
133154
},
134-
children: {
135-
[mockCustomTutorialManifestManifest.id]: mockCustomTutorialManifestManifest,
136-
[mockCustomTutorialManifestManifest2.id]: mockCustomTutorialManifestManifest2,
137-
},
155+
children: [
156+
mockCustomTutorialManifestManifest,
157+
mockCustomTutorialManifestManifest2,
158+
],
138159
},
139-
};
160+
];
140161

141162
export const mockCustomTutorialFsProvider = jest.fn(() => ({
163+
unzipFromMemoryStoredFile: jest.fn().mockResolvedValue(mockCustomTutorialTmpPath),
164+
unzipFromExternalLink: jest.fn().mockResolvedValue(mockCustomTutorialTmpPath),
142165
unzipToTmpFolder: jest.fn().mockResolvedValue(mockCustomTutorialTmpPath),
143166
moveFolder: jest.fn(),
144167
removeFolder: jest.fn(),
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { HttpException, InternalServerErrorException } from '@nestjs/common';
22

3-
export const wrapHttpError = (error: Error) => {
3+
export const wrapHttpError = (error: Error, message?: string) => {
44
if (error instanceof HttpException) {
55
return error;
66
}
77

8-
return new InternalServerErrorException(error.message);
8+
return new InternalServerErrorException(error.message || message);
99
};

redisinsight/api/src/constants/error-messages.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default {
88
CONSUMER_GROUP_NOT_FOUND: 'Consumer Group with such name was not found.',
99
PLUGIN_STATE_NOT_FOUND: 'Plugin state was not found.',
1010
CUSTOM_TUTORIAL_NOT_FOUND: 'Custom Tutorial was not found.',
11+
CUSTOM_TUTORIAL_UNABLE_TO_FETCH_FROM_EXTERNAL: 'Unable fetch zip file from external source.',
1112
UNDEFINED_INSTANCE_ID: 'Undefined redis database instance id.',
1213
NO_CONNECTION_TO_REDIS_DB: 'No connection to the Redis Database.',
1314
WRONG_DATABASE_TYPE: 'Wrong database type.',

redisinsight/api/src/modules/custom-tutorial/custom-tutorial.service.spec.ts

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import {
77
mockCustomTutorialManifestManifest, mockCustomTutorialManifestManifest2,
88
mockCustomTutorialManifestProvider,
99
mockCustomTutorialRepository,
10-
MockType, mockUploadCustomTutorialDto,
10+
MockType, mockUploadCustomTutorialDto, mockUploadCustomTutorialExternalLinkDto
1111
} from 'src/__mocks__';
1212
import * as fs from 'fs-extra';
1313
import { CustomTutorialFsProvider } from 'src/modules/custom-tutorial/providers/custom-tutorial.fs.provider';
14-
import { InternalServerErrorException, NotFoundException } from '@nestjs/common';
14+
import { BadRequestException, InternalServerErrorException, NotFoundException } from '@nestjs/common';
1515
import { CustomTutorialService } from 'src/modules/custom-tutorial/custom-tutorial.service';
1616
import { CustomTutorialRepository } from 'src/modules/custom-tutorial/repositories/custom-tutorial.repository';
1717
import {
@@ -63,12 +63,27 @@ describe('CustomTutorialService', () => {
6363
});
6464

6565
describe('create', () => {
66-
it('Should create custom tutorial', async () => {
66+
it('Should create custom tutorial from file', async () => {
6767
const result = await service.create(mockUploadCustomTutorialDto);
6868

6969
expect(result).toEqual(mockCustomTutorialManifestManifest);
7070
});
7171

72+
it('Should create custom tutorial from external url', async () => {
73+
const result = await service.create(mockUploadCustomTutorialExternalLinkDto);
74+
75+
expect(result).toEqual(mockCustomTutorialManifestManifest);
76+
});
77+
78+
it('Should throw BadRequestException in case when either link or file was not provided', async () => {
79+
try {
80+
await service.create({} as any);
81+
} catch (e) {
82+
expect(e).toBeInstanceOf(BadRequestException);
83+
expect(e.message).toEqual('File or external link should be provided');
84+
}
85+
});
86+
7287
it('Should throw InternalServerError in case of any non-HttpException error', async () => {
7388
customTutorialRepository.create.mockRejectedValueOnce(new Error('Unable to create'));
7489

@@ -98,27 +113,27 @@ describe('CustomTutorialService', () => {
98113

99114
const result = await service.getGlobalManifest();
100115

101-
expect(result).toEqual({
102-
'custom-tutorials': {
103-
...globalCustomTutorialManifest['custom-tutorials'],
104-
children: {
105-
[mockCustomTutorialManifestManifest.id]: mockCustomTutorialManifestManifest,
106-
},
116+
expect(result).toEqual([
117+
{
118+
...globalCustomTutorialManifest[0],
119+
children: [
120+
mockCustomTutorialManifestManifest,
121+
],
107122
},
108-
});
123+
]);
109124
});
110125

111126
it('Should return global manifest without children in case of any error', async () => {
112127
customTutorialRepository.list.mockRejectedValueOnce(new Error('Unable to get list of tutorials'));
113128

114129
const result = await service.getGlobalManifest();
115130

116-
expect(result).toEqual({
117-
'custom-tutorials': {
118-
...globalCustomTutorialManifest['custom-tutorials'],
119-
children: {},
131+
expect(result).toEqual([
132+
{
133+
...globalCustomTutorialManifest[0],
134+
children: [],
120135
},
121-
});
136+
]);
122137
});
123138
});
124139

0 commit comments

Comments
 (0)