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:
Angel Garbarino 2021-09-03 11:08:26 -06:00 committed by GitHub
parent 0704d5b2de
commit 88125d41ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 139 additions and 21 deletions

View file

@ -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">

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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');

View file

@ -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 }}`);

View file

@ -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"]'),
}, },