diff --git a/ui/app/components/tool-wrap.js b/ui/app/components/tool-wrap.js
index 2435b14b24..03f7810d93 100644
--- a/ui/app/components/tool-wrap.js
+++ b/ui/app/components/tool-wrap.js
@@ -4,37 +4,41 @@
*/
import Component from '@glimmer/component';
+import { service } from '@ember/service';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
+import errorMessage from 'vault/utils/error-message';
/**
- * @module ToolWrap
- * ToolWrap components are components that sys/wrapping/wrap functionality. Most of the functionality is passed through as actions from the tool-actions-form and then called back with properties.
+ * @module
- *
- * @param {object} errors=null - errors returned if wrap fails
- * @param {function} onBack - callback that only clears specific values so the action can be repeated. Must be passed as `{{action "onBack"}}`
- * @param {function} onChange - callback that fires when inputs change and passes value and param name back to the parent
- * @param {function} onClear - callback that resets all of values to defaults. Must be passed as `{{action "onClear"}}`
- * @param {string} token=null - returned after user clicks "Wrap data", if there is a token value it displays instead of the JsonEditor
+ *
*/
export default class ToolWrap extends Component {
+ @service store;
+ @service flashMessages;
+
@tracked buttonDisabled = false;
+ @tracked token = '';
+ @tracked wrapTTL = null;
+ @tracked wrapData = '{\n}';
+ @tracked errorMessage = '';
+
+ @action
+ reset(clearData = true) {
+ this.token = '';
+ this.errorMessage = '';
+ this.wrapTTL = null;
+ if (clearData) this.wrapData = '{\n}';
+ }
@action
updateTtl(evt) {
if (!evt) return;
- const ttl = evt.enabled ? `${evt.seconds}s` : '30m';
- this.args.onChange('wrapTTL', ttl);
+ this.wrapTTL = evt.enabled ? `${evt.seconds}s` : '30m';
}
@action
@@ -42,6 +46,21 @@ export default class ToolWrap extends Component {
codemirror.performLint();
const hasErrors = codemirror?.state.lint.marked?.length > 0;
this.buttonDisabled = hasErrors;
- this.args.onChange('data', val);
+ if (!hasErrors) this.wrapData = val;
+ }
+
+ @action
+ async handleSubmit(evt) {
+ evt.preventDefault();
+ const data = JSON.parse(this.wrapData);
+ const wrapTTL = this.wrapTTL || null;
+
+ try {
+ const response = await this.store.adapterFor('tools').toolAction('wrap', data, { wrapTTL });
+ this.token = response.wrap_info.token;
+ this.flashMessages.success('Wrap was successful.');
+ } catch (error) {
+ this.errorMessage = errorMessage(error);
+ }
}
}
diff --git a/ui/app/templates/components/tool-actions-form.hbs b/ui/app/templates/components/tool-actions-form.hbs
index 29188a6faf..cb73f511c9 100644
--- a/ui/app/templates/components/tool-actions-form.hbs
+++ b/ui/app/templates/components/tool-actions-form.hbs
@@ -15,14 +15,7 @@
{{else if (eq this.selectedAction "lookup")}}
{{else if (eq this.selectedAction "wrap")}}
-
+
{{else}}
{{/if}}
diff --git a/ui/app/templates/components/tool-wrap.hbs b/ui/app/templates/components/tool-wrap.hbs
index 04fd967782..e294aba9ac 100644
--- a/ui/app/templates/components/tool-wrap.hbs
+++ b/ui/app/templates/components/tool-wrap.hbs
@@ -11,54 +11,58 @@
-{{#if @token}}
+{{#if this.token}}
-
-
-
-
+
{{/if}}
\ No newline at end of file
diff --git a/ui/tests/integration/components/tools/wrap-test.js b/ui/tests/integration/components/tools/wrap-test.js
index 358d174f53..daf296c081 100644
--- a/ui/tests/integration/components/tools/wrap-test.js
+++ b/ui/tests/integration/components/tools/wrap-test.js
@@ -6,76 +6,140 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'vault/tests/helpers';
import { setupMirage } from 'ember-cli-mirage/test-support';
-import { click, fillIn, render } from '@ember/test-helpers';
+import { Response } from 'miragejs';
+import { click, fillIn, find, render, waitUntil } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
-import sinon from 'sinon';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
-import codemirror from 'vault/tests/helpers/codemirror';
+import { TTL_PICKER as TTL } from 'vault/tests/helpers/components/ttl-picker-selectors';
import { TOOLS_SELECTORS as TS } from 'vault/tests/helpers/tools-selectors';
+import codemirror from 'vault/tests/helpers/codemirror';
-module('Integration | Component | tools/tool-wrap', function (hooks) {
+module('Integration | Component | tools/wrap', function (hooks) {
setupRenderingTest(hooks);
setupMirage(hooks);
hooks.beforeEach(function () {
- this.onBack = sinon.spy();
- this.onClear = sinon.spy();
- this.onChange = sinon.spy();
- this.data = '{\n}';
this.renderComponent = async () => {
await render(hbs`
-
`);
+
`);
};
+ this.wrapData = `{"foo": "bar"}`;
+ this.token = 'blah.jhfel7SmsVeZwihaGiIKHGh2cy5XZWtEeEt5WmRwS1VYSTNDb1BBVUNsVFAQ3JIK';
+ // default mirage response here is overridden in some tests
+ this.server.post('sys/wrapping/wrap', () => {
+ // removed superfluous response data for this test
+ return { wrap_info: { token: this.token } };
+ });
});
test('it renders defaults', async function (assert) {
await this.renderComponent();
assert.dom('h1').hasText('Wrap Data', 'Title renders');
+ assert.dom('label').hasText('Data to wrap (json-formatted)');
assert.strictEqual(codemirror().getValue(' '), '{ }', 'json editor initializes with empty object');
- assert.dom(GENERAL.toggleInput('Wrap TTL')).isNotChecked('Wrap TTL defaults to unchecked');
+ assert.dom(TTL.toggleByLabel('Wrap TTL')).isNotChecked('Wrap TTL defaults to unchecked');
assert.dom(TS.submit).isEnabled();
assert.dom(TS.toolsInput('wrapping-token')).doesNotExist();
assert.dom(TS.button('Back')).doesNotExist();
assert.dom(TS.button('Done')).doesNotExist();
+
+ await click(TTL.toggleByLabel('Wrap TTL'));
+ assert.dom(TTL.valueInputByLabel('Wrap TTL')).hasValue('30', 'ttl defaults to 30 when toggled');
+ assert.dom(TTL.ttlUnit).hasValue('m', 'ttl defaults to minutes when toggled');
});
- test('it renders token view', async function (assert) {
- this.token = 'blah.jhfel7SmsVeZwihaGiIKHGh2cy5XZWtEeEt5WmRwS1VYSTNDb1BBVUNsVFAQ3JIK';
+ test('it renders errors', async function (assert) {
+ this.server.post('sys/wrapping/wrap', () => new Response(500, {}, { errors: ['Something is wrong'] }));
await this.renderComponent();
+ await click(TS.submit);
+ await waitUntil(() => find(GENERAL.messageError));
+ assert.dom(GENERAL.messageError).hasText('Error Something is wrong', 'Error renders');
+ });
- assert.dom('h1').hasText('Wrap Data');
+ test('it submits with defaults', async function (assert) {
+ assert.expect(5);
+ this.server.post('sys/wrapping/wrap', (schema, { requestBody, requestHeaders }) => {
+ const payload = JSON.parse(requestBody);
+ const expectedHeaders = {
+ 'X-Vault-Wrap-TTL': '30m',
+ 'content-type': 'application/json; charset=utf-8',
+ };
+ assert.propEqual(payload, JSON.parse(this.wrapData), `payload contains data: ${requestBody}`);
+ assert.propEqual(requestHeaders, expectedHeaders, 'header has default wrap ttl');
+ return {
+ wrap_info: {
+ token: this.token,
+ accessor: '5yjKx6Om9NmBx1mjiN1aIrnm',
+ ttl: 1800,
+ creation_time: '2024-06-07T12:02:22.096254-07:00',
+ creation_path: 'sys/wrapping/wrap',
+ },
+ };
+ });
+
+ await this.renderComponent();
+ await codemirror().setValue(this.wrapData);
+ await click(TS.submit);
+ await waitUntil(() => find(TS.toolsInput('wrapping-token')));
+ assert.dom(TS.toolsInput('wrapping-token')).hasText(this.token);
assert.dom('label').hasText('Wrapped token');
assert.dom('.CodeMirror').doesNotExist();
- assert.dom(TS.toolsInput('wrapping-token')).hasText(this.token);
- await click(TS.button('Back'));
- assert.true(this.onBack.calledOnce, 'onBack is called');
- await click(TS.button('Done'));
- assert.true(this.onClear.calledOnce, 'onClear is called');
});
- test('it calls onChange for json editor', async function (assert) {
- const data = `{"foo": "bar"}`;
+ test('it submits with updated ttl', async function (assert) {
+ assert.expect(2);
+ this.server.post('sys/wrapping/wrap', (schema, { requestBody, requestHeaders }) => {
+ const payload = JSON.parse(requestBody);
+ const expectedHeaders = {
+ 'X-Vault-Wrap-TTL': '1200s',
+ 'content-type': 'application/json; charset=utf-8',
+ };
+ assert.propEqual(payload, JSON.parse(this.wrapData), `payload contains data: ${requestBody}`);
+ assert.propEqual(requestHeaders, expectedHeaders, 'header has updated wrap ttl');
+ // only testing payload/header assertions, no need for return here
+ return {};
+ });
+
+ await this.renderComponent();
+ await codemirror().setValue(this.wrapData);
+ await click(TTL.toggleByLabel('Wrap TTL'));
+ await fillIn(TTL.valueInputByLabel('Wrap TTL'), '20');
+ await click(TS.submit);
+ });
+
+ test('it resets on done', async function (assert) {
+ await this.renderComponent();
+ await codemirror().setValue(this.wrapData);
+ await click(TTL.toggleByLabel('Wrap TTL'));
+ await fillIn(TTL.valueInputByLabel('Wrap TTL'), '20');
+ await click(TS.submit);
+
+ await waitUntil(() => find(TS.button('Done')));
+ await click(TS.button('Done'));
+ assert.strictEqual(codemirror().getValue(' '), '{ }', 'json editor resets to empty object');
+ assert.dom(TTL.toggleByLabel('Wrap TTL')).isNotChecked('Wrap TTL resets to unchecked');
+ await click(TTL.toggleByLabel('Wrap TTL'));
+ assert.dom(TTL.valueInputByLabel('Wrap TTL')).hasValue('30', 'ttl resets to default when toggled');
+ });
+
+ test('it preserves input data on back', async function (assert) {
+ await this.renderComponent();
+ await codemirror().setValue(this.wrapData);
+ await click(TS.submit);
+
+ await waitUntil(() => find(TS.button('Back')));
+ await click(TS.button('Back'));
+ assert.strictEqual(codemirror().getValue(' '), `{"foo": "bar"}`, 'json editor has original data');
+ assert.dom(TTL.toggleByLabel('Wrap TTL')).isNotChecked('Wrap TTL defaults to unchecked');
+ });
+
+ test('it disables/enables submit based on json linting', async function (assert) {
await this.renderComponent();
await codemirror().setValue(`{bad json}`);
assert.dom(TS.submit).isDisabled('submit disables if json editor has linting errors');
- await codemirror().setValue(data);
+ await codemirror().setValue(this.wrapData);
assert.dom(TS.submit).isEnabled('submit reenables if json editor has no linting errors');
- assert.propEqual(this.onChange.lastCall.args, ['data', data], 'onChange is called with json data');
- });
-
- test('it calls onChange for ttl picker', async function (assert) {
- await this.renderComponent();
- await click(GENERAL.toggleInput('Wrap TTL'));
- await fillIn(GENERAL.ttl.input('Wrap TTL'), '20');
- assert.propEqual(this.onChange.lastCall.args, ['wrapTTL', '1200s'], 'onChange is called with wrapTTL');
});
});