mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-29 09:42:25 +00:00 
			
		
		
		
	 7c14aa6e00
			
		
	
	7c14aa6e00
	
	
	
		
			
			* Show JSON editor by default if secret data is complex, but do not disable JSON toggle * Add text object warning to kv object editor * a11y fix: do not disable show diff toggle * test and language cleanup * update language * Update tests for expected behavior * language! * Add changelog
		
			
				
	
	
		
			536 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			536 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * Copyright (c) HashiCorp, Inc.
 | |
|  * SPDX-License-Identifier: BUSL-1.1
 | |
|  */
 | |
| 
 | |
| import { module, test } from 'qunit';
 | |
| import { v4 as uuidv4 } from 'uuid';
 | |
| import { click, currentURL, fillIn, findAll, setupOnerror, typeIn, visit } from '@ember/test-helpers';
 | |
| import { setupApplicationTest } from 'vault/tests/helpers';
 | |
| import authPage from 'vault/tests/pages/auth';
 | |
| import {
 | |
|   createPolicyCmd,
 | |
|   deleteEngineCmd,
 | |
|   mountEngineCmd,
 | |
|   runCmd,
 | |
|   createTokenCmd,
 | |
| } from 'vault/tests/helpers/commands';
 | |
| import {
 | |
|   dataPolicy,
 | |
|   deleteVersionsPolicy,
 | |
|   destroyVersionsPolicy,
 | |
|   metadataListPolicy,
 | |
|   metadataPolicy,
 | |
| } from 'vault/tests/helpers/policy-generator/kv';
 | |
| import { clearRecords, writeSecret, writeVersionedSecret } from 'vault/tests/helpers/kv/kv-run-commands';
 | |
| import { FORM, PAGE } from 'vault/tests/helpers/kv/kv-selectors';
 | |
| import codemirror from 'vault/tests/helpers/codemirror';
 | |
| 
 | |
| /**
 | |
|  * This test set is for testing edge cases, such as specific bug fixes or reported user workflows
 | |
|  */
 | |
