/** * 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, waitUntil, find, fillIn } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import { Response } from 'miragejs'; import sinon from 'sinon'; module('Integration | Component | kubernetes | Page::Configure', function (hooks) { setupRenderingTest(hooks); setupEngine(hooks, 'kubernetes'); setupMirage(hooks); hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); this.newModel = this.store.createRecord('kubernetes/config', { backend: 'kubernetes-new' }); this.existingConfig = { kubernetes_host: 'https://192.168.99.100:8443', kubernetes_ca_cert: '-----BEGIN CERTIFICATE-----\n.....\n-----END CERTIFICATE-----', service_account_jwt: 'test-jwt', disable_local_ca_jwt: true, }; this.store.pushPayload('kubernetes/config', { modelName: 'kubernetes/config', backend: 'kubernetes-edit', ...this.existingConfig, }); this.editModel = this.store.peekRecord('kubernetes/config', 'kubernetes-edit'); this.breadcrumbs = [ { label: 'secrets', route: 'secrets', linkExternal: true }, { label: 'kubernetes', route: 'overview' }, { label: 'configure' }, ]; this.expectedInferred = { disable_local_ca_jwt: false, kubernetes_ca_cert: null, kubernetes_host: null, service_account_jwt: null, }; }); test('it should display proper options when toggling radio cards', async function (assert) { await render(hbs``, { owner: this.engine, }); assert .dom('[data-test-radio-card="local"] input') .isChecked('Local cluster radio card is checked by default'); assert .dom('[data-test-config] p') .hasText( 'Configuration values can be inferred from the pod and your local environment variables.', 'Inferred text is displayed' ); assert.dom('[data-test-config] button').hasText('Get config values', 'Get config button renders'); assert .dom('[data-test-config-save]') .isDisabled('Save button is disabled when config values have not been inferred'); assert.dom('[data-test-config-cancel]').hasText('Back', 'Back button renders'); await click('[data-test-radio-card="manual"]'); assert.dom('[data-test-field]').exists({ count: 3 }, 'Form fields render'); assert.dom('[data-test-config-save]').isNotDisabled('Save button is enabled'); assert.dom('[data-test-config-cancel]').hasText('Back', 'Back button renders'); }); test('it should check for inferred config variables', async function (assert) { assert.expect(8); let status = 404; this.server.get('/:path/check', () => { assert.ok( waitUntil(() => find('[data-test-config] button').disabled), 'Button is disabled while request is in flight' ); return new Response(status, {}); }); await render(hbs``, { owner: this.engine, }); await click('[data-test-config] button'); assert .dom('[data-test-icon="x-square-fill"]') .hasClass('has-text-danger', 'Icon is displayed for error state with correct styling'); const error = 'Vault could not infer a configuration from your environment variables. Check your configuration file to edit or delete them, or configure manually.'; assert.dom('[data-test-config] span').hasText(error, 'Error text is displayed'); assert.dom('[data-test-config-save]').isDisabled('Save button is disabled in error state'); status = 204; await click('[data-test-radio-card="manual"]'); await click('[data-test-radio-card="local"]'); await click('[data-test-config] button'); assert .dom('[data-test-icon="check-circle-fill"]') .hasClass('has-text-success', 'Icon is displayed for success state with correct styling'); assert .dom('[data-test-config] span') .hasText('Configuration values were inferred successfully.', 'Success text is displayed'); assert.dom('[data-test-config-save]').isNotDisabled('Save button is enabled in success state'); }); test('it should create new manual config', async function (assert) { assert.expect(2); this.server.post('/:path/config', (schema, req) => { const json = JSON.parse(req.requestBody); assert.deepEqual(json, this.existingConfig, 'Values are passed to create endpoint'); return new Response(204, {}); }); const stub = sinon.stub(this.owner.lookup('service:router'), 'transitionTo'); await render(hbs``, { owner: this.engine, }); await click('[data-test-radio-card="manual"]'); await fillIn('[data-test-input="kubernetesHost"]', this.existingConfig.kubernetes_host); await fillIn('[data-test-input="serviceAccountJwt"]', this.existingConfig.service_account_jwt); await fillIn('[data-test-input="kubernetesCaCert"]', this.existingConfig.kubernetes_ca_cert); await click('[data-test-config-save]'); assert.ok( stub.calledWith('vault.cluster.secrets.backend.kubernetes.configuration'), 'Transitions to configuration route on save success' ); }); test('it should edit existing manual config', async function (assert) { assert.expect(6); const stub = sinon.stub(this.owner.lookup('service:router'), 'transitionTo'); await render(hbs``, { owner: this.engine, }); assert.dom('[data-test-radio-card="manual"] input').isChecked('Manual config radio card is checked'); assert .dom('[data-test-input="kubernetesHost"]') .hasValue(this.existingConfig.kubernetes_host, 'Host field is populated'); assert .dom('[data-test-input="serviceAccountJwt"]') .hasValue(this.existingConfig.service_account_jwt, 'JWT field is populated'); assert .dom('[data-test-input="kubernetesCaCert"]') .hasValue(this.existingConfig.kubernetes_ca_cert, 'Cert field is populated'); await fillIn('[data-test-input="kubernetesHost"]', 'http://localhost:1212'); await click('[data-test-config-cancel]'); assert.ok( stub.calledWith('vault.cluster.secrets.backend.kubernetes.configuration'), 'Transitions to configuration route when cancelling edit' ); assert.strictEqual( this.editModel.kubernetesHost, this.existingConfig.kubernetes_host, 'Model values are rolled back on cancel' ); }); test('it should display inferred success message when editing model using local values', async function (assert) { this.store.pushPayload('kubernetes/config', { modelName: 'kubernetes/config', backend: 'kubernetes-edit-2', disable_local_ca_jwt: false, }); this.model = this.store.peekRecord('kubernetes/config', 'kubernetes-edit-2'); await render(hbs``, { owner: this.engine, }); assert.dom('[data-test-radio-card="local"] input').isChecked('Local cluster radio card is checked'); assert .dom('[data-test-icon="check-circle-fill"]') .hasClass('has-text-success', 'Icon is displayed for success state with correct styling'); assert .dom('[data-test-config] span') .hasText('Configuration values were inferred successfully.', 'Success text is displayed'); }); test('it should show confirmation modal when saving edits', async function (assert) { assert.expect(2); this.server.post('/:path/config', () => { assert.ok(true, 'Save request made after confirmation'); return new Response(204, {}); }); await render( hbs` `, { owner: this.engine } ); await click('[data-test-config-save]'); assert .dom('[data-test-edit-config-body]') .hasText( 'Making changes to your configuration may affect how Vault will reach the Kubernetes API and authenticate with it. Are you sure?', 'Confirm modal renders' ); await click('[data-test-config-confirm]'); }); test('it should validate form and show errors', async function (assert) { await render(hbs``, { owner: this.engine, }); await click('[data-test-radio-card="manual"]'); await click('[data-test-config-save]'); assert .dom('[data-test-inline-error-message]') .hasText('Kubernetes host is required', 'Error renders for required field'); assert.dom('[data-test-alert] p').hasText('There is an error with this form.', 'Alert renders'); }); test('it should save inferred config', async function (assert) { assert.expect(2); this.server.get('/:path/check', () => new Response(204, {})); this.server.post('/:path/config', (schema, req) => { const json = JSON.parse(req.requestBody); assert.deepEqual(json, this.expectedInferred, 'Values are passed to create endpoint'); return new Response(204, {}); }); const stub = sinon.stub(this.owner.lookup('service:router'), 'transitionTo'); await render(hbs``, { owner: this.engine, }); await click('[data-test-config] button'); await click('[data-test-config-save]'); assert.ok( stub.calledWith('vault.cluster.secrets.backend.kubernetes.configuration'), 'Transitions to configuration route on save success' ); }); test('it should unset manual config values when saving local cluster option', async function (assert) { assert.expect(1); this.server.get('/:path/check', () => new Response(204, {})); this.server.post('/:path/config', (schema, req) => { const json = JSON.parse(req.requestBody); assert.deepEqual(json, this.expectedInferred, 'Manual config values are unset in server payload'); return new Response(204, {}); }); await render(hbs``, { owner: this.engine, }); await click('[data-test-radio-card="manual"]'); await fillIn('[data-test-input="kubernetesHost"]', this.existingConfig.kubernetes_host); await fillIn('[data-test-input="serviceAccountJwt"]', this.existingConfig.service_account_jwt); await fillIn('[data-test-input="kubernetesCaCert"]', this.existingConfig.kubernetes_ca_cert); await click('[data-test-radio-card="local"]'); await click('[data-test-config] button'); await click('[data-test-config-save]'); }); });