From a2c12add17d08407e6fbcf55cfb632f0d774e670 Mon Sep 17 00:00:00 2001 From: Tomislav Plavcic Date: Fri, 11 Oct 2024 16:26:45 +0200 Subject: [PATCH 1/5] Add test steps into demand backup UI E2E test --- .../everest/.e2e/release/demand-backup.e2e.ts | 157 ++++++++++-------- 1 file changed, 84 insertions(+), 73 deletions(-) diff --git a/ui/apps/everest/.e2e/release/demand-backup.e2e.ts b/ui/apps/everest/.e2e/release/demand-backup.e2e.ts index 95db39fbb..d955dd79e 100644 --- a/ui/apps/everest/.e2e/release/demand-backup.e2e.ts +++ b/ui/apps/everest/.e2e/release/demand-backup.e2e.ts @@ -62,7 +62,6 @@ test.describe.configure({ retries: 0 }); test.describe( 'Demand backup', { - // Create cluster, add data, create backup, destroy data and do restore tag: '@release', }, () => { @@ -118,85 +117,97 @@ test.describe.configure({ retries: 0 }); await page.getByTestId('toggle-button-group-input-db-type').waitFor(); await page.getByTestId('select-input-db-version').waitFor(); - // basic info - await populateBasicInformation( - page, - db, - storageClasses[0], - clusterName - ); - await moveForward(page); - - // resources - await page - .getByRole('button') - .getByText(size + ' node') - .click(); - await expect(page.getByText('Nº nodes: ' + size)).toBeVisible(); - await populateResources(page, 0.6, 1, 1, size); - await moveForward(page); - - // backups - await moveForward(page); - - // advanced - await populateAdvancedConfig(page, db, '', true, ''); - await moveForward(page); - - // monitoring modal form - await populateMonitoringModalForm( - page, - monitoringName, - namespace, - MONITORING_URL, - MONITORING_USER, - MONITORING_PASSWORD - ); - await page.getByTestId('switch-input-monitoring').click(); - await expect( - page.getByTestId('text-input-monitoring-instance') - ).toHaveValue(monitoringName); - await submitWizard(page); + await test.step('Populate basic information', async () => { + await populateBasicInformation( + page, + db, + storageClasses[0], + clusterName + ); + await moveForward(page); + }); + + await test.step('Populate resources', async () => { + await page + .getByRole('button') + .getByText(size + ' node') + .click(); + await expect(page.getByText('Nº nodes: ' + size)).toBeVisible(); + await populateResources(page, 0.6, 1, 1, size); + await moveForward(page); + }); + + await test.step('Populate backups', async () => { + await moveForward(page); + }); + + await test.step('Populate advanced db config', async () => { + await populateAdvancedConfig(page, db, '', true, ''); + await moveForward(page); + }); + + await test.step('Populate monitoring', async () => { + await populateMonitoringModalForm( + page, + monitoringName, + namespace, + MONITORING_URL, + MONITORING_USER, + MONITORING_PASSWORD + ); + await page.getByTestId('switch-input-monitoring').click(); + await expect( + page.getByTestId('text-input-monitoring-instance') + ).toHaveValue(monitoringName); + }); - await expect( - page.getByText('Awesome! Your database is being created!') - ).toBeVisible(); + await test.step('Submit wizard', async () => { + await submitWizard(page); - // go to db list and check status - await page.goto('/databases'); - await waitForStatus(page, clusterName, 'Initializing', 15000); - await waitForStatus(page, clusterName, 'Up', 600000); + await expect( + page.getByText('Awesome! Your database is being created!') + ).toBeVisible(); + }); - const response = await request.get( - `/v1/namespaces/${namespace}/database-clusters`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); + // go to db list and check status + await test.step('Check db list and status', async () => { + await page.goto('/databases'); + await waitForStatus(page, clusterName, 'Initializing', 15000); + await waitForStatus(page, clusterName, 'Up', 600000); + }); + + await test.step('Check db cluster k8s object options', async () => { + const response = await request.get( + `/v1/namespaces/${namespace}/database-clusters`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); - await checkError(response); + await checkError(response); - // TODO: replace with correct payload typings from GET DB Clusters - const { items: clusters } = await response.json(); + // TODO: replace with correct payload typings from GET DB Clusters + const { items: clusters } = await response.json(); - const addedCluster = clusters.find( - (cluster) => cluster.metadata.name === clusterName - ); + const addedCluster = clusters.find( + (cluster) => cluster.metadata.name === clusterName + ); - expect(addedCluster).not.toBeUndefined(); - expect(addedCluster?.spec.engine.type).toBe(db); - expect(addedCluster?.spec.engine.replicas).toBe(size); - expect(['600m', '0.6']).toContain( - addedCluster?.spec.engine.resources?.cpu.toString() - ); - expect(addedCluster?.spec.engine.resources?.memory.toString()).toBe( - '1G' - ); - expect(addedCluster?.spec.engine.storage.size.toString()).toBe('1Gi'); - expect(addedCluster?.spec.proxy.expose.type).toBe('internal'); - expect(addedCluster?.spec.proxy.replicas).toBe(size); + expect(addedCluster).not.toBeUndefined(); + expect(addedCluster?.spec.engine.type).toBe(db); + expect(addedCluster?.spec.engine.replicas).toBe(size); + expect(['600m', '0.6']).toContain( + addedCluster?.spec.engine.resources?.cpu.toString() + ); + expect(addedCluster?.spec.engine.resources?.memory.toString()).toBe( + '1G' + ); + expect(addedCluster?.spec.engine.storage.size.toString()).toBe('1Gi'); + expect(addedCluster?.spec.proxy.expose.type).toBe('internal'); + expect(addedCluster?.spec.proxy.replicas).toBe(size); + }); }); test(`Add data with ${db} and size ${size}`, async () => { From b9cb5404a15cff8041635e787d65f096521eeb37 Mon Sep 17 00:00:00 2001 From: Tomislav Plavcic Date: Fri, 11 Oct 2024 16:27:18 +0200 Subject: [PATCH 2/5] EVEREST-1558 - Add PITR UI E2E test --- ui/apps/everest/.e2e/release/pitr.e2e.ts | 368 ++++++++++++++++++++++ ui/apps/everest/.e2e/utils/db-cmd-line.ts | 39 +++ 2 files changed, 407 insertions(+) create mode 100644 ui/apps/everest/.e2e/release/pitr.e2e.ts diff --git a/ui/apps/everest/.e2e/release/pitr.e2e.ts b/ui/apps/everest/.e2e/release/pitr.e2e.ts new file mode 100644 index 000000000..b31317120 --- /dev/null +++ b/ui/apps/everest/.e2e/release/pitr.e2e.ts @@ -0,0 +1,368 @@ +// everest +// Copyright (C) 2023 Percona LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { expect, test } from '@playwright/test'; +import { + deleteDbCluster, + gotoDbClusterBackups, + gotoDbClusterRestores, + findDbAndClickActions, +} from '@e2e/utils/db-clusters-list'; +import { getTokenFromLocalStorage } from '@e2e/utils/localStorage'; +import { getClusterDetailedInfo } from '@e2e/utils/storage-class'; +import { + moveForward, + submitWizard, + populateBasicInformation, + populateResources, + populateAdvancedConfig, + populateMonitoringModalForm, +} from '@e2e/utils/db-wizard'; +import { EVEREST_CI_NAMESPACES } from '@e2e/constants'; +import { + waitForStatus, + waitForDelete, + findRowAndClickActions, +} from '@e2e/utils/table'; +import { checkError } from '@e2e/utils/generic'; +import { + deleteMonitoringInstance, + listMonitoringInstances, +} from '@e2e/utils/monitoring-instance'; +import { clickOnDemandBackup } from '@e2e/pr/db-cluster-details/utils'; +import { prepareTestDB, dropTestDB, queryTestDB, insertMoreTestDB } from '@e2e/utils/db-cmd-line'; +import { addFirstScheduleInDBWizard } from '@e2e/pr/db-cluster/db-wizard/db-wizard-utils'; +import { + checkDbWizardEditSubmitIsAvailableAndClick, + checkSuccessOfUpdateAndGoToDbClustersList, +} from '@e2e/pr/db-cluster/db-wizard/edit-db-cluster/edit-db-cluster.utils'; + +const { + MONITORING_URL, + MONITORING_USER, + MONITORING_PASSWORD, + SELECT_DB, + SELECT_SIZE, +} = process.env; +let token: string; + +test.describe.configure({ retries: 0 }); + +[ + { db: 'psmdb', size: 3 }, + { db: 'pxc', size: 3 }, + { db: 'postgresql', size: 3 }, +].forEach(({ db, size }) => { + test.describe( + 'PITR', + { + tag: '@release', + }, + () => { + test.skip( + () => + (SELECT_DB !== db && !!SELECT_DB) || + (SELECT_SIZE !== size.toString() && !!SELECT_SIZE) + ); + test.describe.configure({ timeout: 720000 }); + + const clusterName = `${db}-${size}-pitr`; + + let storageClasses = []; + const namespace = EVEREST_CI_NAMESPACES.EVEREST_UI; + const monitoringName = `${db}-${size}-pmm`; + const baseBackupName = `dembkp-${db}-${size}`; + + test.beforeAll(async ({ request }) => { + token = await getTokenFromLocalStorage(); + + const { storageClassNames = [] } = await getClusterDetailedInfo( + token, + request + ); + storageClasses = storageClassNames; + }); + + test.afterAll(async ({ request }) => { + // we try to delete all monitoring instances because cluster creation expects that none exist + // (monitoring instance is added in the form where the warning that none exist is visible) + const monitoringInstances = await listMonitoringInstances( + request, + namespace, + token + ); + for (const instance of monitoringInstances) { + await deleteMonitoringInstance( + request, + namespace, + instance.name, + token + ); + } + }); + + test(`Cluster creation with ${db} and size ${size}`, async ({ + page, + request, + }) => { + expect(storageClasses.length).toBeGreaterThan(0); + + await page.goto('/databases/new'); + await page.getByTestId('toggle-button-group-input-db-type').waitFor(); + await page.getByTestId('select-input-db-version').waitFor(); + + await test.step('Populate basic information', async () => { + await populateBasicInformation( + page, + db, + storageClasses[0], + clusterName + ); + await moveForward(page); + }); + + await test.step('Populate resources', async () => { + await page + .getByRole('button') + .getByText(size + ' node') + .click(); + await expect(page.getByText('Nº nodes: ' + size)).toBeVisible(); + await populateResources(page, 0.6, 1, 1, size); + await moveForward(page); + }); + + await test.step('Populate backups, enable PITR', async () => { + await addFirstScheduleInDBWizard(page); + const pitrCheckbox = page + .getByTestId('switch-input-pitr-enabled') + .getByRole('checkbox'); + await expect(pitrCheckbox).not.toBeChecked(); + await pitrCheckbox.setChecked(true); + + const pitrStorageLocation = page.getByTestId( + 'text-input-pitr-storage-location' + ); + await expect(pitrStorageLocation).toBeVisible(); + await expect(pitrStorageLocation).not.toBeEmpty(); + + await moveForward(page); + }); + + await test.step('Populate advanced db config', async () => { + await populateAdvancedConfig(page, db, '', true, ''); + await moveForward(page); + }); + + await test.step('Populate monitoring', async () => { + await populateMonitoringModalForm( + page, + monitoringName, + namespace, + MONITORING_URL, + MONITORING_USER, + MONITORING_PASSWORD + ); + await page.getByTestId('switch-input-monitoring').click(); + await expect( + page.getByTestId('text-input-monitoring-instance') + ).toHaveValue(monitoringName); + }); + + await test.step('Submit wizard', async () => { + await submitWizard(page); + + await expect( + page.getByText('Awesome! Your database is being created!') + ).toBeVisible(); + }); + + // go to db list and check status + await test.step('Check db list and status', async () => { + await page.goto('/databases'); + await waitForStatus(page, clusterName, 'Initializing', 15000); + await waitForStatus(page, clusterName, 'Up', 600000); + }); + + await test.step('Check db cluster k8s object options', async () => { + const response = await request.get( + `/v1/namespaces/${namespace}/database-clusters`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + await checkError(response); + + // TODO: replace with correct payload typings from GET DB Clusters + const { items: clusters } = await response.json(); + + const addedCluster = clusters.find( + (cluster) => cluster.metadata.name === clusterName + ); + + expect(addedCluster?.spec.backup.pitr.enabled).toBe(true); + expect(addedCluster).not.toBeUndefined(); + expect(addedCluster?.spec.engine.type).toBe(db); + expect(addedCluster?.spec.engine.replicas).toBe(size); + expect(['600m', '0.6']).toContain( + addedCluster?.spec.engine.resources?.cpu.toString() + ); + expect(addedCluster?.spec.engine.resources?.memory.toString()).toBe( + '1G' + ); + expect(addedCluster?.spec.engine.storage.size.toString()).toBe('1Gi'); + expect(addedCluster?.spec.proxy.expose.type).toBe('internal'); + expect(addedCluster?.spec.proxy.replicas).toBe(size); + }); + }); + + test(`Add data with ${db} and size ${size}`, async () => { + await prepareTestDB(clusterName, namespace); + }); + + test(`Create demand backup with ${db} and size ${size}`, async ({ + page, + }) => { + await gotoDbClusterBackups(page, clusterName); + await clickOnDemandBackup(page); + await page.getByTestId('text-input-name').fill(baseBackupName + '-1'); + await expect(page.getByTestId('text-input-name')).not.toBeEmpty(); + await expect( + page.getByTestId('text-input-storage-location') + ).not.toBeEmpty(); + await page.getByTestId('form-dialog-create').click(); + + await waitForStatus(page, baseBackupName + '-1', 'Succeeded', 240000); + }); + + test(`Add more data with ${db} and size ${size}`, async () => { + const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) + await insertMoreTestDB(clusterName, namespace); + + // wait for binlogs/oplogs to be uploaded + await delay(120000); + }); + + test(`Disable PITR with ${db} and size ${size}`, async ({ page, request }) => { + // edit cluster to disable PITR and check via api if the PITR is disabled + await test.step('Navigate to backups page', async () => { + await page.goto('/databases'); + await findDbAndClickActions(page, clusterName, 'Edit'); + await moveForward(page); + await moveForward(page); + }); + + await test.step('Disable PITR', async () => { + const pitrCheckbox = page + .getByTestId('switch-input-pitr-enabled') + .getByRole('checkbox'); + await expect(pitrCheckbox).toBeChecked(); + await pitrCheckbox.setChecked(false); + }); + + await test.step('Finish editing and submit', async () => { + await moveForward(page); + await moveForward(page); + await checkDbWizardEditSubmitIsAvailableAndClick(page); + await checkSuccessOfUpdateAndGoToDbClustersList(page); + }); + + await test.step('Check if PITR disabled in k8s', async () => { + const response = await request.get( + `/v1/namespaces/${namespace}/database-clusters`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + await checkError(response); + + const { items: clusters } = await response.json(); + + const addedCluster = clusters.find( + (cluster) => cluster.metadata.name === clusterName + ); + + expect(addedCluster?.spec.backup.pitr.enabled).toBe(false); + }); + }); + + test(`Delete data with ${db} and size ${size}`, async () => { + await dropTestDB(clusterName, namespace); + }); + + test(`Restore cluster with ${db} and size ${size}`, async ({ page }) => { + await gotoDbClusterBackups(page, clusterName); + await findRowAndClickActions( + page, + baseBackupName + '-1', + 'Restore to this DB' + ); + await expect( + page.getByTestId('select-input-backup-name') + ).not.toBeEmpty(); + await page.getByTestId('form-dialog-restore').click(); + + await page.goto('/databases'); + await waitForStatus(page, clusterName, 'Restoring', 30000); + await waitForStatus(page, clusterName, 'Up', 600000); + + await gotoDbClusterRestores(page, clusterName); + // we select based on backup source since restores cannot be named and we don't know + // in advance what will be the name + await waitForStatus(page, baseBackupName + '-1', 'Succeeded', 120000); + }); + + test(`Check data after restore with ${db} and size ${size}`, async () => { + const result = await queryTestDB(clusterName, namespace); + switch (db) { + case 'pxc': + expect(result.trim()).toBe('1\n2\n3\n4\n5\n6'); + break; + case 'psmdb': + expect(result.trim()).toBe('[ { a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }, { a: 5 }, { a: 6 } ]'); + break; + case 'postgresql': + expect(result.trim()).toBe('1\n 2\n 3\n 4\n 5\n 6'); + break; + } + }); + + test(`Delete restore with ${db} and size ${size}`, async ({ page }) => { + await gotoDbClusterRestores(page, clusterName); + await findRowAndClickActions(page, baseBackupName + '-1', 'Delete'); + await expect(page.getByLabel('Delete restore')).toBeVisible(); + await page.getByTestId('confirm-dialog-delete').click(); + await waitForDelete(page, baseBackupName + '-1', 15000); + }); + + test(`Delete backup with ${db} and size ${size}`, async ({ page }) => { + await gotoDbClusterBackups(page, clusterName); + await findRowAndClickActions(page, baseBackupName + '-1', 'Delete'); + await expect(page.getByLabel('Delete backup')).toBeVisible(); + await page.getByTestId('form-dialog-delete').click(); + await waitForDelete(page, baseBackupName + '-1', 30000); + }); + + test(`Delete cluster with ${db} and size ${size}`, async ({ page }) => { + await deleteDbCluster(page, clusterName); + await waitForStatus(page, clusterName, 'Deleting', 15000); + await waitForDelete(page, clusterName, 120000); + }); + } + ); +}); diff --git a/ui/apps/everest/.e2e/utils/db-cmd-line.ts b/ui/apps/everest/.e2e/utils/db-cmd-line.ts index f51a242a9..87829bd8a 100644 --- a/ui/apps/everest/.e2e/utils/db-cmd-line.ts +++ b/ui/apps/everest/.e2e/utils/db-cmd-line.ts @@ -184,6 +184,45 @@ export const prepareTestDB = async (cluster: string, namespace: string) => { } }; +export const insertMoreTestDB = async (cluster: string, namespace: string) => { + const dbType = await getDBType(cluster, namespace); + + switch (dbType) { + case 'pxc': { + await queryMySQL( + cluster, + namespace, + 'INSERT INTO test.t1 VALUES (4),(5),(6);' + ); + const result = await queryTestDB(cluster, namespace); + expect(result.trim()).toBe('1\n2\n3\n4\n5\n6'); + break; + } + case 'psmdb': { + await queryPSMDB( + cluster, + namespace, + 'test', + 'db.t1.insertMany([{ a: 4 }, { a: 5 }, { a: 6 }]);' + ); + const result = await queryTestDB(cluster, namespace); + expect(result.trim()).toBe('[ { a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }, { a: 5 }, { a: 6 } ]'); + break; + } + case 'postgresql': { + await queryPG( + cluster, + namespace, + 'test', + 'INSERT INTO t1 VALUES (4),(5),(6);' + ); + const result = await queryTestDB(cluster, namespace); + expect(result.trim()).toBe('1\n 2\n 3\n 4\n 5\n 6'); + break; + } + } +}; + export const dropTestDB = async (cluster: string, namespace: string) => { const dbType = await getDBType(cluster, namespace); From 75e6de9b0e2afac53e53a4fefda781b1b5bb3075 Mon Sep 17 00:00:00 2001 From: Tomislav Plavcic Date: Wed, 6 Nov 2024 11:14:14 +0100 Subject: [PATCH 3/5] Update pitr.e2e.ts E2E UI test --- ui/apps/everest/.e2e/release/pitr.e2e.ts | 206 +++++++++++++--------- ui/apps/everest/.e2e/utils/db-cmd-line.ts | 9 + ui/apps/everest/.e2e/utils/table.ts | 4 +- 3 files changed, 134 insertions(+), 85 deletions(-) diff --git a/ui/apps/everest/.e2e/release/pitr.e2e.ts b/ui/apps/everest/.e2e/release/pitr.e2e.ts index b31317120..c16411a9a 100644 --- a/ui/apps/everest/.e2e/release/pitr.e2e.ts +++ b/ui/apps/everest/.e2e/release/pitr.e2e.ts @@ -42,12 +42,8 @@ import { listMonitoringInstances, } from '@e2e/utils/monitoring-instance'; import { clickOnDemandBackup } from '@e2e/pr/db-cluster-details/utils'; -import { prepareTestDB, dropTestDB, queryTestDB, insertMoreTestDB } from '@e2e/utils/db-cmd-line'; +import { prepareTestDB, dropTestDB, queryTestDB, insertMoreTestDB, pgInsertDummyTestDB } from '@e2e/utils/db-cmd-line'; import { addFirstScheduleInDBWizard } from '@e2e/pr/db-cluster/db-wizard/db-wizard-utils'; -import { - checkDbWizardEditSubmitIsAvailableAndClick, - checkSuccessOfUpdateAndGoToDbClustersList, -} from '@e2e/pr/db-cluster/db-wizard/edit-db-cluster/edit-db-cluster.utils'; const { MONITORING_URL, @@ -56,7 +52,55 @@ const { SELECT_DB, SELECT_SIZE, } = process.env; + +type pitrTime = { + day: string, + month: string, + year: string, + hour: string, + minute: string, + second: string, + ampm: string +}; + let token: string; +let pitrRestoreTime: pitrTime = { + day: "", + month: "", + year: "", + hour: "", + minute: "", + second: "", + ampm: "" +}; + +function getCurrentPITRTime(): pitrTime { + const time = {} as pitrTime; + const now: Date = new Date(); + + // Get date parts + time.day = now.getDate().toString(); + time.month = (now.getMonth() + 1).toString(); // Months are zero-indexed, so add 1 + time.year = now.getFullYear().toString(); + + // Get time parts + let hour: number = now.getHours(); + time.minute = now.getMinutes().toString(); + time.second = now.getSeconds().toString(); + + // Determine AM or PM + time.ampm = hour >= 12 ? 'PM' : 'AM'; + hour = hour % 12 || 12; // Convert 24-hour format to 12-hour format, making 0 => 12 + time.hour = hour.toString(); + + return time; +} + +function getFormattedPITRTime(time: pitrTime): string { + const formattedDateTime: string = `${time.day.padStart(2, '0')}/${time.month.padStart(2, '0')}/${time.year} at ${time.hour.padStart(2, '0')}:${time.minute.padStart(2, '0')}:${time.second.padStart(2, '0')} ${time.ampm}`; + + return formattedDateTime; +} test.describe.configure({ retries: 0 }); @@ -113,7 +157,7 @@ test.describe.configure({ retries: 0 }); } }); - test(`Cluster creation with ${db} and size ${size}`, async ({ + test(`Cluster creation [${db} size ${size}]`, async ({ page, request, }) => { @@ -148,15 +192,24 @@ test.describe.configure({ retries: 0 }); const pitrCheckbox = page .getByTestId('switch-input-pitr-enabled') .getByRole('checkbox'); - await expect(pitrCheckbox).not.toBeChecked(); - await pitrCheckbox.setChecked(true); - const pitrStorageLocation = page.getByTestId( - 'text-input-pitr-storage-location' - ); - await expect(pitrStorageLocation).toBeVisible(); - await expect(pitrStorageLocation).not.toBeEmpty(); + if (db !== 'postgresql') { + await expect(pitrCheckbox).not.toBeChecked(); + await pitrCheckbox.setChecked(true); + + if (db === 'pxc'){ + const pitrStorageLocation = page.getByTestId( + 'text-input-pitr-storage-location' + ); + await expect(pitrStorageLocation).toBeVisible(); + await expect(pitrStorageLocation).not.toBeEmpty(); + } + else { + await expect(page.getByText('Storage: bucket-1')).toHaveCount(2); + } + } + await expect(pitrCheckbox).toBeChecked(); await moveForward(page); }); @@ -188,11 +241,28 @@ test.describe.configure({ retries: 0 }); ).toBeVisible(); }); - // go to db list and check status await test.step('Check db list and status', async () => { await page.goto('/databases'); await waitForStatus(page, clusterName, 'Initializing', 15000); - await waitForStatus(page, clusterName, 'Up', 600000); + await waitForStatus(page, clusterName, 'Up', 660000); + }); + + await test.step('Update PSMDB cluster PITR uploadIntervalSec', async () => { + if (db !== "psmdb") { + return; + } + let psmdbCluster = await request.get(`/v1/namespaces/${namespace}/database-clusters/${clusterName}`); + + await checkError(psmdbCluster); + const psmdbPayload = (await psmdbCluster.json()); + + psmdbPayload.spec.backup.pitr.uploadIntervalSec = 60; + + const updatedPSMDBCluster = await request.put(`/v1/namespaces/${namespace}/database-clusters/${clusterName}`, { + data: psmdbPayload, + }); + + await checkError(updatedPSMDBCluster); }); await test.step('Check db cluster k8s object options', async () => { @@ -225,15 +295,17 @@ test.describe.configure({ retries: 0 }); ); expect(addedCluster?.spec.engine.storage.size.toString()).toBe('1Gi'); expect(addedCluster?.spec.proxy.expose.type).toBe('internal'); - expect(addedCluster?.spec.proxy.replicas).toBe(size); + if (db != 'psmdb') { + expect(addedCluster?.spec.proxy.replicas).toBe(size); + } }); }); - test(`Add data with ${db} and size ${size}`, async () => { + test(`Add data [${db} size ${size}]`, async () => { await prepareTestDB(clusterName, namespace); }); - test(`Create demand backup with ${db} and size ${size}`, async ({ + test(`Create demand backup [${db} size ${size}]`, async ({ page, }) => { await gotoDbClusterBackups(page, clusterName); @@ -248,86 +320,54 @@ test.describe.configure({ retries: 0 }); await waitForStatus(page, baseBackupName + '-1', 'Succeeded', 240000); }); - test(`Add more data with ${db} and size ${size}`, async () => { - const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) + test(`Add more data [${db} size ${size}]`, async () => { await insertMoreTestDB(clusterName, namespace); + pitrRestoreTime = getCurrentPITRTime(); - // wait for binlogs/oplogs to be uploaded - await delay(120000); + // for PG we need one more transaction to be able to restore to the previous one + if (db == "postgresql") { + pgInsertDummyTestDB(clusterName, namespace); + } }); - test(`Disable PITR with ${db} and size ${size}`, async ({ page, request }) => { - // edit cluster to disable PITR and check via api if the PITR is disabled - await test.step('Navigate to backups page', async () => { - await page.goto('/databases'); - await findDbAndClickActions(page, clusterName, 'Edit'); - await moveForward(page); - await moveForward(page); - }); - - await test.step('Disable PITR', async () => { - const pitrCheckbox = page - .getByTestId('switch-input-pitr-enabled') - .getByRole('checkbox'); - await expect(pitrCheckbox).toBeChecked(); - await pitrCheckbox.setChecked(false); - }); - - await test.step('Finish editing and submit', async () => { - await moveForward(page); - await moveForward(page); - await checkDbWizardEditSubmitIsAvailableAndClick(page); - await checkSuccessOfUpdateAndGoToDbClustersList(page); - }); - - await test.step('Check if PITR disabled in k8s', async () => { - const response = await request.get( - `/v1/namespaces/${namespace}/database-clusters`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); - await checkError(response); - - const { items: clusters } = await response.json(); - - const addedCluster = clusters.find( - (cluster) => cluster.metadata.name === clusterName - ); - - expect(addedCluster?.spec.backup.pitr.enabled).toBe(false); - }); + test(`Wait 1 min for binlogs to be uploaded [${db} size ${size}]`, async () => { + const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) + await delay(65000); }); - test(`Delete data with ${db} and size ${size}`, async () => { + test(`Delete data [${db} size ${size}]`, async () => { await dropTestDB(clusterName, namespace); }); - test(`Restore cluster with ${db} and size ${size}`, async ({ page }) => { - await gotoDbClusterBackups(page, clusterName); - await findRowAndClickActions( + test(`Restore cluster [${db} size ${size}]`, async ({ page }) => { + await page.goto('databases'); + await findDbAndClickActions( page, - baseBackupName + '-1', - 'Restore to this DB' + clusterName, + 'Restore from a backup' ); - await expect( - page.getByTestId('select-input-backup-name') - ).not.toBeEmpty(); - await page.getByTestId('form-dialog-restore').click(); + await page.getByTestId('radio-option-fromPITR').click({ timeout: 5000 }); + await expect(page.getByTestId('radio-option-fromPITR')).toBeChecked({ timeout: 5000 }); + await expect(page.getByPlaceholder('DD/MM/YYYY at hh:mm:ss aa')).toBeVisible({ timeout: 5000 }); + await expect(page.getByPlaceholder('DD/MM/YYYY at hh:mm:ss aa')).not.toBeEmpty({ timeout: 5000 }); + await page.getByTestId('CalendarIcon').click({ timeout: 5000 }); + await page.getByLabel(pitrRestoreTime.hour + ' hours', { exact: true }).click({ timeout: 5000 }); + await page.getByLabel(pitrRestoreTime.minute + ' minutes', { exact: true }).click({ timeout: 5000 }); + await page.getByLabel(pitrRestoreTime.second + ' seconds', { exact: true }).click({ timeout: 5000 }); + await page.getByLabel(pitrRestoreTime.ampm, { exact: true }).click({ timeout: 5000 }); + await expect(page.getByPlaceholder('DD/MM/YYYY at hh:mm:ss aa')).toHaveValue(getFormattedPITRTime(pitrRestoreTime)) + + await page.getByTestId('form-dialog-restore').click({ timeout: 5000 }); await page.goto('/databases'); await waitForStatus(page, clusterName, 'Restoring', 30000); await waitForStatus(page, clusterName, 'Up', 600000); await gotoDbClusterRestores(page, clusterName); - // we select based on backup source since restores cannot be named and we don't know - // in advance what will be the name - await waitForStatus(page, baseBackupName + '-1', 'Succeeded', 120000); + await waitForStatus(page, 'restore-', 'Succeeded', 120000); }); - test(`Check data after restore with ${db} and size ${size}`, async () => { + test(`Check data after restore [${db} size ${size}]`, async () => { const result = await queryTestDB(clusterName, namespace); switch (db) { case 'pxc': @@ -342,7 +382,7 @@ test.describe.configure({ retries: 0 }); } }); - test(`Delete restore with ${db} and size ${size}`, async ({ page }) => { + test(`Delete restore [${db} size ${size}]`, async ({ page }) => { await gotoDbClusterRestores(page, clusterName); await findRowAndClickActions(page, baseBackupName + '-1', 'Delete'); await expect(page.getByLabel('Delete restore')).toBeVisible(); @@ -350,7 +390,7 @@ test.describe.configure({ retries: 0 }); await waitForDelete(page, baseBackupName + '-1', 15000); }); - test(`Delete backup with ${db} and size ${size}`, async ({ page }) => { + test(`Delete backup [${db} size ${size}]`, async ({ page }) => { await gotoDbClusterBackups(page, clusterName); await findRowAndClickActions(page, baseBackupName + '-1', 'Delete'); await expect(page.getByLabel('Delete backup')).toBeVisible(); @@ -358,10 +398,10 @@ test.describe.configure({ retries: 0 }); await waitForDelete(page, baseBackupName + '-1', 30000); }); - test(`Delete cluster with ${db} and size ${size}`, async ({ page }) => { + test(`Delete cluster [${db} size ${size}]`, async ({ page }) => { await deleteDbCluster(page, clusterName); await waitForStatus(page, clusterName, 'Deleting', 15000); - await waitForDelete(page, clusterName, 120000); + await waitForDelete(page, clusterName, 160000); }); } ); diff --git a/ui/apps/everest/.e2e/utils/db-cmd-line.ts b/ui/apps/everest/.e2e/utils/db-cmd-line.ts index 87829bd8a..fad8a4c74 100644 --- a/ui/apps/everest/.e2e/utils/db-cmd-line.ts +++ b/ui/apps/everest/.e2e/utils/db-cmd-line.ts @@ -223,6 +223,15 @@ export const insertMoreTestDB = async (cluster: string, namespace: string) => { } }; +export const pgInsertDummyTestDB = async (cluster: string, namespace: string) => { + await queryPG( + cluster, + namespace, + 'test', + 'INSERT INTO t1 VALUES (7),(8),(9);' + ); +}; + export const dropTestDB = async (cluster: string, namespace: string) => { const dbType = await getDBType(cluster, namespace); diff --git a/ui/apps/everest/.e2e/utils/table.ts b/ui/apps/everest/.e2e/utils/table.ts index 5656b4e06..a04dd0ccc 100644 --- a/ui/apps/everest/.e2e/utils/table.ts +++ b/ui/apps/everest/.e2e/utils/table.ts @@ -10,7 +10,7 @@ export const findRowAndClickActions = async ( .locator('.MuiTableRow-root') .filter({ hasText: name }) .getByTestId('MoreHorizIcon') - .click({ timeout: 5000 }); + .click({ timeout: 10000 }); if (nameOfAction) { await page.getByRole('menuitem', { name: nameOfAction }).click(); @@ -38,7 +38,7 @@ export const waitForStatus = async ( timeout: number ) => { const dbRow = page.getByRole('row').filter({ hasText: name }); - await expect(dbRow).toBeVisible(); + await expect(dbRow).toBeVisible({ timeout: 10000 }); await expect(dbRow.getByText(status)).toBeVisible({ timeout: timeout }); }; From c736009693b316743281e63f64b8e1fddf3b9698 Mon Sep 17 00:00:00 2001 From: Tomislav Plavcic Date: Wed, 6 Nov 2024 11:54:03 +0100 Subject: [PATCH 4/5] Minor cosmetic change in UI E2E tests --- .../everest/.e2e/release/demand-backup.e2e.ts | 22 ++- .../everest/.e2e/release/init-deploy.e2e.ts | 175 ++++++++++-------- .../.e2e/release/scheduled-backup.e2e.ts | 20 +- 3 files changed, 116 insertions(+), 101 deletions(-) diff --git a/ui/apps/everest/.e2e/release/demand-backup.e2e.ts b/ui/apps/everest/.e2e/release/demand-backup.e2e.ts index d955dd79e..0ec465b4d 100644 --- a/ui/apps/everest/.e2e/release/demand-backup.e2e.ts +++ b/ui/apps/everest/.e2e/release/demand-backup.e2e.ts @@ -107,7 +107,7 @@ test.describe.configure({ retries: 0 }); } }); - test(`Cluster creation with ${db} and size ${size}`, async ({ + test(`Cluster creation [${db} size ${size}]`, async ({ page, request, }) => { @@ -206,15 +206,17 @@ test.describe.configure({ retries: 0 }); ); expect(addedCluster?.spec.engine.storage.size.toString()).toBe('1Gi'); expect(addedCluster?.spec.proxy.expose.type).toBe('internal'); - expect(addedCluster?.spec.proxy.replicas).toBe(size); + if (db != 'psmdb') { + expect(addedCluster?.spec.proxy.replicas).toBe(size); + } }); }); - test(`Add data with ${db} and size ${size}`, async () => { + test(`Add data [${db} size ${size}]`, async () => { await prepareTestDB(clusterName, namespace); }); - test(`Create demand backup with ${db} and size ${size}`, async ({ + test(`Create demand backup [${db} size ${size}]`, async ({ page, }) => { await gotoDbClusterBackups(page, clusterName); @@ -229,11 +231,11 @@ test.describe.configure({ retries: 0 }); await waitForStatus(page, baseBackupName + '-1', 'Succeeded', 240000); }); - test(`Delete data with ${db} and size ${size}`, async () => { + test(`Delete data [${db} size ${size}]`, async () => { await dropTestDB(clusterName, namespace); }); - test(`Restore cluster with ${db} and size ${size}`, async ({ page }) => { + test(`Restore cluster [${db} size ${size}]`, async ({ page }) => { await gotoDbClusterBackups(page, clusterName); await findRowAndClickActions( page, @@ -255,7 +257,7 @@ test.describe.configure({ retries: 0 }); await waitForStatus(page, baseBackupName + '-1', 'Succeeded', 120000); }); - test(`Check data after restore with ${db} and size ${size}`, async () => { + test(`Check data after restore [${db} size ${size}]`, async () => { const result = await queryTestDB(clusterName, namespace); switch (db) { case 'pxc': @@ -270,7 +272,7 @@ test.describe.configure({ retries: 0 }); } }); - test(`Delete restore with ${db} and size ${size}`, async ({ page }) => { + test(`Delete restore [${db} size ${size}]`, async ({ page }) => { await gotoDbClusterRestores(page, clusterName); await findRowAndClickActions(page, baseBackupName + '-1', 'Delete'); await expect(page.getByLabel('Delete restore')).toBeVisible(); @@ -278,7 +280,7 @@ test.describe.configure({ retries: 0 }); await waitForDelete(page, baseBackupName + '-1', 15000); }); - test(`Delete backup with ${db} and size ${size}`, async ({ page }) => { + test(`Delete backup [${db} size ${size}]`, async ({ page }) => { await gotoDbClusterBackups(page, clusterName); await findRowAndClickActions(page, baseBackupName + '-1', 'Delete'); await expect(page.getByLabel('Delete backup')).toBeVisible(); @@ -286,7 +288,7 @@ test.describe.configure({ retries: 0 }); await waitForDelete(page, baseBackupName + '-1', 30000); }); - test(`Delete cluster with ${db} and size ${size}`, async ({ page }) => { + test(`Delete cluster [${db} size ${size}]`, async ({ page }) => { await deleteDbCluster(page, clusterName); await waitForStatus(page, clusterName, 'Deleting', 15000); await waitForDelete(page, clusterName, 120000); diff --git a/ui/apps/everest/.e2e/release/init-deploy.e2e.ts b/ui/apps/everest/.e2e/release/init-deploy.e2e.ts index c2cef93c4..9076c5d91 100644 --- a/ui/apps/everest/.e2e/release/init-deploy.e2e.ts +++ b/ui/apps/everest/.e2e/release/init-deploy.e2e.ts @@ -60,7 +60,6 @@ test.describe.configure({ retries: 0 }); test.describe( 'Initial deployment', { - // Create/stop/resume/delete cluster in different configurations tag: '@release', }, () => { @@ -105,7 +104,7 @@ test.describe.configure({ retries: 0 }); } }); - test(`Cluster creation with ${db} and size ${size}`, async ({ + test(`Cluster creation [${db} size ${size}]`, async ({ page, request, }) => { @@ -115,88 +114,100 @@ test.describe.configure({ retries: 0 }); await page.getByTestId('toggle-button-group-input-db-type').waitFor(); await page.getByTestId('select-input-db-version').waitFor(); - // basic info - await populateBasicInformation( - page, - db, - storageClasses[0], - clusterName - ); - await moveForward(page); - - // resources - await page - .getByRole('button') - .getByText(size + ' node') - .click(); - await expect(page.getByText('Nº nodes: ' + size)).toBeVisible(); - await populateResources(page, 0.6, 1, 1, size); - await moveForward(page); - - // backups - await moveForward(page); - - // advanced - await populateAdvancedConfig(page, db, '', true, ''); - await moveForward(page); - - // monitoring modal form - await populateMonitoringModalForm( - page, - monitoringName, - namespace, - MONITORING_URL, - MONITORING_USER, - MONITORING_PASSWORD - ); - await page.getByTestId('switch-input-monitoring').click(); - await expect( - page.getByTestId('text-input-monitoring-instance') - ).toHaveValue(monitoringName); - await submitWizard(page); - - await expect( - page.getByText('Awesome! Your database is being created!') - ).toBeVisible(); - - // go to db list and check status - await page.goto('/databases'); - await waitForStatus(page, clusterName, 'Initializing', 15000); - await waitForStatus(page, clusterName, 'Up', 360000); - - const response = await request.get( - `/v1/namespaces/${namespace}/database-clusters`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); - - await checkError(response); + await test.step('Populate basic information', async () => { + await populateBasicInformation( + page, + db, + storageClasses[0], + clusterName + ); + await moveForward(page); + }); + + await test.step('Populate resources', async () => { + await page + .getByRole('button') + .getByText(size + ' node') + .click(); + await expect(page.getByText('Nº nodes: ' + size)).toBeVisible(); + await populateResources(page, 0.6, 1, 1, size); + await moveForward(page); + }); + + await test.step('Populate backups', async () => { + await moveForward(page); + }); + + await test.step('Populate advanced db config', async () => { + await populateAdvancedConfig(page, db, '', true, ''); + await moveForward(page); + }); + + await test.step('Populate monitoring', async () => { + await populateMonitoringModalForm( + page, + monitoringName, + namespace, + MONITORING_URL, + MONITORING_USER, + MONITORING_PASSWORD + ); + await page.getByTestId('switch-input-monitoring').click(); + await expect( + page.getByTestId('text-input-monitoring-instance') + ).toHaveValue(monitoringName); + }); + + await test.step('Submit wizard', async () => { + await submitWizard(page); + + await expect( + page.getByText('Awesome! Your database is being created!') + ).toBeVisible(); + }); + + await test.step('Check db list and status', async () => { + await page.goto('/databases'); + await waitForStatus(page, clusterName, 'Initializing', 15000); + await waitForStatus(page, clusterName, 'Up', 360000); + }); + + await test.step('Check db cluster k8s object options', async () => { + const response = await request.get( + `/v1/namespaces/${namespace}/database-clusters`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + await checkError(response); - // TODO: replace with correct payload typings from GET DB Clusters - const { items: clusters } = await response.json(); + // TODO: replace with correct payload typings from GET DB Clusters + const { items: clusters } = await response.json(); - const addedCluster = clusters.find( - (cluster) => cluster.metadata.name === clusterName - ); + const addedCluster = clusters.find( + (cluster) => cluster.metadata.name === clusterName + ); - expect(addedCluster).not.toBeUndefined(); - expect(addedCluster?.spec.engine.type).toBe(db); - expect(addedCluster?.spec.engine.replicas).toBe(size); - expect(['600m', '0.6']).toContain( - addedCluster?.spec.engine.resources?.cpu.toString() - ); - expect(addedCluster?.spec.engine.resources?.memory.toString()).toBe( - '1G' - ); - expect(addedCluster?.spec.engine.storage.size.toString()).toBe('1Gi'); - expect(addedCluster?.spec.proxy.expose.type).toBe('internal'); - expect(addedCluster?.spec.proxy.replicas).toBe(size); + expect(addedCluster).not.toBeUndefined(); + expect(addedCluster?.spec.engine.type).toBe(db); + expect(addedCluster?.spec.engine.replicas).toBe(size); + expect(['600m', '0.6']).toContain( + addedCluster?.spec.engine.resources?.cpu.toString() + ); + expect(addedCluster?.spec.engine.resources?.memory.toString()).toBe( + '1G' + ); + expect(addedCluster?.spec.engine.storage.size.toString()).toBe('1Gi'); + expect(addedCluster?.spec.proxy.expose.type).toBe('internal'); + if (db != 'psmdb') { + expect(addedCluster?.spec.proxy.replicas).toBe(size); + } + }); }); - test(`Suspend cluster with ${db} and size ${size}`, async ({ page }) => { + test(`Suspend cluster [${db} size ${size}]`, async ({ page }) => { await suspendDbCluster(page, clusterName); // One node clusters and Postgresql don't seem to show Stopping state if (size != 1 && db != 'postgresql') { @@ -205,13 +216,13 @@ test.describe.configure({ retries: 0 }); await waitForStatus(page, clusterName, 'Paused', 120000); }); - test(`Resume cluster with ${db} and size ${size}`, async ({ page }) => { + test(`Resume cluster [${db} size ${size}]`, async ({ page }) => { await resumeDbCluster(page, clusterName); await waitForStatus(page, clusterName, 'Initializing', 45000); await waitForStatus(page, clusterName, 'Up', 240000); }); - test(`Restart cluster with ${db} and size ${size}`, async ({ page }) => { + test(`Restart cluster [${db} size ${size}]`, async ({ page }) => { await restartDbCluster(page, clusterName); if (size != 1 && db != 'postgresql') { await waitForStatus(page, clusterName, 'Stopping', 45000); @@ -220,7 +231,7 @@ test.describe.configure({ retries: 0 }); await waitForStatus(page, clusterName, 'Up', 240000); }); - test(`Delete cluster with ${db} and size ${size}`, async ({ page }) => { + test(`Delete cluster [${db} size ${size}]`, async ({ page }) => { await deleteDbCluster(page, clusterName); await waitForStatus(page, clusterName, 'Deleting', 15000); await waitForDelete(page, clusterName, 120000); diff --git a/ui/apps/everest/.e2e/release/scheduled-backup.e2e.ts b/ui/apps/everest/.e2e/release/scheduled-backup.e2e.ts index 788c01301..d4255c5d7 100644 --- a/ui/apps/everest/.e2e/release/scheduled-backup.e2e.ts +++ b/ui/apps/everest/.e2e/release/scheduled-backup.e2e.ts @@ -117,7 +117,7 @@ function getNextScheduleMinute(incrementMinutes: number): string { } }); - test(`Create cluster with ${db} and size ${size}`, async ({ + test(`Create cluster [${db} size ${size}]`, async ({ page, request, }) => { @@ -216,11 +216,13 @@ function getNextScheduleMinute(incrementMinutes: number): string { ); expect(addedCluster?.spec.engine.storage.size.toString()).toBe('1Gi'); expect(addedCluster?.spec.proxy.expose.type).toBe('internal'); - expect(addedCluster?.spec.proxy.replicas).toBe(size); + if (db != 'psmdb') { + expect(addedCluster?.spec.proxy.replicas).toBe(size); + } }); }); - test(`Add data with ${db} and size ${size}`, async () => { + test(`Add data [${db} size ${size}]`, async () => { await prepareTestDB(clusterName, namespace); }); @@ -326,11 +328,11 @@ function getNextScheduleMinute(incrementMinutes: number): string { }); }); - test(`Delete data with ${db} and size ${size}`, async () => { + test(`Delete data [${db} size ${size}]`, async () => { await dropTestDB(clusterName, namespace); }); - test(`Restore cluster with ${db} and size ${size}`, async ({ page }) => { + test(`Restore cluster [${db} size ${size}]`, async ({ page }) => { await gotoDbClusterBackups(page, clusterName); const firstBackup = await page .getByText(`${db}-${size}-schbkp-`) @@ -353,7 +355,7 @@ function getNextScheduleMinute(incrementMinutes: number): string { await waitForStatus(page, firstBackup, 'Succeeded', 120000); }); - test(`Check data after restore with ${db} and size ${size}`, async () => { + test(`Check data after restore [${db} size ${size}]`, async () => { const result = await queryTestDB(clusterName, namespace); switch (db) { case 'pxc': @@ -368,7 +370,7 @@ function getNextScheduleMinute(incrementMinutes: number): string { } }); - test(`Delete restore with ${db} and size ${size}`, async ({ page }) => { + test(`Delete restore [${db} size ${size}]`, async ({ page }) => { await gotoDbClusterRestores(page, clusterName); await findRowAndClickActions(page, `${db}-${size}-schbkp-`, 'Delete'); await expect(page.getByLabel('Delete restore')).toBeVisible(); @@ -376,7 +378,7 @@ function getNextScheduleMinute(incrementMinutes: number): string { await waitForDelete(page, `${db}-${size}-schbkp-`, 15000); }); - test(`Delete backup with ${db} and size ${size}`, async ({ page }) => { + test(`Delete backup [${db} size ${size}]`, async ({ page }) => { await gotoDbClusterBackups(page, clusterName); await test.step('Delete first backup', async () => { @@ -404,7 +406,7 @@ function getNextScheduleMinute(incrementMinutes: number): string { }); }); - test(`Delete cluster with ${db} and size ${size}`, async ({ page }) => { + test(`Delete cluster [${db} size ${size}]`, async ({ page }) => { await deleteDbCluster(page, clusterName); await waitForStatus(page, clusterName, 'Deleting', 15000); await waitForDelete(page, clusterName, 120000); From acc38bc470a44089c32effeb95597ef71bc62a01 Mon Sep 17 00:00:00 2001 From: tplavcic Date: Wed, 6 Nov 2024 11:01:17 +0000 Subject: [PATCH 5/5] chore: lint/format --- .../everest/.e2e/release/demand-backup.e2e.ts | 4 +- ui/apps/everest/.e2e/release/pitr.e2e.ts | 116 +++++++++++------- .../.e2e/release/scheduled-backup.e2e.ts | 5 +- ui/apps/everest/.e2e/utils/db-cmd-line.ts | 9 +- 4 files changed, 79 insertions(+), 55 deletions(-) diff --git a/ui/apps/everest/.e2e/release/demand-backup.e2e.ts b/ui/apps/everest/.e2e/release/demand-backup.e2e.ts index 0ec465b4d..d39e4e402 100644 --- a/ui/apps/everest/.e2e/release/demand-backup.e2e.ts +++ b/ui/apps/everest/.e2e/release/demand-backup.e2e.ts @@ -216,9 +216,7 @@ test.describe.configure({ retries: 0 }); await prepareTestDB(clusterName, namespace); }); - test(`Create demand backup [${db} size ${size}]`, async ({ - page, - }) => { + test(`Create demand backup [${db} size ${size}]`, async ({ page }) => { await gotoDbClusterBackups(page, clusterName); await clickOnDemandBackup(page); await page.getByTestId('text-input-name').fill(baseBackupName + '-1'); diff --git a/ui/apps/everest/.e2e/release/pitr.e2e.ts b/ui/apps/everest/.e2e/release/pitr.e2e.ts index c16411a9a..9edb3f8e9 100644 --- a/ui/apps/everest/.e2e/release/pitr.e2e.ts +++ b/ui/apps/everest/.e2e/release/pitr.e2e.ts @@ -42,7 +42,13 @@ import { listMonitoringInstances, } from '@e2e/utils/monitoring-instance'; import { clickOnDemandBackup } from '@e2e/pr/db-cluster-details/utils'; -import { prepareTestDB, dropTestDB, queryTestDB, insertMoreTestDB, pgInsertDummyTestDB } from '@e2e/utils/db-cmd-line'; +import { + prepareTestDB, + dropTestDB, + queryTestDB, + insertMoreTestDB, + pgInsertDummyTestDB, +} from '@e2e/utils/db-cmd-line'; import { addFirstScheduleInDBWizard } from '@e2e/pr/db-cluster/db-wizard/db-wizard-utils'; const { @@ -54,24 +60,24 @@ const { } = process.env; type pitrTime = { - day: string, - month: string, - year: string, - hour: string, - minute: string, - second: string, - ampm: string + day: string; + month: string; + year: string; + hour: string; + minute: string; + second: string; + ampm: string; }; let token: string; let pitrRestoreTime: pitrTime = { - day: "", - month: "", - year: "", - hour: "", - minute: "", - second: "", - ampm: "" + day: '', + month: '', + year: '', + hour: '', + minute: '', + second: '', + ampm: '', }; function getCurrentPITRTime(): pitrTime { @@ -190,21 +196,20 @@ test.describe.configure({ retries: 0 }); await test.step('Populate backups, enable PITR', async () => { await addFirstScheduleInDBWizard(page); const pitrCheckbox = page - .getByTestId('switch-input-pitr-enabled') - .getByRole('checkbox'); + .getByTestId('switch-input-pitr-enabled') + .getByRole('checkbox'); if (db !== 'postgresql') { await expect(pitrCheckbox).not.toBeChecked(); await pitrCheckbox.setChecked(true); - if (db === 'pxc'){ + if (db === 'pxc') { const pitrStorageLocation = page.getByTestId( 'text-input-pitr-storage-location' ); await expect(pitrStorageLocation).toBeVisible(); await expect(pitrStorageLocation).not.toBeEmpty(); - } - else { + } else { await expect(page.getByText('Storage: bucket-1')).toHaveCount(2); } } @@ -248,19 +253,24 @@ test.describe.configure({ retries: 0 }); }); await test.step('Update PSMDB cluster PITR uploadIntervalSec', async () => { - if (db !== "psmdb") { + if (db !== 'psmdb') { return; } - let psmdbCluster = await request.get(`/v1/namespaces/${namespace}/database-clusters/${clusterName}`); + let psmdbCluster = await request.get( + `/v1/namespaces/${namespace}/database-clusters/${clusterName}` + ); await checkError(psmdbCluster); - const psmdbPayload = (await psmdbCluster.json()); + const psmdbPayload = await psmdbCluster.json(); psmdbPayload.spec.backup.pitr.uploadIntervalSec = 60; - const updatedPSMDBCluster = await request.put(`/v1/namespaces/${namespace}/database-clusters/${clusterName}`, { - data: psmdbPayload, - }); + const updatedPSMDBCluster = await request.put( + `/v1/namespaces/${namespace}/database-clusters/${clusterName}`, + { + data: psmdbPayload, + } + ); await checkError(updatedPSMDBCluster); }); @@ -305,9 +315,7 @@ test.describe.configure({ retries: 0 }); await prepareTestDB(clusterName, namespace); }); - test(`Create demand backup [${db} size ${size}]`, async ({ - page, - }) => { + test(`Create demand backup [${db} size ${size}]`, async ({ page }) => { await gotoDbClusterBackups(page, clusterName); await clickOnDemandBackup(page); await page.getByTestId('text-input-name').fill(baseBackupName + '-1'); @@ -325,13 +333,13 @@ test.describe.configure({ retries: 0 }); pitrRestoreTime = getCurrentPITRTime(); // for PG we need one more transaction to be able to restore to the previous one - if (db == "postgresql") { + if (db == 'postgresql') { pgInsertDummyTestDB(clusterName, namespace); } }); test(`Wait 1 min for binlogs to be uploaded [${db} size ${size}]`, async () => { - const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) + const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); await delay(65000); }); @@ -341,21 +349,35 @@ test.describe.configure({ retries: 0 }); test(`Restore cluster [${db} size ${size}]`, async ({ page }) => { await page.goto('databases'); - await findDbAndClickActions( - page, - clusterName, - 'Restore from a backup' - ); - await page.getByTestId('radio-option-fromPITR').click({ timeout: 5000 }); - await expect(page.getByTestId('radio-option-fromPITR')).toBeChecked({ timeout: 5000 }); - await expect(page.getByPlaceholder('DD/MM/YYYY at hh:mm:ss aa')).toBeVisible({ timeout: 5000 }); - await expect(page.getByPlaceholder('DD/MM/YYYY at hh:mm:ss aa')).not.toBeEmpty({ timeout: 5000 }); + await findDbAndClickActions(page, clusterName, 'Restore from a backup'); + await page + .getByTestId('radio-option-fromPITR') + .click({ timeout: 5000 }); + await expect(page.getByTestId('radio-option-fromPITR')).toBeChecked({ + timeout: 5000, + }); + await expect( + page.getByPlaceholder('DD/MM/YYYY at hh:mm:ss aa') + ).toBeVisible({ timeout: 5000 }); + await expect( + page.getByPlaceholder('DD/MM/YYYY at hh:mm:ss aa') + ).not.toBeEmpty({ timeout: 5000 }); await page.getByTestId('CalendarIcon').click({ timeout: 5000 }); - await page.getByLabel(pitrRestoreTime.hour + ' hours', { exact: true }).click({ timeout: 5000 }); - await page.getByLabel(pitrRestoreTime.minute + ' minutes', { exact: true }).click({ timeout: 5000 }); - await page.getByLabel(pitrRestoreTime.second + ' seconds', { exact: true }).click({ timeout: 5000 }); - await page.getByLabel(pitrRestoreTime.ampm, { exact: true }).click({ timeout: 5000 }); - await expect(page.getByPlaceholder('DD/MM/YYYY at hh:mm:ss aa')).toHaveValue(getFormattedPITRTime(pitrRestoreTime)) + await page + .getByLabel(pitrRestoreTime.hour + ' hours', { exact: true }) + .click({ timeout: 5000 }); + await page + .getByLabel(pitrRestoreTime.minute + ' minutes', { exact: true }) + .click({ timeout: 5000 }); + await page + .getByLabel(pitrRestoreTime.second + ' seconds', { exact: true }) + .click({ timeout: 5000 }); + await page + .getByLabel(pitrRestoreTime.ampm, { exact: true }) + .click({ timeout: 5000 }); + await expect( + page.getByPlaceholder('DD/MM/YYYY at hh:mm:ss aa') + ).toHaveValue(getFormattedPITRTime(pitrRestoreTime)); await page.getByTestId('form-dialog-restore').click({ timeout: 5000 }); @@ -374,7 +396,9 @@ test.describe.configure({ retries: 0 }); expect(result.trim()).toBe('1\n2\n3\n4\n5\n6'); break; case 'psmdb': - expect(result.trim()).toBe('[ { a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }, { a: 5 }, { a: 6 } ]'); + expect(result.trim()).toBe( + '[ { a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }, { a: 5 }, { a: 6 } ]' + ); break; case 'postgresql': expect(result.trim()).toBe('1\n 2\n 3\n 4\n 5\n 6'); diff --git a/ui/apps/everest/.e2e/release/scheduled-backup.e2e.ts b/ui/apps/everest/.e2e/release/scheduled-backup.e2e.ts index d4255c5d7..0f071d176 100644 --- a/ui/apps/everest/.e2e/release/scheduled-backup.e2e.ts +++ b/ui/apps/everest/.e2e/release/scheduled-backup.e2e.ts @@ -117,10 +117,7 @@ function getNextScheduleMinute(incrementMinutes: number): string { } }); - test(`Create cluster [${db} size ${size}]`, async ({ - page, - request, - }) => { + test(`Create cluster [${db} size ${size}]`, async ({ page, request }) => { expect(storageClasses.length).toBeGreaterThan(0); await page.goto('/databases/new'); diff --git a/ui/apps/everest/.e2e/utils/db-cmd-line.ts b/ui/apps/everest/.e2e/utils/db-cmd-line.ts index fad8a4c74..94906d7fc 100644 --- a/ui/apps/everest/.e2e/utils/db-cmd-line.ts +++ b/ui/apps/everest/.e2e/utils/db-cmd-line.ts @@ -206,7 +206,9 @@ export const insertMoreTestDB = async (cluster: string, namespace: string) => { 'db.t1.insertMany([{ a: 4 }, { a: 5 }, { a: 6 }]);' ); const result = await queryTestDB(cluster, namespace); - expect(result.trim()).toBe('[ { a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }, { a: 5 }, { a: 6 } ]'); + expect(result.trim()).toBe( + '[ { a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }, { a: 5 }, { a: 6 } ]' + ); break; } case 'postgresql': { @@ -223,7 +225,10 @@ export const insertMoreTestDB = async (cluster: string, namespace: string) => { } }; -export const pgInsertDummyTestDB = async (cluster: string, namespace: string) => { +export const pgInsertDummyTestDB = async ( + cluster: string, + namespace: string +) => { await queryPG( cluster, namespace,