Skip to content

Commit 36b9992

Browse files
author
arthosofteq
authored
Merge pull request #1731 from RedisInsight/be/bugfix/RI-4132-logical-db-control-is-not-displayed
#RI-4132 add workaround for displaying logical database switcher for …
2 parents ec2b91d + ddd9803 commit 36b9992

File tree

4 files changed

+131
-29
lines changed

4 files changed

+131
-29
lines changed

redisinsight/api/src/modules/database/database.service.spec.ts

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { InternalServerErrorException, NotFoundException, ServiceUnavailableException } from '@nestjs/common';
22
import { Test, TestingModule } from '@nestjs/testing';
33
import { EventEmitter2 } from '@nestjs/event-emitter';
4-
import { omit, forIn, get, update } from 'lodash';
5-
import { plainToClass } from 'class-transformer';
4+
import { omit, get, update } from 'lodash';
65

76
import { classToClass } from 'src/utils';
87
import {
@@ -33,7 +32,7 @@ describe('DatabaseService', () => {
3332
'sshOptions.passphrase',
3433
'sshOptions.privateKey',
3534
'sentinelMaster.password',
36-
]
35+
];
3736

3837
beforeEach(async () => {
3938
jest.clearAllMocks();
@@ -129,18 +128,21 @@ describe('DatabaseService', () => {
129128

130129
describe('update', () => {
131130
it('should update existing database and send analytics event', async () => {
132-
expect(await service.update(
131+
databaseRepository.update.mockReturnValue({
132+
...mockDatabase,
133+
port: 6380,
134+
password: 'password',
135+
provider: 'LOCALHOST',
136+
});
137+
138+
await expect(await service.update(
133139
mockDatabase.id,
134140
{ password: 'password', port: 6380 } as UpdateDatabaseDto,
135141
true,
136-
)).toEqual(mockDatabase);
142+
)).toEqual({ ...mockDatabase, port: 6380, password: 'password' });
137143
expect(analytics.sendInstanceEditedEvent).toHaveBeenCalledWith(
138144
mockDatabase,
139-
{
140-
...mockDatabase,
141-
password: 'password',
142-
port: 6380,
143-
},
145+
{ ...mockDatabase, port: 6380, password: 'password' },
144146
true,
145147
);
146148
});
@@ -199,48 +201,48 @@ describe('DatabaseService', () => {
199201
it('should return multiple databases without Standalone secrets', async () => {
200202
databaseRepository.get.mockResolvedValueOnce(mockDatabaseWithTlsAuth);
201203

202-
expect(await service.export([mockDatabaseWithTlsAuth.id], false)).toEqual([classToClass(ExportDatabase,omit(mockDatabaseWithTlsAuth, 'password'))]);
203-
})
204+
expect(await service.export([mockDatabaseWithTlsAuth.id], false)).toEqual([classToClass(ExportDatabase, omit(mockDatabaseWithTlsAuth, 'password'))]);
205+
});
204206

205207
it('should return multiple databases without SSH secrets', async () => {
206208
// remove SSH secrets
207-
const mockDatabaseWithSshPrivateKeyTemp = {...mockDatabaseWithSshPrivateKey}
209+
const mockDatabaseWithSshPrivateKeyTemp = { ...mockDatabaseWithSshPrivateKey };
208210
exportSecurityFields.forEach((field) => {
209-
if(get(mockDatabaseWithSshPrivateKeyTemp, field)) {
210-
update(mockDatabaseWithSshPrivateKeyTemp, field, () => null)
211+
if (get(mockDatabaseWithSshPrivateKeyTemp, field)) {
212+
update(mockDatabaseWithSshPrivateKeyTemp, field, () => null);
211213
}
212-
})
214+
});
213215

214216
databaseRepository.get.mockResolvedValueOnce(mockDatabaseWithSshPrivateKey);
215-
expect(await service.export([mockDatabaseWithSshPrivateKey.id], false)).toEqual([classToClass(ExportDatabase, mockDatabaseWithSshPrivateKeyTemp)])
216-
})
217+
expect(await service.export([mockDatabaseWithSshPrivateKey.id], false)).toEqual([classToClass(ExportDatabase, mockDatabaseWithSshPrivateKeyTemp)]);
218+
});
217219

218220
it('should return multiple databases without Sentinel secrets', async () => {
219221
// remove secrets
220-
const mockSentinelDatabaseWithTlsAuthTemp = {...mockSentinelDatabaseWithTlsAuth}
222+
const mockSentinelDatabaseWithTlsAuthTemp = { ...mockSentinelDatabaseWithTlsAuth };
221223
exportSecurityFields.forEach((field) => {
222-
if(get(mockSentinelDatabaseWithTlsAuthTemp, field)) {
223-
update(mockSentinelDatabaseWithTlsAuthTemp, field, () => null)
224+
if (get(mockSentinelDatabaseWithTlsAuthTemp, field)) {
225+
update(mockSentinelDatabaseWithTlsAuthTemp, field, () => null);
224226
}
225-
})
227+
});
226228

227229
databaseRepository.get.mockResolvedValue(mockSentinelDatabaseWithTlsAuth);
228230

229-
expect(await service.export([mockSentinelDatabaseWithTlsAuth.id], false)).toEqual([classToClass(ExportDatabase, omit(mockSentinelDatabaseWithTlsAuthTemp, 'password'))])
231+
expect(await service.export([mockSentinelDatabaseWithTlsAuth.id], false)).toEqual([classToClass(ExportDatabase, omit(mockSentinelDatabaseWithTlsAuthTemp, 'password'))]);
230232
});
231233

232234
it('should return multiple databases with secrets', async () => {
233235
// Standalone
234236
databaseRepository.get.mockResolvedValueOnce(mockDatabaseWithTls);
235-
expect(await service.export([mockDatabaseWithTls.id], true)).toEqual([classToClass(ExportDatabase,mockDatabaseWithTls)]);
237+
expect(await service.export([mockDatabaseWithTls.id], true)).toEqual([classToClass(ExportDatabase, mockDatabaseWithTls)]);
236238

237239
// SSH
238240
databaseRepository.get.mockResolvedValueOnce(mockDatabaseWithSshPrivateKey);
239-
expect(await service.export([mockDatabaseWithSshPrivateKey.id], true)).toEqual([classToClass(ExportDatabase, mockDatabaseWithSshPrivateKey)])
241+
expect(await service.export([mockDatabaseWithSshPrivateKey.id], true)).toEqual([classToClass(ExportDatabase, mockDatabaseWithSshPrivateKey)]);
240242

241243
// Sentinel
242244
databaseRepository.get.mockResolvedValueOnce(mockSentinelDatabaseWithTlsAuth);
243-
expect(await service.export([mockSentinelDatabaseWithTlsAuth.id], true)).toEqual([classToClass(ExportDatabase, mockSentinelDatabaseWithTlsAuth)])
245+
expect(await service.export([mockSentinelDatabaseWithTlsAuth.id], true)).toEqual([classToClass(ExportDatabase, mockSentinelDatabaseWithTlsAuth)]);
244246
});
245247

246248
it('should ignore errors', async () => {

redisinsight/api/src/modules/database/providers/database-info.provider.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,35 @@ describe('DatabaseInfoProvider', () => {
209209
});
210210
});
211211

212+
describe('getDatabaseCountFromKeyspace', () => {
213+
it('should return 1 since db0 keys presented only', async () => {
214+
const result = await service['getDatabaseCountFromKeyspace']({
215+
db0: 'keys=11,expires=0,avg_ttl=0',
216+
});
217+
218+
expect(result).toBe(1);
219+
});
220+
it('should return 7 since db6 is the last logical databases with known keys', async () => {
221+
const result = await service['getDatabaseCountFromKeyspace']({
222+
db0: 'keys=21,expires=0,avg_ttl=0',
223+
db1: 'keys=31,expires=0,avg_ttl=0',
224+
db6: 'keys=41,expires=0,avg_ttl=0',
225+
});
226+
227+
expect(result).toBe(7);
228+
});
229+
it('should return 1 when empty keySpace provided', async () => {
230+
const result = await service['getDatabaseCountFromKeyspace']({});
231+
232+
expect(result).toBe(1);
233+
});
234+
it('should return 1 when incorrect keySpace provided', async () => {
235+
const result = await service['getDatabaseCountFromKeyspace'](null);
236+
237+
expect(result).toBe(1);
238+
});
239+
});
240+
212241
describe('determineDatabaseModules', () => {
213242
it('get modules by using MODULE LIST command', async () => {
214243
when(mockIORedisClient.call)

redisinsight/api/src/modules/database/providers/database-info.provider.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ export class DatabaseInfoProvider {
211211
const clientsInfo = info['clients'];
212212
const statsInfo = info['stats'];
213213
const replicationInfo = info['replication'];
214-
const databases = await this.getDatabasesCount(client);
214+
const databases = await this.getDatabasesCount(client, keyspaceInfo);
215215
return {
216216
version: serverInfo?.redis_version,
217217
databases,
@@ -243,10 +243,30 @@ export class DatabaseInfoProvider {
243243
}));
244244
}
245245

246-
public async getDatabasesCount(client: any): Promise<number> {
246+
public async getDatabasesCount(client: any, keyspaceInfo?: object): Promise<number> {
247247
try {
248248
const reply = await client.call('config', ['get', 'databases']);
249249
return reply.length ? parseInt(reply[1], 10) : 1;
250+
} catch (e) {
251+
return this.getDatabaseCountFromKeyspace(keyspaceInfo);
252+
}
253+
}
254+
255+
/**
256+
* Try to determine number of logical database from the `info keyspace`
257+
*
258+
* Note: This is unreliable method which may return less logical databases count that database has
259+
* However this is needed for workaround when `config` command is disabled to understand if we need
260+
* to show logical database switcher on UI
261+
* @param keyspaceInfo
262+
* @private
263+
*/
264+
private getDatabaseCountFromKeyspace(keyspaceInfo: object): number {
265+
try {
266+
const keySpaces = Object.keys(keyspaceInfo);
267+
const matches = keySpaces[keySpaces.length - 1].match(/(\d+)/);
268+
269+
return matches[0] ? parseInt(matches[0], 10) + 1 : 1;
250270
} catch (e) {
251271
return 1;
252272
}

redisinsight/api/test/api/database/GET-databases-id-info.test.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, deps, validateApiCall, before, expect, getMainCheckFn } from '../deps';
1+
import { describe, deps, before, expect, getMainCheckFn, requirements } from '../deps';
22
import { Joi } from '../../helpers/test';
33
const { localDb, request, server, constants, rte } = deps;
44

@@ -61,4 +61,55 @@ describe(`GET /databases/:id/info`, () => {
6161
},
6262
},
6363
].map(mainCheckFn);
64+
65+
66+
67+
describe('ACL', () => {
68+
requirements('rte.acl', 'rte.type=STANDALONE', '!rte.re');
69+
before(async () => rte.data.setAclUserRules('~* +@all'));
70+
beforeEach(rte.data.truncate);
71+
72+
[
73+
{
74+
name: 'Should return 1 for empty databases',
75+
endpoint: () => endpoint(constants.TEST_INSTANCE_ACL_ID),
76+
before: () => rte.data.setAclUserRules('~* +@all -config'),
77+
responseBody: {
78+
databases: 1,
79+
// ...other fields
80+
},
81+
statusCode: 200,
82+
},
83+
{
84+
name: 'Should return 1 for database with keys created for db0 only',
85+
endpoint: () => endpoint(constants.TEST_INSTANCE_ACL_ID),
86+
before: async () => {
87+
await rte.data.setAclUserRules('~* +@all -config')
88+
await rte.data.generateStrings();
89+
},
90+
responseBody: {
91+
databases: 1,
92+
// ...other fields
93+
},
94+
statusCode: 200,
95+
},
96+
{
97+
name: 'Should return > 1 databases since data persists there',
98+
endpoint: () => endpoint(constants.TEST_INSTANCE_ACL_ID),
99+
before: async () => {
100+
await rte.data.setAclUserRules('~* +@all -config')
101+
102+
// generate data in > 0 logical database
103+
await rte.data.executeCommand('select', `${constants.TEST_REDIS_DB_INDEX}`);
104+
await rte.data.executeCommand('set', 'some', 'key');
105+
await rte.data.executeCommand('select', '0');
106+
},
107+
responseBody: {
108+
databases: constants.TEST_REDIS_DB_INDEX + 1,
109+
// ...other fields
110+
},
111+
statusCode: 200,
112+
},
113+
].map(mainCheckFn);
114+
});
64115
});

0 commit comments

Comments
 (0)