KV custom metadata test coverage (#12464)
* test coverage * small changes * another small change * fix test * browserstack blah * add page object
This commit is contained in:
parent
0704d5b2de
commit
88125d41ac
|
@ -136,8 +136,16 @@
|
||||||
|
|
||||||
{{#if (eq @mode "edit")}}
|
{{#if (eq @mode "edit")}}
|
||||||
<form onsubmit={{action "createOrUpdateKey" "edit"}}>
|
<form onsubmit={{action "createOrUpdateKey" "edit"}}>
|
||||||
<div class="box is-sideless is-fullwidth is-marginless is-paddingless">
|
<div class="box is-sideless is-fullwidth is-marginless padding-top">
|
||||||
|
{{#if @model.canReadSecretData}}
|
||||||
<MessageError @model={{@modelForData}} @errorMessage={{this.error}} />
|
<MessageError @model={{@modelForData}} @errorMessage={{this.error}} />
|
||||||
|
{{else}}
|
||||||
|
<AlertBanner
|
||||||
|
@type="warning"
|
||||||
|
@message="You do not have read permissions. If a secret exists here creating a new secret will overwrite it."
|
||||||
|
data-test-warning-no-read-permissions
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
<NamespaceReminder @mode="edit" @noun="secret" />
|
<NamespaceReminder @mode="edit" @noun="secret" />
|
||||||
{{#if this.isCreateNewVersionFromOldVersion}}
|
{{#if this.isCreateNewVersionFromOldVersion}}
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={{this.validationErrorCount}}
|
disabled={{this.validationErrorCount}}
|
||||||
class="button is-primary"
|
class="button is-primary"
|
||||||
|
data-test-save-metadata
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -55,7 +55,11 @@
|
||||||
@title="No custom metadata"
|
@title="No custom metadata"
|
||||||
@bottomBorder={{true}}
|
@bottomBorder={{true}}
|
||||||
@message="This data is version-agnostic and is usually used to describe the secret being stored.">
|
@message="This data is version-agnostic and is usually used to describe the secret being stored.">
|
||||||
<LinkTo @route="vault.cluster.secrets.backend.edit-metadata" @model={{this.model.id}}>
|
<LinkTo
|
||||||
|
@route="vault.cluster.secrets.backend.edit-metadata"
|
||||||
|
@model={{this.model.id}}
|
||||||
|
data-test-add-custom-metadata
|
||||||
|
>
|
||||||
Add metadata
|
Add metadata
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
</EmptyState>
|
</EmptyState>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<Icon @size="s" @glyph="minus-plain"/>
|
<Icon @size="s" @glyph="minus-plain"/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-flex" data-test-value-div>
|
<div class="column is-flex" data-test-value-div="{{label}}">
|
||||||
{{#if (has-block)}}
|
{{#if (has-block)}}
|
||||||
{{yield}}
|
{{yield}}
|
||||||
{{else if valueIsBoolean}}
|
{{else if valueIsBoolean}}
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
class="icon-true"
|
class="icon-true"
|
||||||
@size="l"
|
@size="l"
|
||||||
@glyph="check-circle-outline"
|
@glyph="check-circle-outline"
|
||||||
|
data-test-boolean-true
|
||||||
/> Yes
|
/> Yes
|
||||||
{{else}}
|
{{else}}
|
||||||
<Icon
|
<Icon
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
currentRouteName,
|
currentRouteName,
|
||||||
fillIn,
|
fillIn,
|
||||||
triggerKeyEvent,
|
triggerKeyEvent,
|
||||||
|
typeIn,
|
||||||
} from '@ember/test-helpers';
|
} from '@ember/test-helpers';
|
||||||
import { create } from 'ember-cli-page-object';
|
import { create } from 'ember-cli-page-object';
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
|
@ -69,15 +70,113 @@ module('Acceptance | secrets/secret/create', function(hooks) {
|
||||||
assert.ok(showPage.editIsPresent, 'shows the edit button');
|
assert.ok(showPage.editIsPresent, 'shows the edit button');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it can create a secret with a non default max version', async function(assert) {
|
test('it can create a secret with a non default max version and add metadata', async function(assert) {
|
||||||
let enginePath = `kv-${new Date().getTime()}`;
|
let enginePath = `kv-${new Date().getTime()}`;
|
||||||
let secretPath = 'maxVersions';
|
let secretPath = 'maxVersions';
|
||||||
let maxVersions = 101;
|
let maxVersions = 101;
|
||||||
await mountSecrets.visit();
|
await mountSecrets.visit();
|
||||||
await mountSecrets.enable('kv', enginePath);
|
await mountSecrets.enable('kv', enginePath);
|
||||||
await settled();
|
await settled();
|
||||||
await click('[data-test-secret-create="true"]');
|
await editPage.startCreateSecret();
|
||||||
await fillIn('[data-test-secret-path="true"]', secretPath);
|
await editPage.path(secretPath);
|
||||||
|
await editPage.toggleMetadata();
|
||||||
|
await settled();
|
||||||
|
await editPage.maxVersion(maxVersions);
|
||||||
|
await settled();
|
||||||
|
await editPage.save();
|
||||||
|
await settled();
|
||||||
|
await editPage.metadataTab();
|
||||||
|
await settled();
|
||||||
|
let savedMaxVersions = Number(
|
||||||
|
document.querySelector('[data-test-value-div="Maximum versions"]').innerText
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
maxVersions,
|
||||||
|
savedMaxVersions,
|
||||||
|
'max_version displays the saved number set when creating the secret'
|
||||||
|
);
|
||||||
|
// add metadata
|
||||||
|
await click('[data-test-add-custom-metadata]');
|
||||||
|
await fillIn('[data-test-kv-key]', 'key');
|
||||||
|
await fillIn('[data-test-kv-value]', 'value');
|
||||||
|
await click('[data-test-save-metadata]');
|
||||||
|
let key = document.querySelector('[data-test-row-label="key"]').innerText;
|
||||||
|
let value = document.querySelector('[data-test-row-value="key"]').innerText;
|
||||||
|
assert.equal(key, 'key', 'metadata key displays after adding it.');
|
||||||
|
assert.equal(value, 'value', 'metadata value displays after adding it.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it can handle validation on custom metadata', async function(assert) {
|
||||||
|
let enginePath = `kv-${new Date().getTime()}`;
|
||||||
|
let secretPath = 'customMetadataValidations';
|
||||||
|
|
||||||
|
await mountSecrets.visit();
|
||||||
|
await mountSecrets.enable('kv', enginePath);
|
||||||
|
await settled();
|
||||||
|
await editPage.startCreateSecret();
|
||||||
|
await editPage.path(secretPath);
|
||||||
|
await editPage.toggleMetadata();
|
||||||
|
await settled();
|
||||||
|
await typeIn('[data-test-kv-value]', 'invalid\\/');
|
||||||
|
assert
|
||||||
|
.dom('[data-test-inline-error-message]')
|
||||||
|
.hasText('Custom values cannot contain a backward slash.', 'will not allow backward slash in value.');
|
||||||
|
//remove validation error and cause another error that is captured by the API
|
||||||
|
await fillIn('[data-test-kv-value]', 'removed');
|
||||||
|
await typeIn('[data-test-kv-value]', '!');
|
||||||
|
await click('[data-test-secret-save="true"]');
|
||||||
|
assert
|
||||||
|
.dom('[data-test-error]')
|
||||||
|
.includesText(
|
||||||
|
'custom_metadata validation failed: length of key',
|
||||||
|
'shows API error that is not captured by validation'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it can create a secret with config metadata', async function(assert) {
|
||||||
|
let enginePath = `kv-${new Date().getTime()}`;
|
||||||
|
let maxVersion = 101;
|
||||||
|
await mountSecrets.visit();
|
||||||
|
await click('[data-test-mount-type="kv"]');
|
||||||
|
await settled();
|
||||||
|
await click('[data-test-mount-next]');
|
||||||
|
await settled();
|
||||||
|
await fillIn('[data-test-input="path"]', enginePath);
|
||||||
|
await fillIn('[data-test-input="maxVersions"]', maxVersion);
|
||||||
|
await click('[data-test-input="casRequired"]');
|
||||||
|
await click('[data-test-toggle-label="Automate secret deletion"]');
|
||||||
|
await fillIn('[data-test-ttl-value="Automate secret deletion"]', '1');
|
||||||
|
await click('[data-test-mount-submit="true"]');
|
||||||
|
await settled();
|
||||||
|
await click('[data-test-configuration-tab]');
|
||||||
|
await settled();
|
||||||
|
|
||||||
|
let cas = document.querySelector('[data-test-value-div="Check-and-Set required"]').innerText;
|
||||||
|
let deleteVersionAfter = document.querySelector('[data-test-value-div="Delete version after"]').innerText;
|
||||||
|
let savedMaxVersion = document.querySelector('[data-test-value-div="Maximum versions"]').innerText;
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
maxVersion,
|
||||||
|
savedMaxVersion,
|
||||||
|
'displays the max version set when configuring the secret-engine'
|
||||||
|
);
|
||||||
|
assert.equal(cas.trim(), 'Yes', 'displays the cas set when configuring the secret-engine');
|
||||||
|
assert.equal(
|
||||||
|
deleteVersionAfter.trim(),
|
||||||
|
'1s',
|
||||||
|
'displays the delete version after set when configuring the secret-engine'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it can create a secret and metadata can be created and edited', async function(assert) {
|
||||||
|
let enginePath = `kv-${new Date().getTime()}`;
|
||||||
|
let secretPath = 'metadata';
|
||||||
|
let maxVersions = 101;
|
||||||
|
await mountSecrets.visit();
|
||||||
|
await mountSecrets.enable('kv', enginePath);
|
||||||
|
await settled();
|
||||||
|
await editPage.startCreateSecret();
|
||||||
|
await editPage.path(secretPath);
|
||||||
await editPage.toggleMetadata();
|
await editPage.toggleMetadata();
|
||||||
await settled();
|
await settled();
|
||||||
await fillIn('[data-test-input="maxVersions"]', maxVersions);
|
await fillIn('[data-test-input="maxVersions"]', maxVersions);
|
||||||
|
@ -86,7 +185,6 @@ module('Acceptance | secrets/secret/create', function(hooks) {
|
||||||
await settled();
|
await settled();
|
||||||
await editPage.metadataTab();
|
await editPage.metadataTab();
|
||||||
await settled();
|
await settled();
|
||||||
// convert to number for IE11 browserstack test
|
|
||||||
let savedMaxVersions = Number(document.querySelectorAll('[data-test-value-div]')[0].innerText);
|
let savedMaxVersions = Number(document.querySelectorAll('[data-test-value-div]')[0].innerText);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
maxVersions,
|
maxVersions,
|
||||||
|
@ -94,16 +192,14 @@ module('Acceptance | secrets/secret/create', function(hooks) {
|
||||||
'max_version displays the saved number set when creating the secret'
|
'max_version displays the saved number set when creating the secret'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
// ARG TOD add test here that adds custom metadata
|
|
||||||
|
|
||||||
test('it disables save when validation errors occur', async function(assert) {
|
test('it disables save when validation errors occur', async function(assert) {
|
||||||
let enginePath = `kv-${new Date().getTime()}`;
|
let enginePath = `kv-${new Date().getTime()}`;
|
||||||
await mountSecrets.visit();
|
await mountSecrets.visit();
|
||||||
await mountSecrets.enable('kv', enginePath);
|
await mountSecrets.enable('kv', enginePath);
|
||||||
await settled();
|
await settled();
|
||||||
await click('[data-test-secret-create="true"]');
|
await editPage.startCreateSecret();
|
||||||
await fillIn('[data-test-secret-path="true"]', 'beep');
|
await typeIn('[data-test-secret-path="true"]', 'beep');
|
||||||
await triggerKeyEvent('[data-test-secret-path="true"]', 'keyup', 65);
|
|
||||||
assert
|
assert
|
||||||
.dom('[data-test-inline-error-message]')
|
.dom('[data-test-inline-error-message]')
|
||||||
.hasText(
|
.hasText(
|
||||||
|
@ -113,16 +209,15 @@ module('Acceptance | secrets/secret/create', function(hooks) {
|
||||||
|
|
||||||
await editPage.toggleMetadata();
|
await editPage.toggleMetadata();
|
||||||
await settled();
|
await settled();
|
||||||
document.querySelector('#maxVersions').value = 'abc';
|
await typeIn('[data-test-input="maxVersions"]', 'abc');
|
||||||
await triggerKeyEvent('[data-test-input="maxVersions"]', 'keyup', 65);
|
|
||||||
await settled();
|
await settled();
|
||||||
assert
|
assert
|
||||||
.dom('[data-test-input="maxVersions"]')
|
.dom('[data-test-input="maxVersions"]')
|
||||||
.hasClass('has-error-border', 'shows border error on input with error');
|
.hasClass('has-error-border', 'shows border error on input with error');
|
||||||
assert.dom('[data-test-secret-save="true"]').isDisabled('Save button is disabled');
|
assert.dom('[data-test-secret-save="true"]').isDisabled('Save button is disabled');
|
||||||
await fillIn('[data-test-input="maxVersions"]', 20);
|
await fillIn('[data-test-input="maxVersions"]', 20); // fillIn replaces the text, whereas typeIn only adds to it.
|
||||||
await triggerKeyEvent('[data-test-input="maxVersions"]', 'keyup', 65);
|
await triggerKeyEvent('[data-test-input="maxVersions"]', 'keyup', 65);
|
||||||
await fillIn('[data-test-secret-path="true"]', 'meep');
|
await editPage.path('meep');
|
||||||
await triggerKeyEvent('[data-test-secret-path="true"]', 'keyup', 65);
|
await triggerKeyEvent('[data-test-secret-path="true"]', 'keyup', 65);
|
||||||
await click('[data-test-secret-save="true"]');
|
await click('[data-test-secret-save="true"]');
|
||||||
assert.equal(currentURL(), `/vault/secrets/${enginePath}/show/meep`, 'navigates to show secret');
|
assert.equal(currentURL(), `/vault/secrets/${enginePath}/show/meep`, 'navigates to show secret');
|
||||||
|
@ -155,7 +250,7 @@ module('Acceptance | secrets/secret/create', function(hooks) {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('version 1 performs the correct capabilities lookup', async function(assert) {
|
test('version 1 performs the correct capabilities lookup and does not show metadata tab', async function(assert) {
|
||||||
let enginePath = `kv-${new Date().getTime()}`;
|
let enginePath = `kv-${new Date().getTime()}`;
|
||||||
let secretPath = 'foo/bar';
|
let secretPath = 'foo/bar';
|
||||||
// mount version 1 engine
|
// mount version 1 engine
|
||||||
|
@ -171,6 +266,8 @@ module('Acceptance | secrets/secret/create', function(hooks) {
|
||||||
await editPage.createSecret(secretPath, 'foo', 'bar');
|
await editPage.createSecret(secretPath, 'foo', 'bar');
|
||||||
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.show', 'redirects to the show page');
|
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.show', 'redirects to the show page');
|
||||||
assert.ok(showPage.editIsPresent, 'shows the edit button');
|
assert.ok(showPage.editIsPresent, 'shows the edit button');
|
||||||
|
//check for metadata tab should not exist on KV version 1
|
||||||
|
assert.dom('[data-test-secret-metadata-tab]').doesNotExist('does not show metadata tab');
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://github.com/hashicorp/vault/issues/5960
|
// https://github.com/hashicorp/vault/issues/5960
|
||||||
|
@ -320,7 +417,7 @@ module('Acceptance | secrets/secret/create', function(hooks) {
|
||||||
assert.ok(showPage.editIsPresent, 'shows the edit button');
|
assert.ok(showPage.editIsPresent, 'shows the edit button');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('version 2 with restricted policy still allows edit', async function(assert) {
|
test('version 2 with restricted policy still allows edit but does not show metadata tab', async function(assert) {
|
||||||
let backend = 'kv-v2';
|
let backend = 'kv-v2';
|
||||||
const V2_POLICY = `
|
const V2_POLICY = `
|
||||||
path "kv-v2/metadata/*" {
|
path "kv-v2/metadata/*" {
|
||||||
|
@ -339,16 +436,18 @@ module('Acceptance | secrets/secret/create', function(hooks) {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let userToken = consoleComponent.lastLogOutput;
|
let userToken = consoleComponent.lastLogOutput;
|
||||||
|
// check secret edit
|
||||||
await writeSecret(backend, 'secret', 'foo', 'bar');
|
await writeSecret(backend, 'secret', 'foo', 'bar');
|
||||||
await logout.visit();
|
await logout.visit();
|
||||||
await authPage.login(userToken);
|
await authPage.login(userToken);
|
||||||
|
|
||||||
await editPage.visitEdit({ backend, id: 'secret' });
|
await editPage.visitEdit({ backend, id: 'secret' });
|
||||||
assert.notOk(editPage.hasMetadataFields, 'hides the metadata form');
|
|
||||||
await editPage.editSecret('bar', 'baz');
|
await editPage.editSecret('bar', 'baz');
|
||||||
await settled();
|
await settled();
|
||||||
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.show', 'redirects to the show page');
|
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.show', 'redirects to the show page');
|
||||||
assert.ok(showPage.editIsPresent, 'shows the edit button');
|
assert.ok(showPage.editIsPresent, 'shows the edit button');
|
||||||
|
//check for metadata tab
|
||||||
|
assert.dom('[data-test-secret-metadata-tab]').doesNotExist('does not show metadata tab');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('version 2 with policy with destroy capabilities shows modal', async function(assert) {
|
test('version 2 with policy with destroy capabilities shows modal', async function(assert) {
|
||||||
|
@ -603,7 +702,9 @@ module('Acceptance | secrets/secret/create', function(hooks) {
|
||||||
assert.ok(showPage.editIsPresent, 'shows the edit button');
|
assert.ok(showPage.editIsPresent, 'shows the edit button');
|
||||||
|
|
||||||
await editPage.visitEdit({ backend, id: 'secret' });
|
await editPage.visitEdit({ backend, id: 'secret' });
|
||||||
assert.notOk(editPage.hasMetadataFields, 'hides the metadata form');
|
assert
|
||||||
|
.dom('[data-test-warning-no-read-permissions]')
|
||||||
|
.exists('shows custom warning instead of default API warning about permissions');
|
||||||
|
|
||||||
await editPage.editSecret('bar', 'baz');
|
await editPage.editSecret('bar', 'baz');
|
||||||
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.show', 'redirects to the show page');
|
assert.equal(currentRouteName(), 'vault.cluster.secrets.backend.show', 'redirects to the show page');
|
||||||
|
|
|
@ -94,6 +94,7 @@ module('Integration | Component | secret edit', function(hooks) {
|
||||||
null: 'null',
|
null: 'null',
|
||||||
float: '1.234',
|
float: '1.234',
|
||||||
},
|
},
|
||||||
|
canReadSecretData: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`{{secret-edit mode=mode model=model preferAdvancedEdit=true }}`);
|
await render(hbs`{{secret-edit mode=mode model=model preferAdvancedEdit=true }}`);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { isPresent, clickable, visitable, create, fillable } from 'ember-cli-pag
|
||||||
import { codeFillable } from 'vault/tests/pages/helpers/codemirror';
|
import { codeFillable } from 'vault/tests/pages/helpers/codemirror';
|
||||||
export default create({
|
export default create({
|
||||||
...Base,
|
...Base,
|
||||||
path: fillable('[data-test-secret-path]'),
|
path: fillable('[data-test-secret-path="true"]'),
|
||||||
secretKey: fillable('[data-test-secret-key]'),
|
secretKey: fillable('[data-test-secret-key]'),
|
||||||
secretValue: fillable('[data-test-secret-value] textarea'),
|
secretValue: fillable('[data-test-secret-value] textarea'),
|
||||||
save: clickable('[data-test-secret-save]'),
|
save: clickable('[data-test-secret-save]'),
|
||||||
|
@ -15,6 +15,8 @@ export default create({
|
||||||
toggleMetadata: clickable('[data-test-show-metadata-toggle]'),
|
toggleMetadata: clickable('[data-test-show-metadata-toggle]'),
|
||||||
metadataTab: clickable('[data-test-secret-metadata-tab]'),
|
metadataTab: clickable('[data-test-secret-metadata-tab]'),
|
||||||
hasMetadataFields: isPresent('[data-test-metadata-fields]'),
|
hasMetadataFields: isPresent('[data-test-metadata-fields]'),
|
||||||
|
maxVersion: fillable('[data-test-input="maxVersions"]'),
|
||||||
|
startCreateSecret: clickable('[data-test-secret-create="true"]'),
|
||||||
editor: {
|
editor: {
|
||||||
fillIn: codeFillable('[data-test-component="json-editor"]'),
|
fillIn: codeFillable('[data-test-component="json-editor"]'),
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue