From 5e6516ad830e84b0e773ceee6281e90bb357e8aa Mon Sep 17 00:00:00 2001 From: Angel Garbarino Date: Mon, 16 Jun 2025 13:21:12 -0600 Subject: [PATCH] Fix failing ent test (#30985) * fix transform submit issues * fix and clean up replication * left overs * build waiter for namespace capabilities check to show button, hoping it helps with test flaky issues --- ui/app/components/namespace-picker.ts | 7 + ui/app/components/transform-template-edit.hbs | 6 +- .../components/transform-create-form.hbs | 2 +- .../components/transform-edit-form.hbs | 2 +- .../components/transform-role-edit.hbs | 6 +- .../addon/templates/mode/secondaries/add.hbs | 6 +- .../templates/mode/secondaries/index.hbs | 2 +- .../templates/mode/secondaries/revoke.hbs | 8 +- .../acceptance/enterprise-replication-test.js | 184 ++++-------------- .../acceptance/enterprise-transform-test.js | 28 ++- ui/tests/helpers/replication.js | 35 ++++ .../pages/secrets/backend/transform/roles.js | 1 - .../secrets/backend/transform/templates.js | 19 -- .../backend/transform/transformations.js | 4 +- 14 files changed, 109 insertions(+), 201 deletions(-) delete mode 100644 ui/tests/pages/secrets/backend/transform/templates.js diff --git a/ui/app/components/namespace-picker.ts b/ui/app/components/namespace-picker.ts index 4e0a79e02f..5c8603a8fd 100644 --- a/ui/app/components/namespace-picker.ts +++ b/ui/app/components/namespace-picker.ts @@ -8,6 +8,8 @@ import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; import { service } from '@ember/service'; import keys from 'core/utils/keys'; +import { buildWaiter } from '@ember/test-waiters'; + import type Router from 'vault/router'; import type NamespaceService from 'vault/services/namespace'; import type AuthService from 'vault/vault/services/auth'; @@ -20,6 +22,8 @@ interface NamespaceOption { label: string; } +const waiter = buildWaiter('namespace-picker'); + /** * @module NamespacePicker * @description component is used to display a dropdown listing all namespaces that the current user has access to. @@ -176,6 +180,7 @@ export default class NamespacePicker extends Component { @action async fetchListCapability(): Promise { + const waiterToken = waiter.beginAsync(); try { const namespacePermission = await this.store.findRecord('capabilities', 'sys/namespaces/'); this.canRefreshNamespaces = namespacePermission.get('canList'); @@ -183,6 +188,8 @@ export default class NamespacePicker extends Component { } catch (error) { // If the findRecord call fails, the user lacks permissions to refresh or manage namespaces. this.canRefreshNamespaces = this.canManageNamespaces = false; + } finally { + waiter.endAsync(waiterToken); } } diff --git a/ui/app/components/transform-template-edit.hbs b/ui/app/components/transform-template-edit.hbs index b4735c6fbb..5b352486f7 100644 --- a/ui/app/components/transform-template-edit.hbs +++ b/ui/app/components/transform-template-edit.hbs @@ -70,11 +70,7 @@
- + {{#if (eq @mode "create")}}
- +
- +
- + {{#if (eq this.mode "create")}}

@@ -84,7 +84,7 @@ Path filter config {{/if}} {{#if this.model.canRevokeSecondary}} diff --git a/ui/lib/replication/addon/templates/mode/secondaries/revoke.hbs b/ui/lib/replication/addon/templates/mode/secondaries/revoke.hbs index 380e76854c..ea648f28c1 100644 --- a/ui/lib/replication/addon/templates/mode/secondaries/revoke.hbs +++ b/ui/lib/replication/addon/templates/mode/secondaries/revoke.hbs @@ -14,7 +14,13 @@ Secondary ID

- +

The secondary id to revoke; given initially to generate a secondary token. diff --git a/ui/tests/acceptance/enterprise-replication-test.js b/ui/tests/acceptance/enterprise-replication-test.js index 56df5e3723..52968cb72f 100644 --- a/ui/tests/acceptance/enterprise-replication-test.js +++ b/ui/tests/acceptance/enterprise-replication-test.js @@ -3,31 +3,14 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { clickTrigger } from 'ember-power-select/test-support/helpers'; -import { - click, - fillIn, - findAll, - currentURL, - find, - visit, - settled, - waitUntil, - waitFor, -} from '@ember/test-helpers'; +import { click, fillIn, findAll, currentURL, visit, settled, waitFor } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { login } from 'vault/tests/helpers/auth/auth-helpers'; import { pollCluster } from 'vault/tests/helpers/poll-cluster'; -import { create } from 'ember-cli-page-object'; -import flashMessage from 'vault/tests/pages/components/flash-message'; -import ss from 'vault/tests/pages/components/search-select'; -import { disableReplication } from 'vault/tests/helpers/replication'; +import { addSecondary, disableReplication, enableReplication } from 'vault/tests/helpers/replication'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; -const searchSelect = create(ss); -const flash = create(flashMessage); - module('Acceptance | Enterprise | replication', function (hooks) { setupApplicationTest(hooks); @@ -47,85 +30,48 @@ module('Acceptance | Enterprise | replication', function (hooks) { await settled(); }); - test('replication', async function (assert) { - assert.expect(18); - const secondaryName = 'firstSecondary'; - const mode = 'deny'; - - // confirm unable to visit dr secondary details page when both replications are disabled + test('it shows DR empty state when DR is not configured', async function (assert) { await visit('/vault/replication-dr-promote/details'); assert.dom('[data-test-component="empty-state"]').exists(); assert - .dom('[data-test-empty-state-title]') + .dom(GENERAL.emptyStateTitle) .includesText('Disaster Recovery secondary not set up', 'shows the correct title of the empty state'); assert - .dom('[data-test-empty-state-message]') + .dom(GENERAL.emptyStateMessage) .hasText( 'This cluster has not been enabled as a Disaster Recovery Secondary. You can do so by enabling replication and adding a secondary from the Disaster Recovery Primary.', 'renders default message specific to when no replication is enabled' ); + }); - await visit('/vault/replication'); - - assert.strictEqual(currentURL(), '/vault/replication'); - - // enable perf replication - await click('[data-test-replication-type-select="performance"]'); - - await fillIn('[data-test-replication-cluster-mode-select]', 'primary'); - - await click(GENERAL.submitButton); + test('Performance replication: add secondary and delete config', async function (assert) { + const secondaryName = `performanceSecondary`; + const mode = 'deny'; + await enableReplication('performance', 'primary'); await pollCluster(this.owner); - + await settled(); // confirm that the details dashboard shows - assert.ok(await waitUntil(() => find('[data-test-replication-dashboard]')), 'details dashboard is shown'); - - // add a secondary with a mount filter config - await click('[data-test-replication-link="secondaries"]'); - - await click('[data-test-secondary-add]'); - - await fillIn('[data-test-replication-secondary-id]', secondaryName); - - await click('#deny'); - await clickTrigger(); - await searchSelect.options.objectAt(0).click(); - const mountPath = find('[data-test-selected-option="0"]').innerText?.trim(); - await click('[data-test-secondary-add]'); - + await waitFor('[data-test-replication-dashboard]', 2000); + await addSecondary(secondaryName, mode); await pollCluster(this.owner); - // click into the added secondary's mount filter config + await settled(); await click('[data-test-replication-link="secondaries"]'); - await click(GENERAL.menuTrigger); - await click('[data-test-replication-path-filter-link]'); - assert.strictEqual( currentURL(), `/vault/replication/performance/secondaries/config/show/${secondaryName}` ); assert.dom('[data-test-mount-config-mode]').includesText(mode, 'show page renders the correct mode'); - assert - .dom('[data-test-mount-config-paths]') - .hasTextContaining(`${mountPath}`, 'show page renders the correct mount path'); - // delete config by choosing "no filter" in the edit screen + // delete config by choosing "no filter" on the edit screen await click('[data-test-replication-link="edit-mount-config"]'); - await click('#no-filtering'); - await click('[data-test-config-save]'); await settled(); // eslint-disable-line - - assert.strictEqual( - flash.latestMessage, - `The performance mount filter config for the secondary ${secondaryName} was successfully deleted.`, - 'renders success flash upon deletion' - ); assert.strictEqual( currentURL(), `/vault/replication/performance/secondaries`, @@ -137,36 +83,14 @@ module('Acceptance | Enterprise | replication', function (hooks) { assert .dom(`[data-test-secondaries-node=${secondaryName}]`) .exists('shows a table row the recently added secondary'); + }); - // nav to DR - await visit('/vault/replication/dr'); + test('DR replication: enable and add secondary', async function (assert) { + const secondaryName = 'drSecondary'; - await fillIn('[data-test-replication-cluster-mode-select]', 'secondary'); - assert - .dom('[data-test-replication-enable]') - .isDisabled('dr secondary enable is disabled when other replication modes are on'); - - // disable performance replication - await disableReplication('performance', assert); + await enableReplication('dr', 'primary'); + await pollCluster(this.owner); await settled(); - await pollCluster(this.owner); - - // enable dr replication - await visit('vault/replication/dr'); - - await fillIn('[data-test-replication-cluster-mode-select]', 'primary'); - await click('button[type="submit"]'); - - await pollCluster(this.owner); - await waitUntil(() => find('[data-test-empty-state-title]')); - // empty state inside of know secondaries table - assert - .dom('[data-test-empty-state-title]') - .includesText( - 'No known dr secondary clusters associated with this cluster', - 'shows the correct title of the empty state' - ); - assert .dom('[data-test-replication-title="Disaster Recovery"]') .includesText('Disaster Recovery', 'it displays the replication type correctly'); @@ -174,18 +98,11 @@ module('Acceptance | Enterprise | replication', function (hooks) { .dom('[data-test-replication-mode-display]') .includesText('primary', 'it displays the cluster mode correctly'); - // add dr secondary - await click('[data-test-replication-link="secondaries"]'); - - await click('[data-test-secondary-add]'); - - await fillIn('[data-test-replication-secondary-id]', secondaryName); - - await click('[data-test-secondary-add]'); - + await addSecondary(secondaryName); await pollCluster(this.owner); - await click('[data-test-replication-link="secondaries"]'); + await settled(); + await click('[data-test-replication-link="secondaries"]'); assert .dom('[data-test-secondary-name]') .includesText(secondaryName, 'it displays the secondary in the list of known secondaries'); @@ -237,52 +154,38 @@ module('Acceptance | Enterprise | replication', function (hooks) { test('add secondary and navigate through token generation modal', async function (assert) { const secondaryNameFirst = 'firstSecondary'; const secondaryNameSecond = 'secondSecondary'; - await visit('/vault/replication'); // enable perf replication - await click('[data-test-replication-type-select="performance"]'); - - await fillIn('[data-test-replication-cluster-mode-select]', 'primary'); - await click(GENERAL.submitButton); - + await enableReplication('performance', 'primary'); await pollCluster(this.owner); await settled(); - - // add a secondary with default TTL - await click('[data-test-replication-link="secondaries"]'); - - await click('[data-test-secondary-add]'); - - await fillIn('[data-test-replication-secondary-id]', secondaryNameFirst); - await click('[data-test-secondary-add]'); - + // confirm that the details dashboard shows + await addSecondary(secondaryNameFirst); await pollCluster(this.owner); await settled(); // checks on secondary token modal assert.dom('#replication-copy-token-modal').exists(); - assert.dom('[data-test-inline-error-message]').hasText('Copy token to dismiss modal'); - assert.dom('[data-test-row-value="TTL"]').hasText('1800s', 'shows the correct TTL of 1800s'); + assert.dom(GENERAL.inlineError).hasText('Copy token to dismiss modal'); + assert.dom(GENERAL.infoRowValue('TTL')).hasText('1800s', 'shows the correct TTL of 1800s'); // click off the modal to make sure you don't just have to click on the copy-close button to copy the token - assert.dom('[data-test-modal-close]').isDisabled('cancel is disabled'); - await click('[data-test-modal-copy]'); - assert.dom('[data-test-modal-close]').isEnabled('cancel is enabled after token is copied'); - await click('[data-test-modal-close]'); + assert.dom(GENERAL.cancelButton).isDisabled('cancel is disabled'); + await click(GENERAL.button('Copy token')); + assert.dom(GENERAL.cancelButton).isEnabled('cancel is enabled after token is copied'); + await click(GENERAL.cancelButton); // add another secondary not using the default ttl await click('[data-test-secondary-add]'); - - await fillIn('[data-test-replication-secondary-id]', secondaryNameSecond); + await fillIn('[data-test-input="Secondary ID"]', secondaryNameSecond); await click(GENERAL.toggleInput('Time to Live (TTL) for generated secondary token')); - await fillIn('[data-test-ttl-value]', 3); await click('[data-test-secondary-add]'); await pollCluster(this.owner); await settled(); - assert.dom('[data-test-row-value="TTL"]').hasText('180s', 'shows the correct TTL of 180s'); - await click('[data-test-modal-copy]'); - await click('[data-test-modal-close]'); + assert.dom(GENERAL.infoRowValue('TTL')).hasText('180s', 'shows the correct TTL of 180s'); + await click(GENERAL.button('Copy token')); + await click(GENERAL.cancelButton); // confirm you were redirected to the secondaries page assert.strictEqual( @@ -297,12 +200,7 @@ module('Acceptance | Enterprise | replication', function (hooks) { test('render performance and dr primary and navigate to details page', async function (assert) { // enable perf primary replication - await visit('/vault/replication'); - await click('[data-test-replication-type-select="performance"]'); - - await fillIn('[data-test-replication-cluster-mode-select]', 'primary'); - await click(GENERAL.submitButton); - + await enableReplication('performance', 'primary'); await pollCluster(this.owner); await settled(); @@ -342,13 +240,7 @@ module('Acceptance | Enterprise | replication', function (hooks) { test('render performance secondary and navigate to the details page', async function (assert) { // enable perf replication - await visit('/vault/replication'); - - await click('[data-test-replication-type-select="performance"]'); - - await fillIn('[data-test-replication-cluster-mode-select]', 'primary'); - await click(GENERAL.submitButton); - + await enableReplication('performance', 'primary'); await pollCluster(this.owner); await settled(); diff --git a/ui/tests/acceptance/enterprise-transform-test.js b/ui/tests/acceptance/enterprise-transform-test.js index 1dca7ff4da..ffc3a44219 100644 --- a/ui/tests/acceptance/enterprise-transform-test.js +++ b/ui/tests/acceptance/enterprise-transform-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; -import { currentURL, click, settled, currentRouteName, visit } from '@ember/test-helpers'; +import { click, currentRouteName, currentURL, fillIn, settled, visit } from '@ember/test-helpers'; import { create } from 'ember-cli-page-object'; import { selectChoose } from 'ember-power-select/test-support'; import { typeInSearch, clickTrigger } from 'ember-power-select/test-support/helpers'; @@ -14,7 +14,6 @@ import { login } from 'vault/tests/helpers/auth/auth-helpers'; import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend'; import transformationsPage from 'vault/tests/pages/secrets/backend/transform/transformations'; import rolesPage from 'vault/tests/pages/secrets/backend/transform/roles'; -import templatesPage from 'vault/tests/pages/secrets/backend/transform/templates'; import alphabetsPage from 'vault/tests/pages/secrets/backend/transform/alphabets'; import searchSelect from 'vault/tests/pages/components/search-select'; import { runCmd } from '../helpers/commands'; @@ -59,7 +58,7 @@ const newRole = async (backend, name) => { module('Acceptance | Enterprise | Transform secrets', function (hooks) { setupApplicationTest(hooks); - hooks.beforeEach(function () { + hooks.beforeEach(async function () { return login(); }); @@ -103,7 +102,9 @@ module('Acceptance | Enterprise | Transform secrets', function (hooks) { test('it can create a transformation and add itself to the role attached', async function (assert) { await visit('/vault/settings/mount-secret-backend'); const backend = `transform-${uuidv4()}`; - await mountBackend('transform', backend); + await click('[data-test-mount-type="transform"]'); + await fillIn(GENERAL.inputByAttr('path'), backend); + await click(GENERAL.submitButton); const transformationName = 'foo'; const roleName = 'foo-role'; await settled(); @@ -116,20 +117,19 @@ module('Acceptance | Enterprise | Transform secrets', function (hooks) { ); await transformationsPage.name(transformationName); await settled(); - assert.dom('[data-test-input="type"]').hasValue('fpe', 'Has type FPE by default'); - assert.dom('[data-test-input="tweak_source"]').exists('Shows tweak source when FPE'); + assert.dom(GENERAL.inputByAttr('type')).hasValue('fpe', 'Has type FPE by default'); + assert.dom(GENERAL.inputByAttr('tweak_source')).exists('Shows tweak source when FPE'); await transformationsPage.type('masking'); await settled(); assert - .dom('[data-test-input="masking_character"]') + .dom(GENERAL.inputByAttr('masking_character')) .exists('Shows masking character input when changed to masking type'); - assert.dom('[data-test-input="tweak_source"]').doesNotExist('Does not show tweak source when masking'); + assert.dom(GENERAL.inputByAttr('tweak_source')).doesNotExist('Does not show tweak source when masking'); await clickTrigger('#template'); await settled(); assert.strictEqual(searchSelectComponent.options.length, 2, 'list shows two builtin options by default'); await selectChoose('#template', '.ember-power-select-option', 0); await settled(); - await clickTrigger('#allowed_roles'); await settled(); await typeInSearch(roleName); @@ -228,8 +228,7 @@ module('Acceptance | Enterprise | Transform secrets', function (hooks) { await settled(); await click('#allowed_roles [data-test-selected-list-button="delete"]'); - await transformationsPage.save(); - await settled(); + await click(GENERAL.submitButton); assert.dom('.flash-message.is-info').exists('Shows info message since role could not be updated'); assert.strictEqual( currentURL(), @@ -260,8 +259,8 @@ module('Acceptance | Enterprise | Transform secrets', function (hooks) { `/vault/secrets/${backend}/create?itemType=template`, 'redirects to create template page' ); - await templatesPage.name(templateName); - await templatesPage.pattern(`(\\d{4})`); + await fillIn(GENERAL.inputByAttr('name'), templateName); + await fillIn(GENERAL.inputByAttr('pattern'), `(\\d{4})`); await clickTrigger('#alphabet'); await settled(); assert.ok(searchSelectComponent.options.length > 0, 'lists built-in alphabets'); @@ -273,8 +272,7 @@ module('Acceptance | Enterprise | Transform secrets', function (hooks) { `/vault/secrets/${backend}/show/template/${templateName}`, 'redirects to show template page after submit' ); - await templatesPage.editLink(); - await settled(); + await click('[data-test-edit-link]'); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/edit/template/${templateName}`, diff --git a/ui/tests/helpers/replication.js b/ui/tests/helpers/replication.js index ab8bd60b9d..4409585425 100644 --- a/ui/tests/helpers/replication.js +++ b/ui/tests/helpers/replication.js @@ -4,8 +4,43 @@ */ import { click, fillIn, findAll, currentURL, visit, settled, waitUntil } from '@ember/test-helpers'; +import ss from 'vault/tests/pages/components/search-select'; +import { create } from 'ember-cli-page-object'; +import { clickTrigger } from 'ember-power-select/test-support/helpers'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; +const searchSelect = create(ss); +/** + * Enables replication mode. + * @param {string} type - The replication type ('performance' or 'dr'). + * @param {string} mode - The cluster mode ('primary' or 'secondary'). + */ +export async function enableReplication(type, mode) { + await visit('/vault/replication'); + await click(`[data-test-replication-type-select="${type}"]`); + await fillIn('[data-test-replication-cluster-mode-select]', mode); + await click(GENERAL.submitButton); +} + +/** + * Adds a secondary cluster. + * @param {string} secondaryName - The name of the secondary cluster. + * @param {string} mountFilterMode - The mount filter mode ('deny' or 'allow'). + */ +export async function addSecondary(secondaryName, mountFilterMode = null) { + await click('[data-test-replication-link="secondaries"]'); + await click('[data-test-secondary-add]'); + await fillIn('[data-test-input="Secondary ID"]', secondaryName); + + if (mountFilterMode) { + await click(`#${mountFilterMode}`); + await clickTrigger(); + await searchSelect.options.objectAt(0).click(); + } + + await click('[data-test-secondary-add]'); +} + export const disableReplication = async (type, assert) => { // disable performance replication await visit(`/vault/replication/${type}`); diff --git a/ui/tests/pages/secrets/backend/transform/roles.js b/ui/tests/pages/secrets/backend/transform/roles.js index 3ec92f5685..1238efd5ac 100644 --- a/ui/tests/pages/secrets/backend/transform/roles.js +++ b/ui/tests/pages/secrets/backend/transform/roles.js @@ -13,6 +13,5 @@ export default create({ createLink: clickable('[data-test-secret-create]'), name: fillable('[data-test-input="name"]'), transformations: fillable('[data-test-input="transformations"'), - submit: clickable('[data-test-role-transform-create]'), modalConfirm: clickable('[data-test-edit-confirm-button]'), }); diff --git a/ui/tests/pages/secrets/backend/transform/templates.js b/ui/tests/pages/secrets/backend/transform/templates.js deleted file mode 100644 index 298b6c2883..0000000000 --- a/ui/tests/pages/secrets/backend/transform/templates.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { create, clickable, fillable, visitable } from 'ember-cli-page-object'; -import ListView from 'vault/tests/pages/components/list-view'; - -export default create({ - ...ListView, - visit: visitable('/vault/secrets/:backend/list?tab=templates'), - visitCreate: visitable('/vault/secrets/:backend/create?itemType=template'), - editLink: clickable('[data-test-edit-link]'), - name: fillable('[data-test-input="name"]'), - pattern: fillable('[data-test-input="pattern"'), - alphabet: fillable('[data-test-input="alphabet"'), - submit: clickable('[data-test-template-transform-create]'), - removeAlphabet: clickable('#alphabet [data-test-selected-list-button="delete"]'), -}); diff --git a/ui/tests/pages/secrets/backend/transform/transformations.js b/ui/tests/pages/secrets/backend/transform/transformations.js index 848ecc3d8b..111ab05a59 100644 --- a/ui/tests/pages/secrets/backend/transform/transformations.js +++ b/ui/tests/pages/secrets/backend/transform/transformations.js @@ -3,7 +3,7 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { create, clickable, fillable, visitable } from 'ember-cli-page-object'; +import { create, fillable, visitable } from 'ember-cli-page-object'; import ListView from 'vault/tests/pages/components/list-view'; export default create({ @@ -12,9 +12,7 @@ export default create({ visitShow: visitable('/vault/secrets/:backend/show/:id'), visitCreate: visitable('/vault/secrets/:backend/create'), name: fillable('[data-test-input="name"]'), - submit: clickable('[data-test-transform-create]'), type: fillable('[data-test-input="type"'), tweakSource: fillable('[data-test-input="tweak_source"'), maskingChar: fillable('[data-test-input="masking_character"'), - save: clickable('[data-test-transformation-save-button]'), });