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
8 changes: 8 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,8 @@ jobs:
parallelism: << parameters.parallelism >>
steps:
- checkout
- node/install:
node-version: '18.15.0'
- attach_workspace:
at: .
- run: sudo apt-get install net-tools
Expand Down Expand Up @@ -1179,6 +1181,12 @@ workflows:
- windows:
name: Build app - Windows (stage)
requires: *stageElectronBuildRequires
# e2e desktop tests on AppImage build
- e2e-app-image:
name: E2ETest (AppImage)
parallelism: 2
requires:
- Build app - Linux (stage)
# release to AWS (stage)
- release-aws-test:
name: Release AWS stage
Expand Down
2 changes: 1 addition & 1 deletion .circleci/e2e/test.app-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ docker-compose -f tests/e2e/rte.docker-compose.yml up --force-recreate -d -V
./tests/e2e/wait-for-redis.sh localhost 12000 && \

# run tests
COMMON_URL=$(tail -n 1 apppath)/resources/app.asar/index.html \
COMMON_URL=$(tail -n 1 apppath)/resources/app.asar/dist/renderer/index.html \
ELECTRON_PATH=$(tail -n 1 apppath)/redisinsight \
SOCKETS_CORS=true \
yarn --cwd tests/e2e dotenv -e .desktop.env yarn --cwd tests/e2e test:desktop:ci
4 changes: 2 additions & 2 deletions .circleci/e2e/test.exe.cmd
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@echo off

set COMMON_URL=%USERPROFILE%/AppData/Local/Programs/redisinsight/resources/app.asar/index.html
set ELECTRON_PATH=%USERPROFILE%/AppData/Local/Programs/redisinsight/RedisInsight-preview.exe
set COMMON_URL=%USERPROFILE%/AppData/Local/Programs/redisinsight/resources/app.asar/dist/renderer/index.html
set ELECTRON_PATH=%USERPROFILE%/AppData/Local/Programs/redisinsight/RedisInsight-v2.exe
set OSS_STANDALONE_HOST=%E2E_CLOUD_DATABASE_HOST%
set OSS_STANDALONE_PORT=%E2E_CLOUD_DATABASE_PORT%
set OSS_STANDALONE_USERNAME=%E2E_CLOUD_DATABASE_USERNAME%
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/.desktop.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
COMMON_URL=https://localhost:5031
API_URL=https://localhost:5031/api
COMMON_URL=https://localhost:5530
API_URL=https://localhost:5530/api
OSS_SENTINEL_PASSWORD=password
APP_FOLDER_NAME=.redisinsight-v2-stage

Expand Down
46 changes: 46 additions & 0 deletions tests/e2e/helpers/api/api-common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as request from 'supertest';
import { Common } from '../common';
import { Methods } from '../constants';

const endpoint = Common.getEndpoint();

