/** * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: BUSL-1.1 */ import { module, test } from 'qunit'; import { v4 as uuidv4 } from 'uuid'; import { click, currentRouteName, currentURL, typeIn, visit, waitUntil } from '@ember/test-helpers'; import { setupApplicationTest } from 'vault/tests/helpers'; import authPage from 'vault/tests/pages/auth'; import { createPolicyCmd, deleteEngineCmd, mountEngineCmd, runCmd, createTokenCmd, tokenWithPolicyCmd, } from 'vault/tests/helpers/commands'; import { personas } from 'vault/tests/helpers/kv/policy-generator'; import { addSecretMetadataCmd, clearRecords, writeSecret, writeVersionedSecret, } from 'vault/tests/helpers/kv/kv-run-commands'; import { FORM, PAGE } from 'vault/tests/helpers/kv/kv-selectors'; import { setupControlGroup, grantAccess } from 'vault/tests/helpers/control-groups'; const secretPath = `my-#:$=?-secret`; // This doesn't encode in a normal way, so hardcoding it here until we sort that out const secretPathUrlEncoded = `my-%23:$=%3F-secret`; const navToBackend = async (backend) => { await visit(`/vault/secrets`); return click(PAGE.backends.link(backend)); }; const assertCorrectBreadcrumbs = (assert, expected) => { assert.dom(PAGE.breadcrumb).exists({ count: expected.length }, 'correct number of breadcrumbs'); const breadcrumbs = document.querySelectorAll(PAGE.breadcrumb); expected.forEach((text, idx) => { 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 .dom(PAGE.toolbarAction) .exists({ count: expected.length }, 'correct number of toolbar actions render'); DETAIL_TOOLBARS.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`); } }); }; /** * This test set is for testing the navigation, breadcrumbs, and tabs. * 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 | navigation', function (hooks) { setupApplicationTest(hooks); hooks.beforeEach(async function () { const uid = uuidv4(); this.store = this.owner.lookup('service:store'); this.emptyBackend = `kv-empty-${uid}`; this.backend = `kv-nav-${uid}`; await authPage.login(); await runCmd(mountEngineCmd('kv-v2', this.emptyBackend), false); await runCmd(mountEngineCmd('kv-v2', this.backend), false); await writeSecret(this.backend, 'app/nested/secret', 'foo', 'bar'); await writeVersionedSecret(this.backend, secretPath, 'foo', 'bar', 3); await runCmd(addSecretMetadataCmd(this.backend, secretPath, { max_versions: 5, cas_required: true })); return; }); hooks.afterEach(async function () { await authPage.login(); await runCmd(deleteEngineCmd(this.backend)); await runCmd(deleteEngineCmd(this.emptyBackend)); return; }); module('admin persona', function (hooks) { hooks.beforeEach(async function () { const token = await runCmd( tokenWithPolicyCmd('admin', personas.admin(this.backend) + personas.admin(this.emptyBackend)) ); await authPage.login(token); clearRecords(this.store); return; }); test('empty backend - breadcrumbs, title, tabs, emptyState (a)', async function (assert) { assert.expect(15); const backend = this.emptyBackend; await navToBackend(backend); // URL correct assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list`, 'lands on secrets list page'); // Breadcrumbs correct assertCorrectBreadcrumbs(assert, ['secrets', backend]); // 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.toolbar).exists({ count: 1 }, 'toolbar renders'); assert.dom(PAGE.list.filter).doesNotExist('List filter does not show because no secrets exists.'); // Page content correct assert.dom(PAGE.emptyStateTitle).hasText('No secrets yet'); assert.dom(PAGE.list.createSecret).hasText('Create secret'); // click toolbar CTA await click(PAGE.list.createSecret); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/create`), `url includes /vault/secrets/${backend}/kv/create` ); // Click cancel btn await click(FORM.cancelBtn); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list`), `url includes /vault/secrets/${backend}/kv/list` ); }); test('can access nested secret (a)', async function (assert) { assert.expect(40); const backend = this.backend; await navToBackend(backend); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'title text correct'); assert.dom(PAGE.emptyStateTitle).doesNotExist('No empty state'); assertCorrectBreadcrumbs(assert, ['secret', backend]); assert.dom(PAGE.list.filter).hasNoValue('List filter input is empty'); // Navigate through list items await click(PAGE.list.item('app/')); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list/app/`); assertCorrectBreadcrumbs(assert, ['secret', backend, 'app']); assert.dom(PAGE.title).hasText(`${backend} version 2`); assert.dom(PAGE.list.filter).hasValue('app/', 'List filter input is prefilled'); assert.dom(PAGE.list.item('nested/')).exists('Shows nested secret'); await click(PAGE.list.item('nested/')); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list/app/nested/`); assertCorrectBreadcrumbs(assert, ['secret', backend, 'app', 'nested']); assert.dom(PAGE.title).hasText(`${backend} version 2`); assert.dom(PAGE.list.filter).hasValue('app/nested/', 'List filter input is prefilled'); assert.dom(PAGE.list.item('secret')).exists('Shows deeply nested secret'); await click(PAGE.list.item('secret')); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/app%2Fnested%2Fsecret/details?version=1` ); assertCorrectBreadcrumbs(assert, ['secret', backend, 'app', 'nested', 'secret']); assert.dom(PAGE.title).hasText('app/nested/secret', 'title is full secret path'); assertDetailsToolbar(assert); await click(PAGE.breadcrumbAtIdx(3)); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list/app/nested/`), 'links back to list directory' ); await click(PAGE.breadcrumbAtIdx(2)); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list/app/`), 'links back to list directory' ); await click(PAGE.breadcrumbAtIdx(1)); assert.ok(currentURL().startsWith(`/vault/secrets/${backend}/kv/list`), 'links back to list root'); }); test('it redirects from LIST, SHOW and EDIT views using old non-engine url to ember engine url (a)', async function (assert) { assert.expect(4); const backend = this.backend; // create with initialKey await visit(`/vault/secrets/${backend}/create/test`); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/create?initialKey=test`); // Reported bug, backported fix https://github.com/hashicorp/vault/pull/24281 // list for directory await visit(`/vault/secrets/${backend}/list/app/`); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list/app/`); // show for secret await visit(`/vault/secrets/${backend}/show/app/nested/secret`); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/app%2Fnested%2Fsecret/details?version=1` ); // edit for secret await visit(`/vault/secrets/${backend}/edit/app/nested/secret`); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/app%2Fnested%2Fsecret/details?version=1` ); }); test('versioned secret nav, tabs, breadcrumbs (a)', async function (assert) { assert.expect(45); const backend = this.backend; await navToBackend(backend); await click(PAGE.list.item(secretPath)); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details?version=3`, 'Url includes version query param' ); 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'); assert.dom(PAGE.infoRowValue('foo')).exists('renders current data'); await click(PAGE.detail.createNewVersion); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details/edit?version=3`, 'Url includes version query param' ); assert.dom(FORM.versionAlert).doesNotExist('Does not show version alert for current version'); assert.dom(FORM.inputByAttr('path')).isDisabled(); await click(FORM.cancelBtn); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details?version=3`, 'Goes back to detail view' ); await click(PAGE.detail.versionDropdown); await click(`${PAGE.detail.version(1)} a`); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details?version=1`, 'Goes to detail view for version 1' ); assert.dom(PAGE.detail.versionDropdown).hasText('Version 1', 'Version dropdown shows selected version'); assert.dom(PAGE.detail.versionTimestamp).containsText('Version 1 created'); assert.dom(PAGE.infoRowValue('key-1')).exists('renders previous data'); await click(PAGE.detail.createNewVersion); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details/edit?version=1`, 'Url includes version query param' ); assert.dom(FORM.inputByAttr('path')).isDisabled(); assert.dom(FORM.keyInput()).hasValue('key-1', 'pre-populates form with selected version data'); assert.dom(FORM.maskedValueInput()).hasValue('val-1', 'pre-populates form with selected version data'); assert.dom(FORM.versionAlert).exists('Shows version alert'); await click(FORM.cancelBtn); await click(PAGE.secretTab('Metadata')); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/metadata`, `goes to metadata page` ); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata']); assert.dom(PAGE.title).hasText(secretPath); assert .dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`) .hasText('No custom metadata'); assert .dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateActions}`) .hasText('Add metadata', 'empty state has metadata CTA'); assert.dom(PAGE.metadata.editBtn).hasText('Edit metadata'); await click(PAGE.metadata.editBtn); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/metadata/edit`, `goes to metadata edit page` ); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata', 'edit']); await click(FORM.cancelBtn); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/metadata`, `cancel btn goes back to metadata page` ); }); test('breadcrumbs & page titles are correct (a)', async function (assert) { assert.expect(45); const backend = this.backend; await navToBackend(backend); await click(PAGE.secretTab('Configuration')); assertCorrectBreadcrumbs(assert, ['secrets', backend, 'configuration']); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'correct page title for configuration'); await click(PAGE.secretTab('Secrets')); assertCorrectBreadcrumbs(assert, ['secrets', backend]); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'correct page title for secret list'); await click(PAGE.list.item(secretPath)); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath]); assert.dom(PAGE.title).hasText(secretPath, 'correct page title for secret detail'); await click(PAGE.detail.createNewVersion); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'edit']); assert.dom(PAGE.title).hasText('Create New Version', 'correct page title for secret edit'); await click(PAGE.breadcrumbAtIdx(2)); await click(PAGE.secretTab('Metadata')); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata']); assert.dom(PAGE.title).hasText(secretPath, 'correct page title for metadata'); await click(PAGE.metadata.editBtn); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata', 'edit']); 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'); }); }); module('data-reader persona', function (hooks) { hooks.beforeEach(async function () { const token = await runCmd([ createPolicyCmd( `data-reader-${this.backend}`, personas.dataReader(this.backend) + personas.dataReader(this.emptyBackend) ), createTokenCmd(`data-reader-${this.backend}`), ]); await authPage.login(token); clearRecords(this.store); return; }); test('empty backend - breadcrumbs, title, tabs, emptyState (dr)', async function (assert) { assert.expect(16); const backend = this.emptyBackend; await navToBackend(backend); // URL correct assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list`, 'lands on secrets list page'); // Breadcrumbs correct assertCorrectBreadcrumbs(assert, ['secrets', backend]); // 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.toolbar).exists({ count: 1 }, 'toolbar renders'); assert .dom(PAGE.list.filter) .doesNotExist('list filter input does not render because no list capabilities'); // Page content correct assert .dom(PAGE.emptyStateTitle) .doesNotExist('empty state does not render because no metadata access to list'); assert.dom(PAGE.list.overviewCard).exists('renders overview card'); await typeIn(PAGE.list.overviewInput, 'directory/'); await click(PAGE.list.overviewButton); assert .dom('[data-test-inline-error-message]') .hasText('You do not have the required permissions or the directory does not exist.'); // click toolbar CTA await visit(`/vault/secrets/${backend}/kv/list`); await click(PAGE.list.createSecret); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/create`, `url includes /vault/secrets/${backend}/kv/create` ); // Click cancel btn await click(FORM.cancelBtn); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/list`, `url includes /vault/secrets/${backend}/kv/list` ); }); test('can access nested secret (dr)', async function (assert) { assert.expect(23); const backend = this.backend; await navToBackend(backend); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'title text correct'); assert.dom(PAGE.emptyStateTitle).doesNotExist('No empty state'); assertCorrectBreadcrumbs(assert, ['secret', backend]); assert .dom(PAGE.list.filter) .doesNotExist('List filter input does not render because no list capabilities'); await typeIn(PAGE.list.overviewInput, 'app/nested/secret'); await click(PAGE.list.overviewButton); // Goes to correct detail view assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/app%2Fnested%2Fsecret/details?version=1` ); assertCorrectBreadcrumbs(assert, ['secret', backend, 'app', 'nested', 'secret']); assert.dom(PAGE.title).hasText('app/nested/secret', 'title is full secret path'); assertDetailsToolbar(assert, ['copy']); await click(PAGE.breadcrumbAtIdx(3)); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list/app/nested/`), 'links back to list directory' ); await click(PAGE.breadcrumbAtIdx(2)); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list/app/`), 'links back to list directory' ); await click(PAGE.breadcrumbAtIdx(1)); 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(28); const backend = this.backend; await navToBackend(backend); // Navigate to secret await typeIn(PAGE.list.overviewInput, secretPath); await click(PAGE.list.overviewButton); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details?version=3`, 'Url includes version query param' ); assert.dom(PAGE.title).hasText(secretPath, 'Goes to secret detail view'); 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'); assert.dom(PAGE.infoRowValue('foo')).exists('renders current data'); // data-reader can't navigate to older versions, but they can go to page directly await visit(`/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details?version=1`); assert.dom(PAGE.detail.versionDropdown).doesNotExist('Version dropdown does not exist'); assert.dom(PAGE.detail.versionTimestamp).containsText('Version 1 created'); assert.dom(PAGE.infoRowValue('key-1')).exists('renders previous data'); assert.dom(PAGE.detail.createNewVersion).doesNotExist('cannot create new version'); await click(PAGE.secretTab('Metadata')); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/metadata`, `goes to metadata page` ); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata']); assert.dom(PAGE.title).hasText(secretPath); assert.dom(PAGE.toolbarAction).doesNotExist('no toolbar actions available on metadata'); assert .dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`) .hasText('No custom metadata'); assert .dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.emptyStateTitle}`) .hasText('You do not have access to secret metadata'); assert.dom(PAGE.metadata.editBtn).doesNotExist('edit button hidden'); }); test('breadcrumbs & page titles are correct (dr)', async function (assert) { assert.expect(35); const backend = this.backend; await navToBackend(backend); await click(PAGE.secretTab('Configuration')); assertCorrectBreadcrumbs(assert, ['secrets', backend, 'configuration']); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'title correct on config page'); await click(PAGE.secretTab('Secrets')); assertCorrectBreadcrumbs(assert, ['secrets', backend]); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'title correct on secrets list'); await typeIn(PAGE.list.overviewInput, 'app/nested/secret'); await click(PAGE.list.overviewButton); assertCorrectBreadcrumbs(assert, ['secrets', backend, 'app', 'nested', 'secret']); assert.dom(PAGE.title).hasText('app/nested/secret', 'title correct on secret detail'); assert.dom(PAGE.detail.createNewVersion).doesNotExist('cannot create new version'); await click(PAGE.secretTab('Metadata')); assertCorrectBreadcrumbs(assert, ['secrets', backend, 'app', 'nested', 'secret', 'metadata']); assert.dom(PAGE.title).hasText('app/nested/secret', 'title correct on metadata'); assert.dom(PAGE.metadata.editBtn).doesNotExist('cannot edit metadata'); 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'); }); }); module('data-list-reader persona', function (hooks) { hooks.beforeEach(async function () { const token = await runCmd([ createPolicyCmd( `data-reader-list-${this.backend}`, personas.dataListReader(this.backend) + personas.dataListReader(this.emptyBackend) ), createTokenCmd(`data-reader-list-${this.backend}`), ]); await authPage.login(token); clearRecords(this.store); return; }); test('empty backend - breadcrumbs, title, tabs, emptyState (dlr)', async function (assert) { assert.expect(15); const backend = this.emptyBackend; await navToBackend(backend); // URL correct assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list`, 'lands on secrets list page'); // Breadcrumbs correct assertCorrectBreadcrumbs(assert, ['secrets', backend]); // 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.toolbar).exists({ count: 1 }, 'toolbar renders'); assert.dom(PAGE.list.filter).doesNotExist('List filter does not show because no secrets exists.'); // Page content correct assert.dom(PAGE.emptyStateTitle).hasText('No secrets yet'); assert.dom(PAGE.list.createSecret).hasText('Create secret'); // click toolbar CTA await click(PAGE.list.createSecret); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/create`), `url includes /vault/secrets/${backend}/kv/create` ); // Click cancel btn await click(FORM.cancelBtn); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list`), `url includes /vault/secrets/${backend}/kv/list` ); }); test('can access nested secret (dlr)', async function (assert) { assert.expect(31); const backend = this.backend; await navToBackend(backend); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'title text correct'); assert.dom(PAGE.emptyStateTitle).doesNotExist('No empty state'); assertCorrectBreadcrumbs(assert, ['secret', backend]); assert.dom(PAGE.list.filter).hasNoValue('List filter input is empty'); // Navigate through list items await click(PAGE.list.item('app/')); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list/app/`); assertCorrectBreadcrumbs(assert, ['secret', backend, 'app']); assert.dom(PAGE.title).hasText(`${backend} version 2`); assert.dom(PAGE.list.filter).doesNotExist('List filter hidden since no nested list access'); assert .dom(PAGE.list.overviewInput) .hasValue('app/', 'overview card is pre-filled with directory param'); await typeIn(PAGE.list.overviewInput, 'nested/secret'); await click(PAGE.list.overviewButton); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/app%2Fnested%2Fsecret/details?version=1` ); assertCorrectBreadcrumbs(assert, ['secret', backend, 'app', 'nested', 'secret']); assert.dom(PAGE.title).hasText('app/nested/secret', 'title is full secret path'); assertDetailsToolbar(assert, ['delete', 'copy']); await click(PAGE.breadcrumbAtIdx(3)); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list/app/nested/`), 'links back to list directory' ); await click(PAGE.breadcrumbAtIdx(2)); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list/app/`), 'links back to list directory' ); await click(PAGE.breadcrumbAtIdx(1)); 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(28); const backend = this.backend; await navToBackend(backend); await click(PAGE.list.item(secretPath)); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details?version=3`, 'Url includes version query param' ); assert.dom(PAGE.title).hasText(secretPath, 'Goes to secret detail view'); 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'); assert.dom(PAGE.infoRowValue('foo')).exists('renders current data'); assert.dom(PAGE.detail.createNewVersion).doesNotExist('cannot create new version'); // data-list-reader can't navigate to older versions, but they can go to page directly await visit(`/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details?version=1`); assert.dom(PAGE.detail.versionDropdown).doesNotExist('no version dropdown'); assert.dom(PAGE.detail.versionTimestamp).containsText('Version 1 created'); assert.dom(PAGE.infoRowValue('key-1')).exists('renders previous data'); assert.dom(PAGE.detail.createNewVersion).doesNotExist('cannot create new version from old version'); await click(PAGE.secretTab('Metadata')); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/metadata`, `goes to metadata page` ); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata']); assert.dom(PAGE.title).hasText(secretPath); assert .dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`) .hasText('No custom metadata'); assert .dom(`${PAGE.metadata.secretMetadataSection} ${PAGE.emptyStateTitle}`) .hasText('You do not have access to secret metadata'); assert.dom(PAGE.metadata.editBtn).doesNotExist('edit button hidden'); }); test('breadcrumbs & page titles are correct (dlr)', async function (assert) { assert.expect(29); const backend = this.backend; await navToBackend(backend); await click(PAGE.secretTab('Configuration')); assertCorrectBreadcrumbs(assert, ['secrets', backend, 'configuration']); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'correct page title for configuration'); await click(PAGE.secretTab('Secrets')); assertCorrectBreadcrumbs(assert, ['secrets', backend]); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'correct page title for secret list'); await click(PAGE.list.item(secretPath)); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath]); assert.dom(PAGE.title).hasText(secretPath, 'correct page title for secret detail'); assert.dom(PAGE.detail.createNewVersion).doesNotExist('cannot create new version'); await click(PAGE.secretTab('Metadata')); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata']); assert.dom(PAGE.title).hasText(secretPath, 'correct page title for metadata'); assert.dom(PAGE.metadata.editBtn).doesNotExist('cannot edit metadata'); 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'); }); }); module('metadata-maintainer persona', function (hooks) { hooks.beforeEach(async function () { const token = await runCmd([ createPolicyCmd( `metadata-maintainer-${this.backend}`, personas.metadataMaintainer(this.backend) + personas.metadataMaintainer(this.emptyBackend) ), createTokenCmd(`metadata-maintainer-${this.backend}`), ]); await authPage.login(token); clearRecords(this.store); return; }); test('empty backend - breadcrumbs, title, tabs, emptyState (mm)', async function (assert) { assert.expect(15); const backend = this.emptyBackend; await navToBackend(backend); // URL correct assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list`, 'lands on secrets list page'); // Breadcrumbs correct assertCorrectBreadcrumbs(assert, ['secrets', backend]); // 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.toolbar).exists({ count: 1 }, 'toolbar only renders create secret action'); assert.dom(PAGE.list.filter).doesNotExist('List filter does not show because no secrets exists.'); // Page content correct assert.dom(PAGE.emptyStateTitle).hasText('No secrets yet'); assert.dom(PAGE.list.createSecret).hasText('Create secret'); // click toolbar CTA await click(PAGE.list.createSecret); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/create`), `url includes /vault/secrets/${backend}/kv/create` ); // Click cancel btn await click(FORM.cancelBtn); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list`), `url includes /vault/secrets/${backend}/kv/list` ); }); test('can access nested secret (mm)', async function (assert) { assert.expect(41); const backend = this.backend; await navToBackend(backend); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'title text correct'); assert.dom(PAGE.emptyStateTitle).doesNotExist('No empty state'); assertCorrectBreadcrumbs(assert, ['secret', backend]); assert.dom(PAGE.list.filter).hasNoValue('List filter input is empty'); // Navigate through list items await click(PAGE.list.item('app/')); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list/app/`); assertCorrectBreadcrumbs(assert, ['secret', backend, 'app']); assert.dom(PAGE.title).hasText(`${backend} version 2`); assert.dom(PAGE.list.filter).hasValue('app/', 'List filter input is prefilled'); assert.dom(PAGE.list.item('nested/')).exists('Shows nested secret'); await click(PAGE.list.item('nested/')); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list/app/nested/`); assertCorrectBreadcrumbs(assert, ['secret', backend, 'app', 'nested']); assert.dom(PAGE.title).hasText(`${backend} version 2`); assert.dom(PAGE.list.filter).hasValue('app/nested/', 'List filter input is prefilled'); assert.dom(PAGE.list.item('secret')).exists('Shows deeply nested secret'); await click(PAGE.list.item('secret')); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/app%2Fnested%2Fsecret/details`, `Goes to URL with version` ); assertCorrectBreadcrumbs(assert, ['secret', backend, 'app', 'nested', 'secret']); assert.dom(PAGE.title).hasText('app/nested/secret', 'title is full secret path'); assertDetailsToolbar(assert, ['delete', 'destroy', 'versionDropdown']); assert.dom(PAGE.detail.versionDropdown).hasText('Version 1', 'Shows version timestamp'); await click(PAGE.breadcrumbAtIdx(3)); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list/app/nested/`), 'links back to list directory' ); await click(PAGE.breadcrumbAtIdx(2)); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list/app/`), 'links back to list directory' ); await click(PAGE.breadcrumbAtIdx(1)); 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(37); const backend = this.backend; await navToBackend(backend); await click(PAGE.list.item(secretPath)); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details`, 'Url includes version query param' ); assert.dom(PAGE.title).hasText(secretPath, 'Goes to secret detail view'); 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'); assert.dom(PAGE.infoRowValue('foo')).doesNotExist('does not render current data'); assert .dom(PAGE.emptyStateTitle) .hasText('You do not have permission to read this secret', 'Shows empty state on secret detail'); await click(PAGE.detail.versionDropdown); await click(`${PAGE.detail.version(1)} a`); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details?version=1`, 'Goes to detail view for version 1' ); assert.dom(PAGE.detail.versionDropdown).hasText('Version 1', 'Version dropdown shows selected version'); assert.dom(PAGE.infoRowValue('key-1')).doesNotExist('does not render previous data'); assert .dom(PAGE.emptyStateTitle) .hasText( 'You do not have permission to read this secret', 'Shows empty state on secret detail for older version' ); await click(PAGE.secretTab('Metadata')); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/metadata`, `goes to metadata page` ); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata']); assert.dom(PAGE.title).hasText(secretPath); assert .dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`) .hasText('No custom metadata'); assert .dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateActions}`) .hasText('Add metadata', 'empty state has metadata CTA'); assert.dom(PAGE.metadata.editBtn).hasText('Edit metadata'); await click(PAGE.metadata.editBtn); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/metadata/edit`, `goes to metadata edit page` ); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata', 'edit']); await click(FORM.cancelBtn); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/metadata`, `cancel btn goes back to metadata page` ); }); test('breadcrumbs & page titles are correct (mm)', async function (assert) { assert.expect(39); const backend = this.backend; await navToBackend(backend); await click(PAGE.secretTab('Configuration')); assertCorrectBreadcrumbs(assert, ['secrets', backend, 'configuration']); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'correct page title for configuration'); await click(PAGE.secretTab('Secrets')); assertCorrectBreadcrumbs(assert, ['secrets', backend]); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'correct page title for secret list'); await click(PAGE.list.item(secretPath)); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath]); assert.dom(PAGE.title).hasText(secretPath, 'correct page title for secret detail'); await click(PAGE.secretTab('Metadata')); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata']); assert.dom(PAGE.title).hasText(secretPath, 'correct page title for metadata'); await click(PAGE.metadata.editBtn); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata', 'edit']); 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'); }); }); module('secret-creator persona', function (hooks) { hooks.beforeEach(async function () { const token = await runCmd([ createPolicyCmd( `secret-creator-${this.backend}`, personas.secretCreator(this.backend) + personas.secretCreator(this.emptyBackend) ), createTokenCmd(`secret-creator-${this.backend}`), ]); await authPage.login(token); clearRecords(this.store); return; }); test('empty backend - breadcrumbs, title, tabs, emptyState (sc)', async function (assert) { assert.expect(15); const backend = this.emptyBackend; await navToBackend(backend); // URL correct assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list`, 'lands on secrets list page'); // Breadcrumbs correct assertCorrectBreadcrumbs(assert, ['secrets', backend]); // 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.toolbar).exists({ count: 1 }, 'toolbar only renders create secret action'); assert.dom(PAGE.list.filter).doesNotExist('List filter input is not rendered'); // Page content correct assert.dom(PAGE.list.overviewCard).exists('Overview card renders'); assert.dom(PAGE.list.createSecret).hasText('Create secret'); // click toolbar CTA await click(PAGE.list.createSecret); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/create`, `goes to /vault/secrets/${backend}/kv/create` ); // Click cancel btn await click(FORM.cancelBtn); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/list`, `url includes /vault/secrets/${backend}/kv/list` ); }); test('can access nested secret (sc)', async function (assert) { assert.expect(23); const backend = this.backend; await navToBackend(backend); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'title text correct'); assert.dom(PAGE.emptyStateTitle).doesNotExist('No empty state'); assertCorrectBreadcrumbs(assert, ['secret', backend]); assert.dom(PAGE.list.filter).doesNotExist('List filter input is not rendered'); // Navigate to secret await typeIn(PAGE.list.overviewInput, 'app/nested/secret'); await click(PAGE.list.overviewButton); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/app%2Fnested%2Fsecret/details`, 'goes to secret detail page' ); assertCorrectBreadcrumbs(assert, ['secret', backend, 'app', 'nested', 'secret']); assert.dom(PAGE.title).hasText('app/nested/secret', 'title is full secret path'); assertDetailsToolbar(assert, ['createNewVersion']); await click(PAGE.breadcrumbAtIdx(3)); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list/app/nested/`), 'links back to list directory' ); await click(PAGE.breadcrumbAtIdx(2)); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list/app/`), 'links back to list directory' ); await click(PAGE.breadcrumbAtIdx(1)); 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(36); const backend = this.backend; await navToBackend(backend); await typeIn(PAGE.list.overviewInput, secretPath); await click(PAGE.list.overviewButton); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details`, 'Goes to detail view' ); assert.dom(PAGE.title).hasText(secretPath, 'Goes to secret detail view'); 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'); assert.dom(PAGE.infoRowValue('foo')).doesNotExist('current data not rendered'); assert .dom(PAGE.emptyStateTitle) .hasText('You do not have permission to read this secret', 'empty state shows'); await click(PAGE.detail.createNewVersion); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details/edit`, 'Goes to edit page' ); assert.dom(FORM.versionAlert).doesNotExist('Does not show version alert for current version'); assert .dom(FORM.noReadAlert) .hasText( 'Warning You do not have read permissions for this secret data. Saving will overwrite the existing secret.', 'Shows warning about no read permissions' ); assert.dom(FORM.inputByAttr('path')).isDisabled(); await click(FORM.cancelBtn); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details`, 'Goes back to detail view' ); await visit(`/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details?version=1`); assert.dom(PAGE.detail.versionDropdown).doesNotExist('Version dropdown does not exist'); assert.dom(PAGE.detail.versionTimestamp).doesNotExist('version created data not rendered'); assert.dom(PAGE.infoRowValue('key-1')).doesNotExist('does not render previous data'); await click(PAGE.detail.createNewVersion); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details/edit?version=1`, 'Url includes version query param' ); assert.dom(FORM.inputByAttr('path')).isDisabled(); assert.dom(FORM.keyInput()).hasValue('', 'form does not pre-populate'); assert.dom(FORM.maskedValueInput()).hasValue('', 'form does not pre-populate'); assert.dom(FORM.noReadAlert).exists('Shows no read alert'); await click(FORM.cancelBtn); await click(PAGE.secretTab('Metadata')); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/metadata`, `goes to metadata page` ); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata']); assert.dom(PAGE.title).hasText(secretPath); assert .dom(`${PAGE.metadata.customMetadataSection} ${PAGE.emptyStateTitle}`) .hasText('You do not have access to read custom metadata', 'shows correct empty state'); assert.dom(PAGE.metadata.editBtn).doesNotExist('edit metadata button does not render'); }); test('breadcrumbs & page titles are correct (sc)', async function (assert) { assert.expect(34); const backend = this.backend; await navToBackend(backend); await click(PAGE.secretTab('Configuration')); assertCorrectBreadcrumbs(assert, ['secrets', backend, 'configuration']); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'correct page title for configuration'); await click(PAGE.secretTab('Secrets')); assertCorrectBreadcrumbs(assert, ['secrets', backend]); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'correct page title for secret list'); await typeIn(PAGE.list.overviewInput, secretPath); await click(PAGE.list.overviewButton); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath]); assert.dom(PAGE.title).hasText(secretPath, 'correct page title for secret detail'); await click(PAGE.detail.createNewVersion); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'edit']); assert.dom(PAGE.title).hasText('Create New Version', 'correct page title for secret edit'); await click(PAGE.breadcrumbAtIdx(2)); await click(PAGE.secretTab('Metadata')); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata']); assert.dom(PAGE.title).hasText(secretPath, 'correct page title for metadata'); 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'); }); }); module('enterprise controlled access persona', function (hooks) { hooks.beforeEach(async function () { // Set up control group scenario const userPolicy = ` path "${this.backend}/data/*" { capabilities = ["create", "read", "update", "delete", "list"] control_group = { max_ttl = "24h" factor "ops_manager" { controlled_capabilities = ["read"] identity { group_names = ["managers"] approvals = 1 } } } } path "${this.backend}/*" { capabilities = ["list"] } `; const { userToken } = await setupControlGroup({ userPolicy, backend: this.backend }); this.userToken = userToken; await authPage.login(userToken); clearRecords(this.store); return; }); test('can access nested secret (cg)', async function (assert) { assert.expect(42); const backend = this.backend; await navToBackend(backend); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'title text correct'); assert.dom(PAGE.emptyStateTitle).doesNotExist('No empty state'); assertCorrectBreadcrumbs(assert, ['secret', backend]); assert.dom(PAGE.list.filter).hasNoValue('List filter input is empty'); // Navigate through list items await click(PAGE.list.item('app/')); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list/app/`); assertCorrectBreadcrumbs(assert, ['secret', backend, 'app']); assert.dom(PAGE.title).hasText(`${backend} version 2`); assert.dom(PAGE.list.filter).hasValue('app/', 'List filter input is prefilled'); assert.dom(PAGE.list.item('nested/')).exists('Shows nested secret'); await click(PAGE.list.item('nested/')); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/kv/list/app/nested/`); assertCorrectBreadcrumbs(assert, ['secret', backend, 'app', 'nested']); assert.dom(PAGE.title).hasText(`${backend} version 2`); assert.dom(PAGE.list.filter).hasValue('app/nested/', 'List filter input is prefilled'); assert.dom(PAGE.list.item('secret')).exists('Shows deeply nested secret'); // For some reason when we click on the item in tests it throws a global control group error // But not when we visit the page directly await visit(`/vault/secrets/${backend}/kv/app%2Fnested%2Fsecret/details`); assert.ok( await waitUntil(() => currentRouteName() === 'vault.cluster.access.control-group-accessor'), 'redirects to access control group route' ); await grantAccess({ apiPath: `${backend}/data/app/nested/secret`, originUrl: `/vault/secrets/${backend}/kv/list/app/nested/`, userToken: this.userToken, backend: this.backend, }); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/list/app/nested/`, 'navigates to list url where secret is' ); await click(PAGE.list.item('secret')); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/app%2Fnested%2Fsecret/details?version=1`, 'goes to secret details' ); assertCorrectBreadcrumbs(assert, ['secret', backend, 'app', 'nested', 'secret']); assert.dom(PAGE.title).hasText('app/nested/secret', 'title is full secret path'); assertDetailsToolbar(assert, ['delete', 'copy', 'createNewVersion']); await click(PAGE.breadcrumbAtIdx(3)); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list/app/nested/`), 'links back to list directory' ); await click(PAGE.breadcrumbAtIdx(2)); assert.ok( currentURL().startsWith(`/vault/secrets/${backend}/kv/list/app/`), 'links back to list directory' ); await click(PAGE.breadcrumbAtIdx(1)); 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(36); const backend = this.backend; await navToBackend(backend); await click(PAGE.secretTab('Configuration')); assertCorrectBreadcrumbs(assert, ['secrets', backend, 'configuration']); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'correct page title for configuration'); await click(PAGE.secretTab('Secrets')); assertCorrectBreadcrumbs(assert, ['secrets', backend]); assert.dom(PAGE.title).hasText(`${backend} version 2`, 'correct page title for secret list'); await visit(`/vault/secrets/${backend}/kv/${secretPathUrlEncoded}/details`); assert.ok( await waitUntil(() => currentRouteName() === 'vault.cluster.access.control-group-accessor'), 'redirects to access control group route' ); await grantAccess({ apiPath: `${backend}/data/${encodeURIComponent(secretPath)}`, originUrl: `/vault/secrets/${backend}/kv/list`, userToken: this.userToken, backend: this.backend, }); assert.strictEqual( currentURL(), `/vault/secrets/${backend}/kv/list`, 'navigates back to list url after authorized' ); await click(PAGE.list.item(secretPath)); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath]); assert.dom(PAGE.title).hasText(secretPath, 'correct page title for secret detail'); await click(PAGE.secretTab('Metadata')); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'metadata']); assert.dom(PAGE.title).hasText(secretPath, 'correct page title for metadata'); 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')); await click(PAGE.detail.createNewVersion); assertCorrectBreadcrumbs(assert, ['secrets', backend, secretPath, 'edit']); assert.dom(PAGE.title).hasText('Create New Version', 'correct page title for secret edit'); }); }); });