UI/vault 9268/pki component tests (#17609)

* wip

* work in progress

* pki-role-form-test

* clean up

* radio-select-ttl-or-string test

* clean up

* add yielded check

* 12 to 13

* add pki-key-usage test

* remove meep

* key-params test

* clean up

* clean up

* pr comments
This commit is contained in:
Angel Garbarino
2022-10-25 12:58:11 -07:00
committed by GitHub
parent de848b05b1
commit bfde310ec0
13 changed files with 448 additions and 12 deletions

View File

@@ -22,8 +22,7 @@ export default class PkiRoleEngineModel extends Model {
@attr('string', {
label: 'Issuer reference',
defaultValue: 'default',
subText:
'Specifies the issuer that will be used to create certificates with this role. To find this, run [command]. By default, we will use the mounts default issuer.',
subText: `Specifies the issuer that will be used to create certificates with this role. To find this, run read -field=default pki_int/config/issuers in the console. By default, we will use the mounts default issuer.`,
})
issuerRef;

View File

@@ -5,6 +5,7 @@
@value="ttl"
@onChange={{this.onRadioButtonChange}}
@groupValue={{this.groupValue}}
data-test-radio-button="ttl"
/>
<label class="has-left-margin-xs">
<TtlPicker2
@@ -26,6 +27,7 @@
@value="specificDate"
@onChange={{this.onRadioButtonChange}}
@groupValue={{this.groupValue}}
data-test-radio-button="not_after"
/>
<label class="has-left-margin-xs">
<span class="ttl-picker-label is-large">Specific date</span><br />
@@ -34,14 +36,13 @@
</p>
{{#if (eq this.groupValue "specificDate")}}
<input
data-test-input="not_after"
id="not_after"
autocomplete="off"
spellcheck="false"
value={{this.notAfter}}
{{on "input" this.setAndBroadcastInput}}
class="input"
maxLength="21"
data-test-input="not_after"
/>
{{/if}}
</label>

View File

@@ -28,16 +28,21 @@ export default class RadioSelectTtlOrString extends Component {
// Clear the previous selection if they have clicked the other radio button.
if (selection === 'specificDate') {
this.args.model.set('ttl', '');
this.ttlTime = ''; //clear out the form field
this.ttlTime = '';
}
if (selection === 'tll') {
if (selection === 'ttl') {
this.args.model.set('notAfter', '');
this.notAfter = ''; //clear out the form field
this.notAfter = '';
this.args.model.set('ttl', this.ttlTime);
}
}
@action setAndBroadcastTtl(value) {
let valueToSet = value.enabled === true ? `${value.seconds}s` : 0;
if (this.groupValue === 'specificDate') {
// do not save ttl on the model until the ttl radio button is selected
return;
}
this.args.model.set('ttl', `${valueToSet}`);
}

View File

@@ -19,7 +19,7 @@ export default Helper.extend({
const currentRoute = router.get('currentRouteName');
let currentURL = router.get('currentURL');
// if we have any query params we want to discard them
currentURL = currentURL.split('?')[0];
currentURL = currentURL?.split('?')[0];
const comparator = isExact ? exact : startsWith;
if (!currentRoute) {
return false;

View File

@@ -49,7 +49,7 @@
data-test-input={{attr.name}}
>
{{#each (path-or-array attr.options.possibleValues @model) as |val|}}
<option selected={{eq (get @model this.valuePath) (or val.value val)}} value={{or val.value val}}>
<option selected={{eq (get @model this.valuePath) (or val.value val)}} value={{val.value}}>
{{or val.displayName val}}
</option>
{{/each}}

View File

@@ -8,7 +8,7 @@
data-test-toggle-group={{@group}}
/>
{{#if (get @model prop)}}
<div class="box is-tall is-marginless">
<div class="box is-tall is-marginless" data-test-surrounding-div={{@group}}>
<FormFieldLabel
for="keyUsageLabel"
@label="Key usage"

View File

@@ -56,6 +56,8 @@ export default class PkiKeyUsage extends Component {
this.extKeyUsageFields = {};
Object.assign(this.keyUsageFields, KEY_USAGE_FIELDS);
Object.assign(this.extKeyUsageFields, EXT_KEY_USAGE_FIELDS);
// set default of key_usage to the three params that are true by default.
this.args.model.set('keyUsage', ['DigitalSignature', 'KeyAgreement', 'KeyEncipherment']);
}
@action onStringListChange(value) {

View File

@@ -108,10 +108,21 @@
{{/each}}
</div>
<div class="has-top-padding-s">
<button type="submit" class="button is-primary {{if this.save.isRunning 'is-loading'}}" disabled={{this.save.isRunning}}>
<button
type="submit"
class="button is-primary {{if this.save.isRunning 'is-loading'}}"
disabled={{this.save.isRunning}}
data-test-pki-role-save
>
{{if @model.isNew "Create" "Update"}}
</button>
<button type="button" class="button has-left-margin-s" disabled={{this.save.isRunning}} {{on "click" this.cancel}}>
<button
type="button"
class="button has-left-margin-s"
disabled={{this.save.isRunning}}
{{on "click" this.cancel}}
data-test-pki-role-cancel
>
Cancel
</button>
{{#if this.modelValidations.targets.errors}}

View File

@@ -0,0 +1,30 @@
export const PKI_BASE_URL = `/vault/cluster/secrets/backend/pki/roles`;
export const SELECTORS = {
// Pki role
roleName: '[data-test-input="name"]',
issuerRef: '[data-test-input="issuerRef"]',
customTtl: '[data-test-field="customTtl"]',
backdateValidity: '[data-test-ttl-value="Backdate validity"]',
maxTtl: '[data-test-toggle-label="Max TTL"]',
generateLease: '[data-test-field="generateLease"]',
noStore: '[data-test-field="noStore"]',
addBasicConstraints: '[data-test-input="addBasicConstraints"]',
domainHandling: '[data-test-toggle-group="Domain handling"]',
keyParams: '[data-test-toggle-group="Key parameters"]',
keyType: '[data-test-input="keyType"]',
keyBits: '[data-test-input="keyBits"]',
signatureBits: '[data-test-input="signatureBits"]',
keyUsage: '[data-test-toggle-group="Key usage"]',
extKeyUsageOids: '[data-test-input="extKeyUsageOids"]',
digitalSignature: '[data-test-input="DigitalSignature"]',
keyAgreement: '[data-test-input="KeyAgreement"]',
keyEncipherment: '[data-test-input="KeyEncipherment"]',
any: '[data-test-input="Any"]',
serverAuth: '[data-test-input="ServerAuth"]',
policyIdentifiers: '[data-test-toggle-group="Policy identifiers"]',
san: '[data-test-toggle-group="Subject Alternative Name (SAN) Options"]',
additionalSubjectFields: '[data-test-toggle-group="Additional subject fields"]',
roleCreateButton: '[data-test-pki-role-save]',
roleCancelButton: '[data-test-pki-role-cancel]',
};

View File

@@ -0,0 +1,101 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click, fillIn } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupEngine } from 'ember-engines/test-support';
import { SELECTORS } from 'vault/tests/helpers/pki-engine';
module('Integration | Component | pki-key-parameters', function (hooks) {
setupRenderingTest(hooks);
setupEngine(hooks, 'pki');
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
this.model = this.store.createRecord('pki/pki-role-engine');
this.model.backend = 'pki';
});
test('it should render the component and display the correct defaults', async function (assert) {
assert.expect(3);
await render(
hbs`
<div class="has-top-margin-xxl">
<PkiKeyParameters
@model={{this.model}}
@group="Key parameters"
/>
</div>
`,
{ owner: this.engine }
);
await click(SELECTORS.keyParams);
assert.dom(SELECTORS.keyType).hasValue('rsa');
assert.dom(SELECTORS.keyBits).hasValue('2048');
assert.dom(SELECTORS.signatureBits).hasValue('0');
});
test('it should set the model properties of key_type and key_bits when key_type changes', async function (assert) {
assert.expect(11);
await render(
hbs`
<div class="has-top-margin-xxl">
<PkiKeyParameters
@model={{this.model}}
@group="Key parameters"
/>
</div>
`,
{ owner: this.engine }
);
assert.strictEqual(this.model.keyType, 'rsa', 'sets the default value for key_type on the model.');
assert.strictEqual(this.model.keyBits, 2048, 'sets the default value for key_bits on the model.');
assert.strictEqual(
this.model.signatureBits,
0,
'sets the default value for signature_bits on the model.'
);
await click(SELECTORS.keyParams);
await fillIn(SELECTORS.keyType, 'ec');
assert.strictEqual(this.model.keyType, 'ec', 'sets the new selected value for key_type on the model.');
assert.strictEqual(
this.model.keyBits,
256,
'sets the new selected value for key_bits on the model based on the selection of key_type.'
);
await fillIn(SELECTORS.keyType, 'ed25519');
assert.strictEqual(
this.model.keyType,
'ed25519',
'sets the new selected value for key_type on the model.'
);
assert.strictEqual(
this.model.keyBits,
0,
'sets the new selected value for key_bits on the model based on the selection of key_type.'
);
await fillIn(SELECTORS.keyType, 'ec');
await fillIn(SELECTORS.keyBits, 384);
assert.strictEqual(this.model.keyType, 'ec', 'sets the new selected value for key_type on the model.');
assert.strictEqual(
this.model.keyBits,
384,
'sets the new selected value for key_bits on the model based on the selection of key_type.'
);
await fillIn(SELECTORS.signatureBits, '384');
assert.strictEqual(
this.model.signatureBits,
384,
'sets the new selected value for signature_bits on the model.'
);
await fillIn(SELECTORS.signatureBits, '0');
assert.strictEqual(
this.model.signatureBits,
0,
'sets the default value for signature_bits on the model.'
);
});
});

View File

@@ -0,0 +1,85 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click, findAll } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupEngine } from 'ember-engines/test-support';
import { SELECTORS } from 'vault/tests/helpers/pki-engine';
module('Integration | Component | pki-key-usage', function (hooks) {
setupRenderingTest(hooks);
setupEngine(hooks, 'pki');
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
this.model = this.store.createRecord('pki/pki-role-engine');
this.model.backend = 'pki';
});
test('it should render the component', async function (assert) {
assert.expect(7);
await render(
hbs`
<div class="has-top-margin-xxl">
<PkiKeyUsage
@model={{this.model}}
@group="Key usage"
/>
</div>
`,
{ owner: this.engine }
);
await click(SELECTORS.keyUsage);
assert.strictEqual(findAll('.b-checkbox').length, 19, 'it render 19 checkboxes');
assert.dom(SELECTORS.digitalSignature).isChecked('Digital Signature is true by default');
assert.dom(SELECTORS.keyAgreement).isChecked('Key Agreement is true by default');
assert.dom(SELECTORS.keyEncipherment).isChecked('Key Encipherment is true by default');
assert.dom(SELECTORS.any).isNotChecked('Any is false by default');
assert.dom(SELECTORS.extKeyUsageOids).exists('Extended Key usage oids renders');
// check is flexbox by checking the height of the box
let groupBoxHeight = document.querySelector('[data-test-surrounding-div="Key usage"]').clientHeight;
assert.strictEqual(
groupBoxHeight,
518,
'renders the correct height of the box element if the component is rending as a flexbox'
);
});
test('it should set the model properties of key_usage and ext_key_usage based on the checkbox selections', async function (assert) {
assert.expect(4);
await render(
hbs`
<div class="has-top-margin-xxl">
<PkiKeyUsage
@model={{this.model}}
@group="Key usage"
/>
</div>
`,
{ owner: this.engine }
);
// See PKI API docs https://developer.hashicorp.com/vault/api-docs/secret/pki#key_usage
assert.deepEqual(
this.model.keyUsage,
['DigitalSignature', 'KeyAgreement', 'KeyEncipherment'],
'sets the default values for key_usage on the model.'
);
assert.strictEqual(
this.model.extKeyUsage,
undefined,
'sets no default value set for ext_key_usage on load.'
);
await click(SELECTORS.keyUsage);
await click(SELECTORS.digitalSignature);
await click(SELECTORS.any);
await click(SELECTORS.serverAuth);
assert.deepEqual(
this.model.keyUsage,
['KeyAgreement', 'KeyEncipherment'],
'removes digitalSignature from the model when unchecked.'
);
assert.deepEqual(this.model.extKeyUsage, ['Any', 'ServerAuth'], 'adds new checkboxes to when checked');
});
});

View File

@@ -0,0 +1,112 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click, fillIn } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupEngine } from 'ember-engines/test-support';
import { SELECTORS } from 'vault/tests/helpers/pki-engine';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Integration | Component | pki/role-form', function (hooks) {
setupRenderingTest(hooks);
setupMirage(hooks);
setupEngine(hooks, 'pki'); // https://github.com/ember-engines/ember-engines/pull/653
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
this.model = this.store.createRecord('pki/pki-role-engine');
this.model.backend = 'pki';
});
test('it should render default fields and toggle groups', async function (assert) {
assert.expect(13);
await render(
hbs`
<PkiRoleForm
@model={{this.model}}
@onCancel={{this.onCancel}}
@onSave={{this.onSave}}
/>
`,
{ owner: this.engine }
);
assert.dom(SELECTORS.issuerRef).exists('shows form-field issuer ref');
assert.dom(SELECTORS.backdateValidity).exists('shows form-field backdate validity');
assert.dom(SELECTORS.customTtl).exists('shows custom yielded form field');
assert.dom(SELECTORS.maxTtl).exists('shows form-field max ttl');
assert.dom(SELECTORS.generateLease).exists('shows form-field generateLease');
assert.dom(SELECTORS.noStore).exists('shows form-field no store');
assert.dom(SELECTORS.addBasicConstraints).exists('shows form-field add basic constraints');
assert.dom(SELECTORS.domainHandling).exists('shows form-field group add domain handling');
assert.dom(SELECTORS.keyParams).exists('shows form-field group key params');
assert.dom(SELECTORS.keyUsage).exists('shows form-field group key usage');
assert.dom(SELECTORS.policyIdentifiers).exists('shows form-field group policy identifiers');
assert.dom(SELECTORS.san).exists('shows form-field group SAN');
assert.dom(SELECTORS.additionalSubjectFields).exists('shows form-field group additional subject fields');
});
test('it should save a new pki role with various options selected', async function (assert) {
// Key usage, Key params and Not valid after options are tested in their respective component tests
assert.expect(9);
this.server.post(`/${this.model.backend}/roles/test-role`, (schema, req) => {
assert.ok(true, 'Request made to save role');
const request = JSON.parse(req.requestBody);
const roleName = request.name;
const allowedDomainsTemplate = request.allowed_domains_template;
const policyIdentifiers = request.policy_identifiers;
const allowedUriSansTemplate = request.allow_uri_sans_template;
const allowedSerialNumbers = request.allowed_serial_numbers;
assert.strictEqual(roleName, 'test-role', 'correctly sends the role name');
assert.true(allowedDomainsTemplate, 'correctly sends allowed_domains_template');
assert.strictEqual(policyIdentifiers[0], 'some-oid', 'correctly sends policy_identifiers');
assert.true(allowedUriSansTemplate, 'correctly sends allowed_uri_sans_template');
assert.strictEqual(
allowedSerialNumbers[0],
'some-serial-number',
'correctly sends allowed_serial_numbers'
);
return {};
});
this.onSave = () => assert.ok(true, 'onSave callback fires on save success');
await render(
hbs`
<PkiRoleForm
@model={{this.model}}
@onCancel={{this.onCancel}}
@onSave={{this.onSave}}
/>
`,
{ owner: this.engine }
);
await click(SELECTORS.roleCreateButton);
assert
.dom(SELECTORS.roleName)
.hasClass('has-error-border', 'shows border error on role name field when no role name is submitted');
assert
.dom('[data-test-inline-error-message]')
.includesText('Name is required.', 'show correct error message');
await fillIn(SELECTORS.roleName, 'test-role');
await click('[data-test-input="addBasicConstraints"]');
await click(SELECTORS.domainHandling);
await click('[data-test-input="allowedDomainsTemplate"]');
await click(SELECTORS.policyIdentifiers);
await fillIn('[data-test-input="policyIdentifiers"] [data-test-string-list-input="0"]', 'some-oid');
await click(SELECTORS.san);
await click('[data-test-input="allowUriSansTemplate"]');
await click(SELECTORS.additionalSubjectFields);
await fillIn(
'[data-test-input="allowedSerialNumbers"] [data-test-string-list-input="0"]',
'some-serial-number'
);
await click(SELECTORS.roleCreateButton);
});
/* FUTURE TEST TODO:
* it should update role
* it should unload the record on cancel
*/
});

View File

@@ -0,0 +1,90 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click, fillIn } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupEngine } from 'ember-engines/test-support';
module('Integration | Component | radio-select-ttl-or-string', function (hooks) {
setupRenderingTest(hooks);
setupEngine(hooks, 'pki');
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
this.model = this.store.createRecord('pki/pki-role-engine');
this.model.backend = 'pki';
this.attr = {
helpText: '',
options: {
helperTextEnabled: 'toggled on and shows text',
},
};
});
test('it should render the component and init with ttl selected', async function (assert) {
assert.expect(3);
await render(
hbs`
<div class="has-top-margin-xxl">
<RadioSelectTtlOrString
@model={{this.model}}
@attr={{this.attr}}
/>
</div>
`,
{ owner: this.engine }
);
assert.dom('[data-test-input="ttl"]').exists('shows the TTL component');
let inputValue = document.querySelector('[data-test-ttl-value="TTL"]').value;
assert.strictEqual(inputValue, '', 'default TTL is empty');
assert.dom('[data-test-radio-button="ttl"]').isChecked('ttl is selected by default');
});
test('it should set the model properties ttl or notAfter based on the radio button selections', async function (assert) {
assert.expect(8);
await render(
hbs`
<div class="has-top-margin-xxl">
<RadioSelectTtlOrString
@model={{this.model}}
@attr={{this.attr}}
/>
</div>
`,
{ owner: this.engine }
);
assert.dom('[data-test-input="not_after"]').doesNotExist('does not show input field on initial render');
await click('[data-test-radio-button="not_after"]');
assert
.dom('[data-test-input="not_after"]')
.exists('does show input field after clicking the radio button');
const utcDate = '1994-11-05T08:15:30-05:0';
const ttlDate = 1;
await fillIn('[data-test-input="not_after"]', utcDate);
assert.strictEqual(
this.model.notAfter,
utcDate,
'sets the model property notAfter when this value is selected and filled in.'
);
await fillIn('[data-test-ttl-value="TTL"]', ttlDate);
assert.strictEqual(this.model.ttl, '', 'No ttl is set because the radio button was not selected.');
await click('[data-test-radio-button="ttl"]');
assert.strictEqual(
this.model.notAfter,
'',
'The notAfter is cleared on the model because the radio button was selected.'
);
assert.strictEqual(
this.model.ttl,
ttlDate,
'The ttl is now saved on the model because the radio button was selected.'
);
await click('[data-test-radio-button="not_after"]');
assert.strictEqual(this.model.ttl, '', 'Both ttl and notAfter are cleared.');
assert.strictEqual(this.model.notAfter, '', 'Both ttl and notAfter are cleared.');
});
});