mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 12:26:34 +02:00
UI: fix flaky form-related integration tests (#27537)
* tests: await settled after calling cancelTimers to fix flakiness * chore: don't use assert.ok * tests: fix flaky mfa-test
This commit is contained in:
parent
89e9e0f2cd
commit
4e02a7a673
@ -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);
|
||||
|
||||
@ -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--;
|
||||
}
|
||||
|
||||
@ -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}}
|
||||
</div>
|
||||
{{#if this.newCodeDelay.isRunning}}
|
||||
{{#if this.countdown}}
|
||||
<div>
|
||||
<AlertInline @type="danger" @message={{this.codeDelayMessage}} />
|
||||
</div>
|
||||
@ -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}}
|
||||
<Icon @name="delay" class="has-text-grey" />
|
||||
<span class="has-text-grey is-v-centered" data-test-mfa-countdown>{{this.countdown}}</span>
|
||||
{{/if}}
|
||||
|
||||
@ -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]');
|
||||
|
||||
@ -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!');
|
||||
});
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -71,25 +71,29 @@ module('Integration | Component | control group success', function (hooks) {
|
||||
this.set('model', MODEL);
|
||||
this.set('response', response);
|
||||
await render(hbs`<ControlGroupSuccess @model={{this.model}} @controlGroupResponse={{this.response}} />`);
|
||||
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`<ControlGroupSuccess @model={{this.model}} />`);
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
@ -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`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@ -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`<Mfa::MfaForm @clusterId={{this.clusterId}} @authData={{this.mfaAuthData}} />`);
|
||||
|
||||
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`<Mfa::MfaForm @clusterId={{this.clusterId}} @authData={{this.mfaAuthData}} />`);
|
||||
|
||||
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]')
|
||||
|
||||
@ -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'
|
||||
);
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user