Skip to content

Commit aba348f

Browse files
irinaAbramova-devirinaP-local
authored andcommitted
#RI-4815 change implementation
1 parent 62e7068 commit aba348f

File tree

10 files changed

+141
-55
lines changed

10 files changed

+141
-55
lines changed

redisinsight/api/src/common/decorators/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './session';
66
export * from './client-metadata';
77
export * from './object-as-map.decorator';
88
export * from './is-multi-number.decorator';
9+
export * from './is-bigger-than.decorator';
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {
2+
registerDecorator,
3+
ValidationOptions,
4+
} from 'class-validator';
5+
import { BiggerThan } from 'src/common/validators/bigger-than.validator';
6+
7+
export function IsBiggerThan(property: string, validationOptions?: ValidationOptions) {
8+
return (object: any, propertyName: string) => {
9+
registerDecorator({
10+
name: 'IsBiggerThan',
11+
target: object.constructor,
12+
propertyName,
13+
constraints: [property],
14+
options: validationOptions,
15+
validator: BiggerThan,
16+
});
17+
};
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {
2+
ValidationArguments,
3+
ValidatorConstraint,
4+
ValidatorConstraintInterface,
5+
} from 'class-validator';
6+
7+
@ValidatorConstraint({ name: 'BiggerThan', async: true })
8+
export class BiggerThan implements ValidatorConstraintInterface {
9+
validate(value: any, args: ValidationArguments) {
10+
const [relatedPropertyName] = args.constraints;
11+
const relatedValue = (args.object as any)[relatedPropertyName];
12+
return typeof value === 'number' && typeof relatedValue === 'number' && value > relatedValue;
13+
}
14+
15+
defaultMessage(args: ValidationArguments) {
16+
return `${args.property} must be a greater than ${args.constraints.join(', ')}`;
17+
}
18+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './redis-string.validator';
22
export * from './zset-score.validator';
33
export * from './multi-number.validator';
4+
export * from './bigger-than.validator';

redisinsight/api/src/modules/browser/controllers/string/string.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export class StringController extends BaseController {
7676
const { stream } = await this.stringBusinessService.downloadStringValue(clientMetadata, dto);
7777

7878
res.setHeader('Content-Type', 'application/octet-stream');
79-
res.setHeader('Content-Disposition', 'attachment;filename="string_value.txt"');
79+
res.setHeader('Content-Disposition', 'attachment;filename="string_value"');
8080
res.setHeader('Access-Control-Expose-Headers', 'Content-Disposition');
8181

8282
stream

redisinsight/api/src/modules/browser/dto/string.dto.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,32 @@ import {
55
IsDefined, IsInt, IsOptional, Min,
66
} from 'class-validator';
77
import { RedisString } from 'src/common/constants';
8-
import { IsRedisString, RedisStringType } from 'src/common/decorators';
8+
import { IsRedisString, RedisStringType, IsBiggerThan } from 'src/common/decorators';
99
import { Type } from 'class-transformer';
1010
import { KeyDto, KeyResponse, KeyWithExpireDto } from './keys.dto';
1111

1212
export class GetStringInfoDto extends KeyDto {
1313
@ApiProperty({
14-
description: 'Max length of string',
14+
description: 'Start of string',
15+
type: Number,
16+
default: 0,
17+
})
18+
@IsOptional()
19+
@IsInt({ always: true })
20+
@Type(() => Number)
21+
@Min(0)
22+
start?: number = 0;
23+
24+
@ApiProperty({
25+
description: 'End of string',
1526
type: Number,
1627
})
1728
@IsOptional()
1829
@IsInt({ always: true })
1930
@Type(() => Number)
2031
@Min(1)
21-
stringMaxLen?: number;
32+
@IsBiggerThan('start')
33+
end?: number;
2234
}
2335

2436
export class SetStringDto extends KeyDto {

redisinsight/api/src/modules/browser/services/string-business/string-business.service.ts

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -69,24 +69,31 @@ export class StringBusinessService {
6969
this.logger.log('Succeed to set string key type.');
7070
}
7171

72-
private async getStringValueFromClient(
72+
public async getStringValue(
7373
clientMetadata: ClientMetadata,
74-
keyName: RedisString,
75-
args = [],
76-
toolCommand = BrowserToolStringCommands.Get,
74+
dto: GetStringInfoDto,
7775
): Promise<GetStringValueResponse> {
7876
this.logger.log('Getting string value.');
7977

80-
const scanArgs = [keyName, ...args];
78+
const { keyName, start, end } = dto;
8179
let result: GetStringValueResponse;
8280

8381
try {
84-
const value = await this.browserTool.execCommand(
85-
clientMetadata,
86-
toolCommand,
87-
scanArgs,
88-
);
89-
result = { value, keyName };
82+
if (end) {
83+
const value = await this.browserTool.execCommand(
84+
clientMetadata,
85+
BrowserToolStringCommands.Getrange,
86+
[keyName, `${start}`, `${end}`],
87+
);
88+
result = { value, keyName };
89+
} else {
90+
const value = await this.browserTool.execCommand(
91+
clientMetadata,
92+
BrowserToolStringCommands.Get,
93+
[keyName],
94+
);
95+
result = { value, keyName };
96+
}
9097
} catch (error) {
9198
this.logger.error('Failed to get string value.', error);
9299
if (error.message.includes(RedisErrorCodes.WrongType)) {
@@ -109,35 +116,19 @@ export class StringBusinessService {
109116
);
110117
this.logger.log('Succeed to get string value.');
111118

112-
return result;
113-
}
114-
115-
public async getStringValue(
116-
clientMetadata: ClientMetadata,
117-
dto: GetStringInfoDto,
118-
): Promise<GetStringValueResponse> {
119-
const { keyName, stringMaxLen } = dto;
120-
const result: GetStringValueResponse = await this.getStringValueFromClient(
121-
clientMetadata,
122-
keyName,
123-
stringMaxLen ? ['0', `${(stringMaxLen - 1)}`] : [],
124-
stringMaxLen ? BrowserToolStringCommands.Getrange : BrowserToolStringCommands.Get,
125-
);
126-
127119
return plainToClass(GetStringValueResponse, result);
128120
}
129121

130122
public async downloadStringValue(
131123
clientMetadata: ClientMetadata,
132124
dto: GetKeyInfoDto,
133125
): Promise<{ stream: Readable }> {
134-
const { keyName } = dto;
135-
const result = await this.getStringValueFromClient(
126+
const result = await this.getStringValue(
136127
clientMetadata,
137-
keyName,
128+
dto,
138129
);
139130

140-
const stream = Readable.from(`"${result.value}"`);
131+
const stream = Readable.from(result.value);
141132
return { stream };
142133
}
143134

redisinsight/api/test/api/string/POST-databases-id-string-download_value.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
generateInvalidDataTestCases,
88
validateInvalidDataTestCase,
99
getMainCheckFn
10-
} from '../deps';
10+
} from '../deps'
1111
const { server, request, constants, rte } = deps;
1212

1313
// endpoint to test
@@ -27,7 +27,7 @@ const mainCheckFn = getMainCheckFn(endpoint);
2727

2828
describe('POST /databases/:instanceId/string/download-value', () => {
2929
describe('Main', () => {
30-
before(() => rte.data.generateKeys(true));
30+
before(() => rte.data.generateBinKeys(true));
3131

3232
describe('Validation', () => {
3333
generateInvalidDataTestCases(dataSchema, validInputData).map(
@@ -40,19 +40,19 @@ describe('POST /databases/:instanceId/string/download-value', () => {
4040
{
4141
name: 'Should download value',
4242
data: {
43-
keyName: constants.TEST_STRING_KEY_1,
43+
keyName: constants.TEST_STRING_KEY_BIN_BUF_OBJ_1,
4444
},
4545
responseHeaders: {
4646
'content-type': 'application/octet-stream',
47-
'content-disposition': 'attachment;filename="string_value.txt"',
47+
'content-disposition': 'attachment;filename="string_value"',
4848
'access-control-expose-headers': 'Content-Disposition',
4949
},
50-
responseBody: Buffer.from(`"${constants.TEST_STRING_VALUE_1}"`),
50+
responseBody: constants.TEST_STRING_VALUE_BIN_BUFFER_1,
5151
},
5252
{
5353
name: 'Should return an error when incorrect type',
5454
data: {
55-
keyName: constants.TEST_LIST_KEY_1,
55+
keyName: constants.TEST_LIST_KEY_BIN_BUF_OBJ_1,
5656
},
5757
statusCode: 400,
5858
responseBody: {
@@ -64,7 +64,7 @@ describe('POST /databases/:instanceId/string/download-value', () => {
6464
name: 'Should return NotFound error if instance id does not exists',
6565
endpoint: () => endpoint(constants.TEST_NOT_EXISTED_INSTANCE_ID),
6666
data: {
67-
keyName: constants.TEST_STRING_KEY_1,
67+
keyName: constants.TEST_STRING_KEY_BIN_BUF_OBJ_1,
6868
},
6969
statusCode: 404,
7070
responseBody: {
@@ -85,14 +85,14 @@ describe('POST /databases/:instanceId/string/download-value', () => {
8585
name: 'Should download value',
8686
endpoint: () => endpoint(constants.TEST_INSTANCE_ACL_ID),
8787
data: {
88-
keyName: constants.TEST_STRING_KEY_1,
88+
keyName: constants.TEST_STRING_KEY_BIN_BUF_OBJ_1,
8989
},
9090
responseHeaders: {
9191
'content-type': 'application/octet-stream',
92-
'content-disposition': 'attachment;filename="string_value.txt"',
92+
'content-disposition': 'attachment;filename="string_value"',
9393
'access-control-expose-headers': 'Content-Disposition',
9494
},
95-
responseBody: Buffer.from(`"${constants.TEST_STRING_VALUE_1}"`),
95+
responseBody: constants.TEST_STRING_VALUE_BIN_BUFFER_1,
9696
},
9797
{
9898
name: 'Should throw error if no permissions for "set" command',

redisinsight/api/test/api/string/POST-databases-id-string-get_value.test.ts

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import {
2-
expect,
32
describe,
4-
it,
53
before,
64
Joi,
75
deps,
86
requirements,
97
generateInvalidDataTestCases,
108
validateInvalidDataTestCase,
11-
validateApiCall, getMainCheckFn, JoiRedisString
9+
getMainCheckFn, JoiRedisString
1210
} from '../deps';
1311
const { server, request, constants, rte } = deps;
1412

@@ -87,6 +85,36 @@ describe('POST /databases/:instanceId/string/get-value', () => {
8785
value: constants.TEST_STRING_VALUE_BIN_BUF_OBJ_1,
8886
},
8987
},
88+
{
89+
name: 'Should return part of value in buffer (only "end")',
90+
query: {
91+
encoding: 'buffer',
92+
},
93+
data: {
94+
keyName: constants.TEST_STRING_KEY_BIN_BUF_OBJ_1,
95+
end: constants.TEST_STRING_KEY_END
96+
},
97+
responseBody: {
98+
keyName: constants.TEST_STRING_KEY_BIN_BUF_OBJ_1,
99+
value: constants.TEST_STRING_PARTIAL_VALUE_BIN_BUF_OBJ_1,
100+
},
101+
statusCode: 404,
102+
},
103+
{
104+
name: 'Should return part of value in buffer ("start" & "end")',
105+
query: {
106+
encoding: 'buffer',
107+
},
108+
data: {
109+
keyName: constants.TEST_STRING_KEY_BIN_ASCII_1,
110+
start: constants.TEST_STRING_KEY_START_2,
111+
end: constants.TEST_STRING_KEY_END
112+
},
113+
responseBody: {
114+
keyName: constants.TEST_STRING_KEY_BIN_BUF_OBJ_1,
115+
value: constants.TEST_STRING_PARTIAL_VALUE_BIN_BUF_OBJ_2,
116+
},
117+
},
90118
{
91119
name: 'Should return error when send unicode with unprintable chars',
92120
query: {
@@ -122,14 +150,14 @@ describe('POST /databases/:instanceId/string/get-value', () => {
122150
},
123151
},
124152
{
125-
name: 'Should get part of value (specified length)',
153+
name: 'Should get part of value',
126154
data: {
127155
keyName: constants.TEST_STRING_KEY_1,
128-
stringMaxLen: constants.TEST_STRING_KEY_LENGTH
156+
end: constants.TEST_STRING_KEY_END
129157
},
130158
responseBody: {
131159
keyName: constants.TEST_STRING_KEY_1,
132-
value: constants.TEST_STRING_VALUE_1.slice(0, constants.TEST_STRING_KEY_LENGTH),
160+
value: constants.TEST_STRING_VALUE_1.slice(constants.TEST_STRING_KEY_START_1, constants.TEST_STRING_KEY_END + 1),
133161
},
134162
},
135163
{
@@ -145,10 +173,23 @@ describe('POST /databases/:instanceId/string/get-value', () => {
145173
},
146174
},
147175
{
148-
name: 'Should return an error when incorrect string length',
176+
name: 'Should return an error when incorrect end of string',
177+
data: {
178+
keyName: constants.TEST_STRING_KEY_1,
179+
end: 0
180+
},
181+
statusCode: 400,
182+
responseBody: {
183+
statusCode: 400,
184+
error: 'Bad Request',
185+
},
186+
},
187+
{
188+
name: 'Should return an error when start of string greater than end',
149189
data: {
150190
keyName: constants.TEST_STRING_KEY_1,
151-
stringMaxLen: 0
191+
start: 10,
192+
end: 9
152193
},
153194
statusCode: 400,
154195
responseBody: {

redisinsight/api/test/helpers/constants.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,18 @@ export const constants = {
160160
TEST_STRING_KEY_ASCII_UNICODE: TEST_RUN_ID + '_str_ascii_€' + CLUSTER_HASH_SLOT,
161161
TEST_STRING_KEY_ASCII_VALUE: TEST_RUN_ID + '_value_ascii',
162162
TEST_STRING_KEY_BIN_BUFFER_1: Buffer.concat([Buffer.from(TEST_RUN_ID), Buffer.from('strk'), unprintableBuf]),
163-
TEST_STRING_KEY_LENGTH: 10,
163+
TEST_STRING_VALUE_BIN_BUFFER_1: Buffer.concat([Buffer.from(TEST_RUN_ID), Buffer.from('strv'), unprintableBuf]),
164+
TEST_STRING_KEY_START_1: 0,
165+
TEST_STRING_KEY_START_2: 2,
166+
TEST_STRING_KEY_END: 9,
164167
get TEST_STRING_KEY_BIN_BUF_OBJ_1() { return { type: 'Buffer', data: [...this.TEST_STRING_KEY_BIN_BUFFER_1] } },
168+
get TEST_STRING_VALUE_BIN_BUF_OBJ_1() { return { type: 'Buffer', data: [...this.TEST_STRING_VALUE_BIN_BUFFER_1] } },
169+
get TEST_STRING_PARTIAL_VALUE_BIN_BUF_OBJ_1() { return { type: 'Buffer', data: [...this.TEST_STRING_VALUE_BIN_BUFFER_1.slice(this.TEST_STRING_KEY_START_1, this.TEST_STRING_KEY_END + 1)] } },
170+
get TEST_STRING_PARTIAL_VALUE_BIN_BUF_OBJ_2() { return { type: 'Buffer', data: [...this.TEST_STRING_VALUE_BIN_BUFFER_1.slice(this.TEST_STRING_KEY_START_2, this.TEST_STRING_KEY_END + 1)] } },
165171
get TEST_STRING_KEY_BIN_ASCII_1() { return getASCIISafeStringFromBuffer(this.TEST_STRING_KEY_BIN_BUFFER_1) },
166172
get TEST_STRING_KEY_BIN_UTF8_1() { return this.TEST_STRING_KEY_BIN_BUFFER_1.toString('utf-8') },
167-
TEST_STRING_VALUE_BIN_BUFFER_1: Buffer.concat([Buffer.from(TEST_RUN_ID), Buffer.from('strv'), unprintableBuf]),
168173
get TEST_STRING_VALUE_BIN_ASCII_1() { return getASCIISafeStringFromBuffer(this.TEST_STRING_VALUE_BIN_BUFFER_1) },
169174
get TEST_STRING_VALUE_BIN_UTF8_1() { return this.TEST_STRING_VALUE_BIN_BUFFER_1.toString('utf-8') },
170-
get TEST_STRING_VALUE_BIN_BUF_OBJ_1() { return { type: 'Buffer', data: [...this.TEST_STRING_VALUE_BIN_BUFFER_1] } },
171175

172176
// Redis List
173177
TEST_LIST_TYPE: 'list',

0 commit comments

Comments
 (0)