open-vault/ui/tests/integration/components/search-select-with-modal-te...

291 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { create } from 'ember-cli-page-object';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { Response } from 'miragejs';
import { clickTrigger, typeInSearch } from 'ember-power-select/test-support/helpers';
import { render, fillIn, click, findAll } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import ss from 'vault/tests/pages/components/search-select';
import sinon from 'sinon';
const component = create(ss);
module('Integration | Component | search select with modal', function (hooks) {
setupRenderingTest(hooks);
setupMirage(hooks);
hooks.beforeEach(function () {
this.set('onChange', sinon.spy());
this.server.get('sys/policies/acl', () => {
return {
request_id: 'acl-policy-list',
data: {
keys: ['default', 'root', 'acl-test'],
},
};
});
this.server.get('sys/policies/rgp', () => {
return {
request_id: 'rgp-policy-list',
data: {
keys: ['rgp-test'],
},
};
});
this.server.get('/sys/policies/acl/acl-test', () => {
return {
request_id: 'policy-acl',
data: {
name: 'acl-test',
policy:
'\n# Grant \'create\', \'read\' , \'update\', and list permission\n# to paths prefixed by \'secret/*\'\npath "secret/*" {\n capabilities = [ "create", "read", "update", "list" ]\n}\n\n# Even though we allowed secret/*, this line explicitly denies\n# secret/super-secret. This takes precedence.\npath "secret/super-secret" {\n capabilities = ["deny"]\n}\n',
},
};
});
this.server.get('/sys/policies/rgp/rgp-test', () => {
return {
request_id: 'policy-rgp',
data: {
name: 'rgp-test',
enforcement_level: 'hard-mandatory',
policy:
'\n# Import strings library that exposes common string operations\nimport "strings"\n\n# Conditional rule (precond) checks the incoming request endpoint\n# targeted to sys/policies/acl/admin\nprecond = rule {\n strings.has_prefix(request.path, "sys/policies/admin")\n}\n\n# Vault checks to see if the request was made by an entity\n# named James Thomas or Team Lead role defined as its metadata\nmain = rule when precond {\n identity.entity.metadata.role is "Team Lead" or\n identity.entity.name is "James Thomas"\n}\n',
},
};
});
});
test('it renders passed in models', async function (assert) {
await render(hbs`
<SearchSelectWithModal
@id="policies"
@label="Policies"
@labelClass="title is-4"
@models={{array "policy/acl" "policy/rgp"}}
@inputValue={{this.policies}}
@onChange={{this.onChange}}
@fallbackComponent="string-list"
@modalFormTemplate="modal-form/policy-template"
@excludeOptions={{array "root"}}
@subText="Some modal subtext"
/>
<div id="modal-wormhole"></div>
`);
assert.dom('[data-test-search-select-with-modal]').exists('the component renders');
assert.dom('[data-test-modal-subtext]').hasText('Some modal subtext', 'renders modal text');
assert.strictEqual(component.labelText, 'Policies', 'label text is correct');
assert.ok(component.hasTrigger, 'it renders the power select trigger');
assert.strictEqual(component.selectedOptions.length, 0, 'there are no selected options');
await clickTrigger();
const dropdownOptions = findAll('[data-option-index]').map((o) => o.innerText);
assert.notOk(dropdownOptions.includes('root'), 'root policy is not listed as option');
assert.strictEqual(component.options.length, 3, 'dropdown renders passed in models as options');
assert.ok(this.onChange.notCalled, 'onChange is not called');
});
test('it renders input value', async function (assert) {
this.policies = ['acl-test'];
await render(hbs`
<SearchSelectWithModal
@id="policies"
@label="Policies"
@labelClass="title is-4"
@models={{array "policy/acl" "policy/rgp"}}
@inputValue={{this.policies}}
@onChange={{this.onChange}}
@fallbackComponent="string-list"
@modalFormTemplate="modal-form/policy-template"
@subText="Some modal subtext"
/>
`);
assert.strictEqual(component.selectedOptions.length, 1, 'there is one selected option');
assert.strictEqual(component.selectedOptions.objectAt(0).text, 'acl-test', 'renders inputted policies');
await clickTrigger();
assert.strictEqual(component.options.length, 3, 'does not render all options returned from query');
const dropdownOptions = findAll('[data-option-index]').map((o) => o.innerText);
assert.notOk(dropdownOptions.includes('acl-test'), 'selected option is not included in the dropdown');
assert.ok(this.onChange.notCalled, 'onChange is not called');
});
test('it filters options, shows option to create new item and opens modal on select', async function (assert) {
assert.expect(7);
await render(hbs`
<SearchSelectWithModal
@id="policies"
@label="Policies"
@labelClass="title is-4"
@models={{array "policy/acl" "policy/rgp"}}
@inputValue={{this.policies}}
@onChange={{this.onChange}}
@fallbackComponent="string-list"
@modalFormTemplate="modal-form/policy-template"
/>
<div id="modal-wormhole"></div>
`);
await clickTrigger();
assert.strictEqual(component.options.length, 4, 'dropdown renders all options');
await typeInSearch('a');
assert.strictEqual(component.options.length, 3, 'dropdown renders all matching options plus add option');
await typeInSearch('acl-test');
assert.strictEqual(component.options[0].text, 'acl-test', 'dropdown renders only matching option');
await typeInSearch('acl-test-new');
assert.strictEqual(
component.options[0].text,
'No results found for "acl-test-new". Click here to create it.',
'dropdown gives option to create new option'
);
await component.selectOption();
assert.dom('[data-test-modal-div]').hasAttribute('class', 'modal is-info is-active', 'modal is active');
assert.dom('[data-test-empty-state-title]').hasText('No policy type selected');
assert.ok(this.onChange.notCalled, 'onChange is not called');
});
test('it renders policy template and selects policy type', async function (assert) {
assert.expect(9);
this.server.put('/sys/policies/acl/acl-test-new', async (schema, req) => {
const requestBody = JSON.parse(req.requestBody);
assert.propEqual(
requestBody,
{
name: 'acl-test-new',
policy: 'path "secret/super-secret" { capabilities = ["deny"] }',
},
'onSave sends request to endpoint with correct policy attributes'
);
});
await render(hbs`
<SearchSelectWithModal
@id="policies"
@label="Policies"
@labelClass="title is-4"
@models={{array "policy/acl" "policy/rgp"}}
@inputValue={{this.policies}}
@onChange={{this.onChange}}
@fallbackComponent="string-list"
@modalFormTemplate="modal-form/policy-template"
/>
<div id="modal-wormhole"></div>
`);
await clickTrigger();
await typeInSearch('acl-test-new');
assert.strictEqual(
component.options[0].text,
'No results found for "acl-test-new". Click here to create it.',
'dropdown gives option to create new option'
);
await component.selectOption();
assert.dom('[data-test-empty-state-title]').hasText('No policy type selected');
await fillIn('[data-test-select="policyType"]', 'acl');
assert.dom('[data-test-policy-form]').exists('policy form renders after type is selected');
await click('[data-test-tab-example-policy]');
assert.dom('[data-test-tab-example-policy]').hasClass('is-active');
await click('[data-test-tab-your-policy]');
assert.dom('[data-test-tab-your-policy]').hasClass('is-active');
await fillIn(
'[data-test-component="code-mirror-modifier"] textarea',
'path "secret/super-secret" { capabilities = ["deny"] }'
);
await click('[data-test-policy-save]');
assert.dom('[data-test-modal-div]').doesNotExist('modal closes after save');
assert
.dom('[data-test-selected-option="0"]')
.hasText('acl-test-new', 'adds newly created policy to selected options');
assert.ok(
this.onChange.calledWithExactly(['acl-test-new']),
'onChange is called only after item is created'
);
});
test('it still renders search select if only second model returns 403', async function (assert) {
assert.expect(4);
this.server.get('sys/policies/rgp', () => {
return new Response(
403,
{ 'Content-Type': 'application/json' },
JSON.stringify({ errors: ['permission denied'] })
);
});
await render(hbs`
<SearchSelectWithModal
@id="policies"
@label="Policies"
@labelClass="title is-4"
@models={{array "policy/acl" "policy/rgp"}}
@inputValue={{this.policies}}
@onChange={{this.onChange}}
@fallbackComponent="string-list"
@modalFormTemplate="modal-form/policy-template"
/>
<div id="modal-wormhole"></div>
`);
assert.dom('[data-test-search-select-with-modal]').exists('the component renders');
assert.dom('[data-test-component="string-list"]').doesNotExist('does not render fallback component');
await clickTrigger();
assert.strictEqual(component.options.length, 3, 'only options from successful query render');
assert.ok(this.onChange.notCalled, 'onChange is not called');
});
test('it renders fallback component if both models return 403', async function (assert) {
assert.expect(7);
this.server.get('sys/policies/acl', () => {
return new Response(
403,
{ 'Content-Type': 'application/json' },
JSON.stringify({ errors: ['permission denied'] })
);
});
this.server.get('sys/policies/rgp', () => {
return new Response(
403,
{ 'Content-Type': 'application/json' },
JSON.stringify({ errors: ['permission denied'] })
);
});
await render(hbs`
<SearchSelectWithModal
@id="policies"
@label="Policies"
@labelClass="title is-4"
@models={{array "policy/acl" "policy/rgp"}}
@inputValue={{this.policies}}
@onChange={{this.onChange}}
@fallbackComponent="string-list"
@modalFormTemplate="modal-form/policy-template"
/>
<div id="modal-wormhole"></div>
`);
assert.dom('[data-test-component="string-list"]').exists('renders fallback component');
assert.false(component.hasTrigger, 'does not render power select trigger');
await fillIn('[data-test-string-list-input="0"]', 'string-list-policy');
await click('[data-test-string-list-button="add"]');
assert
.dom('[data-test-string-list-input="0"]')
.hasValue('string-list-policy', 'first row renders inputted string');
assert
.dom('[data-test-string-list-row="0"] [data-test-string-list-button="delete"]')
.exists('first row renders delete icon');
assert.dom('[data-test-string-list-row="1"]').exists('renders second input row');
assert
.dom('[data-test-string-list-row="1"] [data-test-string-list-button="add"]')
.exists('second row renders add icon');
assert.ok(
this.onChange.calledWithExactly(['string-list-policy']),
'onChange is called only after item is created'
);
});
});