mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-19 05:31:10 +02:00
* wip control group fix? * dont rely on models for capabilities; * Revert "wip control group fix?" This reverts commit cf3e896ba05d2fdfe1f6287bba5c862df4e5d553. * make explicit request for data * remove dangerous triple curlies * cleanup template logic and reuse each-in * remove capability checks from model * update tests to reflect new behavior * add test coverage * fix mirage factory, update details tests * test control groups VAULT-29471 * finish patch test * alphabetize! * does await help? * fix factory * add conditionals for control group error
375 lines
13 KiB
JavaScript
375 lines
13 KiB
JavaScript
/**
|
|
* Copyright (c) HashiCorp, Inc.
|
|
* SPDX-License-Identifier: BUSL-1.1
|
|
*/
|
|
|
|
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 { blur, click, fillIn, find, render, waitUntil } from '@ember/test-helpers';
|
|
import { hbs } from 'ember-cli-htmlbars';
|
|
import sinon from 'sinon';
|
|
import { FORM, PAGE } from 'vault/tests/helpers/kv/kv-selectors';
|
|
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
|
import { baseSetup } from 'vault/tests/helpers/kv/kv-run-commands';
|
|
import codemirror from 'vault/tests/helpers/codemirror';
|
|
import { encodePath } from 'vault/utils/path-encoding-helpers';
|
|
import { overrideResponse } from 'vault/tests/helpers/stubs';
|
|
|
|
module('Integration | Component | kv-v2 | Page::Secret::Patch', function (hooks) {
|
|
setupRenderingTest(hooks);
|
|
setupEngine(hooks, 'kv');
|
|
setupMirage(hooks);
|
|
|
|
hooks.beforeEach(async function () {
|
|
baseSetup(this);
|
|
this.transitionStub = sinon.stub(this.owner.lookup('service:router'), 'transitionTo');
|
|
this.breadcrumbs = [
|
|
{ label: 'Secrets', route: 'secrets', linkExternal: true },
|
|
{ label: this.backend, route: 'list' },
|
|
{ label: this.path, route: 'index' },
|
|
{ label: 'Patch' },
|
|
];
|
|
this.subkeys = {
|
|
foo: null,
|
|
bar: {
|
|
baz: null,
|
|
},
|
|
quux: null,
|
|
};
|
|
this.subkeysMeta = {
|
|
created_time: '2021-12-14T20:28:00.773477Z',
|
|
custom_metadata: null,
|
|
deletion_time: '',
|
|
destroyed: false,
|
|
version: 1,
|
|
};
|
|
|
|
this.renderComponent = async () => {
|
|
return render(
|
|
hbs`
|
|
<Page::Secret::Patch
|
|
@backend={{this.backend}}
|
|
@breadcrumbs={{this.breadcrumbs}}
|
|
@metadata={{this.metadata}}
|
|
@path={{this.path}}
|
|
@subkeys={{this.subkeys}}
|
|
@subkeysMeta={{this.subkeysMeta}}
|
|
/>`,
|
|
{ owner: this.engine }
|
|
);
|
|
};
|
|
});
|
|
|
|
test('it renders', async function (assert) {
|
|
await this.renderComponent();
|
|
assert.dom(PAGE.breadcrumbs).hasText(`Secrets ${this.backend} ${this.path} Patch`);
|
|
assert.dom(PAGE.title).hasText('Patch Secret to New Version');
|
|
assert.dom(GENERAL.fieldByAttr('Path')).isDisabled();
|
|
assert.dom(GENERAL.fieldByAttr('Path')).hasValue(this.path);
|
|
assert.dom(GENERAL.inputByAttr('JSON')).isNotChecked();
|
|
assert.dom(GENERAL.inputByAttr('UI')).isChecked();
|
|
assert.dom(FORM.patchEditorForm).exists('it renders editor form by default');
|
|
assert.dom(GENERAL.codemirror).doesNotExist();
|
|
Object.keys(this.subkeys).forEach((key, idx) => {
|
|
assert.dom(FORM.keyInput(idx)).hasValue(key);
|
|
assert.dom(FORM.keyInput(idx)).isDisabled();
|
|
});
|
|
});
|
|
|
|
test('it selects JSON as an edit option', async function (assert) {
|
|
await this.renderComponent();
|
|
assert.dom(FORM.patchEditorForm).exists();
|
|
await click(GENERAL.inputByAttr('JSON'));
|
|
assert.dom(GENERAL.inputByAttr('JSON')).isChecked();
|
|
assert.dom(GENERAL.inputByAttr('UI')).isNotChecked();
|
|
assert.dom(FORM.patchEditorForm).doesNotExist();
|
|
assert.dom(GENERAL.codemirror).exists();
|
|
});
|
|
|
|
test('it transitions on cancel', async function (assert) {
|
|
await this.renderComponent();
|
|
await click(FORM.cancelBtn);
|
|
const [route] = this.transitionStub.lastCall.args;
|
|
assert.strictEqual(
|
|
route,
|
|
'vault.cluster.secrets.backend.kv.secret.index',
|
|
`it transitions on cancel to: ${route}`
|
|
);
|
|
});
|
|
|
|
module('it submits', function (hooks) {
|
|
const EXAMPLE_KV_DATA_CREATE_RESPONSE = {
|
|
request_id: 'foobar',
|
|
data: {
|
|
created_time: '2023-06-21T16:18:31.479993Z',
|
|
custom_metadata: null,
|
|
deletion_time: '',
|
|
destroyed: false,
|
|
version: 1,
|
|
},
|
|
};
|
|
|
|
hooks.beforeEach(async function () {
|
|
this.endpoint = `${encodePath(this.backend)}/data/${encodePath(this.path)}`;
|
|
});
|
|
|
|
test('patch data from kv editor form', async function (assert) {
|
|
assert.expect(3);
|
|
this.server.patch(this.endpoint, (schema, req) => {
|
|
const payload = JSON.parse(req.requestBody);
|
|
const expected = {
|
|
data: { bar: null, foo: 'foovalue', aKey: '1', bKey: 'null' },
|
|
options: {
|
|
cas: this.metadata.currentVersion,
|
|
},
|
|
};
|
|
assert.true(true, `PATCH request made to ${this.endpoint}`);
|
|
assert.propEqual(
|
|
payload,
|
|
expected,
|
|
`payload: ${JSON.stringify(payload)} matches expected: ${JSON.stringify(payload)}`
|
|
);
|
|
return EXAMPLE_KV_DATA_CREATE_RESPONSE;
|
|
});
|
|
|
|
await this.renderComponent();
|
|
// patch existing, delete and create a new key key
|
|
await click(FORM.patchEdit());
|
|
await fillIn(FORM.valueInput(), 'foovalue');
|
|
await blur(FORM.valueInput());
|
|
await click(FORM.patchDelete(1));
|
|
await fillIn(FORM.keyInput('new'), 'aKey');
|
|
await fillIn(FORM.valueInput('new'), '1');
|
|
await click(FORM.patchAdd);
|
|
// add new key and do NOT click add
|
|
await fillIn(FORM.keyInput('new'), 'bKey');
|
|
await fillIn(FORM.valueInput('new'), 'null');
|
|
await click(FORM.saveBtn);
|
|
const [route] = this.transitionStub.lastCall.args;
|
|
assert.strictEqual(
|
|
route,
|
|
'vault.cluster.secrets.backend.kv.secret.index',
|
|
`it transitions on save to: ${route}`
|
|
);
|
|
});
|
|
|
|
test('patch data from json form', async function (assert) {
|
|
assert.expect(3);
|
|
this.server.patch(this.endpoint, (schema, req) => {
|
|
const payload = JSON.parse(req.requestBody);
|
|
const expected = {
|
|
data: { foo: 'foovalue', bar: null, number: 1 },
|
|
options: {
|
|
cas: 4,
|
|
},
|
|
};
|
|
assert.true(true, `PATCH request made to ${this.endpoint}`);
|
|
assert.propEqual(
|
|
payload,
|
|
expected,
|
|
`payload: ${JSON.stringify(payload)} matches expected: ${JSON.stringify(payload)}`
|
|
);
|
|
return EXAMPLE_KV_DATA_CREATE_RESPONSE;
|
|
});
|
|
await this.renderComponent();
|
|
await click(GENERAL.inputByAttr('JSON'));
|
|
await waitUntil(() => find('.CodeMirror'));
|
|
await codemirror().setValue('{ "foo": "foovalue", "bar":null, "number":1 }');
|
|
await click(FORM.saveBtn);
|
|
const [route] = this.transitionStub.lastCall.args;
|
|
assert.strictEqual(
|
|
route,
|
|
'vault.cluster.secrets.backend.kv.secret.index',
|
|
`it transitions on save to: ${route}`
|
|
);
|
|
});
|
|
|
|
// this assertion confirms submit allows empty values
|
|
test('empty string values from kv editor form', async function (assert) {
|
|
assert.expect(1);
|
|
this.server.patch(this.endpoint, (schema, req) => {
|
|
const payload = JSON.parse(req.requestBody);
|
|
const expected = {
|
|
data: { foo: '', aKey: '', bKey: '' },
|
|
options: {
|
|
cas: this.metadata.currentVersion,
|
|
},
|
|
};
|
|
assert.propEqual(
|
|
payload,
|
|
expected,
|
|
`payload: ${JSON.stringify(payload)} matches expected: ${JSON.stringify(payload)}`
|
|
);
|
|
return EXAMPLE_KV_DATA_CREATE_RESPONSE;
|
|
});
|
|
|
|
await this.renderComponent();
|
|
await click(FORM.patchEdit());
|
|
// edit existing key's value
|
|
await fillIn(FORM.valueInput(), '');
|
|
// add a new key with empty value, click add
|
|
await fillIn(FORM.keyInput('new'), 'aKey');
|
|
await fillIn(FORM.valueInput('new'), '');
|
|
await click(FORM.patchAdd);
|
|
// add new key and do NOT click add
|
|
await fillIn(FORM.keyInput('new'), 'bKey');
|
|
await fillIn(FORM.valueInput('new'), '');
|
|
await click(FORM.saveBtn);
|
|
});
|
|
|
|
// this assertion confirms submit allows empty values
|
|
test('empty string value from json form', async function (assert) {
|
|
assert.expect(1);
|
|
this.server.patch(this.endpoint, (schema, req) => {
|
|
const payload = JSON.parse(req.requestBody);
|
|
const expected = {
|
|
data: { foo: '' },
|
|
options: {
|
|
cas: this.metadata.currentVersion,
|
|
},
|
|
};
|
|
assert.propEqual(
|
|
payload,
|
|
expected,
|
|
`payload: ${JSON.stringify(payload)} matches expected: ${JSON.stringify(payload)}`
|
|
);
|
|
return EXAMPLE_KV_DATA_CREATE_RESPONSE;
|
|
});
|
|
|
|
await this.renderComponent();
|
|
await click(GENERAL.inputByAttr('JSON'));
|
|
await waitUntil(() => find('.CodeMirror'));
|
|
await codemirror().setValue('{ "foo": "" }');
|
|
await click(FORM.saveBtn);
|
|
});
|
|
|
|
test('patch data without metadata permissions', async function (assert) {
|
|
assert.expect(3);
|
|
this.metadata = null;
|
|
this.server.patch(this.endpoint, (schema, req) => {
|
|
const payload = JSON.parse(req.requestBody);
|
|
const expected = {
|
|
data: { aKey: '1' },
|
|
options: {
|
|
cas: this.subkeysMeta.version,
|
|
},
|
|
};
|
|
assert.true(true, `PATCH request made to ${this.endpoint}`);
|
|
assert.propEqual(
|
|
payload,
|
|
expected,
|
|
`payload: ${JSON.stringify(payload)} matches expected: ${JSON.stringify(payload)}`
|
|
);
|
|
return EXAMPLE_KV_DATA_CREATE_RESPONSE;
|
|
});
|
|
|
|
await this.renderComponent();
|
|
await fillIn(FORM.keyInput('new'), 'aKey');
|
|
await fillIn(FORM.valueInput('new'), '1');
|
|
await click(FORM.saveBtn);
|
|
const [route] = this.transitionStub.lastCall.args;
|
|
assert.strictEqual(
|
|
route,
|
|
'vault.cluster.secrets.backend.kv.secret.index',
|
|
`it transitions on save to: ${route}`
|
|
);
|
|
});
|
|
});
|
|
|
|
module('it does not submit', function (hooks) {
|
|
hooks.beforeEach(async function () {
|
|
this.endpoint = `${encodePath(this.backend)}/data/${encodePath(this.path)}`;
|
|
this.flashSpy = sinon.spy(this.owner.lookup('service:flash-messages'), 'info');
|
|
});
|
|
|
|
test('if no changes from kv editor form', async function (assert) {
|
|
assert.expect(3);
|
|
this.server.patch(this.endpoint, () =>
|
|
overrideResponse(500, `Request made to: ${this.endpoint}. This should not have happened!`)
|
|
);
|
|
await this.renderComponent();
|
|
await click(FORM.saveBtn);
|
|
assert.dom(GENERAL.messageError).doesNotExist('PATCH request is not made');
|
|
const route = this.transitionStub.lastCall?.args[0] || '';
|
|
const flash = this.flashSpy.lastCall?.args[0] || '';
|
|
assert.strictEqual(
|
|
route,
|
|
'vault.cluster.secrets.backend.kv.secret.index',
|
|
`it transitions to overview route: ${route}`
|
|
);
|
|
assert.strictEqual(
|
|
flash,
|
|
`No changes to submit. No updates made to "${this.path}".`,
|
|
`flash message has message: "${flash}"`
|
|
);
|
|
});
|
|
|
|
test('if no changes from json form', async function (assert) {
|
|
assert.expect(3);
|
|
this.server.patch(this.endpoint, () =>
|
|
overrideResponse(500, `Request made to: ${this.endpoint}. This should not have happened!`)
|
|
);
|
|
await this.renderComponent();
|
|
await click(GENERAL.inputByAttr('JSON'));
|
|
await waitUntil(() => find('.CodeMirror'));
|
|
await click(FORM.saveBtn);
|
|
assert.dom(GENERAL.messageError).doesNotExist('PATCH request is not made');
|
|
const route = this.transitionStub.lastCall?.args[0] || '';
|
|
const flash = this.flashSpy.lastCall?.args[0] || '';
|
|
assert.strictEqual(
|
|
route,
|
|
'vault.cluster.secrets.backend.kv.secret.index',
|
|
`it transitions to overview route: ${route}`
|
|
);
|
|
assert.strictEqual(
|
|
flash,
|
|
`No changes to submit. No updates made to "${this.path}".`,
|
|
`flash message has message: "${flash}"`
|
|
);
|
|
});
|
|
});
|
|
|
|
module('it passes error', function (hooks) {
|
|
hooks.beforeEach(async function () {
|
|
this.endpoint = `${encodePath(this.backend)}/data/${encodePath(this.path)}`;
|
|
this.server.patch(this.endpoint, () => {
|
|
return overrideResponse(403);
|
|
});
|
|
});
|
|
|
|
test('to kv editor form', async function (assert) {
|
|
assert.expect(2);
|
|
|
|
await this.renderComponent();
|
|
// patch existing, delete and create a new key key
|
|
await click(FORM.patchEdit());
|
|
await fillIn(FORM.valueInput(), 'foovalue');
|
|
await blur(FORM.valueInput());
|
|
await click(FORM.patchDelete(1));
|
|
await fillIn(FORM.keyInput('new'), 'aKey');
|
|
await fillIn(FORM.valueInput('new'), 'aValue');
|
|
await click(FORM.patchAdd);
|
|
// add new key and do NOT click add
|
|
await fillIn(FORM.keyInput('new'), 'bKey');
|
|
await fillIn(FORM.valueInput('new'), 'bValue');
|
|
await click(FORM.saveBtn);
|
|
assert.dom(GENERAL.messageError).hasText('Error permission denied');
|
|
assert.dom(GENERAL.inlineError).hasText('There was an error submitting this form.');
|
|
});
|
|
|
|
test('to json form', async function (assert) {
|
|
assert.expect(2);
|
|
await this.renderComponent();
|
|
await click(GENERAL.inputByAttr('JSON'));
|
|
await waitUntil(() => find('.CodeMirror'));
|
|
await codemirror().setValue('{ "foo": "foovalue", "bar":null, "number":1 }');
|
|
await click(FORM.saveBtn);
|
|
await click(FORM.saveBtn);
|
|
assert.dom(GENERAL.messageError).hasText('Error permission denied');
|
|
assert.dom(GENERAL.inlineError).hasText('There was an error submitting this form.');
|
|
});
|
|
});
|
|
});
|