diff --git a/ui/lib/kv/addon/components/page/secret/edit.js b/ui/lib/kv/addon/components/page/secret/edit.js
index c8888a5594..d56c20326b 100644
--- a/ui/lib/kv/addon/components/page/secret/edit.js
+++ b/ui/lib/kv/addon/components/page/secret/edit.js
@@ -26,15 +26,23 @@ import errorMessage from 'vault/utils/error-message';
* @param {array} breadcrumbs - breadcrumb objects to render in page header
*/
+/* eslint-disable no-undef */
export default class KvSecretEdit extends Component {
@service controlGroup;
@service flashMessages;
@service router;
@tracked showJsonView = false;
+ @tracked showDiff = false;
@tracked errorMessage;
@tracked modelValidations;
@tracked invalidFormAlert;
+ originalSecret;
+
+ constructor() {
+ super(...arguments);
+ this.originalSecret = JSON.stringify(this.args.secret.secretData || {});
+ }
get showOldVersionAlert() {
const { currentVersion, previousVersion } = this.args;
@@ -44,6 +52,22 @@ export default class KvSecretEdit extends Component {
return false;
}
+ get diffDelta() {
+ const oldData = JSON.parse(this.originalSecret);
+ const newData = this.args.secret.secretData;
+
+ const diffpatcher = jsondiffpatch.create({});
+ return diffpatcher.diff(oldData, newData);
+ }
+
+ get visualDiff() {
+ if (!this.showDiff) return null;
+ const newData = this.args.secret.secretData;
+ return this.diffDelta
+ ? jsondiffpatch.formatters.html.format(this.diffDelta, newData)
+ : JSON.stringify(newData, undefined, 2);
+ }
+
@action
toggleJsonView() {
this.showJsonView = !this.showJsonView;
diff --git a/ui/lib/kv/addon/components/page/secret/metadata/version-diff.hbs b/ui/lib/kv/addon/components/page/secret/metadata/version-diff.hbs
deleted file mode 100644
index 81a8dc3dd7..0000000000
--- a/ui/lib/kv/addon/components/page/secret/metadata/version-diff.hbs
+++ /dev/null
@@ -1,91 +0,0 @@
-
- <:toolbarFilters>
- {{! left side version selector }}
-
-
- Version
- {{this.leftSideVersion}}
-
-
-
-
-
-
- {{! right side version selector }}
-
-
- Version
- {{this.rightSideVersion}}
-
-
-
-
-
-
- {{! status icon }}
- {{#if this.statesMatch}}
-
-
- States match
-
- {{/if}}
-
-
-{{! Show an empty state if the current version of the secret is deleted or destroyed. This would only happen on init. }}
-{{#if (and (loose-equal this.leftSideVersion @metadata.currentVersion) @metadata.currentSecret.isDeactivated)}}
-
-{{else}}
-
-
{{sanitized-html this.visualDiff}}
-
-{{/if}}
\ No newline at end of file
diff --git a/ui/lib/kv/addon/components/page/secret/metadata/version-diff.js b/ui/lib/kv/addon/components/page/secret/metadata/version-diff.js
deleted file mode 100644
index b7784fb863..0000000000
--- a/ui/lib/kv/addon/components/page/secret/metadata/version-diff.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: MPL-2.0
- */
-
-import Component from '@glimmer/component';
-import { inject as service } from '@ember/service';
-import { tracked } from '@glimmer/tracking';
-import { action } from '@ember/object';
-import { kvDataPath } from 'vault/utils/kv-path';
-
-/**
- * @module KvVersionDiff
- * This component produces a JSON diff view between 2 secret versions. It uses the library jsondiffpatch.
- *
- * @param {string} backend - Backend from the kv/data model.
- * @param {string} path - Backend from the kv/data model.
- * @param {array} metadata - The kv/metadata model. It is version agnostic.
- * @param {array} breadcrumbs - Array to generate breadcrumbs, passed to the page header component.
- */
-
-export default class KvVersionDiffComponent extends Component {
- @service store;
- @tracked leftSideVersion;
- @tracked rightSideVersion;
- @tracked visualDiff;
- @tracked statesMatch = false;
-
- constructor() {
- super(...arguments);
- // tracked properties set here because they use args.
- this.leftSideVersion = this.args.metadata.currentVersion;
- this.rightSideVersion = this.defaultRightSideVersion;
- this.createVisualDiff();
- }
-
- get defaultRightSideVersion() {
- // unless the version is destroyed or deleted we return the version prior to the current version.
- const versionData = this.args.metadata.sortedVersions.find(
- (version) =>
- version.destroyed === false && version.deletion_time === '' && version.version != this.leftSideVersion
- );
- return versionData ? versionData.version : this.leftSideVersion - 1;
- }
-
- async fetchSecretData(version) {
- const { backend, path } = this.args;
- // check the store first, avoiding an extra network request if possible.
- const storeData = await this.store.peekRecord('kv/data', kvDataPath(backend, path, version));
- const data = storeData ? storeData : await this.store.queryRecord('kv/data', { backend, path, version });
-
- return data?.secretData;
- }
-
- async createVisualDiff() {
- /* eslint-disable no-undef */
- const leftSideData = await this.fetchSecretData(Number(this.leftSideVersion));
- const rightSideData = await this.fetchSecretData(Number(this.rightSideVersion));
- const diffpatcher = jsondiffpatch.create({});
- const delta = diffpatcher.diff(rightSideData, leftSideData);
-
- this.statesMatch = !delta;
- this.visualDiff = delta
- ? jsondiffpatch.formatters.html.format(delta, rightSideData)
- : JSON.stringify(leftSideData, undefined, 2);
- }
-
- @action selectVersion(version, actions, side) {
- if (side === 'right') {
- this.rightSideVersion = version;
- }
- if (side === 'left') {
- this.leftSideVersion = version;
- }
- // close dropdown menu.
- if (actions) actions.close();
- this.createVisualDiff();
- }
-}
diff --git a/ui/lib/kv/addon/routes.js b/ui/lib/kv/addon/routes.js
index 529ecbe8d5..efd759b17f 100644
--- a/ui/lib/kv/addon/routes.js
+++ b/ui/lib/kv/addon/routes.js
@@ -19,7 +19,6 @@ export default buildRoutes(function () {
this.route('metadata', function () {
this.route('edit');
this.route('versions');
- this.route('diff');
});
});
this.route('configuration');
diff --git a/ui/lib/kv/addon/routes/secret/metadata.js b/ui/lib/kv/addon/routes/secret/metadata.js
new file mode 100644
index 0000000000..36bd3f6c8c
--- /dev/null
+++ b/ui/lib/kv/addon/routes/secret/metadata.js
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Route from '@ember/routing/route';
+import { service } from '@ember/service';
+
+export default class KvSecretMetadataRoute extends Route {
+ @service store;
+ @service secretMountPath;
+
+ fetchMetadata(backend, path) {
+ return this.store.queryRecord('kv/metadata', { backend, path }).catch((error) => {
+ if (error.message === 'Control Group encountered') {
+ throw error;
+ }
+ return {};
+ });
+ }
+
+ async model() {
+ const backend = this.secretMountPath.currentPath;
+ const { name: path } = this.paramsFor('secret');
+ const parentModel = this.modelFor('secret');
+ if (!parentModel.metadata) {
+ // metadata read on the secret root fails silently
+ // if there's no metadata, try again in case it's a control group
+ const metadata = await this.fetchMetadata(backend, path);
+ return {
+ ...parentModel,
+ metadata,
+ };
+ }
+ return parentModel;
+ }
+}
diff --git a/ui/lib/kv/addon/routes/secret/metadata/diff.js b/ui/lib/kv/addon/routes/secret/metadata/diff.js
deleted file mode 100644
index ad94103ad4..0000000000
--- a/ui/lib/kv/addon/routes/secret/metadata/diff.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: MPL-2.0
- */
-
-import Route from '@ember/routing/route';
-import { breadcrumbsForSecret } from 'kv/utils/kv-breadcrumbs';
-
-export default class KvSecretMetadataDiffRoute extends Route {
- setupController(controller, resolvedModel) {
- super.setupController(controller, resolvedModel);
-
- const breadcrumbsArray = [
- { label: 'secrets', route: 'secrets', linkExternal: true },
- { label: resolvedModel.backend, route: 'list' },
- ...breadcrumbsForSecret(resolvedModel.path),
- { label: 'version diff' },
- ];
-
- controller.set('breadcrumbs', breadcrumbsArray);
- }
-}
diff --git a/ui/lib/kv/addon/templates/secret/metadata/diff.hbs b/ui/lib/kv/addon/templates/secret/metadata/diff.hbs
deleted file mode 100644
index 97b37f1261..0000000000
--- a/ui/lib/kv/addon/templates/secret/metadata/diff.hbs
+++ /dev/null
@@ -1,6 +0,0 @@
-
\ No newline at end of file
diff --git a/ui/tests/acceptance/secrets/backend/kv/diff-test.js b/ui/tests/acceptance/secrets/backend/kv/diff-test.js
deleted file mode 100644
index 8abb1c29a1..0000000000
--- a/ui/tests/acceptance/secrets/backend/kv/diff-test.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { click, settled, fillIn } from '@ember/test-helpers';
-import { create } from 'ember-cli-page-object';
-import { module, test } from 'qunit';
-import { setupApplicationTest } from 'ember-qunit';
-import editPage from 'vault/tests/pages/secrets/backend/kv/edit-secret';
-import listPage from 'vault/tests/pages/secrets/backend/list';
-import apiStub from 'vault/tests/helpers/noop-all-api-requests';
-import authPage from 'vault/tests/pages/auth';
-import consoleClass from 'vault/tests/pages/components/console/ui-panel';
-
-const consoleComponent = create(consoleClass);
-
-// TODO: kv engine cleanup replace with workflow-diff-test
-module('Acceptance | kv2 diff view', function (hooks) {
- setupApplicationTest(hooks);
-
- hooks.beforeEach(async function () {
- this.server = apiStub({ usePassthrough: true });
- return authPage.login();
- });
-
- hooks.afterEach(function () {
- this.server.shutdown();
- });
-
- test.skip('it shows correct diff status based on versions', async function (assert) {
- const secretPath = `my-secret`;
-
- await consoleComponent.runCommands([
- `write sys/mounts/secret type=kv options=version=2`,
- // delete any kv previously written here so that tests can be re-run
- `delete secret/metadata/${secretPath}`,
- 'write -field=client_token auth/token/create policies=kv-v2-degrade',
- ]);
-
- await listPage.visitRoot({ backend: 'secret' });
- await settled();
- await listPage.create();
- await settled();
- await editPage.createSecret(secretPath, 'version1', 'hello');
- await settled();
- await click('[data-test-popup-menu-trigger="version"]');
-
- assert.dom('[data-test-view-diff]').doesNotExist('does not show diff view with only one version');
- // add another version
- await click('[data-test-secret-edit="true"]');
-
- const secondKey = document.querySelectorAll('[data-test-secret-key]')[1];
- const secondValue = document.querySelectorAll('.masked-value')[1];
- await fillIn(secondKey, 'version2');
- await fillIn(secondValue, 'world!');
- await click('[data-test-secret-save]');
-
- await click('[data-test-popup-menu-trigger="version"]');
-
- assert.dom('[data-test-view-diff]').exists('does show diff view with two versions');
-
- await click('[data-test-view-diff]');
-
- const diffBetweenVersion2and1 = document.querySelector('.jsondiffpatch-added').innerText;
- assert.strictEqual(diffBetweenVersion2and1, 'version2"world!"', 'shows the correct added part');
-
- await click('[data-test-popup-menu-trigger="right-version"]');
-
- await click('[data-test-rightSide-version="2"]');
-
- assert.dom('.diff-status').exists('shows States Match');
- });
-});
diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-create-new-version-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-create-new-version-test.js
deleted file mode 100644
index 8c64718d6c..0000000000
--- a/ui/tests/acceptance/secrets/backend/kv/kv-create-new-version-test.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import { module, test } from 'qunit';
-import { setupApplicationTest } from 'ember-qunit';
-import { v4 as uuidv4 } from 'uuid';
-import { click, currentURL, fillIn, visit } from '@ember/test-helpers';
-import authPage from 'vault/tests/pages/auth';
-import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
-import { FORM, PAGE } from 'vault/tests/helpers/kv/kv-selectors';
-import { deleteEngineCmd, runCmd } from 'vault/tests/helpers/commands';
-
-module('Acceptance | kv | creates a secret and a new version', function (hooks) {
- setupApplicationTest(hooks);
- hooks.beforeEach(async function () {
- await authPage.login();
- // Setup KV engine
- this.mountPath = `kv-engine-${uuidv4()}`;
- await enablePage.enable('kv', this.mountPath);
- });
-
- hooks.afterEach(async function () {
- await authPage.login();
- // Cleanup engine
- await runCmd(deleteEngineCmd(this.mountPath), false);
- });
-
- test('it creates a new secret then a new secret version and navigates to details route', async function (assert) {
- assert.expect(9);
-
- const secretPath = 'my-secret';
- await visit(`/vault/secrets/${this.mountPath}/kv/list`);
- assert.dom(PAGE.emptyStateTitle).hasText('No secrets yet');
- assert
- .dom(`${PAGE.emptyStateActions} a`)
- .hasAttribute('href', `/ui/vault/secrets/${this.mountPath}/kv/create`);
- await click(PAGE.list.createSecret);
- assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/kv/create`, 'url is correct');
-
- await fillIn(FORM.inputByAttr('path'), secretPath);
- await fillIn(FORM.keyInput(), 'foo-1');
- await fillIn(FORM.maskedValueInput(), 'bar-1');
- await click(FORM.saveBtn);
- assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/kv/${secretPath}/details?version=1`);
-
- await click(PAGE.detail.createNewVersion);
- assert.strictEqual(
- currentURL(),
- `/vault/secrets/${this.mountPath}/kv/${secretPath}/details/edit?version=1`
- );
- assert.dom(FORM.inputByAttr('path')).isDisabled();
-
- await fillIn(FORM.keyInput(1), 'foo-2');
- await fillIn(FORM.maskedValueInput(1), 'bar-2');
- await click(FORM.saveBtn);
- assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/kv/${secretPath}/details?version=2`);
-
- await visit(`/vault/secrets/${this.mountPath}/kv/list`);
- await click(PAGE.list.item(secretPath));
- assert.strictEqual(
- currentURL(),
- `/vault/secrets/${this.mountPath}/kv/${secretPath}/details?version=2`,
- 'list view navigates to latest version'
- );
- assert.dom(PAGE.detail.versionTimestamp).hasTextContaining('Version 2');
- });
-});
diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-engine-permissions-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-engine-permissions-test.js
deleted file mode 100644
index 7eeea27ac3..0000000000
--- a/ui/tests/acceptance/secrets/backend/kv/kv-engine-permissions-test.js
+++ /dev/null
@@ -1,110 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: MPL-2.0
- */
-
-import { module, test } from 'qunit';
-import { setupApplicationTest } from 'ember-qunit';
-import { v4 as uuidv4 } from 'uuid';
-
-import authPage from 'vault/tests/pages/auth';
-import { currentURL, visit } from '@ember/test-helpers';
-import { adminPolicy, dataPolicy, metadataPolicy } from 'vault/tests/helpers/policy-generator/kv';
-import { deleteEngineCmd, mountEngineCmd, runCmd, tokenWithPolicyCmd } from 'vault/tests/helpers/commands';
-import { writeSecret } from 'vault/tests/helpers/kv/kv-run-commands';
-import { PAGE } from 'vault/tests/helpers/kv/kv-selectors';
-
-/*
-This test module tests KV permissions views, each module is is a separate tab (i.e. secret, metadata)
-each sub-module is a different state, for example:
-- it renders secret details
-- it renders secret details after a version is deleted
-
-And each test authenticates using varying permissions testing that view state renders as expected.
-*/
-// TODO: replace with workflow-* tests
-module('Acceptance | kv permissions', function (hooks) {
- setupApplicationTest(hooks);
-
- hooks.beforeEach(async function () {
- await authPage.login();
- this.uid = uuidv4();
- // Setup KV engine
- this.mountPath = `kv-engine-${this.uid}`;
- await runCmd(mountEngineCmd('kv-v2', this.mountPath));
- return authPage.logout();
- });
-
- hooks.afterEach(async function () {
- await authPage.login();
- // Cleanup engine
- await runCmd(deleteEngineCmd(this.mountPath));
- });
-
- module('secret tab', function (hooks) {
- hooks.beforeEach(async function () {
- // Create secret
- await authPage.login();
- this.secretPath = `my-secret-${this.uid}`;
- await writeSecret(this.mountPath, this.secretPath, 'foo', 'bar');
- // Create different policy test cases
- const kv_admin_policy = adminPolicy(this.mountPath);
- this.kvAdminToken = await runCmd(tokenWithPolicyCmd('kv-admin', kv_admin_policy));
-
- const no_metadata_read =
- dataPolicy({ backend: this.mountPath, secretPath: this.secretPath }) +
- metadataPolicy({ backend: this.mountPath, capabilities: ['list'] });
- this.cannotReadMetadata = await runCmd(tokenWithPolicyCmd('kv-no-metadata-read', no_metadata_read));
-
- const no_data_read = dataPolicy({
- backend: this.mountPath,
- secretPath: this.secretPath,
- capabilities: ['list'],
- });
- this.cannotReadData = await runCmd(tokenWithPolicyCmd('kv-no-metadata-read', no_data_read));
- await authPage.logout();
- });
-
- module('it renders secret details page', function () {
- test('it shows all tabs for admin policy', async function (assert) {
- assert.expect(4);
- await authPage.login(this.kvAdminToken);
- await visit(`/vault/secrets/${this.mountPath}/kv/${this.secretPath}/details`);
- assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/kv/${this.secretPath}/details`);
- assert.dom(PAGE.secretTab('Secret')).exists();
- assert.dom(PAGE.secretTab('Metadata')).exists();
- assert.dom(PAGE.secretTab('Version History')).exists();
- });
-
- test('it hides tabs when no metadata read', async function (assert) {
- assert.expect(4);
- await authPage.login(this.cannotReadMetadata);
- await visit(`/vault/secrets/${this.mountPath}/kv/${this.secretPath}/details`);
- assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/kv/${this.secretPath}/details`);
- assert.dom(PAGE.secretTab('Secret')).exists();
- assert.dom(PAGE.secretTab('Metadata')).exists();
- assert.dom(PAGE.secretTab('Version History')).doesNotExist();
- });
-
- test('it shows empty state when cannot read secret data', async function (assert) {
- assert.expect(6);
- await authPage.login(this.cannotReadData);
- await visit(`/vault/secrets/${this.mountPath}/kv/${this.secretPath}/details`);
- assert.strictEqual(currentURL(), `/vault/secrets/${this.mountPath}/kv/${this.secretPath}/details`);
- assert.dom(PAGE.secretTab('Secret')).exists();
- assert.dom(PAGE.secretTab('Metadata')).exists();
- assert.dom(PAGE.secretTab('Version History')).doesNotExist();
- assert.dom(PAGE.emptyStateTitle).hasText('You do not have permission to read this secret');
- assert
- .dom(PAGE.emptyStateMessage)
- .hasText(
- 'Your policies may permit you to write a new version of this secret, but do not allow you to read its current contents.'
- );
- });
- });
-
- module('it renders secret details page after deleting a version', function () {
- // TODO delete secret and test different policy views
- });
- });
-});
diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-create-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-create-test.js
index f2ad2e8f43..6b49b53941 100644
--- a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-create-test.js
+++ b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-create-test.js
@@ -100,7 +100,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
'Goes to details page after save'
);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 1 created');
- assert.dom(PAGE.secretRow).exists({ count: 1 }, '1 row of data shows');
+ assert.dom(PAGE.infoRow).exists({ count: 1 }, '1 row of data shows');
assert.dom(PAGE.infoRowValue('api_key')).hasText('***********');
await click(PAGE.infoRowToggleMasked('api_key'));
assert.dom(PAGE.infoRowValue('api_key')).hasText('partyparty', 'secret value shows after toggle');
@@ -111,7 +111,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
.dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText('No custom metadata', 'No custom metadata empty state');
assert
- .dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.secretRow}`)
+ .dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.infoRow}`)
.exists({ count: 4 }, '4 metadata rows show');
assert.dom(PAGE.infoRowValue('Maximum versions')).hasText('0', 'max versions shows 0');
assert.dom(PAGE.infoRowValue('Check-and-Set required')).hasText('No', 'cas not enforced');
@@ -137,7 +137,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
`/vault/secrets/${backend}/kv/${encodeURIComponent(secretPath)}/details?version=2`
);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 2 created');
- assert.dom(PAGE.secretRow).exists({ count: 2 }, '2 rows of data shows');
+ assert.dom(PAGE.infoRow).exists({ count: 2 }, '2 rows of data shows');
assert.dom(PAGE.infoRowValue('api_key')).hasText('***********');
assert.dom(PAGE.infoRowValue('api_url')).hasText('***********');
await click(PAGE.infoRowToggleMasked('api_key'));
@@ -190,7 +190,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
`/vault/secrets/${backend}/kv/${encodeURIComponent('my/secret')}/details?version=1`
);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 1 created');
- assert.dom(PAGE.secretRow).exists({ count: 1 }, '1 row of data shows');
+ assert.dom(PAGE.infoRow).exists({ count: 1 }, '1 row of data shows');
assert.dom(PAGE.infoRowValue('password')).hasText('***********');
await click(PAGE.infoRowToggleMasked('password'));
assert.dom(PAGE.infoRowValue('password')).hasText('kittens1234', 'secret value shows after toggle');
@@ -198,12 +198,12 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
// Metadata
await click(PAGE.secretTab('Metadata'));
assert
- .dom(`${PAGE.metadata.customMetadataSection} ${PAGE.secretRow}`)
+ .dom(`${PAGE.metadata.customMetadataSection} ${PAGE.infoRow}`)
.exists({ count: 1 }, 'One custom metadata row shows');
assert.dom(`${PAGE.metadata.customMetadataSection} ${PAGE.infoRowValue('team')}`).hasText('UI');
assert
- .dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.secretRow}`)
+ .dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.infoRow}`)
.exists({ count: 4 }, '4 metadata rows show');
assert.dom(PAGE.infoRowValue('Maximum versions')).hasText('7', 'max versions shows 0');
assert.dom(PAGE.infoRowValue('Check-and-Set required')).hasText('Yes', 'cas enforced');
@@ -328,7 +328,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
// Since this persona can't create a new secret, test update with existing:
await visit(`/vault/secrets/${backend}/kv/app%2Ffirst/details`);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 2 created');
- assert.dom(PAGE.secretRow).exists({ count: 1 }, '1 row of data shows');
+ assert.dom(PAGE.infoRow).exists({ count: 1 }, '1 row of data shows');
assert.dom(PAGE.infoRowValue('foo')).hasText('***********');
await click(PAGE.infoRowToggleMasked('foo'));
assert.dom(PAGE.infoRowValue('foo')).hasText('bar', 'secret value shows after toggle');
@@ -473,7 +473,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
// Since this persona can't create a new secret, test update with existing:
await visit(`/vault/secrets/${backend}/kv/app%2Ffirst/details`);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 2 created');
- assert.dom(PAGE.secretRow).exists({ count: 1 }, '1 row of data shows');
+ assert.dom(PAGE.infoRow).exists({ count: 1 }, '1 row of data shows');
assert.dom(PAGE.infoRowValue('foo')).hasText('***********');
await click(PAGE.infoRowToggleMasked('foo'));
assert.dom(PAGE.infoRowValue('foo')).hasText('bar', 'secret value shows after toggle');
@@ -620,7 +620,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
// Since this persona can't create a new secret, test update with existing:
await visit(`/vault/secrets/${backend}/kv/app%2Ffirst/details`);
assert.dom(PAGE.detail.versionTimestamp).doesNotExist('Version created tooltip does not show');
- assert.dom(PAGE.secretRow).doesNotExist('secret data not shown');
+ assert.dom(PAGE.infoRow).doesNotExist('secret data not shown');
assert.dom(PAGE.emptyStateTitle).hasText('You do not have permission to read this secret');
// Metadata page
@@ -629,7 +629,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
.dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText('No custom metadata', 'No custom metadata empty state');
assert
- .dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.secretRow}`)
+ .dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.infoRow}`)
.exists({ count: 4 }, '4 metadata rows show');
assert.dom(PAGE.infoRowValue('Maximum versions')).hasText('0', 'max versions shows 0');
assert.dom(PAGE.infoRowValue('Check-and-Set required')).hasText('No', 'cas not enforced');
@@ -835,7 +835,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
'Goes to details page after save'
);
assert.dom(PAGE.detail.versionTimestamp).doesNotExist('Version created not shown');
- assert.dom(PAGE.secretRow).doesNotExist('does not show data contents');
+ assert.dom(PAGE.infoRow).doesNotExist('does not show data contents');
assert
.dom(PAGE.emptyStateTitle)
.hasText('You do not have permission to read this secret', 'shows permissions empty state');
@@ -871,7 +871,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
'goes back to details page'
);
assert.dom(PAGE.detail.versionTimestamp).doesNotExist('Version created does not show');
- assert.dom(PAGE.secretRow).doesNotExist('does not show data contents');
+ assert.dom(PAGE.infoRow).doesNotExist('does not show data contents');
assert
.dom(PAGE.emptyStateTitle)
.hasText('You do not have permission to read this secret', 'shows permissions empty state');
@@ -922,7 +922,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
'goes back to details page'
);
assert.dom(PAGE.detail.versionTimestamp).doesNotExist('version created not shown');
- assert.dom(PAGE.secretRow).doesNotExist('does not show data contents');
+ assert.dom(PAGE.infoRow).doesNotExist('does not show data contents');
assert
.dom(PAGE.emptyStateTitle)
.hasText('You do not have permission to read this secret', 'shows permissions empty state');
@@ -991,7 +991,7 @@ module('Acceptance | kv-v2 workflow | secret and version create', function (hook
`/vault/secrets/${backend}/kv/app%2Ffirst/details`,
'redirects to details page'
);
- assert.dom(PAGE.secretRow).doesNotExist('does not show data contents');
+ assert.dom(PAGE.infoRow).doesNotExist('does not show data contents');
assert
.dom(PAGE.emptyStateTitle)
.hasText('You do not have permission to read this secret', 'shows permissions empty state');
@@ -1084,7 +1084,7 @@ path "${this.backend}/metadata/*" {
'Goes to details page after save'
);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 1 created');
- assert.dom(PAGE.secretRow).exists({ count: 1 }, '1 row of data shows');
+ assert.dom(PAGE.infoRow).exists({ count: 1 }, '1 row of data shows');
assert.dom(PAGE.infoRowValue('api_key')).hasText('***********');
await click(PAGE.infoRowToggleMasked('api_key'));
assert.dom(PAGE.infoRowValue('api_key')).hasText('partyparty', 'secret value shows after toggle');
@@ -1095,7 +1095,7 @@ path "${this.backend}/metadata/*" {
.dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`)
.hasText('No custom metadata', 'No custom metadata empty state');
assert
- .dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.secretRow}`)
+ .dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.infoRow}`)
.exists({ count: 4 }, '4 metadata rows show');
assert.dom(PAGE.infoRowValue('Maximum versions')).hasText('0', 'max versions shows 0');
assert.dom(PAGE.infoRowValue('Check-and-Set required')).hasText('No', 'cas not enforced');
@@ -1154,7 +1154,7 @@ path "${this.backend}/metadata/*" {
`/vault/secrets/${backend}/kv/${encodeURIComponent(secretPath)}/details?version=2`
);
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 2 created');
- assert.dom(PAGE.secretRow).exists({ count: 2 }, '2 rows of data shows');
+ assert.dom(PAGE.infoRow).exists({ count: 2 }, '2 rows of data shows');
assert.dom(PAGE.infoRowValue('api_key')).hasText('***********');
assert.dom(PAGE.infoRowValue('api_url')).hasText('***********');
await click(PAGE.infoRowToggleMasked('api_key'));
diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-delete-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-delete-test.js
index 5ee834a971..480d2110bd 100644
--- a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-delete-test.js
+++ b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-delete-test.js
@@ -6,6 +6,7 @@ import { deleteEngineCmd, mountEngineCmd, runCmd, tokenWithPolicyCmd } from 'vau
import { personas } from 'vault/tests/helpers/policy-generator/kv';
import {
clearRecords,
+ deleteLatestCmd,
setupControlGroup,
writeVersionedSecret,
} from 'vault/tests/helpers/kv/kv-run-commands';
@@ -39,7 +40,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook
await writeVersionedSecret(this.backend, this.secretPath, 'foo', 'bar', 4);
await writeVersionedSecret(this.backend, 'nuke', 'foo', 'bar', 2);
// Delete latest version for testing undelete for users that can't delete
- await runCmd(`delete ${this.backend}/data/nuke`);
+ await runCmd(deleteLatestCmd(this.backend, 'nuke'));
return;
});
@@ -61,7 +62,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook
await visit(`/vault/secrets/${this.backend}/kv/${this.secretPath}/details`);
// correct toolbar options & details show
assertDeleteActions(assert);
- assert.dom(PAGE.secretRow).exists('shows secret data');
+ assert.dom(PAGE.infoRow).exists('shows secret data');
// delete flow
await click(PAGE.detail.delete);
assert.dom(PAGE.detail.deleteModalTitle).includesText('Delete version?', 'shows correct modal title');
@@ -79,7 +80,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook
// undelete flow
await click(PAGE.detail.undelete);
// details update accordingly
- assert.dom(PAGE.secretRow).exists('shows secret data');
+ assert.dom(PAGE.infoRow).exists('shows secret data');
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 4 created');
// correct toolbar options
assertDeleteActions(assert, ['delete', 'destroy']);
@@ -90,7 +91,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook
await visit(`/vault/secrets/${this.backend}/kv/${this.secretPath}/details?version=2`);
// correct toolbar options & details show
assertDeleteActions(assert);
- assert.dom(PAGE.secretRow).exists('shows secret data');
+ assert.dom(PAGE.infoRow).exists('shows secret data');
// delete flow
await click(PAGE.detail.delete);
assert.dom(PAGE.detail.deleteModalTitle).includesText('Delete version?', 'shows correct modal title');
@@ -108,7 +109,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook
// undelete flow
await click(PAGE.detail.undelete);
// details update accordingly
- assert.dom(PAGE.secretRow).exists('shows secret data');
+ assert.dom(PAGE.infoRow).exists('shows secret data');
assert.dom(PAGE.detail.versionTimestamp).includesText('Version 2 created');
// correct toolbar options
assertDeleteActions(assert, ['delete', 'destroy']);
@@ -160,7 +161,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook
await visit(`/vault/secrets/${this.backend}/kv/${this.secretPath}/details`);
// correct toolbar options & details show
assertDeleteActions(assert, []);
- assert.dom(PAGE.secretRow).exists('shows secret data');
+ assert.dom(PAGE.infoRow).exists('shows secret data');
// data-reader can't delete, so check undelete with already-deleted version
await visit(`/vault/secrets/${this.backend}/kv/nuke/details`);
@@ -176,7 +177,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook
await visit(`/vault/secrets/${this.backend}/kv/${this.secretPath}/details?version=2`);
// correct toolbar options & details show
assertDeleteActions(assert, []);
- assert.dom(PAGE.secretRow).exists('shows secret data');
+ assert.dom(PAGE.infoRow).exists('shows secret data');
});
test('cannot destroy a secret version (dr)', async function (assert) {
assert.expect(3);
@@ -209,7 +210,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook
await visit(`/vault/secrets/${this.backend}/kv/${this.secretPath}/details`);
// correct toolbar options & details show
assertDeleteActions(assert, ['delete']);
- assert.dom(PAGE.secretRow).exists('shows secret data');
+ assert.dom(PAGE.infoRow).exists('shows secret data');
// delete flow
await click(PAGE.detail.delete);
assert.dom(PAGE.detail.deleteModalTitle).includesText('Delete version?', 'shows correct modal title');
@@ -232,7 +233,7 @@ module('Acceptance | kv-v2 workflow | delete, undelete, destroy', function (hook
await visit(`/vault/secrets/${this.backend}/kv/${this.secretPath}/details?version=2`);
// correct toolbar options & details show
assertDeleteActions(assert, ['delete']);
- assert.dom(PAGE.secretRow).exists('shows secret data');
+ assert.dom(PAGE.infoRow).exists('shows secret data');
// delete flow
await click(PAGE.detail.delete);
assert.dom(PAGE.detail.deleteModalTitle).includesText('Delete version?', 'shows correct modal title');
diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-navigation-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-navigation-test.js
index 8fc66ea461..4f6ed7f15a 100644
--- a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-navigation-test.js
+++ b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-navigation-test.js
@@ -35,6 +35,21 @@ const assertCorrectBreadcrumbs = (assert, expected) => {
assert.dom(breadcrumbs[idx]).includesText(text, `position ${idx} breadcrumb includes text ${text}`);
});
};
+const assertDetailTabs = (assert, current, hidden = []) => {
+ const allTabs = ['Secret', 'Metadata', 'Paths', 'Version History'];
+ allTabs.forEach((tab) => {
+ if (hidden.includes(tab)) {
+ assert.dom(PAGE.secretTab(tab)).doesNotExist(`${tab} tab does not render`);
+ return;
+ }
+ assert.dom(PAGE.secretTab(tab)).hasText(tab);
+ if (current === tab) {
+ assert.dom(PAGE.secretTab(tab)).hasClass('active');
+ } else {
+ assert.dom(PAGE.secretTab(tab)).doesNotHaveClass('active');
+ }
+ });
+};
const DETAIL_TOOLBARS = ['delete', 'destroy', 'copy', 'versionDropdown', 'createNewVersion'];
const assertDetailsToolbar = (assert, expected = DETAIL_TOOLBARS) => {
assert
@@ -188,7 +203,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
assert.ok(currentURL().startsWith(`/vault/secrets/${backend}/kv/list`), 'links back to list root');
});
test('versioned secret nav, tabs, breadcrumbs (a)', async function (assert) {
- assert.expect(43);
+ assert.expect(45);
const backend = this.backend;
await navToBackend(backend);
await click(PAGE.list.item(secretPath));
@@ -197,13 +212,8 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
`/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details?version=3`,
'Url includes version query param'
);
- assert.dom(PAGE.title).hasText(secretPath, 'Goes to secret detail view');
- assert.dom(PAGE.secretTab('Secret')).hasText('Secret');
- assert.dom(PAGE.secretTab('Secret')).hasClass('active');
- assert.dom(PAGE.secretTab('Metadata')).hasText('Metadata');
- assert.dom(PAGE.secretTab('Metadata')).doesNotHaveClass('active');
- assert.dom(PAGE.secretTab('Version History')).hasText('Version History');
- assert.dom(PAGE.secretTab('Version History')).doesNotHaveClass('active');
+ assert.dom(PAGE.title).hasText(secretPath, 'title is correct on detail view');
+ assertDetailTabs(assert, 'Secret');
assert.dom(PAGE.detail.versionDropdown).hasText('Version 3', 'Version dropdown shows current version');
assert.dom(PAGE.detail.createNewVersion).hasText('Create new version', 'Create version button shows');
assert.dom(PAGE.detail.versionTimestamp).containsText('Version 3 created');
@@ -279,7 +289,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
);
});
test('breadcrumbs & page titles are correct (a)', async function (assert) {
- assert.expect(39);
+ assert.expect(45);
const backend = this.backend;
await navToBackend(backend);
await click(PAGE.secretTab('Configuration'));
@@ -308,6 +318,10 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
assert.dom(PAGE.title).hasText('Edit Secret Metadata', 'correct page title for metadata edit');
await click(PAGE.breadcrumbAtIdx(3));
+ await click(PAGE.secretTab('Paths'));
+ assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'paths']);
+ assert.dom(PAGE.title).hasText(secretPath, 'correct page title for paths');
+
await click(PAGE.secretTab('Version History'));
assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'version history']);
assert.dom(PAGE.title).hasText(secretPath, 'correct page title for version history');
@@ -409,7 +423,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
assert.ok(currentURL().startsWith(`/vault/secrets/${backend}/kv/list`), 'links back to list root');
});
test('versioned secret nav, tabs, breadcrumbs (dr)', async function (assert) {
- assert.expect(25);
+ assert.expect(28);
const backend = this.backend;
await navToBackend(backend);
@@ -423,10 +437,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
'Url includes version query param'
);
assert.dom(PAGE.title).hasText(secretPath, 'Goes to secret detail view');
- assert.dom(PAGE.secretTab('Secret')).hasText('Secret');
- assert.dom(PAGE.secretTab('Secret')).hasClass('active');
- assert.dom(PAGE.secretTab('Metadata')).hasText('Metadata');
- assert.dom(PAGE.secretTab('Metadata')).doesNotHaveClass('active');
+ assertDetailTabs(assert, 'Secret', ['Version History']);
assert.dom(PAGE.detail.versionDropdown).doesNotExist('Version dropdown hidden');
assert.dom(PAGE.detail.createNewVersion).doesNotExist('unable to create a new version');
assert.dom(PAGE.detail.versionTimestamp).containsText('Version 3 created');
@@ -458,7 +469,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
assert.dom(PAGE.metadata.editBtn).doesNotExist('edit button hidden');
});
test('breadcrumbs & page titles are correct (dr)', async function (assert) {
- assert.expect(27);
+ assert.expect(35);
const backend = this.backend;
await navToBackend(backend);
await click(PAGE.secretTab('Configuration'));
@@ -482,7 +493,10 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
assert.dom(PAGE.metadata.editBtn).doesNotExist('cannot edit metadata');
- await click(PAGE.breadcrumbAtIdx(2));
+ await click(PAGE.secretTab('Paths'));
+ assertCorrectBreadcrumbs(assert, ['secrets', backend, 'app', 'nested', 'secret', 'paths']);
+ assert.dom(PAGE.title).hasText('app/nested/secret', 'correct page title for paths');
+
assert.dom(PAGE.secretTab('Version History')).doesNotExist('Version History tab not shown');
});
});
@@ -599,7 +613,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
assert.ok(currentURL().startsWith(`/vault/secrets/${backend}/kv/list`), 'links back to list root');
});
test('versioned secret nav, tabs, breadcrumbs (dlr)', async function (assert) {
- assert.expect(25);
+ assert.expect(28);
const backend = this.backend;
await navToBackend(backend);
await click(PAGE.list.item(secretPath));
@@ -609,10 +623,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
'Url includes version query param'
);
assert.dom(PAGE.title).hasText(secretPath, 'Goes to secret detail view');
- assert.dom(PAGE.secretTab('Secret')).hasText('Secret');
- assert.dom(PAGE.secretTab('Secret')).hasClass('active');
- assert.dom(PAGE.secretTab('Metadata')).hasText('Metadata');
- assert.dom(PAGE.secretTab('Metadata')).doesNotHaveClass('active');
+ assertDetailTabs(assert, 'Secret', ['Version History']);
assert.dom(PAGE.detail.versionDropdown).doesNotExist('does not show version dropdown');
assert.dom(PAGE.detail.createNewVersion).doesNotExist('unable to create a new version');
assert.dom(PAGE.detail.versionTimestamp).containsText('Version 3 created');
@@ -645,7 +656,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
assert.dom(PAGE.metadata.editBtn).doesNotExist('edit button hidden');
});
test('breadcrumbs & page titles are correct (dlr)', async function (assert) {
- assert.expect(23);
+ assert.expect(29);
const backend = this.backend;
await navToBackend(backend);
@@ -669,7 +680,10 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
assert.dom(PAGE.metadata.editBtn).doesNotExist('cannot edit metadata');
- await click(PAGE.breadcrumbAtIdx(2));
+ await click(PAGE.secretTab('Paths'));
+ assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'paths']);
+ assert.dom(PAGE.title).hasText(secretPath, 'correct page title for paths');
+
assert.dom(PAGE.secretTab('Version History')).doesNotExist('Version History tab not shown');
});
});
@@ -790,7 +804,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
assert.ok(currentURL().startsWith(`/vault/secrets/${backend}/kv/list`), 'links back to list root');
});
test('versioned secret nav, tabs, breadcrumbs (mm)', async function (assert) {
- assert.expect(35);
+ assert.expect(37);
const backend = this.backend;
await navToBackend(backend);
await click(PAGE.list.item(secretPath));
@@ -801,12 +815,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
'Url includes version query param'
);
assert.dom(PAGE.title).hasText(secretPath, 'Goes to secret detail view');
- assert.dom(PAGE.secretTab('Secret')).hasText('Secret');
- assert.dom(PAGE.secretTab('Secret')).hasClass('active');
- assert.dom(PAGE.secretTab('Metadata')).hasText('Metadata');
- assert.dom(PAGE.secretTab('Metadata')).doesNotHaveClass('active');
- assert.dom(PAGE.secretTab('Version History')).hasText('Version History');
- assert.dom(PAGE.secretTab('Version History')).doesNotHaveClass('active');
+ assertDetailTabs(assert, 'Secret');
assert.dom(PAGE.detail.versionDropdown).hasText('Version 3', 'Version dropdown shows current version');
assert.dom(PAGE.detail.createNewVersion).doesNotExist('Create new version button not shown');
assert.dom(PAGE.detail.versionTimestamp).doesNotExist('Version created text not shown');
@@ -863,7 +872,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
);
});
test('breadcrumbs & page titles are correct (mm)', async function (assert) {
- assert.expect(33);
+ assert.expect(39);
const backend = this.backend;
await navToBackend(backend);
await click(PAGE.secretTab('Configuration'));
@@ -887,6 +896,10 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
assert.dom(PAGE.title).hasText('Edit Secret Metadata', 'correct page title for metadata edit');
await click(PAGE.breadcrumbAtIdx(3));
+ await click(PAGE.secretTab('Paths'));
+ assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'paths']);
+ assert.dom(PAGE.title).hasText(secretPath, 'correct page title for paths');
+
await click(PAGE.secretTab('Version History'));
assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'version history']);
assert.dom(PAGE.title).hasText(secretPath, 'correct page title for version history');
@@ -983,7 +996,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
assert.ok(currentURL().startsWith(`/vault/secrets/${backend}/kv/list`), 'links back to list root');
});
test('versioned secret nav, tabs, breadcrumbs (sc)', async function (assert) {
- assert.expect(34);
+ assert.expect(36);
const backend = this.backend;
await navToBackend(backend);
@@ -995,11 +1008,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
'Goes to detail view'
);
assert.dom(PAGE.title).hasText(secretPath, 'Goes to secret detail view');
- assert.dom(PAGE.secretTab('Secret')).hasText('Secret');
- assert.dom(PAGE.secretTab('Secret')).hasClass('active');
- assert.dom(PAGE.secretTab('Metadata')).hasText('Metadata');
- assert.dom(PAGE.secretTab('Metadata')).doesNotHaveClass('active');
- assert.dom(PAGE.secretTab('Version History')).doesNotExist('Version History tab does not render');
+ assertDetailTabs(assert, 'Secret', ['Version History']);
assert.dom(PAGE.detail.versionDropdown).doesNotExist('Version dropdown does not render');
assert.dom(PAGE.detail.createNewVersion).hasText('Create new version', 'Create version button shows');
assert.dom(PAGE.detail.versionTimestamp).doesNotExist('Version created info is not rendered');
@@ -1061,7 +1070,7 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
assert.dom(PAGE.metadata.editBtn).doesNotExist('edit metadata button does not render');
});
test('breadcrumbs & page titles are correct (sc)', async function (assert) {
- assert.expect(28);
+ assert.expect(34);
const backend = this.backend;
await navToBackend(backend);
await click(PAGE.secretTab('Configuration'));
@@ -1089,6 +1098,10 @@ module('Acceptance | kv-v2 workflow | navigation', function (hooks) {
assert.dom(PAGE.metadata.editBtn).doesNotExist('cannot edit metadata');
await click(PAGE.breadcrumbAtIdx(2));
+ await click(PAGE.secretTab('Paths'));
+ assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'paths']);
+ assert.dom(PAGE.title).hasText(secretPath, 'correct page title for paths');
+
assert.dom(PAGE.secretTab('Version History')).doesNotExist('Version History tab not shown');
});
});
@@ -1189,7 +1202,7 @@ path "${this.backend}/*" {
assert.ok(currentURL().startsWith(`/vault/secrets/${backend}/kv/list`), 'links back to list root');
});
test('breadcrumbs & page titles are correct (cg)', async function (assert) {
- assert.expect(30);
+ assert.expect(36);
const backend = this.backend;
await navToBackend(backend);
await click(PAGE.secretTab('Configuration'));
@@ -1230,6 +1243,10 @@ path "${this.backend}/*" {
assert.dom(PAGE.metadata.editBtn).doesNotExist('cannot edit metadata');
await click(PAGE.breadcrumbAtIdx(2));
+ await click(PAGE.secretTab('Paths'));
+ assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'paths']);
+ assert.dom(PAGE.title).hasText(secretPath, 'correct page title for paths');
+
assert.dom(PAGE.secretTab('Version History')).doesNotExist('Version History tab not shown');
await click(PAGE.secretTab('Secret'));
diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-version-history-diff-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-version-history-diff-test.js
deleted file mode 100644
index ac38bbb474..0000000000
--- a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-version-history-diff-test.js
+++ /dev/null
@@ -1,171 +0,0 @@
-import { module, test } from 'qunit';
-import { v4 as uuidv4 } from 'uuid';
-import { setupApplicationTest } from 'vault/tests/helpers';
-import authPage from 'vault/tests/pages/auth';
-import { deleteEngineCmd, mountEngineCmd, runCmd, tokenWithPolicyCmd } from 'vault/tests/helpers/commands';
-import { personas } from 'vault/tests/helpers/policy-generator/kv';
-import { setupControlGroup, writeSecret, writeVersionedSecret } from 'vault/tests/helpers/kv/kv-run-commands';
-
-import { PAGE } from 'vault/tests/helpers/kv/kv-selectors';
-import { click, currentURL, visit } from '@ember/test-helpers';
-
-/**
- * This test set is for testing version history & diff pages
- * VAULT-18817
- */
-module('Acceptance | kv-v2 workflow | version history & diff', function (hooks) {
- setupApplicationTest(hooks);
-
- hooks.beforeEach(async function () {
- this.backend = `kv-workflow-${uuidv4()}`;
- this.secretPath = 'app/first-secret';
- const urlPath = `${this.backend}/kv/${encodeURIComponent(this.secretPath)}`;
- this.navToSecret = async () => {
- return visit(`/vault/secrets/${urlPath}/details?version=2`);
- };
- await authPage.login();
- await runCmd(mountEngineCmd('kv-v2', this.backend), false);
- await writeSecret(this.backend, this.secretPath, 'foo', 'bar');
- await writeVersionedSecret(this.backend, this.secretPath, 'hello', 'there');
- });
-
- hooks.afterEach(async function () {
- await authPage.login();
- return runCmd(deleteEngineCmd(this.backend));
- });
-
- module('admin persona', function (hooks) {
- hooks.beforeEach(async function () {
- const token = await runCmd(tokenWithPolicyCmd('admin', personas.admin(this.backend)));
- await authPage.login(token);
- });
- test('can navigate to the version history page', async function (assert) {
- assert.expect(10);
- await this.navToSecret();
- await click(PAGE.secretTab('Version History'));
- assert.strictEqual(
- currentURL(),
- `/vault/secrets/${this.backend}/kv/${encodeURIComponent(this.secretPath)}/metadata/versions`,
- 'navigates to version history'
- );
- assert.dom(PAGE.secretTab('Secret')).hasText('Secret');
- assert.dom(PAGE.secretTab('Secret')).doesNotHaveClass('active');
- assert.dom(PAGE.secretTab('Metadata')).hasText('Metadata');
- assert.dom(PAGE.secretTab('Metadata')).doesNotHaveClass('active');
- assert.dom(PAGE.secretTab('Version History')).hasText('Version History');
- assert.dom(PAGE.secretTab('Version History')).hasClass('active');
- assert.dom(PAGE.versions.linkedBlock(2)).hasTextContaining('Version 2');
- assert.dom(PAGE.versions.icon(2)).hasTextContaining('Current');
- assert.dom(PAGE.versions.linkedBlock(1)).hasTextContaining('Version 1');
- });
- test.skip('history works correctly when no secrets', async function (assert) {
- assert.expect(0);
- });
- test.skip('history works correctly when only one secret version', async function (assert) {
- assert.expect(0);
- });
- test.skip('history works correctly when many secret versions in various states', async function (assert) {
- assert.expect(0);
- });
- test('can navigate to the version diff view', async function (assert) {
- assert.expect(4);
- await this.navToSecret();
- await click(PAGE.detail.versionDropdown);
- await click(`${PAGE.detail.version('diff')} a`);
- assert.strictEqual(
- currentURL(),
- `/vault/secrets/${this.backend}/kv/${encodeURIComponent(this.secretPath)}/metadata/diff`,
- 'navigates to version diff'
- );
-
- // No tabs render
- assert.dom(PAGE.secretTab('Secret')).doesNotExist();
- assert.dom(PAGE.secretTab('Metadata')).doesNotExist();
- assert.dom(PAGE.secretTab('Version History')).doesNotExist();
- });
- test.skip('diff works correctly when no secrets', async function (assert) {
- assert.expect(0);
- });
- test.skip('diff works correctly when only one secret version', async function (assert) {
- assert.expect(0);
- });
- test.skip('diff works correctly between various secret versions', async function (assert) {
- assert.expect(0);
- });
- });
-
- module('data-reader persona', function (hooks) {
- hooks.beforeEach(async function () {
- const token = await runCmd([tokenWithPolicyCmd('data-reader', personas.dataReader(this.backend))]);
- await authPage.login(token);
- });
- // Copy test outline from admin persona
- });
-
- module('data-list-reader persona', function (hooks) {
- hooks.beforeEach(async function () {
- const token = await runCmd([
- tokenWithPolicyCmd('data-list-reader', personas.dataListReader(this.backend)),
- ]);
- await authPage.login(token);
- });
- // Copy test outline from admin persona
- });
-
- module('metadata-maintainer persona', function (hooks) {
- hooks.beforeEach(async function () {
- const token = await runCmd([
- tokenWithPolicyCmd('metadata-maintainer', personas.metadataMaintainer(this.backend)),
- ]);
- await authPage.login(token);
- });
- // Copy test outline from admin persona
- });
-
- module('secret-creator persona', function (hooks) {
- hooks.beforeEach(async function () {
- const token = await runCmd([
- tokenWithPolicyCmd('secret-creator', personas.secretCreator(this.backend)),
- ]);
- await authPage.login(token);
- });
- // Copy test outline from admin persona
- });
-
- module('enterprise controlled access persona', function (hooks) {
- hooks.beforeEach(async function () {
- const userPolicy = `
-path "${this.backend}/data/*" {
- capabilities = ["create", "read", "update", "delete", "list"]
- control_group = {
- max_ttl = "24h"
- factor "approver" {
- controlled_capabilities = ["write"]
- identity {
- group_names = ["managers"]
- approvals = 1
- }
- }
- }
-}
-
-path "${this.backend}/*" {
- capabilities = ["list"]
-}
-
-// Can we allow this so user can self-authorize?
-path "sys/control-group/authorize" {
- capabilities = ["update"]
-}
-
-path "sys/control-group/request" {
- capabilities = ["update"]
-}
-`;
- const { userToken } = await setupControlGroup({ userPolicy });
- this.userToken = userToken;
- return authPage.login(userToken);
- });
- // Copy test outline from admin persona
- });
-});
diff --git a/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-version-history-paths-test.js b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-version-history-paths-test.js
new file mode 100644
index 0000000000..ddb2601fe0
--- /dev/null
+++ b/ui/tests/acceptance/secrets/backend/kv/kv-v2-workflow-version-history-paths-test.js
@@ -0,0 +1,314 @@
+import { module, test } from 'qunit';
+import { v4 as uuidv4 } from 'uuid';
+import { setupApplicationTest } from 'vault/tests/helpers';
+import authPage from 'vault/tests/pages/auth';
+import { deleteEngineCmd, mountEngineCmd, runCmd, tokenWithPolicyCmd } from 'vault/tests/helpers/commands';
+import { personas } from 'vault/tests/helpers/policy-generator/kv';
+import {
+ clearRecords,
+ deleteVersionCmd,
+ destroyVersionCmd,
+ writeVersionedSecret,
+} from 'vault/tests/helpers/kv/kv-run-commands';
+
+import { PAGE } from 'vault/tests/helpers/kv/kv-selectors';
+import { click, currentRouteName, currentURL, visit, waitUntil } from '@ember/test-helpers';
+import { grantAccess, setupControlGroup } from 'vault/tests/helpers/control-groups';
+
+/**
+ * This test set is for testing version history & path pages for secret.
+ * Letter(s) in parenthesis at the end are shorthand for the persona,
+ * for ease of tracking down specific tests failures from CI
+ */
+module('Acceptance | kv-v2 workflow | version history, paths', function (hooks) {
+ setupApplicationTest(hooks);
+
+ hooks.beforeEach(async function () {
+ this.store = this.owner.lookup('service:store');
+ this.backend = `kv-workflow-${uuidv4()}`;
+ this.secretPath = 'app/first-secret';
+ this.urlPath = `${this.backend}/kv/${encodeURIComponent(this.secretPath)}`;
+ this.navToSecret = async () => {
+ return visit(`/vault/secrets/${this.urlPath}/details?version=4`);
+ };
+ await authPage.login();
+ await runCmd(mountEngineCmd('kv-v2', this.backend), false);
+ await writeVersionedSecret(this.backend, this.secretPath, 'hello', 'there', 6);
+ await runCmd([
+ destroyVersionCmd(this.backend, this.secretPath),
+ deleteVersionCmd(this.backend, this.secretPath, 2),
+ ]);
+ });
+
+ hooks.afterEach(async function () {
+ await authPage.login();
+ return runCmd(deleteEngineCmd(this.backend));
+ });
+
+ module('admin persona', function (hooks) {
+ hooks.beforeEach(async function () {
+ const token = await runCmd(tokenWithPolicyCmd('admin', personas.admin(this.backend)));
+ await authPage.login(token);
+ clearRecords(this.store);
+ });
+ test('can navigate to the version history page (a)', async function (assert) {
+ await this.navToSecret();
+ await click(PAGE.secretTab('Version History'));
+ assert.strictEqual(
+ currentURL(),
+ `/vault/secrets/${this.urlPath}/metadata/versions`,
+ 'navigates to version history'
+ );
+ assert.dom(PAGE.versions.linkedBlock()).exists({ count: 6 });
+
+ assert.dom(PAGE.versions.linkedBlock(6)).hasTextContaining('Version 6');
+ assert.dom(PAGE.versions.icon(6)).hasTextContaining('Current');
+
+ assert.dom(PAGE.versions.linkedBlock(2)).hasTextContaining('Version 2');
+ assert.dom(PAGE.versions.icon(2)).hasTextContaining('Deleted');
+
+ assert.dom(PAGE.versions.linkedBlock(1)).hasTextContaining('Version 1');
+ assert.dom(PAGE.versions.icon(1)).hasText('Destroyed');
+
+ await click(PAGE.versions.linkedBlock(5));
+ assert.strictEqual(
+ currentURL(),
+ `/vault/secrets/${this.urlPath}/details?version=5`,
+ 'navigates to detail at specific version'
+ );
+ });
+ test('can navigate to the paths page (a)', async function (assert) {
+ await this.navToSecret();
+ await click(PAGE.secretTab('Paths'));
+ assert.strictEqual(
+ currentURL(),
+ `/vault/secrets/${this.urlPath}/paths`,
+ 'navigates to secret paths route'
+ );
+ assert.dom(PAGE.infoRow).exists({ count: 3 }, 'shows 3 rows of information');
+ assert.dom(PAGE.infoRowValue('API path')).hasText(`/v1/${this.backend}/data/${this.secretPath}`);
+ assert.dom(PAGE.infoRowValue('CLI path')).hasText(`-mount="${this.backend}" "${this.secretPath}"`);
+ assert
+ .dom(PAGE.infoRowValue('API path for metadata'))
+ .hasText(`/v1/${this.backend}/metadata/${this.secretPath}`);
+ });
+ });
+
+ module('data-reader persona', function (hooks) {
+ hooks.beforeEach(async function () {
+ const token = await runCmd(tokenWithPolicyCmd('data-reader', personas.dataReader(this.backend)));
+ await authPage.login(token);
+ clearRecords(this.store);
+ });
+ test('cannot navigate to the version history page (dr)', async function (assert) {
+ await this.navToSecret();
+ assert.dom(PAGE.secretTab('Version History')).doesNotExist('Does not render Version History tab');
+ });
+ test('can navigate to the paths page (dr)', async function (assert) {
+ await this.navToSecret();
+ await click(PAGE.secretTab('Paths'));
+ assert.strictEqual(
+ currentURL(),
+ `/vault/secrets/${this.urlPath}/paths`,
+ 'navigates to secret paths route'
+ );
+ assert.dom(PAGE.infoRow).exists({ count: 3 }, 'shows 3 rows of information');
+ assert.dom(PAGE.infoRowValue('API path')).hasText(`/v1/${this.backend}/data/${this.secretPath}`);
+ assert.dom(PAGE.infoRowValue('CLI path')).hasText(`-mount="${this.backend}" "${this.secretPath}"`);
+ assert
+ .dom(PAGE.infoRowValue('API path for metadata'))
+ .hasText(`/v1/${this.backend}/metadata/${this.secretPath}`);
+ });
+ });
+
+ module('data-list-reader persona', function (hooks) {
+ hooks.beforeEach(async function () {
+ const token = await runCmd(
+ tokenWithPolicyCmd('data-list-reader', personas.dataListReader(this.backend))
+ );
+ await authPage.login(token);
+ clearRecords(this.store);
+ });
+ test('cannot navigate to the version history page (dlr)', async function (assert) {
+ await this.navToSecret();
+ assert.dom(PAGE.secretTab('Version History')).doesNotExist('Does not render Version History tab');
+ });
+ test('can navigate to the paths page (dlr)', async function (assert) {
+ await this.navToSecret();
+ await click(PAGE.secretTab('Paths'));
+ assert.strictEqual(
+ currentURL(),
+ `/vault/secrets/${this.urlPath}/paths`,
+ 'navigates to secret paths route'
+ );
+ assert.dom(PAGE.infoRow).exists({ count: 3 }, 'shows 3 rows of information');
+ assert.dom(PAGE.infoRowValue('API path')).hasText(`/v1/${this.backend}/data/${this.secretPath}`);
+ assert.dom(PAGE.infoRowValue('CLI path')).hasText(`-mount="${this.backend}" "${this.secretPath}"`);
+ assert
+ .dom(PAGE.infoRowValue('API path for metadata'))
+ .hasText(`/v1/${this.backend}/metadata/${this.secretPath}`);
+ });
+ });
+
+ module('metadata-maintainer persona', function (hooks) {
+ hooks.beforeEach(async function () {
+ const token = await runCmd(
+ tokenWithPolicyCmd('metadata-maintainer', personas.metadataMaintainer(this.backend))
+ );
+ await authPage.login(token);
+ clearRecords(this.store);
+ });
+ test('can navigate to the version history page (mm)', async function (assert) {
+ await this.navToSecret();
+ await click(PAGE.secretTab('Version History'));
+ assert.strictEqual(
+ currentURL(),
+ `/vault/secrets/${this.urlPath}/metadata/versions`,
+ 'navigates to version history'
+ );
+ assert.dom(PAGE.versions.linkedBlock()).exists({ count: 6 });
+
+ assert.dom(PAGE.versions.linkedBlock(6)).hasTextContaining('Version 6');
+ assert.dom(PAGE.versions.icon(6)).hasTextContaining('Current');
+
+ assert.dom(PAGE.versions.linkedBlock(2)).hasTextContaining('Version 2');
+ assert.dom(PAGE.versions.icon(2)).hasTextContaining('Deleted');
+
+ assert.dom(PAGE.versions.linkedBlock(1)).hasTextContaining('Version 1');
+ assert.dom(PAGE.versions.icon(1)).hasText('Destroyed');
+
+ await click(PAGE.versions.linkedBlock(5));
+ assert.strictEqual(
+ currentURL(),
+ `/vault/secrets/${this.urlPath}/details?version=5`,
+ 'navigates to detail at specific version'
+ );
+ });
+ test('can navigate to the paths page (mm)', async function (assert) {
+ await this.navToSecret();
+ await click(PAGE.secretTab('Paths'));
+ assert.strictEqual(
+ currentURL(),
+ `/vault/secrets/${this.urlPath}/paths`,
+ 'navigates to secret paths route'
+ );
+ assert.dom(PAGE.infoRow).exists({ count: 3 }, 'shows 3 rows of information');
+ assert.dom(PAGE.infoRowValue('API path')).hasText(`/v1/${this.backend}/data/${this.secretPath}`);
+ assert.dom(PAGE.infoRowValue('CLI path')).hasText(`-mount="${this.backend}" "${this.secretPath}"`);
+ assert
+ .dom(PAGE.infoRowValue('API path for metadata'))
+ .hasText(`/v1/${this.backend}/metadata/${this.secretPath}`);
+ });
+ });
+
+ module('secret-creator persona', function (hooks) {
+ hooks.beforeEach(async function () {
+ const token = await runCmd(tokenWithPolicyCmd('secret-creator', personas.secretCreator(this.backend)));
+ await authPage.login(token);
+ clearRecords(this.store);
+ });
+ test('cannot navigate to the version history page (sc)', async function (assert) {
+ await this.navToSecret();
+ assert.dom(PAGE.secretTab('Version History')).doesNotExist('Does not render Version History tab');
+ });
+ test('can navigate to the paths page (sc)', async function (assert) {
+ await this.navToSecret();
+ await click(PAGE.secretTab('Paths'));
+ assert.strictEqual(
+ currentURL(),
+ `/vault/secrets/${this.urlPath}/paths`,
+ 'navigates to secret paths route'
+ );
+ assert.dom(PAGE.infoRow).exists({ count: 3 }, 'shows 3 rows of information');
+ assert.dom(PAGE.infoRowValue('API path')).hasText(`/v1/${this.backend}/data/${this.secretPath}`);
+ assert.dom(PAGE.infoRowValue('CLI path')).hasText(`-mount="${this.backend}" "${this.secretPath}"`);
+ assert
+ .dom(PAGE.infoRowValue('API path for metadata'))
+ .hasText(`/v1/${this.backend}/metadata/${this.secretPath}`);
+ });
+ });
+
+ module('enterprise controlled access persona', function (hooks) {
+ hooks.beforeEach(async function () {
+ const userPolicy = `
+path "${this.backend}/metadata/*" {
+ capabilities = ["create", "read", "update", "delete", "list"]
+ control_group = {
+ max_ttl = "24h"
+ factor "approver" {
+ controlled_capabilities = ["read"]
+ identity {
+ group_names = ["managers"]
+ approvals = 1
+ }
+ }
+ }
+}
+
+path "${this.backend}/*" {
+ capabilities = ["list"]
+}
+`;
+ const { userToken } = await setupControlGroup({ userPolicy });
+ this.userToken = userToken;
+ await authPage.login(userToken);
+ clearRecords(this.store);
+ });
+ test('can navigate to the version history page (cg)', async function (assert) {
+ await this.navToSecret();
+ await click(PAGE.secretTab('Version History'));
+ assert.ok(
+ await waitUntil(() => currentRouteName() === 'vault.cluster.access.control-group-accessor'),
+ 'redirects to access control group route'
+ );
+ await grantAccess({
+ apiPath: `${this.backend}/metadata/${this.secretPath}`,
+ originUrl: `/vault/secrets/${this.urlPath}/details`,
+ userToken: this.userToken,
+ });
+ assert.strictEqual(
+ currentURL(),
+ `/vault/secrets/${this.urlPath}/details`,
+ 'navigates back to secret overview after authorized'
+ );
+ await click(PAGE.secretTab('Version History'));
+ assert.strictEqual(
+ currentURL(),
+ `/vault/secrets/${this.urlPath}/metadata/versions`,
+ 'goes to version history page'
+ );
+ assert.dom(PAGE.versions.linkedBlock()).exists({ count: 6 });
+
+ assert.dom(PAGE.versions.linkedBlock(6)).hasTextContaining('Version 6');
+ assert.dom(PAGE.versions.icon(6)).hasTextContaining('Current');
+
+ assert.dom(PAGE.versions.linkedBlock(2)).hasTextContaining('Version 2');
+ assert.dom(PAGE.versions.icon(2)).hasTextContaining('Deleted');
+
+ assert.dom(PAGE.versions.linkedBlock(1)).hasTextContaining('Version 1');
+ assert.dom(PAGE.versions.icon(1)).hasText('Destroyed');
+
+ await click(PAGE.versions.linkedBlock(5));
+ assert.strictEqual(
+ currentURL(),
+ `/vault/secrets/${this.urlPath}/details?version=5`,
+ 'navigates to detail at specific version'
+ );
+ });
+ test('can navigate to the paths page (cg)', async function (assert) {
+ await this.navToSecret();
+ await click(PAGE.secretTab('Paths'));
+ assert.strictEqual(
+ currentURL(),
+ `/vault/secrets/${this.urlPath}/paths`,
+ 'navigates to secret paths route'
+ );
+ assert.dom(PAGE.infoRow).exists({ count: 3 }, 'shows 3 rows of information');
+ assert.dom(PAGE.infoRowValue('API path')).hasText(`/v1/${this.backend}/data/${this.secretPath}`);
+ assert.dom(PAGE.infoRowValue('CLI path')).hasText(`-mount="${this.backend}" "${this.secretPath}"`);
+ assert
+ .dom(PAGE.infoRowValue('API path for metadata'))
+ .hasText(`/v1/${this.backend}/metadata/${this.secretPath}`);
+ });
+ });
+});
diff --git a/ui/tests/helpers/kv/kv-run-commands.js b/ui/tests/helpers/kv/kv-run-commands.js
index 9f1b53a68a..602317a866 100644
--- a/ui/tests/helpers/kv/kv-run-commands.js
+++ b/ui/tests/helpers/kv/kv-run-commands.js
@@ -5,6 +5,7 @@
import { click, fillIn, visit } from '@ember/test-helpers';
import { FORM } from './kv-selectors';
+import { encodePath } from 'vault/utils/path-encoding-helpers';
// CUSTOM ACTIONS RELEVANT TO KV-V2
@@ -32,6 +33,16 @@ export const writeVersionedSecret = async function (backend, path, key, val, ver
return;
};
+export const deleteVersionCmd = function (backend, secretPath, version = 1) {
+ return `write ${backend}/delete/${encodePath(secretPath)} versions=${version}`;
+};
+export const destroyVersionCmd = function (backend, secretPath, version = 1) {
+ return `write ${backend}/destroy/${encodePath(secretPath)} versions=${version}`;
+};
+export const deleteLatestCmd = function (backend, secretPath) {
+ return `delete ${backend}/data/${encodePath(secretPath)}`;
+};
+
export const addSecretMetadataCmd = (backend, secret, options = { max_versions: 10 }) => {
const stringOptions = Object.keys(options).reduce((prev, curr) => {
return `${prev} ${curr}=${options[curr]}`;
diff --git a/ui/tests/helpers/kv/kv-selectors.js b/ui/tests/helpers/kv/kv-selectors.js
index c44ea9eb65..2d935aaca2 100644
--- a/ui/tests/helpers/kv/kv-selectors.js
+++ b/ui/tests/helpers/kv/kv-selectors.js
@@ -9,9 +9,10 @@ export const PAGE = {
breadcrumbs: '[data-test-breadcrumbs]',
breadcrumb: '[data-test-breadcrumbs] li',
breadcrumbAtIdx: (idx) => `[data-test-crumb="${idx}"] a`,
+ infoRow: '[data-test-component="info-table-row"]',
infoRowValue: (label) => `[data-test-value-div="${label}"]`,
infoRowToggleMasked: (label) => `[data-test-value-div="${label}"] [data-test-button="toggle-masked"]`,
- secretTab: (tab) => `[data-test-secrets-tab="${tab}"]`,
+ secretTab: (tab) => (tab ? `[data-test-secrets-tab="${tab}"]` : '[data-test-secrets-tab]'),
emptyStateTitle: '[data-test-empty-state-title]',
emptyStateMessage: '[data-test-empty-state-message]',
emptyStateActions: '[data-test-empty-state-actions]',
@@ -22,7 +23,7 @@ export const PAGE = {
},
toolbar: 'nav.toolbar',
toolbarAction: 'nav.toolbar-actions .toolbar-link',
- secretRow: '[data-test-component="info-table-row"]',
+ secretRow: '[data-test-component="info-table-row"]', // replace with infoRow
// specific page selectors
backends: {
link: (backend) => `[data-test-secrets-backend-link="${backend}"]`,
@@ -49,6 +50,13 @@ export const PAGE = {
deleteOptionLatest: 'input#delete-latest-version',
deleteConfirm: '[data-test-delete-modal-confirm]',
},
+ edit: {
+ toggleDiff: '[data-test-toggle-input="Show diff"',
+ toggleDiffDescription: '[data-test-diff-description]',
+ visualDiff: '[data-test-visual-diff]',
+ added: `.jsondiffpatch-added`,
+ deleted: `.jsondiffpatch-deleted`,
+ },
list: {
createSecret: '[data-test-toolbar-create-secret]',
item: (secret) => (!secret ? '[data-test-list-item]' : `[data-test-list-item="${secret}"]`),
diff --git a/ui/tests/integration/components/kv/page/kv-page-secret-edit-test.js b/ui/tests/integration/components/kv/page/kv-page-secret-edit-test.js
index f40a0a8e47..cfcaaefa9e 100644
--- a/ui/tests/integration/components/kv/page/kv-page-secret-edit-test.js
+++ b/ui/tests/integration/components/kv/page/kv-page-secret-edit-test.js
@@ -11,7 +11,7 @@ import { Response } from 'miragejs';
import { hbs } from 'ember-cli-htmlbars';
import { click, fillIn, render } from '@ember/test-helpers';
import codemirror from 'vault/tests/helpers/codemirror';
-import { FORM } from 'vault/tests/helpers/kv/kv-selectors';
+import { FORM, PAGE } from 'vault/tests/helpers/kv/kv-selectors';
import sinon from 'sinon';
module('Integration | Component | kv-v2 | Page::Secret::Edit', function (hooks) {
@@ -95,6 +95,39 @@ module('Integration | Component | kv-v2 | Page::Secret::Edit', function (hooks)
);
});
+ test('diff works correctly', async function (assert) {
+ await render(
+ hbs`
`,
+ { owner: this.engine }
+ );
+
+ assert.dom(PAGE.edit.toggleDiff).isDisabled('Diff toggle is disabled');
+ assert.dom(PAGE.edit.toggleDiffDescription).hasText('No changes to show. Update secret to view diff');
+ assert.dom(PAGE.edit.visualDiff).doesNotExist('Does not show visual diff');
+
+ await fillIn(FORM.keyInput(1), 'foo2');
+ await fillIn(FORM.maskedValueInput(1), 'bar2');
+
+ assert.dom(PAGE.edit.toggleDiff).isNotDisabled('Diff toggle is not disabled');
+ assert.dom(PAGE.edit.toggleDiffDescription).hasText('Showing the diff will reveal secret values');
+ assert.dom(PAGE.edit.visualDiff).doesNotExist('Does not show visual diff');
+ await click(PAGE.edit.toggleDiff);
+ assert.dom(PAGE.edit.visualDiff).exists('Shows visual diff');
+ assert.dom(PAGE.edit.added).hasText(`foo2"bar2"`);
+
+ await click(FORM.toggleJson);
+ codemirror().setValue('{ "foo3": "bar3" }');
+
+ assert.dom(PAGE.edit.visualDiff).exists('Visual diff updates');
+ assert.dom(PAGE.edit.deleted).hasText(`foo"bar"`);
+ assert.dom(PAGE.edit.added).hasText(`foo3"bar3"`);
+ });
+
test('it saves nested secrets', async function (assert) {
assert.expect(3);
const nestedSecret = 'path/to/secret';
diff --git a/ui/tests/integration/components/kv/page/kv-page-version-diff-test.js b/ui/tests/integration/components/kv/page/kv-page-version-diff-test.js
deleted file mode 100644
index 33c34a9d1e..0000000000
--- a/ui/tests/integration/components/kv/page/kv-page-version-diff-test.js
+++ /dev/null
@@ -1,125 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: MPL-2.0
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { setupEngine } from 'ember-engines/test-support';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import { click, render } from '@ember/test-helpers';
-import { hbs } from 'ember-cli-htmlbars';
-import { kvMetadataPath, kvDataPath } from 'vault/utils/kv-path';
-import { PAGE } from 'vault/tests/helpers/kv/kv-selectors';
-import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
-import { encodePath } from 'vault/utils/path-encoding-helpers';
-
-const EXAMPLE_KV_DATA_GET_RESPONSE = {
- request_id: 'foobar',
- data: {
- data: { hello: 'world' },
- metadata: {
- created_time: '2023-06-20T21:26:47.592306Z',
- custom_metadata: null,
- deletion_time: '',
- destroyed: false,
- version: 1,
- },
- },
-};
-
-module('Integration | Component | kv | Page::Secret::Metadata::Version-Diff', function (hooks) {
- setupRenderingTest(hooks);
- setupEngine(hooks, 'kv');
- setupMirage(hooks);
-
- hooks.beforeEach(async function () {
- const store = this.owner.lookup('service:store');
- this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub());
- const metadata = this.server.create('kv-metadatum');
-
- metadata.id = kvMetadataPath('kv-engine', 'my-secret');
- store.pushPayload('kv/metadata', {
- modelName: 'kv/metadata',
- ...metadata,
- });
- this.metadata = store.peekRecord('kv/metadata', metadata.id);
- this.breadcrumbs = [
- { label: 'secrets', route: 'secrets', linkExternal: true },
- { label: this.metadata.backend, route: 'list' },
- { label: this.metadata.path, route: 'secret.details', model: this.metadata.path },
- { label: 'version diff' },
- ];
-
- // compare version 4
- const dataId = kvDataPath('kv-engine', 'my-secret', 4);
- store.pushPayload('kv/data', {
- modelName: 'kv/data',
- id: dataId,
- secret_data: { foo: 'bar' },
- created_time: '2023-07-20T02:12:17.379762Z',
- custom_metadata: null,
- deletion_time: '',
- destroyed: false,
- version: 4,
- });
-
- this.endpoint = `${encodePath('kv-engine')}/data/${'my-secret'}`;
- });
-
- test('it renders compared data of the two versions and shows icons for deleted, destroyed and current', async function (assert) {
- assert.expect(12);
- this.server.get(this.endpoint, (schema, req) => {
- assert.ok('request made to the correct endpoint.');
- assert.strictEqual(
- req.queryParams.version,
- '1',
- 'request includes the version flag on queryRecord and correctly only queries for the record not in the store, which is retrieved by peekRecord.'
- );
- return EXAMPLE_KV_DATA_GET_RESPONSE;
- });
-
- await render(
- hbs`
-
- `,
- { owner: this.engine }
- );
- /* eslint-disable no-useless-escape */
- assert
- .dom('[data-test-visual-diff]')
- .hasText(
- `foo"bar"\hello"world"`,
- 'correctly pull in the data from version 4 and compared to version 1.'
- );
- assert
- .dom('[data-test-version-dropdown-left]')
- .hasText('Version 4', 'shows the current version for the left side default version.');
- assert
- .dom('[data-test-version-dropdown-right]')
- .hasText(
- 'Version 1',
- 'shows the first version that is not deleted or destroyed for the right version on init.'
- );
-
- await click('[data-test-version-dropdown-right]');
- for (const version in this.metadata.versions) {
- const data = this.metadata.versions[version];
- assert.dom(PAGE.versions.button(version)).exists('renders the button for each version.');
-
- if (data.destroyed || data.deletion_time) {
- assert
- .dom(`${PAGE.versions.button(version)} [data-test-icon="x-square-fill"]`)
- .hasClass(`${data.destroyed ? 'has-text-danger' : 'has-text-grey'}`);
- }
- }
- assert
- .dom(`${PAGE.versions.button('1')}`)
- .hasClass('is-active', 'correctly shows the selected version 1 as active giving it text blue.');
- });
-});