mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
UI: kv-v2 version history & path tests (#22593)
This commit is contained in:
3
changelog/22593.txt
Normal file
3
changelog/22593.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
ui: JSON diff view available in "Create New Version" form for KV v2
|
||||
```
|
||||
@@ -12,6 +12,7 @@ import { tracked } from '@glimmer/tracking';
|
||||
/**
|
||||
* @module DiffVersionSelector
|
||||
* DiffVersionSelector component includes a toolbar and diff view between KV 2 versions. It uses the library jsondiffpatch.
|
||||
* TODO kv engine cleanup
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
|
||||
@@ -22,15 +22,6 @@
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/each}}
|
||||
{{! version diff }}
|
||||
{{#if (gt @metadata.sortedVersions.length 1)}}
|
||||
<hr />
|
||||
<li data-test-version="diff">
|
||||
<LinkTo @route="secret.metadata.diff" {{on "click" (fn @onClose D)}}>
|
||||
Version Diff
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</D.Content>
|
||||
|
||||
@@ -38,6 +38,29 @@
|
||||
@modelValidations={{this.modelValidations}}
|
||||
@isEdit={{true}}
|
||||
/>
|
||||
|
||||
<div class="has-top-margin-m">
|
||||
<Toggle
|
||||
@name="Show diff"
|
||||
@status="success"
|
||||
@size="small"
|
||||
@onChange={{fn (mut this.showDiff)}}
|
||||
@checked={{this.showDiff}}
|
||||
@disabled={{not this.diffDelta}}
|
||||
>
|
||||
<span class="ttl-picker-label is-large">Show diff</span><br />
|
||||
<div class="description has-text-grey" data-test-diff-description>{{if
|
||||
this.diffDelta
|
||||
"Showing the diff will reveal secret values"
|
||||
"No changes to show. Update secret to view diff"
|
||||
}}</div>
|
||||
{{#if this.showDiff}}
|
||||
<div class="form-section visual-diff text-grey-lightest background-color-black has-top-margin-s">
|
||||
<pre data-test-visual-diff>{{sanitized-html this.visualDiff}}</pre>
|
||||
</div>
|
||||
{{/if}}
|
||||
</Toggle>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
<KvPageHeader @breadcrumbs={{@breadcrumbs}} @pageTitle={{@path}}>
|
||||
<:toolbarFilters>
|
||||
{{! left side version selector }}
|
||||
<BasicDropdown @class="popup-menu" @horizontalPosition="auto-right" @verticalPosition="below" as |D|>
|
||||
<D.Trigger data-test-version-dropdown-left class="toolbar-link {{if D.isOpen ' is-active'}}">
|
||||
Version
|
||||
{{this.leftSideVersion}}
|
||||
<Chevron @direction="down" @isButton={{true}} />
|
||||
</D.Trigger>
|
||||
<D.Content @defaultClass="popup-menu-content">
|
||||
<nav class="box menu" aria-label="version-left">
|
||||
<ul class="menu-list">
|
||||
{{#each @metadata.sortedVersions as |versionData|}}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="link {{if (loose-equal versionData.version this.leftSideVersion) 'is-active'}}"
|
||||
{{on "click" (fn this.selectVersion versionData.version D.actions "left")}}
|
||||
disabled={{(or versionData.destroyed versionData.deletion_time)}}
|
||||
>
|
||||
Version
|
||||
{{versionData.version}}
|
||||
{{#if versionData.destroyed}}
|
||||
<Icon @name="x-square-fill" class="has-text-danger is-pulled-right" />
|
||||
{{else if versionData.deletion_time}}
|
||||
<Icon @name="x-square-fill" class="has-text-grey is-pulled-right" />
|
||||
{{else if (loose-equal @metadata.currentVersion versionData.version)}}
|
||||
<Icon @name="check-circle-fill" class="has-text-success is-pulled-right" />
|
||||
{{/if}}
|
||||
</button>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</nav>
|
||||
</D.Content>
|
||||
</BasicDropdown>
|
||||
{{! right side version selector }}
|
||||
<BasicDropdown @class="popup-menu" @horizontalPosition="auto-right" @verticalPosition="below" as |D|>
|
||||
<D.Trigger data-test-version-dropdown-right class="toolbar-link {{if D.isOpen ' is-active'}}">
|
||||
Version
|
||||
{{this.rightSideVersion}}
|
||||
<Chevron @direction="down" @isButton={{true}} />
|
||||
</D.Trigger>
|
||||
<D.Content @defaultClass="popup-menu-content">
|
||||
<nav class="box menu" aria-label="version-right">
|
||||
<ul class="menu-list">
|
||||
{{#each @metadata.sortedVersions as |versionData|}}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="link {{if (loose-equal versionData.version this.rightSideVersion) 'is-active'}}"
|
||||
{{on "click" (fn this.selectVersion versionData.version D.actions "right")}}
|
||||
disabled={{(or versionData.destroyed versionData.deletion_time)}}
|
||||
data-test-version-button={{versionData.version}}
|
||||
>
|
||||
Version
|
||||
{{versionData.version}}
|
||||
{{#if versionData.destroyed}}
|
||||
<Icon @name="x-square-fill" class="has-text-danger is-pulled-right" />
|
||||
{{else if versionData.deletion_time}}
|
||||
<Icon @name="x-square-fill" class="has-text-grey is-pulled-right" />
|
||||
{{else if (loose-equal @metadata.currentVersion versionData.version)}}
|
||||
<Icon @name="check-circle-fill" class="has-text-success is-pulled-right" />
|
||||
{{/if}}
|
||||
</button>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</nav>
|
||||
</D.Content>
|
||||
</BasicDropdown>
|
||||
{{! status icon }}
|
||||
{{#if this.statesMatch}}
|
||||
<div class="has-left-padding-s">
|
||||
<Icon @name="check-circle-fill" class="has-text-success" />
|
||||
<span>States match</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
</:toolbarFilters>
|
||||
</KvPageHeader>
|
||||
{{! 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)}}
|
||||
<EmptyState
|
||||
@title="Version {{@metadata.currentVersion}} of {{@path}} has been {{@metadata.currentSecret.state}}"
|
||||
@message="Please select another version of the secret to compare."
|
||||
/>
|
||||
{{else}}
|
||||
<div class="form-section visual-diff text-grey-lightest background-color-black">
|
||||
<pre data-test-visual-diff>{{sanitized-html this.visualDiff}}</pre>
|
||||
</div>
|
||||
{{/if}}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ export default buildRoutes(function () {
|
||||
this.route('metadata', function () {
|
||||
this.route('edit');
|
||||
this.route('versions');
|
||||
this.route('diff');
|
||||
});
|
||||
});
|
||||
this.route('configuration');
|
||||
|
||||
37
ui/lib/kv/addon/routes/secret/metadata.js
Normal file
37
ui/lib/kv/addon/routes/secret/metadata.js
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<Page::Secret::Metadata::VersionDiff
|
||||
@metadata={{this.model.metadata}}
|
||||
@path={{this.model.path}}
|
||||
@backend={{this.model.backend}}
|
||||
@breadcrumbs={{this.breadcrumbs}}
|
||||
/>
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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'));
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
@@ -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}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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]}`;
|
||||
|
||||
@@ -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}"]`),
|
||||
|
||||
@@ -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`<Page::Secret::Edit
|
||||
@secret={{this.secret}}
|
||||
@previousVersion={{4}}
|
||||
@currentVersion={{4}}
|
||||
@breadcrumbs={{this.breadcrumbs}}
|
||||
/>`,
|
||||
{ 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';
|
||||
|
||||
@@ -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`
|
||||
<Page::Secret::Metadata::VersionDiff
|
||||
@metadata={{this.metadata}}
|
||||
@path={{this.metadata.path}}
|
||||
@backend={{this.metadata.backend}}
|
||||
@breadcrumbs={{this.breadcrumbs}}
|
||||
/>
|
||||
`,
|
||||
{ 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.');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user