diff --git a/.circleci/config.yml b/.circleci/config.yml index 83059e1d11..2b4aa0f03e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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 @@ -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 diff --git a/.circleci/e2e/test.app-image.sh b/.circleci/e2e/test.app-image.sh index 99b6758b4b..66c7b75c3e 100755 --- a/.circleci/e2e/test.app-image.sh +++ b/.circleci/e2e/test.app-image.sh @@ -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 diff --git a/.circleci/e2e/test.exe.cmd b/.circleci/e2e/test.exe.cmd index 2291a259dc..417b54541c 100755 --- a/.circleci/e2e/test.exe.cmd +++ b/.circleci/e2e/test.exe.cmd @@ -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% diff --git a/tests/e2e/.desktop.env b/tests/e2e/.desktop.env index 64742bf163..6ac125a4bd 100644 --- a/tests/e2e/.desktop.env +++ b/tests/e2e/.desktop.env @@ -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 diff --git a/tests/e2e/helpers/api/api-common.ts b/tests/e2e/helpers/api/api-common.ts new file mode 100644 index 0000000000..7966022f6c --- /dev/null +++ b/tests/e2e/helpers/api/api-common.ts @@ -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 +): Promise { + 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); +} diff --git a/tests/e2e/helpers/api/api-database.ts b/tests/e2e/helpers/api/api-database.ts index 503be8da54..5017125ab7 100644 --- a/tests/e2e/helpers/api/api-database.ts +++ b/tests/e2e/helpers/api/api-database.ts @@ -1,12 +1,11 @@ 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 @@ -14,34 +13,46 @@ const endpoint = Common.getEndpoint(); */ export async function addNewStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters): Promise { 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`); } /** @@ -50,7 +61,7 @@ export async function addNewStandaloneDatabaseApi(databaseParameters: AddNewData */ export async function addNewStandaloneDatabasesApi(databasesParameters: AddNewDatabaseParameters[]): Promise { if (databasesParameters.length) { - await databasesParameters.forEach(async parameter => { + databasesParameters.forEach(async parameter => { await addNewStandaloneDatabaseApi(parameter); }); } @@ -61,15 +72,13 @@ export async function addNewStandaloneDatabasesApi(databasesParameters: AddNewDa * @param databaseParameters The database parameters */ export async function addNewOSSClusterDatabaseApi(databaseParameters: OSSClusterParameters): Promise { - 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`); } /** @@ -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 { - 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; } @@ -111,7 +117,7 @@ export async function getDatabaseIdByName(databaseName?: string): Promise { await doAsyncStuff(); @@ -153,10 +159,8 @@ export async function deleteAllDatabasesApi(): Promise { 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); } } } @@ -168,11 +172,10 @@ export async function deleteAllDatabasesApi(): Promise { export async function deleteStandaloneDatabaseApi(databaseParameters: AddNewDatabaseParameters): Promise { 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'); } } @@ -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'); } }); @@ -201,10 +203,8 @@ export async function deleteStandaloneDatabasesByNamesApi(databaseNames: string[ */ export async function deleteOSSClusterDatabaseApi(databaseParameters: OSSClusterParameters): Promise { 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); } /** @@ -214,9 +214,8 @@ export async function deleteOSSClusterDatabaseApi(databaseParameters: OSSCluster export async function deleteAllSentinelDatabasesApi(databaseParameters: SentinelParameters): Promise { 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); } } @@ -225,9 +224,8 @@ export async function deleteAllSentinelDatabasesApi(databaseParameters: Sentinel */ export async function deleteAllDatabasesByConnectionTypeApi(connectionType: string): Promise { 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); } /** @@ -248,10 +246,7 @@ export async function deleteStandaloneDatabasesApi(databasesParameters: AddNewDa */ export async function getClusterNodesApi(databaseParameters: OSSClusterParameters): Promise { 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; diff --git a/tests/e2e/helpers/api/api-info.ts b/tests/e2e/helpers/api/api-info.ts index c327294ccc..940f13fe75 100644 --- a/tests/e2e/helpers/api/api-info.ts +++ b/tests/e2e/helpers/api/api-info.ts @@ -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 { - 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); } diff --git a/tests/e2e/helpers/common.ts b/tests/e2e/helpers/common.ts index b1ecc24ba8..3edf85637a 100644 --- a/tests/e2e/helpers/common.ts +++ b/tests/e2e/helpers/common.ts @@ -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 = { @@ -157,6 +164,13 @@ export class Common { return apiUrl; } + /** + * Return windowId + */ + static getWindowId(): Promise { + return t.eval(() => window.windowId); + } + /** * Check opened URL * @param expectedUrl Expected link that is compared with actual diff --git a/tests/e2e/helpers/constants.ts b/tests/e2e/helpers/constants.ts index 9db95462f8..17a4ebc232 100644 --- a/tests/e2e/helpers/constants.ts +++ b/tests/e2e/helpers/constants.ts @@ -47,3 +47,9 @@ export enum RecommendationIds { avoidLogicalDatabases = 'avoidLogicalDatabases', searchJson = 'searchJSON', } + +export enum Methods { + post = 'post', + get = 'get', + delete = 'delete' +} diff --git a/tests/e2e/tests/smoke/database/delete-the-db.e2e.ts b/tests/e2e/tests/smoke/database/delete-the-db.e2e.ts index a431b85921..9f7d735d01 100644 --- a/tests/e2e/tests/smoke/database/delete-the-db.e2e.ts +++ b/tests/e2e/tests/smoke/database/delete-the-db.e2e.ts @@ -1,19 +1,30 @@ +import { Chance } from 'chance'; import { addNewStandaloneDatabase, acceptLicenseTerms } from '../../../helpers/database'; import { rte } from '../../../helpers/constants'; import { MyRedisDatabasePage } from '../../../pageObjects'; import { commonUrl, ossStandaloneConfig } from '../../../helpers/conf'; +const chance = new Chance(); const myRedisDatabasePage = new MyRedisDatabasePage(); +const uniqueId = chance.string({ length: 10 }); +let database = { + ...ossStandaloneConfig, + databaseName: `test_standalone-${uniqueId}` +}; fixture `Delete database` .meta({ type: 'smoke' }) .page(commonUrl) .beforeEach(async() => { await acceptLicenseTerms(); + database = { + ...ossStandaloneConfig, + databaseName: `test_standalone-${uniqueId}` + }; }); test .meta({ rte: rte.standalone })('Verify that user can delete databases', async t => { - await addNewStandaloneDatabase(ossStandaloneConfig); - await myRedisDatabasePage.deleteDatabaseByName(ossStandaloneConfig.databaseName); - await t.expect(myRedisDatabasePage.dbNameList.withExactText(ossStandaloneConfig.databaseName).exists).notOk('The database not deleted', { timeout: 10000 }); + await addNewStandaloneDatabase(database); + await myRedisDatabasePage.deleteDatabaseByName(database.databaseName); + await t.expect(myRedisDatabasePage.dbNameList.withExactText(database.databaseName).exists).notOk('The database not deleted', { timeout: 10000 }); });