mirror of
https://github.com/hashicorp/vault.git
synced 2025-12-26 03:41:18 +01:00
* convert file to typescript * remove unused util * add support for nested options * move automation snippets outside of builder component * update snippet utils * Revert "remove unused util" This reverts commit bcb53271e63dd1fc3d2f735d7f7fcc54e5e31988. * render automation snippets for only acl policy types * cleanup old args * add default arg for formatEot * make tfvp formatters easier to follow, maybe? Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>
234 lines
9.3 KiB
JavaScript
234 lines
9.3 KiB
JavaScript
/**
|
|
* Copyright IBM Corp. 2016, 2025
|
|
* SPDX-License-Identifier: BUSL-1.1
|
|
*/
|
|
|
|
import { module, test } from 'qunit';
|
|
import { setupRenderingTest } from 'ember-qunit';
|
|
import { render, click, fillIn, setupOnerror } from '@ember/test-helpers';
|
|
import { hbs } from 'ember-cli-htmlbars';
|
|
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
|
import { ACL_CAPABILITIES, PolicyStanza } from 'core/utils/code-generators/policy';
|
|
|
|
module('Integration | Component | code-generator/policy/builder', function (hooks) {
|
|
setupRenderingTest(hooks);
|
|
|
|
hooks.beforeEach(function () {
|
|
this.onPolicyChange = ({ policy, stanzas }) => {
|
|
this.set('policyCallback', policy);
|
|
this.set('stanzas', stanzas);
|
|
};
|
|
|
|
this.policyCallback = '';
|
|
this.policyName = undefined;
|
|
this.stanzas = [new PolicyStanza()];
|
|
|
|
this.renderComponent = () => {
|
|
return render(hbs`
|
|
<CodeGenerator::Policy::Builder
|
|
@onPolicyChange={{this.onPolicyChange}}
|
|
@policyName={{this.policyName}}
|
|
@stanzas={{this.stanzas}}
|
|
/>`);
|
|
};
|
|
|
|
this.assertPolicyUpdate = (assert, expected, message) => {
|
|
// this.policyCallback is set by onPolicyChange
|
|
assert.strictEqual(this.policyCallback, expected, `onPolicyChange is called ${message}`);
|
|
};
|
|
|
|
this.assertEmptyTemplate = async (assert, { index } = {}) => {
|
|
const container = index ? GENERAL.cardContainer(index) : '';
|
|
assert.dom(`${container} ${GENERAL.inputByAttr('path')}`).hasValue('');
|
|
assert.dom(`${container} ${GENERAL.toggleInput('preview')}`).isNotChecked();
|
|
ACL_CAPABILITIES.forEach((capability) => {
|
|
assert.dom(`${container} ${GENERAL.checkboxByAttr(capability)}`).isNotChecked();
|
|
});
|
|
// check empty preview state
|
|
await click(`${container} ${GENERAL.toggleInput('preview')}`);
|
|
const expectedPreview = `path "" {
|
|
capabilities = []
|
|
}`;
|
|
assert
|
|
.dom(GENERAL.fieldByAttr('preview'))
|
|
.exists('it renders preview')
|
|
.hasText(expectedPreview, 'preview is empty');
|
|
};
|
|
});
|
|
|
|
test('it renders', async function (assert) {
|
|
await this.renderComponent();
|
|
await this.assertEmptyTemplate(assert);
|
|
assert.dom(GENERAL.button('Add rule')).exists({ count: 1 });
|
|
});
|
|
|
|
test('it throws an error when stanzas are not provided', async function (assert) {
|
|
this.stanzas = undefined;
|
|
// catches error so qunit test doesn't fail
|
|
setupOnerror(({ message }) => {
|
|
assert.strictEqual(
|
|
message,
|
|
'Assertion Failed: @stanzas are required and must be an array of PolicyStanza instances'
|
|
);
|
|
});
|
|
await this.renderComponent();
|
|
});
|
|
|
|
test('it adds a rule', async function (assert) {
|
|
await this.renderComponent();
|
|
await click(GENERAL.button('Add rule'));
|
|
assert
|
|
.dom(GENERAL.cardContainer())
|
|
.exists({ count: 2 }, 'two templates render after clicking "Add rule"');
|
|
await this.assertEmptyTemplate(assert, { index: '1' });
|
|
});
|
|
|
|
test('it deletes a rule', async function (assert) {
|
|
await this.renderComponent();
|
|
assert.dom(GENERAL.cardContainer()).exists({ count: 1 });
|
|
// Fill in template
|
|
await fillIn(GENERAL.inputByAttr('path'), 'some/api/path');
|
|
await click(GENERAL.checkboxByAttr('patch'));
|
|
// Delete the only rendered template
|
|
await click(GENERAL.button('Delete'));
|
|
// One template renders but content should reset
|
|
assert
|
|
.dom(GENERAL.cardContainer())
|
|
.exists({ count: 1 }, 'it still renders one rule after deleting the only rule');
|
|
await this.assertEmptyTemplate(assert);
|
|
});
|
|
|
|
test('it maintains state across multiple rules', async function (assert) {
|
|
await this.renderComponent();
|
|
// Set up first rule
|
|
await fillIn(GENERAL.inputByAttr('path'), 'first/path');
|
|
await click(GENERAL.checkboxByAttr('read'));
|
|
// Add second rule
|
|
await click(GENERAL.button('Add rule'));
|
|
await fillIn(`${GENERAL.cardContainer('1')} ${GENERAL.inputByAttr('path')}`, 'second/path');
|
|
await click(`${GENERAL.cardContainer('1')} ${GENERAL.checkboxByAttr('update')}`);
|
|
|
|
assert.dom(`${GENERAL.cardContainer('0')} ${GENERAL.inputByAttr('path')}`).hasValue('first/path');
|
|
assert.dom(`${GENERAL.cardContainer('0')} ${GENERAL.checkboxByAttr('read')}`).isChecked();
|
|
assert.dom(`${GENERAL.cardContainer('0')} ${GENERAL.checkboxByAttr('update')}`).isNotChecked();
|
|
assert.dom(`${GENERAL.cardContainer('1')} ${GENERAL.inputByAttr('path')}`).hasValue('second/path');
|
|
assert.dom(`${GENERAL.cardContainer('1')} ${GENERAL.checkboxByAttr('update')}`).isChecked();
|
|
assert.dom(`${GENERAL.cardContainer('1')} ${GENERAL.checkboxByAttr('read')}`).isNotChecked();
|
|
});
|
|
|
|
test('it deletes the correct rule when multiple exist', async function (assert) {
|
|
await this.renderComponent();
|
|
await fillIn(GENERAL.inputByAttr('path'), 'first/path');
|
|
await click(GENERAL.checkboxByAttr('read'));
|
|
// Second rule
|
|
await click(GENERAL.button('Add rule'));
|
|
await fillIn(`${GENERAL.cardContainer('1')} ${GENERAL.inputByAttr('path')}`, 'second/path');
|
|
await click(`${GENERAL.cardContainer('1')} ${GENERAL.checkboxByAttr('update')}`);
|
|
// Third rule
|
|
await click(GENERAL.button('Add rule'));
|
|
await fillIn(`${GENERAL.cardContainer('2')} ${GENERAL.inputByAttr('path')}`, 'third/path');
|
|
await click(`${GENERAL.cardContainer('2')} ${GENERAL.checkboxByAttr('list')}`);
|
|
assert.dom(GENERAL.cardContainer()).exists({ count: 3 });
|
|
// Delete middle rule
|
|
await click(`${GENERAL.cardContainer('1')} ${GENERAL.button('Delete')}`);
|
|
assert.dom(GENERAL.cardContainer()).exists({ count: 2 });
|
|
assert.dom(`${GENERAL.cardContainer('0')} ${GENERAL.inputByAttr('path')}`).hasValue('first/path');
|
|
assert.dom(`${GENERAL.cardContainer('0')} ${GENERAL.checkboxByAttr('read')}`).isChecked();
|
|
assert.dom(`${GENERAL.cardContainer('1')} ${GENERAL.inputByAttr('path')}`).hasValue('third/path');
|
|
assert.dom(`${GENERAL.cardContainer('1')} ${GENERAL.checkboxByAttr('list')}`).isChecked();
|
|
});
|
|
|
|
test('it passes policy updates as changes are made', async function (assert) {
|
|
await this.renderComponent();
|
|
// Inputting path triggers callback
|
|
await fillIn(GENERAL.inputByAttr('path'), 'my/super/secret/*');
|
|
let expectedPolicy = `path "my/super/secret/*" {
|
|
capabilities = []
|
|
}`;
|
|
|
|
this.assertPolicyUpdate(assert, expectedPolicy, 'when path changes');
|
|
|
|
// Clicking checkbox triggers callback
|
|
await click(GENERAL.checkboxByAttr('update'));
|
|
expectedPolicy = `path "my/super/secret/*" {
|
|
capabilities = ["update"]
|
|
}`;
|
|
|
|
this.assertPolicyUpdate(assert, expectedPolicy, 'when a capability is selected');
|
|
|
|
// Adding a rule triggers callback
|
|
await click(GENERAL.button('Add rule'));
|
|
expectedPolicy = `path "my/super/secret/*" {
|
|
capabilities = ["update"]
|
|
}
|
|
path "" {
|
|
capabilities = []
|
|
}`;
|
|
this.assertPolicyUpdate(assert, expectedPolicy, 'when a rule is added');
|
|
|
|
// Updating added rule triggers callback
|
|
await fillIn(`${GENERAL.cardContainer('1')} ${GENERAL.inputByAttr('path')}`, 'prod/');
|
|
await click(`${GENERAL.cardContainer('1')} ${GENERAL.checkboxByAttr('list')}`);
|
|
await click(`${GENERAL.cardContainer('1')} ${GENERAL.checkboxByAttr('read')}`);
|
|
expectedPolicy = `path "my/super/secret/*" {
|
|
capabilities = ["update"]
|
|
}
|
|
path "prod/" {
|
|
capabilities = ["read", "list"]
|
|
}`;
|
|
this.assertPolicyUpdate(assert, expectedPolicy, 'when an additional rule updates');
|
|
|
|
// Unchecking box triggers callback
|
|
await click(`${GENERAL.cardContainer('1')} ${GENERAL.checkboxByAttr('read')}`);
|
|
expectedPolicy = `path "my/super/secret/*" {
|
|
capabilities = ["update"]
|
|
}
|
|
path "prod/" {
|
|
capabilities = ["list"]
|
|
}`;
|
|
this.assertPolicyUpdate(assert, expectedPolicy, 'when checkbox is unselected');
|
|
|
|
// Deleting a rule triggers callback
|
|
await click(GENERAL.button('Delete'));
|
|
expectedPolicy = `path "prod/" {
|
|
capabilities = ["list"]
|
|
}`;
|
|
this.assertPolicyUpdate(assert, expectedPolicy, 'when a rule is deleted');
|
|
});
|
|
|
|
// These tests ensure paths are never used as input identifiers.
|
|
// The policy generator may not render in a form and needs to be flexible so it intentionally supports
|
|
// multiple templates with the same or no path.
|
|
test('it supports multiple rules with the same path', async function (assert) {
|
|
await this.renderComponent();
|
|
await fillIn(GENERAL.inputByAttr('path'), 'test/path');
|
|
await click(GENERAL.checkboxByAttr('patch'));
|
|
await click(GENERAL.button('Add rule'));
|
|
await fillIn(`${GENERAL.cardContainer('1')} ${GENERAL.inputByAttr('path')}`, 'test/path');
|
|
await click(`${GENERAL.cardContainer('1')} ${GENERAL.checkboxByAttr('update')}`);
|
|
|
|
const expectedPolicy = `path "test/path" {
|
|
capabilities = ["patch"]
|
|
}
|
|
path "test/path" {
|
|
capabilities = ["update"]
|
|
}`;
|
|
this.assertPolicyUpdate(assert, expectedPolicy, 'when rules have the same path');
|
|
});
|
|
|
|
test('it supports multiple rules with an empty path', async function (assert) {
|
|
await this.renderComponent();
|
|
await click(GENERAL.checkboxByAttr('list'));
|
|
await click(GENERAL.button('Add rule'));
|
|
await click(`${GENERAL.cardContainer('1')} ${GENERAL.checkboxByAttr('delete')}`);
|
|
|
|
const expectedPolicy = `path "" {
|
|
capabilities = ["list"]
|
|
}
|
|
path "" {
|
|
capabilities = ["delete"]
|
|
}`;
|
|
this.assertPolicyUpdate(assert, expectedPolicy, 'when rules do have an empty path');
|
|
});
|
|
});
|