Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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