vault/ui/tests/integration/components/search-select-with-modal-test.js
Angel Garbarino 3abca46464
WIF sidebranch (#28148)
* manual cherry pick to deal with all the merge things

* changelog

* test fixes

* Update 28148.txt

* fix tests failures after main merge

* fix test failures after main merge

* Add Access Type and conditionally render WIF fields (#28149)

* initial work.

* remove access_type

* better no model logic well kind of

* rollback attrs

* remove defaults

* stopping point

* wip changing back to sidebranch

* hustling shuffling and serializing

* some of the component test coverage

* disable acces type if editing

* test coverage

* hide max retries that sneaky bugger

* cleanup

* cleanup

* Update root-config.js

* remove flash message check, locally passes great but on ci flaky

* clean up

* thank you chelsea

* test clean up per enterprise vs community

* address pr comments

* welp a miss add

* UI (sidebranch) WIF Issuer field (#28187)

* Add type declaration files for aws config models

* use updated task syntax for save method on configure-aws

* fix types on edit route

* fetch issuer on configure edit page if aws + enterprise

* track issuer within configure-aws component

* add placeholder support on form-field

* Add warning if issuer changed from previous value or could not be read

* cleanup

* preliminary tests

* dont use while loop so we can test the modal

* tests

* cleanup

* fix tests

* remove extra tracked value and duplicate changed attrs check

* modal footer

---------

Co-authored-by: Angel Garbarino <argarbarino@gmail.com>

* Display issuer on Configuration details (#28209)

* display issuer on configuration details

* workflow complete, now on to testing

* handle issuer things

* fix all the broken tests things

* add test coveragE:

* cleanup

* rename model/adapter

* Update configure-aws.ts

* Update aws-configuration-test.js

* 90 percent there for pr comments

* last one for tonight

* a few more because why not

* hasDirtyAttributes fixes

* revert back to previous noRead->queryIssuerError

---------

Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>
2024-08-29 12:17:51 -06:00

357 lines
14 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: 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';
import { setRunOptions } from 'ember-a11y-testing/test-support';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
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',
},
};
});
setRunOptions({
rules: {
// TODO: Fix this component
'color-contrast': { enabled: false },
label: { enabled: false },
'aria-input-field-name': { enabled: false },
'aria-required-attr': { enabled: false },
'aria-valid-attr-value': { enabled: false },
},
});
});
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"
/>
`);
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"
/>
`);
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('#search-select-modal').exists('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"
/>
`);
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] button');
assert.dom('[data-test-tab-example-policy] button').hasAttribute('aria-selected', 'true');
await click('[data-test-tab-your-policy] button');
assert.dom('[data-test-tab-your-policy] button').hasAttribute('aria-selected', 'true');
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"
/>
`);
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');
});
module('fallback component', function (hooks) {
hooks.beforeEach(function () {
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'] })
);
});
this.server.get('identity/oidc/key?list=true', () => {
return new Response(
403,
{ 'Content-Type': 'application/json' },
JSON.stringify({ errors: ['permission denied'] })
);
});
});
test('it renders fallback component if both models return 403', 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"
/>
`);
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'
);
});
test('it renders fallback placeholder text for fallback component', async function (assert) {
assert.expect(1);
await render(hbs`
<SearchSelectWithModal
@id="key"
@label="Keys"
@models={{array "oidc/key"}}
@inputValue={{this.policies}}
@onChange={{this.onChange}}
@fallbackComponent="input-search"
@modalFormTemplate="modal-form/oidc-key-template"
@selectLimit="1"
@placeholder="Search or type to create a key item"
@fallbackComponentPlaceholder="Input key name"
/>
`);
assert
.dom(GENERAL.inputSearch('key'))
.hasAttribute('placeholder', 'Input key name', 'Fallback placeholder was passed to input search');
});
test('it renders placeholder text for fallback component', async function (assert) {
assert.expect(1);
await render(hbs`
<SearchSelectWithModal
@id="key"
@label="Keys"
@models={{array "oidc/key"}}
@inputValue={{this.policies}}
@onChange={{this.onChange}}
@fallbackComponent="input-search"
@modalFormTemplate="modal-form/oidc-key-template"
@selectLimit="1"
@placeholder="Search or type to create a key item"
/>
`);
assert
.dom(GENERAL.inputSearch('key'))
.hasAttribute(
'placeholder',
'Search or type to create a key item',
'Placeholder was passed to input search'
);
});
});
});