/**
* Send request using API
* @param method http method
* @param resourcePath URI path segment
* @param statusCode Expected status code of the response
* @param body Request body
*/
export async function sendRequest(
method: string,
resourcePath: string,
statusCode: number,
body?: Record<string, unknown>
): Promise<any> {
const windowId = Common.getWindowId();
let requestEndpoint;

if (method === Methods.post) {
(requestEndpoint = request(endpoint)
.post(resourcePath)
.send(body)
.set('Accept', 'application/json'));
}
else if (method === Methods.get) {
(requestEndpoint = request(endpoint)
.get(resourcePath)
.set('Accept', 'application/json'));
}
else if (method === Methods.delete) {
(requestEndpoint = request(endpoint)
.delete(resourcePath)
.send(body)
.set('Accept', 'application/json'));
}

if (await windowId) {
requestEndpoint.set('X-Window-Id', await windowId);
}

return await requestEndpoint.expect(statusCode);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that we need such wrapper.
Is it possible to initiate supertest instance per api service (if needed) or globally with x-window-id header preconfigured like it possible for axios?
It is up to you. Just wanted to highlight this

137 changes: 66 additions & 71 deletions tests/e2e/helpers/api/api-database.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,58 @@
import { t } from 'testcafe';
import { Chance } from 'chance';
import * as request from 'supertest';
import { asyncFilter, doAsyncStuff } from '../async-helper';
import { AddNewDatabaseParameters, OSSClusterParameters, databaseParameters, SentinelParameters, ClusterNodes } from '../../pageObjects/components/myRedisDatabase/add-redis-database';
import { Common } from '../common';
import { Methods } from '../constants';
import { sendRequest } from './api-common';

const chance = new Chance();
const endpoint = Common.getEndpoint();

/**
* Add a new Standalone database through api using host and port
* @param databaseParameters The database parameters
*/
export async function addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters): Promise<void> {
const uniqueId = chance.string({ length: 10 });
const requestBody = {
'name': databaseParameters.databaseName,
'host': databaseParameters.host,
'port': Number(databaseParameters.port),
'username': databaseParameters.databaseUsername,
'password': databaseParameters.databasePassword
};
const requestBody: {
name?: string,
host: string,
port: number,
username?: string,
password?: string,
tls?: boolean,
verifyServerCert?: boolean,
caCert?: {
name: string,
certificate?: string
},
clientCert?: {
name: string,
certificate?: string,
key?: string
}
} = {
'name': databaseParameters.databaseName,
'host': databaseParameters.host,
'port': Number(databaseParameters.port),
'username': databaseParameters.databaseUsername,
'password': databaseParameters.databasePassword
};

if (databaseParameters.caCert) {
requestBody['tls'] = true;
requestBody['verifyServerCert'] = false;
requestBody['caCert'] = {
requestBody.tls = true;
requestBody.verifyServerCert = false;
requestBody.caCert = {
'name': `ca}-${uniqueId}`,
'certificate': databaseParameters.caCert.certificate
};
requestBody['clientCert'] = {
requestBody.clientCert = {
'name': `client}-${uniqueId}`,
'certificate': databaseParameters.clientCert!.certificate,
'key': databaseParameters.clientCert!.key
};
}

const response = await request(endpoint).post('/databases')
.send(requestBody)
.set('Accept', 'application/json');
await t
.expect(response.status).eql(201, `The creation of ${databaseParameters.databaseName} standalone database request failed: ${await response.body.message}`)
.expect(await response.body.name).eql(databaseParameters.databaseName, `Database Name is not equal to ${databaseParameters.databaseName} in response`);
const response = await sendRequest(Methods.post, '/databases', 201, requestBody);
await t.expect(await response.body.name).eql(databaseParameters.databaseName, `Database Name is not equal to ${databaseParameters.databaseName} in response`);
}

/**
Expand All @@ -50,7 +61,7 @@ export async function addNewStandaloneDatabaseApi(databaseParameters: AddNewData
*/
export async function addNewStandaloneDatabasesApi(databasesParameters: AddNewDatabaseParameters[]): Promise<void> {
if (databasesParameters.length) {
await databasesParameters.forEach(async parameter => {
databasesParameters.forEach(async parameter => {
await addNewStandaloneDatabaseApi(parameter);
});
}
Expand All @@ -61,15 +72,13 @@ export async function addNewStandaloneDatabasesApi(databasesParameters: AddNewDa
* @param databaseParameters The database parameters
*/
export async function addNewOSSClusterDatabaseApi(databaseParameters: OSSClusterParameters): Promise<void> {
const response = await request(endpoint).post('/databases')
.send({
'name': databaseParameters.ossClusterDatabaseName,
'host': databaseParameters.ossClusterHost,
'port': Number(databaseParameters.ossClusterPort) })
.set('Accept', 'application/json');

await t.expect(await response.status).eql(201, 'The creation of new oss cluster database request failed')
.expect(await response.body.name).eql(databaseParameters.ossClusterDatabaseName, `Database Name is not equal to ${databaseParameters.ossClusterDatabaseName} in response`);
const requestBody = {
'name': databaseParameters.ossClusterDatabaseName,
'host': databaseParameters.ossClusterHost,
'port': Number(databaseParameters.ossClusterPort)
};
const response = await sendRequest(Methods.post, '/databases', 201, requestBody);
await t.expect(await response.body.name).eql(databaseParameters.ossClusterDatabaseName, `Database Name is not equal to ${databaseParameters.ossClusterDatabaseName} in response`);
}

/**
Expand All @@ -82,24 +91,21 @@ export async function discoverSentinelDatabaseApi(databaseParameters: SentinelPa
if (primaryGroupsNumber) {
masters = databaseParameters.masters!.slice(0, primaryGroupsNumber);
}
const response = await request(endpoint).post('/redis-sentinel/databases')
.send({
'host': databaseParameters.sentinelHost,
'port': Number(databaseParameters.sentinelPort),
'password': databaseParameters.sentinelPassword,
'masters': masters
})
.set('Accept', 'application/json');
const requestBody = {
'host': databaseParameters.sentinelHost,
'port': Number(databaseParameters.sentinelPort),
'password': databaseParameters.sentinelPassword,
'masters': masters
};

await t.expect(response.status).eql(201, 'Autodiscovery of Sentinel database request failed');
await sendRequest(Methods.post, '/redis-sentinel/databases', 201, requestBody);
}

/**
* Get all databases through api
*/
export async function getAllDatabases(): Promise<string[]> {
const response = await request(endpoint).get('/databases')
.set('Accept', 'application/json').expect(200);
const response = await sendRequest(Methods.get, '/databases', 200);
return await response.body;
}

Expand All @@ -111,7 +117,7 @@ export async function getDatabaseIdByName(databaseName?: string): Promise<string
if (!databaseName) {
throw new Error('Error: Missing databaseName');
}
let databaseId: any;
let databaseId;
const allDataBases = await getAllDatabases();
const response = await asyncFilter(allDataBases, async(item: databaseParameters) => {
await doAsyncStuff();
Expand Down Expand Up @@ -153,10 +159,8 @@ export async function deleteAllDatabasesApi(): Promise<void> {
databaseIds.push(dbData.id);
}
if (databaseIds.length > 0) {
await request(endpoint).delete('/databases')
.send({ 'ids': databaseIds })
.set('Accept', 'application/json')
.expect(200);
const requestBody = { 'ids': databaseIds };
await sendRequest(Methods.delete, '/databases', 200, requestBody);
}
}
}
Expand All @@ -168,11 +172,10 @@ export async function deleteAllDatabasesApi(): Promise<void> {
export async function deleteStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters): Promise<void> {
const databaseId = await getDatabaseIdByName(databaseParameters.databaseName);
if (databaseId) {
await request(endpoint).delete('/databases')
.send({ 'ids': [`${databaseId}`] })
.set('Accept', 'application/json')
.expect(200);
} else {
const requestBody = { 'ids': [`${databaseId}`] };
await sendRequest(Methods.delete, '/databases', 200, requestBody);
}
else {
throw new Error('Error: Missing databaseId');
}
}
Expand All @@ -185,11 +188,10 @@ export async function deleteStandaloneDatabasesByNamesApi(databaseNames: string[
databaseNames.forEach(async databaseName => {
const databaseId = await getDatabaseIdByName(databaseName);
if (databaseId) {
await request(endpoint).delete('/databases')
.send({ 'ids': [`${databaseId}`] })
.set('Accept', 'application/json')
.expect(200);
} else {
const requestBody = { 'ids': [`${databaseId}`] };
await sendRequest(Methods.delete, '/databases', 200, requestBody);
}
else {
throw new Error('Error: Missing databaseId');
}
});
Expand All @@ -201,10 +203,8 @@ export async function deleteStandaloneDatabasesByNamesApi(databaseNames: string[
*/
export async function deleteOSSClusterDatabaseApi(databaseParameters: OSSClusterParameters): Promise<void> {
const databaseId = await getDatabaseIdByName(databaseParameters.ossClusterDatabaseName);
const response = await request(endpoint).delete('/databases')
.send({ 'ids': [`${databaseId}`] }).set('Accept', 'application/json');

await t.expect(response.status).eql(200, 'Delete OSS cluster database request failed');
const requestBody = { 'ids': [`${databaseId}`] };
await sendRequest(Methods.delete, '/databases', 200, requestBody);
}

/**
Expand All @@ -214,9 +214,8 @@ export async function deleteOSSClusterDatabaseApi(databaseParameters: OSSCluster
export async function deleteAllSentinelDatabasesApi(databaseParameters: SentinelParameters): Promise<void> {
for (let i = 0; i < databaseParameters.name!.length; i++) {
const databaseId = await getDatabaseIdByName(databaseParameters.name![i]);
const response = await request(endpoint).delete('/databases')
.send({ 'ids': [`${databaseId}`] }).set('Accept', 'application/json');
await t.expect(response.status).eql(200, 'Delete Sentinel database request failed');
const requestBody = { 'ids': [`${databaseId}`] };
await sendRequest(Methods.delete, '/databases', 200, requestBody);
}
}

Expand All @@ -225,9 +224,8 @@ export async function deleteAllSentinelDatabasesApi(databaseParameters: Sentinel
*/
export async function deleteAllDatabasesByConnectionTypeApi(connectionType: string): Promise<void> {
const databaseIds = await getDatabaseByConnectionType(connectionType);
const response = await request(endpoint).delete('/databases')
.send({ 'ids': [`${databaseIds}`] }).set('Accept', 'application/json');
await t.expect(response.status).eql(200, 'Delete Sentinel database request failed');
const requestBody = { 'ids': [`${databaseIds}`] };
await sendRequest(Methods.delete, '/databases', 200, requestBody);
}

/**
Expand All @@ -248,10 +246,7 @@ export async function deleteStandaloneDatabasesApi(databasesParameters: AddNewDa
*/
export async function getClusterNodesApi(databaseParameters: OSSClusterParameters): Promise<string[]> {
const databaseId = await getDatabaseIdByName(databaseParameters.ossClusterDatabaseName);
const response = await request(endpoint)
.get(`/databases/${databaseId}/cluster-details`)
.set('Accept', 'application/json')
.expect(200);
const response = await sendRequest(Methods.get, `/databases/${databaseId}/cluster-details`, 200);
const nodes = await response.body.nodes;
const nodeNames = await nodes.map((node: ClusterNodes) => (`${node.host }:${ node.port}`));
return nodeNames;
Expand Down
11 changes: 3 additions & 8 deletions tests/e2e/helpers/api/api-info.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import { t } from 'testcafe';
import * as request from 'supertest';
import { Common } from '../common';

const endpoint = Common.getEndpoint();
import { sendRequest } from './api-common';
import { Methods } from '../constants';

/**
* Synchronize features
*/
export async function syncFeaturesApi(): Promise<void> {
const response = await request(endpoint).post('/features/sync')
.set('Accept', 'application/json');
await t.expect(response.status).eql(200, `Synchronization request failed: ${await response.body.message}`);
await sendRequest(Methods.post, '/features/sync', 200);
}
14 changes: 14 additions & 0 deletions tests/e2e/helpers/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import { apiUrl, commonUrl } from './conf';

const chance = new Chance();

declare global {
interface Window {
windowId?: string
}
}


const settingsApiUrl = `${commonUrl}/api/settings`;
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // lgtm[js/disabling-certificate-validation]
const mockedSettingsResponse = {
Expand Down Expand Up @@ -157,6 +164,13 @@ export class Common {
return apiUrl;
}

/**
* Return windowId
*/
static getWindowId(): Promise<string> {
return t.eval(() => window.windowId);
}

/**
* Check opened URL
* @param expectedUrl Expected link that is compared with actual
Expand Down
6 changes: 6 additions & 0 deletions tests/e2e/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,9 @@ export enum RecommendationIds {
avoidLogicalDatabases = 'avoidLogicalDatabases',
searchJson = 'searchJSON',
}

export enum Methods {
post = 'post',
get = 'get',
delete = 'delete'
}
Loading