Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions redisinsight/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@
"jest-junit": "^16.0.0",
"jest-when": "^3.2.1",
"joi": "^17.4.0",
"mocha": "^11.1.0",
"mocha-junit-reporter": "^2.0.0",
"mocha": "^11.4.0",
"mocha-junit-reporter": "^2.2.1",
"mocha-multi-reporters": "^1.5.1",
"nock": "^13.3.0",
"nyc": "^15.1.0",
Expand All @@ -143,7 +143,7 @@
"supertest": "^4.0.2",
"ts-jest": "^29.2.5",
"ts-loader": "^6.2.1",
"ts-mocha": "^8.0.0",
"ts-mocha": "^11.1.0",
"ts-node": "^10.9.2",
"tsconfig-paths": "^3.9.0",
"tsconfig-paths-webpack-plugin": "^3.3.0",
Expand Down
12 changes: 11 additions & 1 deletion redisinsight/api/src/constants/custom-error-codes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
export enum CustomErrorCodes {
// General [10000, 10999]
// General [10000, 10899]
WindowUnauthorized = 10_001,

// Redis Connection [10900, 10999]
RedisConnectionFailed = 10_900,
RedisConnectionTimeout = 10_901,
RedisConnectionUnauthorized = 10_902,
RedisConnectionClusterNodesUnavailable = 10_903,
RedisConnectionUnavailable = 10_904,
RedisConnectionAuthUnsupported = 10_905,
RedisConnectionSentinelMasterRequired = 10_906,
RedisConnectionIncorrectCertificate = 10_907,

// Cloud API [11001, 11099]
CloudApiInternalServerError = 11_000,
CloudApiUnauthorized = 11_001,
Expand Down
3 changes: 2 additions & 1 deletion redisinsight/api/src/constants/error-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,9 @@ export default {
UNDEFINED_WINDOW_ID: 'Undefined window id.',
LIBRARY_NOT_EXIST: 'This library does not exist.',

CLOUD_CAPI_KEY_UNAUTHORIZED: 'Unable to authorize such CAPI key',
REDIS_CONNECTION_FAILED: 'Unable to connect to the Redis database',

CLOUD_CAPI_KEY_UNAUTHORIZED: 'Unable to authorize such CAPI key',
CLOUD_OAUTH_CANCELED: 'Authorization request was canceled.',
CLOUD_OAUTH_MISCONFIGURATION: 'Authorization server misconfiguration.',
CLOUD_OAUTH_GITHUB_EMAIL_PERMISSION:
Expand Down
12 changes: 7 additions & 5 deletions redisinsight/api/src/modules/database/database.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
InternalServerErrorException,
NotFoundException,
ServiceUnavailableException,
} from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { EventEmitter2 } from '@nestjs/event-emitter';
Expand Down Expand Up @@ -33,11 +32,14 @@ import { DatabaseRepository } from 'src/modules/database/repositories/database.r
import { DatabaseInfoProvider } from 'src/modules/database/providers/database-info.provider';
import { DatabaseFactory } from 'src/modules/database/providers/database.factory';
import { UpdateDatabaseDto } from 'src/modules/database/dto/update.database.dto';
import { RedisErrorCodes } from 'src/constants';
import ERROR_MESSAGES from 'src/constants/error-messages';
import { Compressor } from 'src/modules/database/entities/database.entity';
import { RedisClientFactory } from 'src/modules/redis/redis.client.factory';
import { RedisClientStorage } from 'src/modules/redis/redis.client.storage';
import {
RedisConnectionSentinelMasterRequiredException,
RedisConnectionUnavailableException,
} from 'src/modules/redis/exceptions/connection';
import { ExportDatabase } from './models/export-database';

const updateDatabaseTests = [
Expand Down Expand Up @@ -347,20 +349,20 @@ describe('DatabaseService', () => {
});
it('should successfully test valid sentinel config (without sentinelMaster)', async () => {
databaseFactory.createDatabaseModel.mockRejectedValueOnce(
new Error(RedisErrorCodes.SentinelParamsRequired),
new RedisConnectionSentinelMasterRequiredException(),
);
expect(
await service.testConnection(mockSessionMetadata, mockDatabase),
).toEqual(undefined);
});
it('should throw connection error', async () => {
databaseFactory.createDatabaseModel.mockRejectedValueOnce(
new Error(RedisErrorCodes.ConnectionRefused),
new RedisConnectionUnavailableException(),
);

await expect(
service.testConnection(mockSessionMetadata, mockDatabase),
).rejects.toThrow(ServiceUnavailableException);
).rejects.toThrow(RedisConnectionUnavailableException);
});
it('should not call get database by id', async () => {
const spy = jest.spyOn(service as any, 'get');
Expand Down
23 changes: 10 additions & 13 deletions redisinsight/api/src/modules/database/database.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,12 @@ import { Database } from 'src/modules/database/models/database';
import ERROR_MESSAGES from 'src/constants/error-messages';
import { DatabaseRepository } from 'src/modules/database/repositories/database.repository';
import { DatabaseAnalytics } from 'src/modules/database/database.analytics';
import {
catchRedisConnectionError,
classToClass,
getRedisConnectionException,
} from 'src/utils';
import { classToClass } from 'src/utils';
import { CreateDatabaseDto } from 'src/modules/database/dto/create.database.dto';
import { DatabaseInfoProvider } from 'src/modules/database/providers/database-info.provider';
import { DatabaseFactory } from 'src/modules/database/providers/database.factory';
import { UpdateDatabaseDto } from 'src/modules/database/dto/update.database.dto';
import { AppRedisInstanceEvents, RedisErrorCodes } from 'src/constants';
import { AppRedisInstanceEvents } from 'src/constants';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { DeleteDatabasesResponse } from 'src/modules/database/dto/delete.databases.response';
import { ClientContext, SessionMetadata } from 'src/common/models';
Expand All @@ -31,6 +27,7 @@ import {
RedisClientFactory,
} from 'src/modules/redis/redis.client.factory';
import { RedisClientStorage } from 'src/modules/redis/redis.client.storage';
import { RedisConnectionSentinelMasterRequiredException } from 'src/modules/redis/exceptions/connection';

@Injectable()
export class DatabaseService {
Expand Down Expand Up @@ -221,11 +218,9 @@ export class DatabaseService {
} catch (error) {
this.logger.error('Failed to add database.', error, sessionMetadata);

const exception = getRedisConnectionException(error, dto);

this.analytics.sendInstanceAddFailedEvent(sessionMetadata, exception);
this.analytics.sendInstanceAddFailedEvent(sessionMetadata, error);

throw exception;
throw error;
}
}

Expand Down Expand Up @@ -279,7 +274,8 @@ export class DatabaseService {
error,
sessionMetadata,
);
throw catchRedisConnectionError(error, database);

throw error;
}
}

Expand Down Expand Up @@ -317,12 +313,13 @@ export class DatabaseService {
return;
} catch (error) {
// don't throw an error to support sentinel autodiscovery flow
if (error.message === RedisErrorCodes.SentinelParamsRequired) {
if (error instanceof RedisConnectionSentinelMasterRequiredException) {
return;
}

this.logger.error('Connection test failed', error, sessionMetadata);
throw catchRedisConnectionError(error, database);

throw error;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { UnauthorizedException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import {
mockCommonClientMetadata,
mockDatabase,
mockDatabaseAnalytics,
mockDatabaseRepository,
mockDatabaseService,
mockRedisNoAuthError,
MockType,
mockRedisClientFactory,
mockStandaloneRedisClient,
Expand All @@ -16,7 +14,6 @@ import {
import { DatabaseAnalytics } from 'src/modules/database/database.analytics';
import { DatabaseService } from 'src/modules/database/database.service';
import { DatabaseRepository } from 'src/modules/database/repositories/database.repository';
import ERROR_MESSAGES from 'src/constants/error-messages';
import { DatabaseClientFactory } from 'src/modules/database/providers/database.client.factory';
import { RedisClientStorage } from 'src/modules/redis/redis.client.storage';
import { RedisClientFactory } from 'src/modules/redis/redis.client.factory';
Expand All @@ -31,6 +28,10 @@ import {
} from 'src/__mocks__/redis-client';
import { RedisClient } from 'src/modules/redis/client';
import { ConnectionType } from 'src/modules/database/entities/database.entity';
import {
RedisConnectionTimeoutException,
RedisConnectionUnauthorizedException,
} from 'src/modules/redis/exceptions/connection';

describe('DatabaseClientFactory', () => {
let service: DatabaseClientFactory;
Expand Down Expand Up @@ -246,17 +247,17 @@ describe('DatabaseClientFactory', () => {
},
);
});
it('should throw Unauthorized error in case of NOAUTH', async () => {
it('should throw original error', async () => {
jest
.spyOn(redisClientFactory, 'createClient')
.mockRejectedValue(mockRedisNoAuthError);
.mockRejectedValue(new RedisConnectionTimeoutException());
await expect(
service.createClient(mockCommonClientMetadata),
).rejects.toThrow(UnauthorizedException);
).rejects.toThrow(RedisConnectionTimeoutException);
expect(analytics.sendConnectionFailedEvent).toHaveBeenCalledWith(
mockSessionMetadata,
mockDatabase,
new UnauthorizedException(ERROR_MESSAGES.AUTHENTICATION_FAILED()),
new RedisConnectionTimeoutException(),
);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,17 +155,14 @@ export class DatabaseClientFactory {
return client;
} catch (error) {
this.logger.error('Failed to create database client', error);
const exception = getRedisConnectionException(
error,
database,
database.name,
);

this.analytics.sendConnectionFailedEvent(
clientMetadata.sessionMetadata,
database,
exception,
error,
);
throw exception;

throw error;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
ConnectionType,
HostingProvider,
} from 'src/modules/database/entities/database.entity';
import { catchRedisConnectionError, getHostingProvider } from 'src/utils';
import { getHostingProvider } from 'src/utils';
import { Database } from 'src/modules/database/models/database';
import { ClientContext, SessionMetadata } from 'src/common/models';
import ERROR_MESSAGES from 'src/constants/error-messages';
Expand Down Expand Up @@ -156,7 +156,8 @@ export class DatabaseFactory {
return model;
} catch (error) {
this.logger.error('Failed to add oss cluster.', error, sessionMetadata);
throw catchRedisConnectionError(error, database);

throw error;
}
}

Expand Down Expand Up @@ -209,7 +210,8 @@ export class DatabaseFactory {
error,
sessionMetadata,
);
throw catchRedisConnectionError(error, database);

throw error;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ jest.doMock(
);

import { Test, TestingModule } from '@nestjs/testing';
import { BadRequestException } from '@nestjs/common';
import ERROR_MESSAGES from 'src/constants/error-messages';
import {
mockConstantsProvider,
mockDatabaseFactory,
Expand All @@ -30,6 +28,9 @@ import { DatabaseService } from 'src/modules/database/database.service';
import { DatabaseFactory } from 'src/modules/database/providers/database.factory';
import { RedisClientFactory } from 'src/modules/redis/redis.client.factory';
import { ConstantsProvider } from 'src/modules/constants/providers/constants.provider';
import {
RedisConnectionIncorrectCertificateException,
} from 'src/modules/redis/exceptions/connection';

describe('RedisSentinelService', () => {
let service: RedisSentinelService;
Expand Down Expand Up @@ -89,15 +90,15 @@ describe('RedisSentinelService', () => {
redisClientFactory
.getConnectionStrategy()
.createStandaloneClient.mockRejectedValue(
new Error(ERROR_MESSAGES.NO_CONNECTION_TO_REDIS_DB),
new RedisConnectionIncorrectCertificateException(),
);

await expect(
service.getSentinelMasters(
mockSessionMetadata,
mockSentinelDatabaseWithTlsAuth,
),
).rejects.toThrow(BadRequestException);
).rejects.toThrow(RedisConnectionIncorrectCertificateException);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpException, Injectable, Logger } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid';
import { CreateSentinelDatabaseResponse } from 'src/modules/redis-sentinel/dto/create.sentinel.database.response';
import { CreateSentinelDatabasesDto } from 'src/modules/redis-sentinel/dto/create.sentinel.databases.dto';
Expand All @@ -9,7 +9,6 @@ import {
SessionMetadata,
} from 'src/common/models';
import { DatabaseService } from 'src/modules/database/database.service';
import { getRedisConnectionException } from 'src/utils';
import { SentinelMaster } from 'src/modules/redis-sentinel/models/sentinel-master';
import { RedisSentinelAnalytics } from 'src/modules/redis-sentinel/redis-sentinel.analytics';
import { DatabaseFactory } from 'src/modules/database/providers/database.factory';
Expand Down Expand Up @@ -113,7 +112,7 @@ export class RedisSentinelService {
error,
sessionMetadata,
);
throw getRedisConnectionException(error, connectionOptions);
throw error;
}
}

Expand Down Expand Up @@ -153,12 +152,12 @@ export class RedisSentinelService {

await client.disconnect();
} catch (error) {
const exception: HttpException = getRedisConnectionException(error, dto);
this.redisSentinelAnalytics.sendGetSentinelMastersFailedEvent(
sessionMetadata,
exception,
error,
);
throw exception;

throw error;
}
return result;
}
Expand Down
Loading
Loading