mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-16 03:27:01 +02:00
* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License. Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUS-1.1 * Fix test that expected exact offset on hcl file --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Sarah Thompson <sthompson@hashicorp.com> Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
291 lines
11 KiB
JavaScript
291 lines
11 KiB
JavaScript
/**
|
||
* Copyright (c) HashiCorp, Inc.
|
||
* SPDX-License-Identifier: BUSL-1.1
|
||
*/
|
||
|
||
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'
|
||
);
|
||
});
|
||
});
|