/** * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: BUSL-1.1 */ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { setupEngine } from 'ember-engines/test-support'; import { setupMirage } from 'ember-cli-mirage/test-support'; import { render, click, fillIn } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import sinon from 'sinon'; module('Integration | Component | kubernetes | Page::Role::CreateAndEdit', function (hooks) { setupRenderingTest(hooks); setupEngine(hooks, 'kubernetes'); setupMirage(hooks); hooks.beforeEach(function () { const router = this.owner.lookup('service:router'); const routerStub = sinon.stub(router, 'transitionTo'); this.transitionCalledWith = (routeName, name) => { const route = `vault.cluster.secrets.backend.kubernetes.${routeName}`; const args = name ? [route, name] : [route]; return routerStub.calledWith(...args); }; const store = this.owner.lookup('service:store'); this.getRole = (trait) => { const role = this.server.create('kubernetes-role', trait); store.pushPayload('kubernetes/role', { modelName: 'kubernetes/role', backend: 'kubernetes-test', ...role, }); return store.peekRecord('kubernetes/role', role.name); }; this.newModel = store.createRecord('kubernetes/role', { backend: 'kubernetes-test' }); this.breadcrumbs = [ { label: this.newModel.backend, route: 'overview' }, { label: 'roles', route: 'roles' }, { label: 'create' }, ]; }); test('it should display placeholder when generation preference is not selected', async function (assert) { await render( hbs``, { owner: this.engine } ); assert .dom('[data-test-empty-state-title]') .hasText('Choose an option above', 'Empty state title renders'); assert .dom('[data-test-empty-state-message]') .hasText( 'To configure a Vault role, choose what should be generated in Kubernetes by Vault.', 'Empty state message renders' ); assert.dom('[data-test-save]').isDisabled('Save button is disabled'); }); test('it should display different form fields based on generation preference selection', async function (assert) { await render( hbs``, { owner: this.engine } ); const commonFields = [ 'name', 'allowedKubernetesNamespaces', 'tokenMaxTtl', 'tokenDefaultTtl', 'annotations', ]; await click('[data-test-radio-card="basic"]'); ['serviceAccountName', ...commonFields].forEach((field) => { assert.dom(`[data-test-field="${field}"]`).exists(`${field} field renders`); }); await click('[data-test-radio-card="expanded"]'); ['kubernetesRoleType', 'kubernetesRoleName', 'nameTemplate', ...commonFields].forEach((field) => { assert.dom(`[data-test-field="${field}"]`).exists(`${field} field renders`); }); await click('[data-test-radio-card="full"]'); ['kubernetesRoleType', 'nameTemplate', ...commonFields].forEach((field) => { assert.dom(`[data-test-field="${field}"]`).exists(`${field} field renders`); }); assert.dom('[data-test-generated-role-rules]').exists('Generated role rules section renders'); }); test('it should clear specific form fields when switching generation preference', async function (assert) { await render( hbs``, { owner: this.engine } ); await click('[data-test-radio-card="basic"]'); await fillIn('[data-test-input="serviceAccountName"]', 'test'); await click('[data-test-radio-card="expanded"]'); assert.strictEqual( this.newModel.serviceAccountName, null, 'Service account name cleared when switching from basic to expanded' ); await fillIn('[data-test-input="kubernetesRoleName"]', 'test'); await click('[data-test-radio-card="full"]'); assert.strictEqual( this.newModel.kubernetesRoleName, null, 'Kubernetes role name cleared when switching from expanded to full' ); await click('[data-test-input="kubernetesRoleType"] input'); await click('[data-test-toggle-input="show-nameTemplate"]'); await fillIn('[data-test-input="nameTemplate"]', 'bar'); await fillIn('[data-test-select-template]', '6'); await click('[data-test-radio-card="expanded"]'); assert.strictEqual( this.newModel.generatedRoleRules, null, 'Role rules cleared when switching from full to expanded' ); await click('[data-test-radio-card="basic"]'); assert.strictEqual( this.newModel.kubernetesRoleType, null, 'Kubernetes role type cleared when switching from expanded to basic' ); assert.strictEqual( this.newModel.kubernetesRoleName, null, 'Kubernetes role name cleared when switching from expanded to basic' ); assert.strictEqual( this.newModel.nameTemplate, null, 'Name template cleared when switching from expanded to basic' ); }); test('it should create new role', async function (assert) { assert.expect(3); this.server.post('/kubernetes-test/roles/role-1', () => assert.ok('POST request made to save role')); await render( hbs``, { owner: this.engine } ); await click('[data-test-radio-card="basic"]'); await click('[data-test-save]'); assert.dom('[data-test-inline-error-message]').hasText('Name is required', 'Validation error renders'); await fillIn('[data-test-input="name"]', 'role-1'); await fillIn('[data-test-input="serviceAccountName"]', 'default'); await click('[data-test-save]'); assert.ok( this.transitionCalledWith('roles.role.details', this.newModel.name), 'Transitions to details route on save' ); }); test('it should populate fields when editing role', async function (assert) { assert.expect(15); this.server.post('/kubernetes-test/roles/:name', () => assert.ok('POST request made to save role')); for (const pref of ['basic', 'expanded', 'full']) { const trait = { expanded: 'withRoleName', full: 'withRoleRules' }[pref]; this.role = this.getRole(trait); await render( hbs``, { owner: this.engine } ); assert.dom(`[data-test-radio-card="${pref}"] input`).isChecked('Correct radio card is checked'); assert.dom('[data-test-input="name"]').hasValue(this.role.name, 'Role name is populated'); const selector = { basic: { name: '[data-test-input="serviceAccountName"]', method: 'hasValue', value: 'default' }, expanded: { name: '[data-test-input="kubernetesRoleName"]', method: 'hasValue', value: 'vault-k8s-secrets-role', }, full: { name: '[data-test-select-template]', method: 'hasValue', value: '5', }, }[pref]; assert.dom(selector.name)[selector.method](selector.value); await click('[data-test-save]'); assert.ok( this.transitionCalledWith('roles.role.details', this.role.name), 'Transitions to details route on save' ); } }); test('it should show and hide annotations and labels', async function (assert) { await render( hbs``, { owner: this.engine } ); await click('[data-test-radio-card="basic"]'); assert.dom('[data-test-annotations]').doesNotExist('Annotations and labels are hidden'); await click('[data-test-field="annotations"]'); await fillIn('[data-test-kv="annotations"] [data-test-kv-key]', 'foo'); await fillIn('[data-test-kv="annotations"] [data-test-kv-value]', 'bar'); await click('[data-test-kv="annotations"] [data-test-kv-add-row]'); assert.deepEqual(this.newModel.extraAnnotations, { foo: 'bar' }, 'Annotations set'); await fillIn('[data-test-kv="labels"] [data-test-kv-key]', 'bar'); await fillIn('[data-test-kv="labels"] [data-test-kv-value]', 'baz'); await click('[data-test-kv="labels"] [data-test-kv-add-row]'); assert.deepEqual(this.newModel.extraLabels, { bar: 'baz' }, 'Labels set'); }); test('it should expand annotations and labels when editing if they were populated', async function (assert) { this.role = this.getRole(); await render(hbs``, { owner: this.engine, }); assert .dom('[data-test-annotations]') .doesNotExist('Annotations and labels are collapsed initially when not defined'); this.role = this.getRole('withRoleRules'); await render(hbs``, { owner: this.engine, }); assert .dom('[data-test-annotations]') .exists('Annotations and labels are expanded initially when defined'); }); test('it should restore role rule example', async function (assert) { this.role = this.getRole('withRoleRules'); await render(hbs``, { owner: this.engine, }); const addedText = 'this will be add to the start of the first line in the JsonEditor'; await fillIn('[data-test-component="code-mirror-modifier"] textarea', addedText); await click('[data-test-restore-example]'); assert.dom('.CodeMirror-code').doesNotContainText(addedText, 'Role rules example restored'); }); test('it should set generatedRoleRoles model prop on save', async function (assert) { assert.expect(1); this.server.post('/kubernetes-test/roles/role-1', (schema, req) => { const payload = JSON.parse(req.requestBody); const role = this.server.create('kubernetes-role', 'withRoleRules'); assert.strictEqual( payload.generated_role_rules, role.generated_role_rules, 'Generated roles rules are passed in save request' ); }); await render( hbs``, { owner: this.engine } ); await click('[data-test-radio-card="full"]'); await fillIn('[data-test-input="name"]', 'role-1'); await fillIn('[data-test-select-template]', '5'); await click('[data-test-save]'); }); test('it should unset selectedTemplateId when switching from full generation preference', async function (assert) { assert.expect(1); this.server.post('/kubernetes-test/roles/role-1', (schema, req) => { const payload = JSON.parse(req.requestBody); assert.strictEqual(payload.generated_role_rules, null, 'Generated roles rules are not set'); }); await render( hbs``, { owner: this.engine } ); await click('[data-test-radio-card="full"]'); await fillIn('[data-test-input="name"]', 'role-1'); await fillIn('[data-test-select-template]', '5'); await click('[data-test-radio-card="basic"]'); await fillIn('[data-test-input="serviceAccountName"]', 'default'); await click('[data-test-save]'); }); test('it should go back to list route and clean up model', async function (assert) { const unloadSpy = sinon.spy(this.newModel, 'unloadRecord'); await render( hbs``, { owner: this.engine } ); await click('[data-test-cancel]'); assert.ok(unloadSpy.calledOnce, 'New model is unloaded on cancel'); assert.ok(this.transitionCalledWith('roles'), 'Transitions to roles list on cancel'); this.role = this.getRole(); const rollbackSpy = sinon.spy(this.role, 'rollbackAttributes'); await render(hbs``, { owner: this.engine, }); await click('[data-test-cancel]'); assert.ok(rollbackSpy.calledOnce, 'Attributes are rolled back for existing model on cancel'); assert.ok(this.transitionCalledWith('roles'), 'Transitions to roles list on cancel'); }); test('it should check for form errors', async function (assert) { await render( hbs``, { owner: this.engine } ); await click('[data-test-radio-card="basic"]'); await click('[data-test-save]'); assert .dom('[data-test-input="name"]') .hasClass('has-error-border', 'shows border error on input with error'); assert.dom('[data-test-inline-error-message]').hasText('Name is required'); assert .dom('[data-test-invalid-form-alert] [data-test-inline-error-message]') .hasText('There is an error with this form.'); }); test('it should save edited role with correct properties', async function (assert) { assert.expect(1); this.role = this.getRole(); this.server.post('/kubernetes-test/roles/:name', (schema, req) => { const data = JSON.parse(req.requestBody); const expected = { name: 'role-0', service_account_name: 'demo', kubernetes_role_type: 'Role', allowed_kubernetes_namespaces: '*', token_max_ttl: 86400, token_default_ttl: 600, }; assert.deepEqual(expected, data, 'POST request made to save role with correct properties'); }); await render(hbs``, { owner: this.engine, }); await fillIn('[data-test-input="serviceAccountName"]', 'demo'); await click('[data-test-save]'); }); });