diff --git a/ui/app/components/auth-jwt.js b/ui/app/components/auth-jwt.js index a373ee86cf..610b6d0993 100644 --- a/ui/app/components/auth-jwt.js +++ b/ui/app/components/auth-jwt.js @@ -2,14 +2,13 @@ * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: BUSL-1.1 */ - +import Ember from 'ember'; import { service } from '@ember/service'; // ARG NOTE: Once you remove outer-html after glimmerizing you can remove the outer-html component import Component from './outer-html'; import { task, timeout, waitForEvent } from 'ember-concurrency'; import { debounce } from '@ember/runloop'; -const WAIT_TIME = 500; const ERROR_WINDOW_CLOSED = 'The provider window was closed before authentication was complete. Your web browser may have blocked or closed a pop-up window. Please check your settings and click Sign In to try again.'; const ERROR_MISSING_PARAMS = @@ -109,6 +108,8 @@ export default Component.extend({ watchPopup: task(function* (oidcWindow) { while (true) { + const WAIT_TIME = Ember.testing ? 50 : 500; + yield timeout(WAIT_TIME); if (!oidcWindow || oidcWindow.closed) { return this.handleOIDCError(ERROR_WINDOW_CLOSED); diff --git a/ui/app/components/mfa/mfa-form.js b/ui/app/components/mfa/mfa-form.js index 20b09ac4f6..484d13905f 100644 --- a/ui/app/components/mfa/mfa-form.js +++ b/ui/app/components/mfa/mfa-form.js @@ -3,6 +3,7 @@ * SPDX-License-Identifier: BUSL-1.1 */ +import Ember from 'ember'; import Component from '@glimmer/component'; import { service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; @@ -29,7 +30,7 @@ export const TOTP_VALIDATION_ERROR = export default class MfaForm extends Component { @service auth; - @tracked countdown; + @tracked countdown = 0; @tracked error; @tracked codeDelayMessage; @@ -104,7 +105,10 @@ export default class MfaForm extends Component { @task *newCodeDelay(message) { // parse validity period from error string to initialize countdown this.countdown = parseInt(message.match(/(\d\w seconds)/)[0].split(' ')[0]); - while (this.countdown) { + + if (Ember.testing) return; + + while (this.countdown > 0) { yield timeout(1000); this.countdown--; } diff --git a/ui/app/templates/components/mfa/mfa-form.hbs b/ui/app/templates/components/mfa/mfa-form.hbs index f5025b6457..c1f1b1d911 100644 --- a/ui/app/templates/components/mfa/mfa-form.hbs +++ b/ui/app/templates/components/mfa/mfa-form.hbs @@ -43,7 +43,7 @@ placeholder={{if (gt constraint.methods.length 1) "Enter passcode"}} spellcheck="false" autofocus="true" - disabled={{or this.validate.isRunning this.newCodeDelay.isRunning}} + disabled={{or this.validate.isRunning this.countdown}} @value={{constraint.passcode}} data-test-mfa-passcode={{index}} /> @@ -56,7 +56,7 @@ {{/if}} {{/each}} - {{#if this.newCodeDelay.isRunning}} + {{#if this.countdown}}
@@ -66,10 +66,10 @@ @icon={{if this.validate.isRunning "loading"}} id="validate" type="submit" - disabled={{or this.validate.isRunning this.newCodeDelay.isRunning}} + disabled={{or this.validate.isRunning this.countdown}} data-test-mfa-validate /> - {{#if this.newCodeDelay.isRunning}} + {{#if this.countdown}} {{this.countdown}} {{/if}} diff --git a/ui/tests/acceptance/oidc-auth-method-test.js b/ui/tests/acceptance/oidc-auth-method-test.js index 82fb72a5e8..0dcaaffd2f 100644 --- a/ui/tests/acceptance/oidc-auth-method-test.js +++ b/ui/tests/acceptance/oidc-auth-method-test.js @@ -70,6 +70,7 @@ module('Acceptance | oidc auth method', function (hooks) { window.postMessage(buildMessage().data, window.origin); cancelTimers(); }, 100); + await click('[data-test-auth-submit]'); }); @@ -98,6 +99,7 @@ module('Acceptance | oidc auth method', function (hooks) { window.postMessage(buildMessage().data, window.origin); cancelTimers(); }, 50); + await click('[data-test-auth-submit]'); }); @@ -109,6 +111,7 @@ module('Acceptance | oidc auth method', function (hooks) { window.postMessage(buildMessage().data, window.origin); cancelTimers(); }, 50); + await click('[data-test-auth-submit]'); await waitUntil(() => find('[data-test-user-menu-trigger]')); await click('[data-test-user-menu-trigger]'); diff --git a/ui/tests/integration/components/auth-form-test.js b/ui/tests/integration/components/auth-form-test.js index 07060597b3..c82ce4acf1 100644 --- a/ui/tests/integration/components/auth-form-test.js +++ b/ui/tests/integration/components/auth-form-test.js @@ -147,7 +147,6 @@ module('Integration | Component | auth form', function (hooks) { await this.renderComponent(); later(() => cancelTimers(), 50); - await settled(); assert.dom(GENERAL.messageError).hasText('Error Token unwrap failed: There was an error unwrapping!'); }); diff --git a/ui/tests/integration/components/auth-jwt-test.js b/ui/tests/integration/components/auth-jwt-test.js index eb6c26cdb6..1512129ef3 100644 --- a/ui/tests/integration/components/auth-jwt-test.js +++ b/ui/tests/integration/components/auth-jwt-test.js @@ -166,7 +166,10 @@ module('Integration | Component | auth jwt', function (hooks) { await waitUntil(() => { return this.openSpy.calledOnce; }); + cancelTimers(); + await settled(); + const call = this.openSpy.getCall(0); assert.deepEqual( call.args, @@ -201,6 +204,8 @@ module('Integration | Component | auth jwt', function (hooks) { buildMessage({ data: { source: 'oidc-callback', state: 'state', foo: 'bar' } }) ); cancelTimers(); + await settled(); + assert.strictEqual(this.error, ERROR_MISSING_PARAMS, 'calls onError with params missing error'); }); @@ -226,9 +231,11 @@ module('Integration | Component | auth jwt', function (hooks) { return this.openSpy.calledOnce; }); this.window.trigger('message', buildMessage({ origin: 'http://hackerz.com' })); + cancelTimers(); await settled(); - assert.notOk(this.handler.called, 'should not call the submit handler'); + + assert.false(this.handler.called, 'should not call the submit handler'); }); test('oidc: fails silently when event is not trusted', async function (assert) { @@ -242,7 +249,8 @@ module('Integration | Component | auth jwt', function (hooks) { this.window.trigger('message', buildMessage({ isTrusted: false })); cancelTimers(); await settled(); - assert.notOk(this.handler.called, 'should not call the submit handler'); + + assert.false(this.handler.called, 'should not call the submit handler'); }); test('oidc: it should trigger error callback when role is not found', async function (assert) { diff --git a/ui/tests/integration/components/control-group-success-test.js b/ui/tests/integration/components/control-group-success-test.js index bda51e70bf..64ccf0ccc9 100644 --- a/ui/tests/integration/components/control-group-success-test.js +++ b/ui/tests/integration/components/control-group-success-test.js @@ -71,25 +71,29 @@ module('Integration | Component | control group success', function (hooks) { this.set('model', MODEL); this.set('response', response); await render(hbs``); - assert.ok(component.showsNavigateMessage, 'shows unwrap message'); + + assert.true(component.showsNavigateMessage, 'shows unwrap message'); + await component.navigate(); later(() => cancelTimers(), 50); - return settled().then(() => { - assert.ok(this.controlGroup.markTokenForUnwrap.calledOnce, 'marks token for unwrap'); - assert.ok(this.router.transitionTo.calledOnce, 'calls router transition'); - }); + await settled(); + + assert.true(this.controlGroup.markTokenForUnwrap.calledOnce, 'marks token for unwrap'); + assert.true(this.router.transitionTo.calledOnce, 'calls router transition'); }); test('render without token', async function (assert) { assert.expect(2); this.set('model', MODEL); await render(hbs``); - assert.ok(component.showsUnwrapForm, 'shows unwrap form'); + + assert.true(component.showsUnwrapForm, 'shows unwrap form'); + await component.token('token'); component.unwrap(); later(() => cancelTimers(), 50); - return settled().then(() => { - assert.ok(component.showsJsonViewer, 'shows unwrapped data'); - }); + await settled(); + + assert.true(component.showsJsonViewer, 'shows unwrapped data'); }); }); diff --git a/ui/tests/integration/components/edit-form-kmip-role-test.js b/ui/tests/integration/components/edit-form-kmip-role-test.js index a949ca2e8c..dac56709a4 100644 --- a/ui/tests/integration/components/edit-form-kmip-role-test.js +++ b/ui/tests/integration/components/edit-form-kmip-role-test.js @@ -224,15 +224,15 @@ module('Integration | Component | edit form kmip role', function (hooks) { click('[data-test-edit-form-submit]'); later(() => cancelTimers(), 50); - return settled().then(() => { - for (const afterStateKey of Object.keys(stateAfterSave)) { - assert.strictEqual( - model.get(afterStateKey), - stateAfterSave[afterStateKey], - `sets ${afterStateKey} on save` - ); - } - }); + await settled(); + + for (const afterStateKey of Object.keys(stateAfterSave)) { + assert.strictEqual( + model.get(afterStateKey), + stateAfterSave[afterStateKey], + `sets ${afterStateKey} on save` + ); + } }); } }); diff --git a/ui/tests/integration/components/edit-form-test.js b/ui/tests/integration/components/edit-form-test.js index ee4f9ebf72..1c8a1a0596 100644 --- a/ui/tests/integration/components/edit-form-test.js +++ b/ui/tests/integration/components/edit-form-test.js @@ -68,12 +68,12 @@ module('Integration | Component | edit form', function (hooks) { component.submit(); later(() => cancelTimers(), 50); - return settled().then(() => { - assert.ok(saveSpy.calledOnce, 'calls passed onSave'); - assert.strictEqual(saveSpy.getCall(0).args[0].saveType, 'save'); - assert.deepEqual(saveSpy.getCall(0).args[0].model, this.model, 'passes model to onSave'); - const flash = this.owner.lookup('service:flash-messages'); - assert.strictEqual(flash.success.callCount, 1, 'calls flash message success'); - }); + await settled(); + + assert.true(saveSpy.calledOnce, 'calls passed onSave'); + assert.strictEqual(saveSpy.getCall(0).args[0].saveType, 'save'); + assert.deepEqual(saveSpy.getCall(0).args[0].model, this.model, 'passes model to onSave'); + const flash = this.owner.lookup('service:flash-messages'); + assert.strictEqual(flash.success.callCount, 1, 'calls flash message success'); }); }); diff --git a/ui/tests/integration/components/mfa-form-test.js b/ui/tests/integration/components/mfa-form-test.js index c5c65ed696..128b5b3851 100644 --- a/ui/tests/integration/components/mfa-form-test.js +++ b/ui/tests/integration/components/mfa-form-test.js @@ -5,10 +5,9 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { render } from '@ember/test-helpers'; +import { render, settled, fillIn, click, waitUntil, waitFor } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupMirage } from 'ember-cli-mirage/test-support'; -import { fillIn, click, waitUntil } from '@ember/test-helpers'; import { _cancelTimers as cancelTimers, later } from '@ember/runloop'; import { TOTP_VALIDATION_ERROR } from 'vault/components/mfa/mfa-form'; @@ -84,6 +83,12 @@ module('Integration | Component | mfa-form', function (hooks) { ); }); + test('it should render a submit button', async function (assert) { + await render(hbs``); + + assert.dom('[data-test-mfa-validate]').isNotDisabled('Button is not disabled by default'); + }); + test('it should render method selects and passcode inputs', async function (assert) { assert.expect(2); const duoConstraint = this.server.create('mfa-method', { type: 'duo', uses_passcode: true }); @@ -170,7 +175,6 @@ module('Integration | Component | mfa-form', function (hooks) { await click('[data-test-mfa-validate]'); }); - // TODO JLR: It doesn't appear that cancelTimers is working and tests wait for the full countdown test('it should show countdown on passcode already used and rate limit errors', async function (assert) { const messages = { used: 'code already used; new code is available in 45 seconds', @@ -184,12 +188,16 @@ module('Integration | Component | mfa-form', function (hooks) { throw { errors: [messages[code]] }; }, }); + const expectedTime = code === 'used' ? 45 : 15; + await render(hbs``); await fillIn('[data-test-mfa-passcode]', code); - later(() => cancelTimers(), 50); + await click('[data-test-mfa-validate]'); - const expectedTime = code === 'used' ? '45' : '15'; + + await waitFor('[data-test-mfa-countdown]'); + assert .dom('[data-test-mfa-countdown]') .includesText(expectedTime, 'countdown renders with correct initial value from error response'); @@ -209,6 +217,8 @@ module('Integration | Component | mfa-form', function (hooks) { await fillIn('[data-test-mfa-passcode]', 'test-code'); later(() => cancelTimers(), 50); + await settled(); + await click('[data-test-mfa-validate]'); assert .dom('[data-test-message-error]') diff --git a/ui/tests/integration/components/mount-backend-form-test.js b/ui/tests/integration/components/mount-backend-form-test.js index f1acedc1fe..b824faa51b 100644 --- a/ui/tests/integration/components/mount-backend-form-test.js +++ b/ui/tests/integration/components/mount-backend-form-test.js @@ -116,8 +116,8 @@ module('Integration | Component | mount backend form', function (hooks) { later(() => cancelTimers(), 50); await settled(); - assert.ok(spy.calledOnce, 'calls the passed success method'); - assert.ok( + assert.true(spy.calledOnce, 'calls the passed success method'); + assert.true( this.flashSuccessSpy.calledWith('Successfully mounted the approle auth method at foo.'), 'Renders correct flash message' ); @@ -184,8 +184,8 @@ module('Integration | Component | mount backend form', function (hooks) { later(() => cancelTimers(), 50); await settled(); - assert.ok(spy.calledOnce, 'calls the passed success method'); - assert.ok( + assert.true(spy.calledOnce, 'calls the passed success method'); + assert.true( this.flashSuccessSpy.calledWith('Successfully mounted the ssh secrets engine at foo.'), 'Renders correct flash message' ); diff --git a/ui/tests/unit/components/auth-jwt-test.js b/ui/tests/unit/components/auth-jwt-test.js index d4c3b0d066..41ba7e3d65 100644 --- a/ui/tests/unit/components/auth-jwt-test.js +++ b/ui/tests/unit/components/auth-jwt-test.js @@ -5,6 +5,7 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; +import { settled } from '@ember/test-helpers'; import EmberObject from '@ember/object'; import Evented from '@ember/object/evented'; import sinon from 'sinon'; @@ -29,9 +30,14 @@ module('Unit | Component | auth-jwt', function (hooks) { this.component.prepareForOIDC.perform(mockWindow.create()); this.component.window.trigger('message', { origin: 'http://anotherdomain.com', isTrusted: true }); - assert.ok(this.errorSpy.notCalled, 'Error handler not triggered while waiting for oidc callback message'); + assert.true( + this.errorSpy.notCalled, + 'Error handler not triggered while waiting for oidc callback message' + ); assert.strictEqual(this.component.exchangeOIDC.performCount, 0, 'exchangeOIDC method not fired'); + cancelTimers(); + await settled(); }); test('it should ignore untrusted messages while waiting for oidc callback', async function (assert) { @@ -40,10 +46,11 @@ module('Unit | Component | auth-jwt', function (hooks) { this.component.window.trigger('message', { origin: 'http://localhost:4200', isTrusted: false }); assert.ok(this.errorSpy.notCalled, 'Error handler not triggered while waiting for oidc callback message'); assert.strictEqual(this.component.exchangeOIDC.performCount, 0, 'exchangeOIDC method not fired'); + cancelTimers(); + await settled(); }); - // TODO: Flaky // test case for https://github.com/hashicorp/vault/issues/12436 test('it should ignore messages sent from outside the app while waiting for oidc callback', async function (assert) { assert.expect(2); @@ -65,12 +72,17 @@ module('Unit | Component | auth-jwt', function (hooks) { message.data.source = 'oidc-callback'; this.component.window.trigger('message', message); - assert.ok(this.errorSpy.notCalled, 'Error handler not triggered while waiting for oidc callback message'); + assert.true( + this.errorSpy.notCalled, + 'Error handler not triggered while waiting for oidc callback message' + ); assert.strictEqual( this.component.exchangeOIDC.performCount, 1, 'exchangeOIDC method fires when oidc callback message is received' ); + cancelTimers(); + await settled(); }); });