| module('Acceptance | kv-v2 workflow | edge cases', function (hooks) {
 | |
|   setupApplicationTest(hooks);
 | |
| 
 | |
|   hooks.beforeEach(async function () {
 | |
|     const uid = uuidv4();
 | |
|     this.backend = `kv-edge-${uid}`;
 | |
|     this.rootSecret = 'root-directory';
 | |
|     this.fullSecretPath = `${this.rootSecret}/nested/child-secret`;
 | |
|     await authPage.login();
 | |
|     await runCmd(mountEngineCmd('kv-v2', this.backend), false);
 | |
|     await writeSecret(this.backend, this.fullSecretPath, 'foo', 'bar');
 | |
|     await writeSecret(this.backend, 'edge/one', 'foo', 'bar');
 | |
|     await writeSecret(this.backend, 'edge/two', 'foo', 'bar');
 | |
|     return;
 | |
|   });
 | |
| 
 | |
|   hooks.afterEach(async function () {
 | |
|     await authPage.login();
 | |
|     await runCmd(deleteEngineCmd(this.backend));
 | |
|     return;
 | |
|   });
 | |
| 
 | |
|   module('persona with read and list access on the secret level', function (hooks) {
 | |
|     // see github issue for more details https://github.com/hashicorp/vault/issues/5362
 | |
|     hooks.beforeEach(async function () {
 | |
|       const secretPath = `${this.rootSecret}/*`; // user has LIST and READ access within this root secret directory
 | |
|       const capabilities = ['list', 'read'];
 | |
|       const backend = this.backend;
 | |
|       const token = await runCmd([
 | |
|         createPolicyCmd(
 | |
|           `nested-secret-list-reader-${this.backend}`,
 | |
|           metadataPolicy({ backend, secretPath, capabilities }) +
 | |
|             dataPolicy({ backend, secretPath, capabilities })
 | |
|         ),
 | |
|         createTokenCmd(`nested-secret-list-reader-${this.backend}`),
 | |
|       ]);
 | |
|       await authPage.login(token);
 | |
|     });
 | |
| 
 | |
|     test('it can navigate to secrets within a secret directory', async function (assert) {
 | |
|       assert.expect(21);
 | |
|       const backend = this.backend;
 | |
|       const [root, subdirectory, secret] = this.fullSecretPath.split('/');
 | |
| 
 | |
|       await visit(`/vault/secrets/${backend}/kv/list`);
 | |
|       assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list`, 'lands on secrets list page');
 | |
| 
 | |
|       await typeIn(PAGE.list.overviewInput, `${root}/no-access/`);
 | |
|       assert
 | |
|         .dom(PAGE.list.overviewButton)
 | |
|         .hasText('View list', 'shows list and not secret because search is a directory');
 | |
|       await click(PAGE.list.overviewButton);
 | |
|       assert.dom(PAGE.emptyStateTitle).hasText(`There are no secrets matching "${root}/no-access/".`);
 | |
| 
 | |
|       await visit(`/vault/secrets/${backend}/kv/list`);
 | |
|       await typeIn(PAGE.list.overviewInput, `${root}/`); // add slash because this is a directory
 | |
|       await click(PAGE.list.overviewButton);
 | |
| 
 | |
|       // URL correct
 | |
|       assert.strictEqual(
 | |
|         currentURL(),
 | |
|         `/vault/secrets/${backend}/kv/list/${root}/`,
 | |
|         'visits list-directory of root'
 | |
|       );
 | |
| 
 | |
|       // Title correct
 | |
|       assert.dom(PAGE.title).hasText(`${backend} version 2`);
 | |
|       // Tabs correct
 | |
|       assert.dom(PAGE.secretTab('Secrets')).hasText('Secrets');
 | |
|       assert.dom(PAGE.secretTab('Secrets')).hasClass('active');
 | |
|       assert.dom(PAGE.secretTab('Configuration')).hasText('Configuration');
 | |
|       assert.dom(PAGE.secretTab('Configuration')).doesNotHaveClass('active');
 | |
|       // Toolbar correct
 | |
|       assert.dom(PAGE.toolbarAction).exists({ count: 1 }, 'toolbar only renders create secret action');
 | |
|       assert.dom(PAGE.list.filter).hasValue(`${root}/`);
 | |
|       // List content correct
 | |
|       assert.dom(PAGE.list.item(`${subdirectory}/`)).exists('renders linked block for subdirectory');
 | |
|       await click(PAGE.list.item(`${subdirectory}/`));
 | |
|       assert.dom(PAGE.list.item(secret)).exists('renders linked block for child secret');
 | |
|       await click(PAGE.list.item(secret));
 | |
|       // Secret details visible
 | |
|       assert.dom(PAGE.title).hasText(this.fullSecretPath);
 | |
|       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.toolbarAction).exists({ count: 5 }, 'toolbar renders all actions');
 | |
|     });
 | |
| 
 | |
|     test('it navigates back to engine index route via breadcrumbs from secret details', async function (assert) {
 | |
|       assert.expect(6);
 | |
|       const backend = this.backend;
 | |
|       const [root, subdirectory, secret] = this.fullSecretPath.split('/');
 | |
| 
 | |
|       await visit(`vault/secrets/${backend}/kv/${encodeURIComponent(this.fullSecretPath)}/details?version=1`);
 | |
|       // navigate back through crumbs
 | |
|       let previousCrumb = findAll('[data-test-breadcrumbs] li').length - 2;
 | |
|       await click(PAGE.breadcrumbAtIdx(previousCrumb));
 | |
|       assert.strictEqual(
 | |
|         currentURL(),
 | |
|         `/vault/secrets/${backend}/kv/list/${root}/${subdirectory}/`,
 | |
|         'goes back to subdirectory list'
 | |
|       );
 | |
|       assert.dom(PAGE.list.filter).hasValue(`${root}/${subdirectory}/`);
 | |
|       assert.dom(PAGE.list.item(secret)).exists('renders linked block for child secret');
 | |
| 
 | |
|       // back again
 | |
|       previousCrumb = findAll('[data-test-breadcrumbs] li').length - 2;
 | |
|       await click(PAGE.breadcrumbAtIdx(previousCrumb));
 | |
|       assert.strictEqual(
 | |
|         currentURL(),
 | |
|         `/vault/secrets/${backend}/kv/list/${root}/`,
 | |
|         'goes back to root directory'
 | |
|       );
 | |
|       assert.dom(PAGE.list.item(`${subdirectory}/`)).exists('renders linked block for subdirectory');
 | |
| 
 | |
|       // and back to the engine list view
 | |
|       previousCrumb = findAll('[data-test-breadcrumbs] li').length - 2;
 | |
|       await click(PAGE.breadcrumbAtIdx(previousCrumb));
 | |
|       assert.strictEqual(
 | |
|         currentURL(),
 | |
|         `/vault/secrets/${backend}/kv/list`,
 | |
|         'navigates back to engine list from crumbs'
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     test('it handles errors when attempting to view details of a secret that is a directory', async function (assert) {
 | |
|       assert.expect(7);
 | |
|       const backend = this.backend;
 | |
|       const [root, subdirectory] = this.fullSecretPath.split('/');
 | |
|       setupOnerror((error) => assert.strictEqual(error.httpStatus, 404), '404 error is thrown'); // catches error so qunit test doesn't fail
 | |
| 
 | |
|       await visit(`/vault/secrets/${backend}/kv/list`);
 | |
|       await typeIn(PAGE.list.overviewInput, `${root}/${subdirectory}`); // intentionally leave out trailing slash
 | |
|       await click(PAGE.list.overviewButton);
 | |
|       assert.dom(PAGE.error.title).hasText('404 Not Found');
 | |
|       assert
 | |
|         .dom(PAGE.error.message)
 | |
|         .hasText(`Sorry, we were unable to find any content at /v1/${backend}/data/${root}/${subdirectory}.`);
 | |
| 
 | |
|       assert.dom(PAGE.breadcrumbAtIdx(0)).hasText('secrets');
 | |
|       assert.dom(PAGE.breadcrumbAtIdx(1)).hasText(backend);
 | |
|       assert.dom(PAGE.secretTab('Secrets')).doesNotHaveClass('is-active');
 | |
|       assert.dom(PAGE.secretTab('Configuration')).doesNotHaveClass('is-active');
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   module('destruction without read', function (hooks) {
 | |
|     hooks.beforeEach(async function () {
 | |
|       const backend = this.backend;
 | |
|       const testSecrets = [
 | |
|         'data-delete-only',
 | |
|         'delete-version-only',
 | |
|         'destroy-version-only',
 | |
|         'destroy-metadata-only',
 | |
|       ];
 | |
| 
 | |
|       // user has different permissions for each secret path
 | |
|       const token = await runCmd([
 | |
|         createPolicyCmd(
 | |
|           `destruction-no-read-${this.backend}`,
 | |
|           dataPolicy({ backend, secretPath: 'data-delete-only', capabilities: ['delete'] }) +
 | |
|             deleteVersionsPolicy({ backend, secretPath: 'delete-version-only' }) +
 | |
|             destroyVersionsPolicy({ backend, secretPath: 'destroy-version-only' }) +
 | |
|             metadataPolicy({ backend, secretPath: 'destroy-metadata-only', capabilities: ['delete'] }) +
 | |
|             metadataListPolicy(backend)
 | |
|         ),
 | |
|         createTokenCmd(`destruction-no-read-${this.backend}`),
 | |
|       ]);
 | |
|       for (const secret of testSecrets) {
 | |
|         await writeVersionedSecret(backend, secret, 'foo', 'bar', 2);
 | |
|       }
 | |
|       await authPage.login(token);
 | |
|     });
 | |
| 
 | |
|     test('it renders the delete action and disables delete this version option', async function (assert) {
 | |
|       assert.expect(4);
 | |
|       const testSecret = 'data-delete-only';
 | |
|       await visit(`/vault/secrets/${this.backend}/kv/${testSecret}/details`);
 | |
| 
 | |
|       assert.dom(PAGE.detail.delete).exists('renders delete button');
 | |
|       await click(PAGE.detail.delete);
 | |
|       assert
 | |
|         .dom(PAGE.detail.deleteModal)
 | |
|         .hasTextContaining('Delete this version This deletes a specific version of the secret');
 | |
|       assert.dom(PAGE.detail.deleteOption).isDisabled('disables version specific option');
 | |
|       assert.dom(PAGE.detail.deleteOptionLatest).isEnabled('enables version specific option');
 | |
|     });
 | |
| 
 | |
|     test('it renders the delete action and disables delete latest version option', async function (assert) {
 | |
|       assert.expect(4);
 | |
|       const testSecret = 'delete-version-only';
 | |
|       await visit(`/vault/secrets/${this.backend}/kv/${testSecret}/details`);
 | |
| 
 | |
|       assert.dom(PAGE.detail.delete).exists('renders delete button');
 | |
|       await click(PAGE.detail.delete);
 | |
|       assert
 | |
|         .dom(PAGE.detail.deleteModal)
 | |
|         .hasTextContaining('Delete this version This deletes a specific version of the secret');
 | |
| 
 | |
|       assert.dom(PAGE.detail.deleteOption).isEnabled('enables version specific option');
 | |
|       assert.dom(PAGE.detail.deleteOptionLatest).isDisabled('disables version specific option');
 | |
|     });
 | |
| 
 | |
|     test('it hides destroy option without version number', async function (assert) {
 | |
|       assert.expect(1);
 | |
|       const testSecret = 'destroy-version-only';
 | |
|       await visit(`/vault/secrets/${this.backend}/kv/${testSecret}/details`);
 | |
| 
 | |
|       assert.dom(PAGE.detail.destroy).doesNotExist();
 | |
|     });
 | |
| 
 | |
|     test('it renders the destroy metadata action and expected modal copy', async function (assert) {
 | |
|       assert.expect(2);
 | |
| 
 | |
|       const testSecret = 'destroy-metadata-only';
 | |
|       await visit(`/vault/secrets/${this.backend}/kv/${testSecret}/metadata`);
 | |
|       assert.dom(PAGE.metadata.deleteMetadata).exists('renders delete metadata button');
 | |
|       await click(PAGE.metadata.deleteMetadata);
 | |
|       assert
 | |
|         .dom(PAGE.detail.deleteModal)
 | |
|         .hasText(
 | |
|           'Delete metadata and secret data? This will permanently delete the metadata and versions of the secret. All version history will be removed. This cannot be undone. Confirm Cancel'
 | |
|         );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   test('no ghost item after editing metadata', async function (assert) {
 | |
|     await visit(`/vault/secrets/${this.backend}/kv/list/edge/`);
 | |
|     assert.dom(PAGE.list.item()).exists({ count: 2 }, 'two secrets are listed');
 | |
|     await click(PAGE.list.item('two'));
 | |
|     await click(PAGE.secretTab('Metadata'));
 | |
|     await click(PAGE.metadata.editBtn);
 | |
|     await fillIn(FORM.keyInput(), 'foo');
 | |
|     await fillIn(FORM.valueInput(), 'bar');
 | |
|     await click(FORM.saveBtn);
 | |
|     await click(PAGE.breadcrumbAtIdx(2));
 | |
|     assert.dom(PAGE.list.item()).exists({ count: 2 }, 'two secrets are listed');
 | |
|   });
 | |
| 
 | |
|   test('advanced secret values default to JSON display', async function (assert) {
 | |
|     const obscuredData = `{
 | |
|   "foo3": {
 | |
|     "name": "********"
 | |
|   }
 | |
| }`;
 | |
|     await visit(`/vault/secrets/${this.backend}/kv/create`);
 | |
|     await fillIn(FORM.inputByAttr('path'), 'complex');
 | |
| 
 | |
|     await click(FORM.toggleJson);
 | |
|     assert.strictEqual(codemirror().getValue(), '{ "": "" }');
 | |
|     codemirror().setValue('{ "foo3": { "name": "bar3" } }');
 | |
|     await click(FORM.saveBtn);
 | |
| 
 | |
|     // Details view
 | |
|     assert.dom(FORM.toggleJson).isNotDisabled();
 | |
|     assert.dom(FORM.toggleJson).isChecked();
 | |
|     assert.strictEqual(
 | |
|       codemirror().getValue(),
 | |
|       obscuredData,
 | |
|       'Value is obscured by default on details view when advanced'
 | |
|     );
 | |
|     await click('[data-test-toggle-input="revealValues"]');
 | |
|     assert.false(codemirror().getValue().includes('*'), 'Value unobscured after toggle');
 | |
| 
 | |
|     // New version view
 | |
|     await click(PAGE.detail.createNewVersion);
 | |
|     assert.dom(FORM.toggleJson).isNotDisabled();
 | |
|     assert.dom(FORM.toggleJson).isChecked();
 | |
|     assert.false(codemirror().getValue().includes('*'), 'Values are not obscured on edit view');
 | |
|   });
 | |
| 
 | |
|   test('viewing advanced secret data versions displays the correct version data', async function (assert) {
 | |
|     assert.expect(2);
 | |
|     const obscuredDataV1 = `{
 | |
|   "foo1": {
 | |
|     "name": "********"
 | |
|   }
 | |
| }`;
 | |
|     const obscuredDataV2 = `{
 | |
|   "foo2": {
 | |
|     "name": "********"
 | |
|   }
 | |
| }`;
 | |
| 
 | |
|     await visit(`/vault/secrets/${this.backend}/kv/create`);
 | |
|     await fillIn(FORM.inputByAttr('path'), 'complex_version_test');
 | |
| 
 | |
|     await click(FORM.toggleJson);
 | |
|     codemirror().setValue('{ "foo1": { "name": "bar1" } }');
 | |
|     await click(FORM.saveBtn);
 | |
| 
 | |
|     // Create another version
 | |
|     await click(PAGE.detail.createNewVersion);
 | |
|     codemirror().setValue('{ "foo2": { "name": "bar2" } }');
 | |
|     await click(FORM.saveBtn);
 | |
| 
 | |
|     // View the first version and make sure the secret data is correct
 | |
|     await click(PAGE.detail.versionDropdown);
 | |
|     await click(`${PAGE.detail.version(1)} a`);
 | |
|     assert.strictEqual(codemirror().getValue(), obscuredDataV1, 'Version one data is displayed');
 | |
| 
 | |
|     // Navigate back the second version and make sure the secret data is correct
 | |
|     await click(PAGE.detail.versionDropdown);
 | |
|     await click(`${PAGE.detail.version(2)} a`);
 | |
|     assert.strictEqual(codemirror().getValue(), obscuredDataV2, 'Version two data is displayed');
 | |
|   });
 | |
| 
 | |
|   test('does not register as advanced when value includes {', async function (assert) {
 | |
|     await visit(`/vault/secrets/${this.backend}/kv/create`);
 | |
|     await fillIn(FORM.inputByAttr('path'), 'not-advanced');
 | |
| 
 | |
|     await fillIn(FORM.keyInput(), 'foo');
 | |
|     await fillIn(FORM.maskedValueInput(), '{bar}');
 | |
|     await click(FORM.saveBtn);
 | |
|     await click(PAGE.detail.createNewVersion);
 | |
|     assert.dom(FORM.toggleJson).isNotDisabled();
 | |
|     assert.dom(FORM.toggleJson).isNotChecked();
 | |
|   });
 | |
| });
 | |
| 
 | |
| // NAMESPACE TESTS
 | |
| module('Acceptance | Enterprise | kv-v2 workflow | edge cases', function (hooks) {
 | |
|   setupApplicationTest(hooks);
 | |
| 
 | |
|   const navToEngine = async (backend) => {
 | |
|     await click('[data-test-sidebar-nav-link="Secrets Engines"]');
 | |
|     return await click(PAGE.backends.link(backend));
 | |
|   };
 | |
| 
 | |
|   const assertDeleteActions = (assert, expected = ['delete', 'destroy']) => {
 | |
|     ['delete', 'destroy', 'undelete'].forEach((toolbar) => {
 | |
|       if (expected.includes(toolbar)) {
 | |
|         assert.dom(PAGE.detail[toolbar]).exists(`${toolbar} toolbar action exists`);
 | |
|       } else {
 | |
|         assert.dom(PAGE.detail[toolbar]).doesNotExist(`${toolbar} toolbar action not rendered`);
 | |
|       }
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   const assertVersionDropdown = async (assert, deleted = [], versions = [2, 1]) => {
 | |
|     assert.dom(PAGE.detail.versionDropdown).hasText(`Version ${versions[0]}`);
 | |
|     await click(PAGE.detail.versionDropdown);
 | |
|     versions.forEach((num) => {
 | |
|       assert.dom(PAGE.detail.version(num)).exists(`renders version ${num} link in dropdown`);
 | |
|     });
 | |
|     // also asserts destroyed icon
 | |
|     deleted.forEach((num) => {
 | |
|       assert.dom(`${PAGE.detail.version(num)} [data-test-icon="x-square"]`);
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   // each test uses a different secret path
 | |
|   hooks.beforeEach(async function () {
 | |
|     const uid = uuidv4();
 | |
|     this.store = this.owner.lookup('service:store');
 | |
|     this.backend = `kv-enterprise-edge-${uid}`;
 | |
|     this.namespace = `ns-${uid}`;
 | |
|     await authPage.login();
 | |
|     await runCmd([`write sys/namespaces/${this.namespace} -force`]);
 | |
|     return;
 | |
|   });
 | |
| 
 | |
|   hooks.afterEach(async function () {
 | |
|     await authPage.login();
 | |
|     await runCmd([`delete /sys/auth/${this.namespace}`]);
 | |
|     await runCmd(deleteEngineCmd(this.backend));
 | |
|     return;
 | |
|   });
 | |
| 
 | |
|   module('admin persona', function (hooks) {
 | |
|     hooks.beforeEach(async function () {
 | |
|       await authPage.loginNs(this.namespace);
 | |
|       // mount engine within namespace
 | |
|       await runCmd(mountEngineCmd('kv-v2', this.backend), false);
 | |
|       clearRecords(this.store);
 | |
|       return;
 | |
|     });
 | |
|     hooks.afterEach(async function () {
 | |
|       // visit logout with namespace query param because we're transitioning from within an engine
 | |
|       // and navigating directly to /vault/auth caused test context routing problems :(
 | |
|       await visit(`/vault/logout?namespace=${this.namespace}`);
 | |
|       await authPage.namespaceInput(''); // clear login form namespace input
 | |
|     });
 | |
| 
 | |
|     test('namespace: it can create a secret and new secret version', async function (assert) {
 | |
|       assert.expect(15);
 | |
|       const backend = this.backend;
 | |
|       const ns = this.namespace;
 | |
|       const secret = 'my-create-secret';
 | |
|       await navToEngine(backend);
 | |
|       assert.strictEqual(
 | |
|         currentURL(),
 | |
|         `/vault/secrets/${backend}/kv/list?namespace=${ns}`,
 | |
|         'navigates to list'
 | |
|       );
 | |
|       // Create first version of secret
 | |
|       await click(PAGE.list.createSecret);
 | |
|       await fillIn(FORM.inputByAttr('path'), secret);
 | |
|       assert.dom(FORM.toggleMetadata).exists('Shows metadata toggle when creating new secret');
 | |
|       await fillIn(FORM.keyInput(), 'foo');
 | |
|       await fillIn(FORM.maskedValueInput(), 'woahsecret');
 | |
|       await click(FORM.saveBtn);
 | |
|       assert.strictEqual(
 | |
|         currentURL(),
 | |
|         `/vault/secrets/${backend}/kv/${secret}/details?namespace=${ns}&version=1`,
 | |
|         'navigates to details'
 | |
|       );
 | |
| 
 | |
|       // Create a new version
 | |
|       await click(PAGE.detail.createNewVersion);
 | |
|       assert.dom(FORM.inputByAttr('path')).isDisabled('path input is disabled');
 | |
|       assert.dom(FORM.inputByAttr('path')).hasValue(secret);
 | |
|       assert.dom(FORM.toggleMetadata).doesNotExist('Does not show metadata toggle when creating new version');
 | |
|       assert.dom(FORM.keyInput()).hasValue('foo');
 | |
|       assert.dom(FORM.maskedValueInput()).hasValue('woahsecret');
 | |
|       await fillIn(FORM.keyInput(1), 'foo-two');
 | |
|       await fillIn(FORM.maskedValueInput(1), 'supersecret');
 | |
|       await click(FORM.saveBtn);
 | |
| 
 | |
|       // Check details
 | |
|       assert.strictEqual(
 | |
|         currentURL(),
 | |
|         `/vault/secrets/${backend}/kv/${secret}/details?namespace=${ns}&version=2`,
 | |
|         'navigates to details'
 | |
|       );
 | |
|       await assertVersionDropdown(assert);
 | |
|       assert
 | |
|         .dom(`${PAGE.detail.version(2)} [data-test-icon="check-circle"]`)
 | |
|         .exists('renders current version icon');
 | |
|       assert.dom(PAGE.infoRowValue('foo-two')).hasText('***********');
 | |
|       await click(PAGE.infoRowToggleMasked('foo-two'));
 | |
|       assert.dom(PAGE.infoRowValue('foo-two')).hasText('supersecret', 'secret value shows after toggle');
 | |
|     });
 | |
| 
 | |
|     test('namespace: it manages state throughout delete, destroy and undelete operations', async function (assert) {
 | |
|       assert.expect(34);
 | |
|       const backend = this.backend;
 | |
|       const ns = this.namespace;
 | |
|       const secret = 'my-delete-secret';
 | |
|       await writeVersionedSecret(backend, secret, 'foo', 'bar', 2, ns);
 | |
|       await navToEngine(backend);
 | |
| 
 | |
|       await click(PAGE.list.item(secret));
 | |
|       assert.strictEqual(
 | |
|         currentURL(),
 | |
|         `/vault/secrets/${backend}/kv/${secret}/details?namespace=${ns}&version=2`,
 | |
|         'navigates to details'
 | |
|       );
 | |
| 
 | |
|       // correct toolbar options & details show
 | |
|       assertDeleteActions(assert);
 | |
|       await assertVersionDropdown(assert);
 | |
|       // delete flow
 | |
|       await click(PAGE.detail.delete);
 | |
|       await click(PAGE.detail.deleteOption);
 | |
|       await click(PAGE.detail.deleteConfirm);
 | |
|       // check empty state and toolbar
 | |
|       assertDeleteActions(assert, ['undelete', 'destroy']);
 | |
|       assert
 | |
|         .dom(PAGE.emptyStateTitle)
 | |
|         .hasText('Version 2 of this secret has been deleted', 'Shows deleted message');
 | |
|       assert.dom(PAGE.detail.versionTimestamp).includesText('Version 2 deleted');
 | |
|       await assertVersionDropdown(assert, [2]); // important to test dropdown versions are accurate
 | |
| 
 | |
|       // navigate to sibling route to make sure empty state remains for details tab
 | |
|       await click(PAGE.secretTab('Version History'));
 | |
|       assert.dom(PAGE.versions.linkedBlock()).exists({ count: 2 });
 | |
| 
 | |
|       // back to secret tab to confirm deleted state
 | |
|       await click(PAGE.secretTab('Secret'));
 | |
|       // if this assertion fails, the view is rendering a stale model
 | |
|       assert.dom(PAGE.emptyStateTitle).exists('still renders empty state!!');
 | |
|       await assertVersionDropdown(assert, [2]);
 | |
| 
 | |
|       // undelete flow
 | |
|       await click(PAGE.detail.undelete);
 | |
|       // details update accordingly
 | |
|       assertDeleteActions(assert, ['delete', 'destroy']);
 | |
|       assert.dom(PAGE.infoRow).exists('shows secret data');
 | |
|       assert.dom(PAGE.detail.versionTimestamp).includesText('Version 2 created');
 | |
| 
 | |
|       // destroy flow
 | |
|       await click(PAGE.detail.destroy);
 | |
|       await click(PAGE.detail.deleteConfirm);
 | |
|       assertDeleteActions(assert, []);
 | |
|       assert
 | |
|         .dom(PAGE.emptyStateTitle)
 | |
|         .hasText('Version 2 of this secret has been permanently destroyed', 'Shows destroyed message');
 | |
| 
 | |
|       // navigate to sibling route to make sure empty state remains for details tab
 | |
|       await click(PAGE.secretTab('Version History'));
 | |
|       assert.dom(PAGE.versions.linkedBlock()).exists({ count: 2 });
 | |
| 
 | |
|       // back to secret tab to confirm destroyed state
 | |
|       await click(PAGE.secretTab('Secret'));
 | |
|       // if this assertion fails, the view is rendering a stale model
 | |
|       assert.dom(PAGE.emptyStateTitle).exists('still renders empty state!!');
 | |
|       await assertVersionDropdown(assert, [2]);
 | |
|     });
 | |
|   });
 | |
| });
 |