mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
UI: Fix oidc auth method missing default_role field (#28539)
* add auth-config/oidc to openapi model helper * alphabetize * update maskedinput selector to be standard data-test-input * add test * add changelog * fix maskedinput test and kv selector * final textarea selector!
This commit is contained in:
3
changelog/28539.txt
Normal file
3
changelog/28539.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:bug
|
||||
ui: fix `default_role` input missing from oidc auth method configuration form
|
||||
```
|
||||
@@ -136,7 +136,6 @@ export function filterPathsByItemType(pathInfo: PathsInfo, itemType: string): Pa
|
||||
* This object maps model names to the openAPI path that hydrates the model, given the backend path.
|
||||
*/
|
||||
const OPENAPI_POWERED_MODELS = {
|
||||
'role-ssh': (backend: string) => `/v1/${backend}/roles/example?help=1`,
|
||||
'auth-config/azure': (backend: string) => `/v1/auth/${backend}/config?help=1`,
|
||||
'auth-config/cert': (backend: string) => `/v1/auth/${backend}/config?help=1`,
|
||||
'auth-config/gcp': (backend: string) => `/v1/auth/${backend}/config?help=1`,
|
||||
@@ -144,18 +143,20 @@ const OPENAPI_POWERED_MODELS = {
|
||||
'auth-config/jwt': (backend: string) => `/v1/auth/${backend}/config?help=1`,
|
||||
'auth-config/kubernetes': (backend: string) => `/v1/auth/${backend}/config?help=1`,
|
||||
'auth-config/ldap': (backend: string) => `/v1/auth/${backend}/config?help=1`,
|
||||
'auth-config/oidc': (backend: string) => `/v1/auth/${backend}/config?help=1`,
|
||||
'auth-config/okta': (backend: string) => `/v1/auth/${backend}/config?help=1`,
|
||||
'auth-config/radius': (backend: string) => `/v1/auth/${backend}/config?help=1`,
|
||||
'kmip/config': (backend: string) => `/v1/${backend}/config?help=1`,
|
||||
'kmip/role': (backend: string) => `/v1/${backend}/scope/example/role/example?help=1`,
|
||||
'pki/role': (backend: string) => `/v1/${backend}/roles/example?help=1`,
|
||||
'pki/tidy': (backend: string) => `/v1/${backend}/config/auto-tidy?help=1`,
|
||||
'pki/sign-intermediate': (backend: string) => `/v1/${backend}/issuer/example/sign-intermediate?help=1`,
|
||||
'pki/certificate/generate': (backend: string) => `/v1/${backend}/issue/example?help=1`,
|
||||
'pki/certificate/sign': (backend: string) => `/v1/${backend}/sign/example?help=1`,
|
||||
'pki/config/acme': (backend: string) => `/v1/${backend}/config/acme?help=1`,
|
||||
'pki/config/cluster': (backend: string) => `/v1/${backend}/config/cluster?help=1`,
|
||||
'pki/config/urls': (backend: string) => `/v1/${backend}/config/urls?help=1`,
|
||||
'pki/role': (backend: string) => `/v1/${backend}/roles/example?help=1`,
|
||||
'pki/sign-intermediate': (backend: string) => `/v1/${backend}/issuer/example/sign-intermediate?help=1`,
|
||||
'pki/tidy': (backend: string) => `/v1/${backend}/config/auto-tidy?help=1`,
|
||||
'role-ssh': (backend: string) => `/v1/${backend}/roles/example?help=1`,
|
||||
};
|
||||
|
||||
export function getHelpUrlForModel(modelType: string, backend: string) {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
aria-label={{or @name "masked input"}}
|
||||
{{on "change" this.onChange}}
|
||||
{{on "keyup" (fn this.handleKeyUp @name)}}
|
||||
data-test-textarea={{or @name ""}}
|
||||
data-test-input={{or @name ""}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @allowCopy}}
|
||||
|
||||
@@ -18,8 +18,6 @@ import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
const SELECTORS = {
|
||||
backendLink: (path) => `[data-test-auth-backend-link="${path}"]`,
|
||||
createUser: '[data-test-entity-create-link="user"]',
|
||||
input: (attr) => `[data-test-input="${attr}"]`,
|
||||
password: '[data-test-textarea]',
|
||||
saveBtn: '[data-test-save-config]',
|
||||
methods: '[data-test-access-methods] a',
|
||||
listItem: '[data-test-list-item-content]',
|
||||
@@ -49,8 +47,8 @@ module('Acceptance | auth backend list', function (hooks) {
|
||||
await click(SELECTORS.backendLink(this.path1));
|
||||
assert.dom(GENERAL.emptyStateTitle).exists('shows empty state');
|
||||
await click(SELECTORS.createUser);
|
||||
await fillIn(SELECTORS.input('username'), this.user1);
|
||||
await fillIn(SELECTORS.password, this.user1);
|
||||
await fillIn(GENERAL.inputByAttr('username'), this.user1);
|
||||
await fillIn(GENERAL.inputByAttr('password'), this.user1);
|
||||
await click(SELECTORS.saveBtn);
|
||||
assert.strictEqual(currentURL(), `/vault/access/${this.path1}/item/user`);
|
||||
|
||||
@@ -61,8 +59,8 @@ module('Acceptance | auth backend list', function (hooks) {
|
||||
await click(SELECTORS.backendLink(this.path2));
|
||||
assert.dom(GENERAL.emptyStateTitle).exists('shows empty state');
|
||||
await click(SELECTORS.createUser);
|
||||
await fillIn(SELECTORS.input('username'), this.user2);
|
||||
await fillIn(SELECTORS.password, this.user2);
|
||||
await fillIn(GENERAL.inputByAttr('username'), this.user2);
|
||||
await fillIn(GENERAL.inputByAttr('password'), this.user2);
|
||||
await click(SELECTORS.saveBtn);
|
||||
assert.strictEqual(currentURL(), `/vault/access/${this.path2}/item/user`);
|
||||
// Confirm that the user was created. There was a bug where the apiPath was not being updated when toggling between auth routes.
|
||||
|
||||
216
ui/tests/acceptance/auth/enable-tune-form-test.js
Normal file
216
ui/tests/acceptance/auth/enable-tune-form-test.js
Normal file
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { login } from 'vault/tests/helpers/auth/auth-helpers';
|
||||
import { visit } from '@ember/test-helpers';
|
||||
import { deleteAuthCmd, runCmd } from 'vault/tests/helpers/commands';
|
||||
import testHelper from './test-helper';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
||||
// These models use openAPI so we assert the form inputs using an acceptance test
|
||||
// The default selector is to use GENERAL.inputByAttr()
|
||||
// custom fields should be added to the this.customSelectorss object
|
||||
module('Acceptance | auth enable tune form test', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
hooks.beforeEach(async function () {
|
||||
// these tend to be the same across models because they share the same mount-config model
|
||||
// if necessary, they can be overridden in the individual module
|
||||
this.mountFields = [
|
||||
'path',
|
||||
'description',
|
||||
'local',
|
||||
'sealWrap',
|
||||
'config.listingVisibility',
|
||||
'config.defaultLeaseTtl',
|
||||
'config.maxLeaseTtl',
|
||||
'config.tokenType',
|
||||
'config.auditNonHmacRequestKeys',
|
||||
'config.auditNonHmacResponseKeys',
|
||||
'config.passthroughRequestHeaders',
|
||||
'config.allowedResponseHeaders',
|
||||
'config.pluginVersion',
|
||||
];
|
||||
});
|
||||
|
||||
module('azure', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
this.type = 'azure';
|
||||
this.path = `${this.type}-${uuidv4()}`;
|
||||
this.tuneFields = [
|
||||
'environment',
|
||||
'identityTokenAudience',
|
||||
'identityTokenTtl',
|
||||
'maxRetries',
|
||||
'maxRetryDelay',
|
||||
'resource',
|
||||
'retryDelay',
|
||||
'rootPasswordTtl',
|
||||
'tenantId',
|
||||
];
|
||||
this.tuneToggles = { 'Azure Options': ['clientId', 'clientSecret'] };
|
||||
await login();
|
||||
return visit('/vault/settings/auth/enable');
|
||||
});
|
||||
hooks.afterEach(async function () {
|
||||
await runCmd(deleteAuthCmd(this.path), false);
|
||||
});
|
||||
testHelper(test);
|
||||
});
|
||||
|
||||
module('jwt', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
this.type = 'jwt';
|
||||
this.path = `${this.type}-${uuidv4()}`;
|
||||
this.customSelectors = {
|
||||
providerConfig: `${GENERAL.fieldByAttr('providerConfig')} textarea`,
|
||||
};
|
||||
this.tuneFields = [
|
||||
'defaultRole',
|
||||
'jwksCaPem',
|
||||
'jwksUrl',
|
||||
'namespaceInState',
|
||||
'oidcDiscoveryUrl',
|
||||
'oidcResponseMode',
|
||||
'oidcResponseTypes',
|
||||
'providerConfig',
|
||||
'unsupportedCriticalCertExtensions',
|
||||
];
|
||||
this.tuneToggles = {
|
||||
'JWT Options': [
|
||||
'oidcClientId',
|
||||
'oidcClientSecret',
|
||||
'oidcDiscoveryCaPem',
|
||||
'jwtValidationPubkeys',
|
||||
'jwtSupportedAlgs',
|
||||
'boundIssuer',
|
||||
],
|
||||
};
|
||||
await login();
|
||||
return visit('/vault/settings/auth/enable');
|
||||
});
|
||||
hooks.afterEach(async function () {
|
||||
await runCmd(deleteAuthCmd(this.path), false);
|
||||
});
|
||||
testHelper(test);
|
||||
});
|
||||
|
||||
module('ldap', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
this.type = 'ldap';
|
||||
this.path = `${this.type}-${uuidv4()}`;
|
||||
this.tuneFields = [
|
||||
'url',
|
||||
'caseSensitiveNames',
|
||||
'connectionTimeout',
|
||||
'dereferenceAliases',
|
||||
'maxPageSize',
|
||||
'passwordPolicy',
|
||||
'requestTimeout',
|
||||
'tokenBoundCidrs',
|
||||
'tokenExplicitMaxTtl',
|
||||
'tokenMaxTtl',
|
||||
'tokenNoDefaultPolicy',
|
||||
'tokenNumUses',
|
||||
'tokenPeriod',
|
||||
'tokenPolicies',
|
||||
'tokenTtl',
|
||||
'tokenType',
|
||||
'usePre111GroupCnBehavior',
|
||||
'usernameAsAlias',
|
||||
];
|
||||
this.tuneToggles = {
|
||||
'LDAP Options': [
|
||||
'starttls',
|
||||
'insecureTls',
|
||||
'discoverdn',
|
||||
'denyNullBind',
|
||||
'tlsMinVersion',
|
||||
'tlsMaxVersion',
|
||||
'certificate',
|
||||
'clientTlsCert',
|
||||
'clientTlsKey',
|
||||
'userattr',
|
||||
'upndomain',
|
||||
'anonymousGroupSearch',
|
||||
],
|
||||
'Customize User Search': ['binddn', 'userdn', 'bindpass', 'userfilter'],
|
||||
'Customize Group Membership Search': ['groupfilter', 'groupattr', 'groupdn', 'useTokenGroups'],
|
||||
};
|
||||
await login();
|
||||
return visit('/vault/settings/auth/enable');
|
||||
});
|
||||
hooks.afterEach(async function () {
|
||||
await runCmd(deleteAuthCmd(this.path), false);
|
||||
});
|
||||
testHelper(test);
|
||||
});
|
||||
|
||||
module('oidc', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
this.type = 'oidc';
|
||||
this.path = `${this.type}-${uuidv4()}`;
|
||||
this.customSelectors = {
|
||||
providerConfig: `${GENERAL.fieldByAttr('providerConfig')} textarea`,
|
||||
};
|
||||
this.tuneFields = [
|
||||
'oidcDiscoveryUrl',
|
||||
'defaultRole',
|
||||
'jwksCaPem',
|
||||
'jwksUrl',
|
||||
'oidcResponseMode',
|
||||
'oidcResponseTypes',
|
||||
'namespaceInState',
|
||||
'providerConfig',
|
||||
'unsupportedCriticalCertExtensions',
|
||||
];
|
||||
this.tuneToggles = {
|
||||
'OIDC Options': [
|
||||
'oidcClientId',
|
||||
'oidcClientSecret',
|
||||
'oidcDiscoveryCaPem',
|
||||
'jwtValidationPubkeys',
|
||||
'jwtSupportedAlgs',
|
||||
'boundIssuer',
|
||||
],
|
||||
};
|
||||
await login();
|
||||
return visit('/vault/settings/auth/enable');
|
||||
});
|
||||
hooks.afterEach(async function () {
|
||||
await runCmd(deleteAuthCmd(this.path), false);
|
||||
});
|
||||
testHelper(test);
|
||||
});
|
||||
|
||||
module('okta', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
this.type = 'okta';
|
||||
this.path = `${this.type}-${uuidv4()}`;
|
||||
this.tuneFields = [
|
||||
'orgName',
|
||||
'tokenBoundCidrs',
|
||||
'tokenExplicitMaxTtl',
|
||||
'tokenMaxTtl',
|
||||
'tokenNoDefaultPolicy',
|
||||
'tokenNumUses',
|
||||
'tokenPeriod',
|
||||
'tokenPolicies',
|
||||
'tokenTtl',
|
||||
'tokenType',
|
||||
];
|
||||
this.tuneToggles = { Options: ['apiToken', 'baseUrl', 'bypassOktaMfa'] };
|
||||
await login();
|
||||
return visit('/vault/settings/auth/enable');
|
||||
});
|
||||
hooks.afterEach(async function () {
|
||||
await runCmd(deleteAuthCmd(this.path), false);
|
||||
});
|
||||
testHelper(test);
|
||||
});
|
||||
});
|
||||
49
ui/tests/acceptance/auth/test-helper.js
Normal file
49
ui/tests/acceptance/auth/test-helper.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { click, currentURL, fillIn } from '@ember/test-helpers';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
||||
const SELECTORS = {
|
||||
mountType: (name) => `[data-test-mount-type="${name}"]`,
|
||||
submit: '[data-test-mount-submit]',
|
||||
};
|
||||
|
||||
const assertFields = (assert, fields, customSelectors = {}) => {
|
||||
fields.forEach((param) => {
|
||||
if (Object.keys(customSelectors).includes(param)) {
|
||||
assert.dom(customSelectors[param]).exists();
|
||||
} else {
|
||||
assert.dom(GENERAL.inputByAttr(param)).exists();
|
||||
}
|
||||
});
|
||||
};
|
||||
export default (test) => {
|
||||
test('it renders mount fields', async function (assert) {
|
||||
await click(SELECTORS.mountType(this.type));
|
||||
await click(GENERAL.toggleGroup('Method Options'));
|
||||
assertFields(assert, this.mountFields, this.customSelectors);
|
||||
});
|
||||
|
||||
test('it renders tune fields', async function (assert) {
|
||||
// enable auth method to check tune fields
|
||||
await click(SELECTORS.mountType(this.type));
|
||||
await fillIn(GENERAL.inputByAttr('path'), this.path);
|
||||
await click(SELECTORS.submit);
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
`/vault/settings/auth/configure/${this.path}/configuration`,
|
||||
`${this.type}: it mounts navigates to tune form`
|
||||
);
|
||||
|
||||
assertFields(assert, this.tuneFields, this.customSelectors);
|
||||
|
||||
for (const toggle in this.tuneToggles) {
|
||||
const fields = this.tuneToggles[toggle];
|
||||
await click(GENERAL.toggleGroup(toggle));
|
||||
assertFields(assert, fields, this.customSelectors);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -11,6 +11,7 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import mfaConfigHandler from 'vault/mirage/handlers/mfa-config';
|
||||
import { Response } from 'miragejs';
|
||||
import { underscore } from '@ember/string';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
||||
module('Acceptance | mfa-method', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
@@ -181,17 +182,10 @@ module('Acceptance | mfa-method', function (hooks) {
|
||||
.dom('[data-test-inline-error-message]')
|
||||
.exists({ count: required.length }, `Required field validations display for ${type}`);
|
||||
|
||||
for (const [i, field] of required.entries()) {
|
||||
let inputType = 'input';
|
||||
// this is less than ideal but updating the test selectors in masked-input break a bunch of tests
|
||||
// add value to the masked input text area data-test attributes for selection
|
||||
if (['secret_key', 'integration_key'].includes(field)) {
|
||||
inputType = 'textarea';
|
||||
const textareas = this.element.querySelectorAll('[data-test-textarea]');
|
||||
textareas[i].setAttribute('data-test-textarea', field);
|
||||
}
|
||||
await fillIn(`[data-test-${inputType}="${field}"]`, 'foo');
|
||||
for (const field of required) {
|
||||
await fillIn(GENERAL.inputByAttr(field), 'foo');
|
||||
}
|
||||
|
||||
await click('[data-test-mfa-create-save]');
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
|
||||
@@ -59,11 +59,11 @@ module('Acceptance | reset password', function (hooks) {
|
||||
);
|
||||
|
||||
assert.dom('[data-test-title]').hasText('Reset password', 'page title');
|
||||
await fillIn('[data-test-textarea]', 'newpassword');
|
||||
await fillIn('[data-test-input="reset-password"]', 'newpassword');
|
||||
await click('[data-test-reset-password-save]');
|
||||
await waitFor('[data-test-flash-message]');
|
||||
assert.dom('[data-test-flash-message]').hasText(`Success ${SUCCESS_MESSAGE}`);
|
||||
assert.dom('[data-test-textarea]').hasValue('', 'Resets input after save');
|
||||
assert.dom('[data-test-input="reset-password"]').hasValue('', 'Resets input after save');
|
||||
});
|
||||
|
||||
test('allows password reset for userpass users logged in via tab', async function (assert) {
|
||||
@@ -91,10 +91,10 @@ module('Acceptance | reset password', function (hooks) {
|
||||
);
|
||||
|
||||
assert.dom('[data-test-title]').hasText('Reset password', 'page title');
|
||||
await fillIn('[data-test-textarea]', 'newpassword');
|
||||
await fillIn('[data-test-input="reset-password"]', 'newpassword');
|
||||
await click('[data-test-reset-password-save]');
|
||||
await waitFor('[data-test-flash-message]');
|
||||
assert.dom('[data-test-flash-message]').hasText(`Success ${SUCCESS_MESSAGE}`);
|
||||
assert.dom('[data-test-textarea]').hasValue('', 'Resets input after save');
|
||||
assert.dom('[data-test-input="reset-password"]').hasValue('', 'Resets input after save');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -368,11 +368,7 @@ module('Acceptance | aws | configuration', function (hooks) {
|
||||
// check all the form fields are present
|
||||
await click(GENERAL.toggleGroup('Root config options'));
|
||||
for (const key of expectedConfigKeys('aws-root-create')) {
|
||||
if (key === 'secretKey') {
|
||||
assert.dom(GENERAL.maskedInput(key)).exists(`${key} shows for root section.`);
|
||||
} else {
|
||||
assert.dom(GENERAL.inputByAttr(key)).exists(`${key} shows for root section.`);
|
||||
}
|
||||
assert.dom(GENERAL.inputByAttr(key)).exists(`${key} shows for root section.`);
|
||||
}
|
||||
for (const key of expectedConfigKeys('aws-lease')) {
|
||||
assert.dom(`[data-test-ttl-form-label="${key}"]`).exists(`${key} shows for Lease section.`);
|
||||
|
||||
@@ -86,7 +86,7 @@ module('Acceptance | ssh | configuration', function (hooks) {
|
||||
`/vault/secrets/${sshPath}/configuration/edit`,
|
||||
'after deleting public key stays on edit page'
|
||||
);
|
||||
assert.dom(GENERAL.maskedInput('privateKey')).hasNoText('Private key is empty and reset');
|
||||
assert.dom(GENERAL.inputByAttr('privateKey')).hasNoText('Private key is empty and reset');
|
||||
assert.dom(GENERAL.inputByAttr('publicKey')).hasNoText('Public key is empty and reset');
|
||||
assert.dom(GENERAL.inputByAttr('generateSigningKey')).isChecked('Generate signing key is checked');
|
||||
await click(SES.viewBackend);
|
||||
|
||||
@@ -87,7 +87,7 @@ module('Acceptance | sync | destination (singular)', function (hooks) {
|
||||
|
||||
await visit('vault/sync/secrets/destinations/vercel-project/destination-vercel/edit');
|
||||
await click(ts.enableField('accessToken'));
|
||||
await fillIn(ts.maskedInput('accessToken'), 'foobar');
|
||||
await fillIn(GENERAL.inputByAttr('accessToken'), 'foobar');
|
||||
await click(ts.saveButton);
|
||||
await click(ts.toolbar('Edit destination'));
|
||||
await click(ts.saveButton);
|
||||
|
||||
@@ -94,7 +94,6 @@ export const GENERAL = {
|
||||
navLink: (label: string) => `[data-test-sidebar-nav-link="${label}"]`,
|
||||
cancelButton: '[data-test-cancel]',
|
||||
saveButton: '[data-test-save]',
|
||||
maskedInput: (name: string) => `[data-test-textarea="${name}"]`,
|
||||
codemirror: `[data-test-component="code-mirror-modifier"]`,
|
||||
codemirrorTextarea: `[data-test-component="code-mirror-modifier"] textarea`,
|
||||
};
|
||||
|
||||
@@ -110,7 +110,7 @@ export const FORM = {
|
||||
kvRow: '[data-test-kv-row]',
|
||||
keyInput: (idx = 0) => `[data-test-kv-key="${idx}"]`,
|
||||
valueInput: (idx = 0) => `[data-test-kv-value="${idx}"]`,
|
||||
maskedValueInput: (idx = 0) => `[data-test-kv-value="${idx}"] [data-test-textarea]`,
|
||||
maskedValueInput: (idx = 0) => `[data-test-kv-value="${idx}"] [data-test-input]`,
|
||||
addRow: (idx = 0) => `[data-test-kv-add-row="${idx}"]`,
|
||||
deleteRow: (idx = 0) => `[data-test-kv-delete-row="${idx}"]`,
|
||||
// <KvPatchEditor>
|
||||
|
||||
@@ -182,7 +182,7 @@ export const expectedValueOfConfigKeys = (type, string) => {
|
||||
export const fillInAwsConfig = async (situation = 'withAccess') => {
|
||||
if (situation === 'withAccess') {
|
||||
await fillIn(GENERAL.inputByAttr('accessKey'), 'foo');
|
||||
await fillIn(GENERAL.maskedInput('secretKey'), 'bar');
|
||||
await fillIn(GENERAL.inputByAttr('secretKey'), 'bar');
|
||||
}
|
||||
if (situation === 'withAccessOptions') {
|
||||
await click(GENERAL.toggleGroup('Root config options'));
|
||||
|
||||
@@ -99,11 +99,6 @@ export const PAGE = {
|
||||
case 'customTags':
|
||||
await fillIn('[data-test-kv-key="0"]', 'foo');
|
||||
return fillIn('[data-test-kv-value="0"]', value);
|
||||
case 'accessKeyId':
|
||||
case 'secretAccessKey':
|
||||
case 'clientSecret':
|
||||
case 'accessToken':
|
||||
return fillIn(GENERAL.maskedInput(attr), value);
|
||||
case 'deploymentEnvironments':
|
||||
await click('[data-test-input="deploymentEnvironments"] input#development');
|
||||
await click('[data-test-input="deploymentEnvironments"] input#preview');
|
||||
|
||||
@@ -6,13 +6,16 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, focus, triggerKeyEvent, typeIn, fillIn, click } from '@ember/test-helpers';
|
||||
import { create } from 'ember-cli-page-object';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import sinon from 'sinon';
|
||||
import maskedInput from 'vault/tests/pages/components/masked-input';
|
||||
|
||||
const component = create(maskedInput);
|
||||
|
||||
const SELECTORS = {
|
||||
copyBtn: '[data-test-copy-button]',
|
||||
downloadBtn: '[data-test-download-button]',
|
||||
toggle: '[data-test-button="toggle-masked"]',
|
||||
downloadIcon: '[data-test-download-icon]',
|
||||
stringify: '[data-test-stringify-toggle]',
|
||||
};
|
||||
module('Integration | Component | masked input', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
@@ -26,14 +29,14 @@ module('Integration | Component | masked input', function (hooks) {
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`<MaskedInput />`);
|
||||
assert.dom('[data-test-masked-input]').exists('shows masked input');
|
||||
assert.ok(component.textareaIsPresent);
|
||||
assert.dom('[data-test-textarea]').hasClass('masked-font', 'it renders an input with obscure font');
|
||||
assert.notOk(component.copyButtonIsPresent, 'does not render copy button by default');
|
||||
assert.notOk(component.downloadButtonIsPresent, 'does not render download button by default');
|
||||
assert.dom('textarea').exists();
|
||||
assert.dom('textarea').hasClass('masked-font', 'it renders an input with obscure font');
|
||||
assert.dom(SELECTORS.copyBtn).doesNotExist('does not render copy button by default');
|
||||
assert.dom('[data-test-download-button]').doesNotExist('does not render download button by default');
|
||||
|
||||
await component.toggleMasked();
|
||||
await click(SELECTORS.toggle);
|
||||
assert.dom('.masked-value').doesNotHaveClass('masked-font', 'it unmasks when show button is clicked');
|
||||
await component.toggleMasked();
|
||||
await click(SELECTORS.toggle);
|
||||
assert.dom('.masked-value').hasClass('masked-font', 'it remasks text when button is clicked');
|
||||
});
|
||||
|
||||
@@ -42,21 +45,21 @@ module('Integration | Component | masked input', function (hooks) {
|
||||
await render(hbs`<MaskedInput @displayOnly={{true}} @value={{this.value}} />`);
|
||||
|
||||
assert.dom('.masked-value').hasClass('masked-font', 'value has obscured font');
|
||||
assert.notOk(component.textareaIsPresent, 'it does not render a textarea when displayOnly is true');
|
||||
assert.dom('textarea').doesNotExist('it does not render a textarea when displayOnly is true');
|
||||
});
|
||||
|
||||
test('it renders a copy button when allowCopy is true', async function (assert) {
|
||||
this.set('value', { some: 'object' });
|
||||
await render(hbs`<MaskedInput @allowCopy={{true}} @value={{this.value}} />`);
|
||||
assert.ok(component.copyButtonIsPresent);
|
||||
assert.dom(SELECTORS.copyBtn).exists();
|
||||
});
|
||||
|
||||
test('it renders a download button when allowDownload is true', async function (assert) {
|
||||
await render(hbs`<MaskedInput @allowDownload={{true}} /> `);
|
||||
assert.ok(component.downloadIconIsPresent);
|
||||
assert.dom(SELECTORS.downloadIcon).exists();
|
||||
|
||||
await click('[data-test-download-icon]');
|
||||
assert.ok(component.downloadButtonIsPresent, 'clicking download icon opens modal with download button');
|
||||
await click(SELECTORS.downloadIcon);
|
||||
assert.dom(SELECTORS.downloadBtn).exists('clicking download icon opens modal with download button');
|
||||
});
|
||||
|
||||
test('it shortens all outputs when displayOnly and masked', async function (assert) {
|
||||
@@ -65,7 +68,7 @@ module('Integration | Component | masked input', function (hooks) {
|
||||
const maskedValue = document.querySelector('.masked-value').innerText;
|
||||
assert.strictEqual(maskedValue.length, 11);
|
||||
|
||||
await component.toggleMasked();
|
||||
await click(SELECTORS.toggle);
|
||||
const unMaskedValue = document.querySelector('.masked-value').innerText;
|
||||
assert.strictEqual(unMaskedValue.length, this.value.length);
|
||||
});
|
||||
@@ -83,7 +86,7 @@ module('Integration | Component | masked input', function (hooks) {
|
||||
this.set('value', 'before');
|
||||
this.set('onChange', changeSpy);
|
||||
await render(hbs`<MaskedInput @value={{this.value}} @onChange={{this.onChange}} />`);
|
||||
await fillIn('[data-test-textarea]', 'after');
|
||||
await fillIn('textarea', 'after');
|
||||
assert.true(changeSpy.calledWith('after'));
|
||||
});
|
||||
|
||||
@@ -92,7 +95,7 @@ module('Integration | Component | masked input', function (hooks) {
|
||||
this.set('value', '');
|
||||
this.set('onKeyUp', keyupSpy);
|
||||
await render(hbs`<MaskedInput @name="foo" @value={{this.value}} @onKeyUp={{this.onKeyUp}} />`);
|
||||
await typeIn('[data-test-textarea]', 'baz');
|
||||
await typeIn('textarea', 'baz');
|
||||
assert.true(keyupSpy.calledThrice, 'calls for each letter of typing');
|
||||
assert.true(keyupSpy.firstCall.calledWithExactly('foo', 'b'));
|
||||
assert.true(keyupSpy.secondCall.calledWithExactly('foo', 'ba'));
|
||||
@@ -102,8 +105,8 @@ module('Integration | Component | masked input', function (hooks) {
|
||||
test('it does not remove value on tab', async function (assert) {
|
||||
this.set('value', 'hello');
|
||||
await render(hbs`<MaskedInput @value={{this.value}} />`);
|
||||
await triggerKeyEvent('[data-test-textarea]', 'keydown', 9);
|
||||
await component.toggleMasked();
|
||||
await triggerKeyEvent('textarea', 'keydown', 9);
|
||||
await click(SELECTORS.toggle);
|
||||
const unMaskedValue = document.querySelector('.masked-value').value;
|
||||
assert.strictEqual(unMaskedValue, this.value);
|
||||
});
|
||||
@@ -119,11 +122,11 @@ module('Integration | Component | masked input', function (hooks) {
|
||||
/>
|
||||
`);
|
||||
assert.dom('[data-test-masked-input]').exists('shows masked input');
|
||||
assert.ok(component.copyButtonIsPresent);
|
||||
assert.ok(component.downloadIconIsPresent);
|
||||
assert.dom('[data-test-button="toggle-masked"]').exists('shows toggle mask button');
|
||||
assert.dom(SELECTORS.copyBtn).exists();
|
||||
assert.dom(SELECTORS.downloadIcon).exists();
|
||||
assert.dom(SELECTORS.toggle).exists('shows toggle mask button');
|
||||
|
||||
await component.toggleMasked();
|
||||
await click(SELECTORS.toggle);
|
||||
assert.dom('.masked-value').doesNotHaveClass('masked-font', 'it unmasks when show button is clicked');
|
||||
assert
|
||||
.dom('[data-test-icon="minus"]')
|
||||
@@ -154,12 +157,12 @@ module('Integration | Component | masked input', function (hooks) {
|
||||
/>
|
||||
`);
|
||||
|
||||
await click('[data-test-download-icon]');
|
||||
assert.dom('[data-test-stringify-toggle]').isNotChecked('Stringify toggle off as default');
|
||||
await click('[data-test-download-button]');
|
||||
await click(SELECTORS.downloadIcon);
|
||||
assert.dom(SELECTORS.stringify).isNotChecked('Stringify toggle off as default');
|
||||
await click(SELECTORS.downloadBtn);
|
||||
|
||||
await click('[data-test-download-icon]');
|
||||
await click('[data-test-stringify-toggle]');
|
||||
await click('[data-test-download-button]');
|
||||
await click(SELECTORS.downloadIcon);
|
||||
await click(SELECTORS.stringify);
|
||||
await click(SELECTORS.downloadBtn);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ const S = {
|
||||
infoBanner: '[data-test-current-user-banner]',
|
||||
save: '[data-test-reset-password-save]',
|
||||
error: '[data-test-reset-password-error]',
|
||||
input: '[data-test-textarea]',
|
||||
input: '[data-test-input="reset-password"]',
|
||||
};
|
||||
module('Integration | Component | page/userpass-reset-password', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
@@ -69,11 +69,7 @@ module('Integration | Component | SecretEngine/ConfigureAws', function (hooks) {
|
||||
// check all the form fields are present
|
||||
await click(GENERAL.toggleGroup('Root config options'));
|
||||
for (const key of expectedConfigKeys('aws-root-create')) {
|
||||
if (key === 'secretKey') {
|
||||
assert.dom(GENERAL.maskedInput(key)).exists(`${key} shows for root section.`);
|
||||
} else {
|
||||
assert.dom(GENERAL.inputByAttr(key)).exists(`${key} shows for root section.`);
|
||||
}
|
||||
assert.dom(GENERAL.inputByAttr(key)).exists(`${key} shows for root section.`);
|
||||
}
|
||||
for (const key of expectedConfigKeys('aws-lease')) {
|
||||
assert.dom(`[data-test-ttl-form-label="${key}"]`).exists(`${key} shows for Lease section.`);
|
||||
@@ -94,11 +90,7 @@ module('Integration | Component | SecretEngine/ConfigureAws', function (hooks) {
|
||||
}
|
||||
// check iam fields do not show
|
||||
for (const key of expectedConfigKeys('aws-root-create-iam')) {
|
||||
if (key === 'secretKey') {
|
||||
assert.dom(GENERAL.maskedInput(key)).doesNotExist(`${key} does not show when wif is selected.`);
|
||||
} else {
|
||||
assert.dom(GENERAL.inputByAttr(key)).doesNotExist(`${key} does not show when wif is selected.`);
|
||||
}
|
||||
assert.dom(GENERAL.inputByAttr(key)).doesNotExist(`${key} does not show when wif is selected.`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -113,7 +105,7 @@ module('Integration | Component | SecretEngine/ConfigureAws', function (hooks) {
|
||||
.dom(GENERAL.inputByAttr('accessKey'))
|
||||
.hasValue('', 'accessKey is cleared after toggling accessType');
|
||||
assert
|
||||
.dom(GENERAL.maskedInput('secretKey'))
|
||||
.dom(GENERAL.inputByAttr('secretKey'))
|
||||
.hasValue('', 'secretKey is cleared after toggling accessType');
|
||||
|
||||
await click(SES.aws.accessType('wif'));
|
||||
@@ -435,11 +427,7 @@ module('Integration | Component | SecretEngine/ConfigureAws', function (hooks) {
|
||||
// check all the form fields are present
|
||||
await click(GENERAL.toggleGroup('Root config options'));
|
||||
for (const key of expectedConfigKeys('aws-root-create')) {
|
||||
if (key === 'secretKey') {
|
||||
assert.dom(GENERAL.maskedInput(key)).exists(`${key} shows for root section.`);
|
||||
} else {
|
||||
assert.dom(GENERAL.inputByAttr(key)).exists(`${key} shows for root section.`);
|
||||
}
|
||||
assert.dom(GENERAL.inputByAttr(key)).exists(`${key} shows for root section.`);
|
||||
}
|
||||
for (const key of expectedConfigKeys('aws-lease')) {
|
||||
assert.dom(`[data-test-ttl-form-label="${key}"]`).exists(`${key} shows for Lease section.`);
|
||||
@@ -559,7 +547,7 @@ module('Integration | Component | SecretEngine/ConfigureAws', function (hooks) {
|
||||
|
||||
await click(GENERAL.enableField('secretKey'));
|
||||
await click('[data-test-button="toggle-masked"]');
|
||||
await fillIn(GENERAL.maskedInput('secretKey'), 'new-secret');
|
||||
await fillIn(GENERAL.inputByAttr('secretKey'), 'new-secret');
|
||||
await click(GENERAL.saveButton);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ module('Integration | Component | SecretEngine/configure-ssh', function (hooks)
|
||||
@id={{this.id}}
|
||||
/>
|
||||
`);
|
||||
assert.dom(GENERAL.maskedInput('privateKey')).hasNoText('Private key is empty and reset');
|
||||
assert.dom(GENERAL.inputByAttr('privateKey')).hasNoText('Private key is empty and reset');
|
||||
assert.dom(GENERAL.inputByAttr('publicKey')).hasNoText('Public key is empty and reset');
|
||||
assert
|
||||
.dom(GENERAL.inputByAttr('generateSigningKey'))
|
||||
|
||||
@@ -183,9 +183,9 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
|
||||
|
||||
await this.renderFormComponent();
|
||||
await click(PAGE.enableField('accessKeyId'));
|
||||
await click(PAGE.maskedInput('accessKeyId')); // click on input but do not change value
|
||||
await click(PAGE.inputByAttr('accessKeyId')); // click on input but do not change value
|
||||
await click(PAGE.enableField('secretAccessKey'));
|
||||
await fillIn(PAGE.maskedInput('secretAccessKey'), 'new-secret');
|
||||
await fillIn(PAGE.inputByAttr('secretAccessKey'), 'new-secret');
|
||||
await click(PAGE.saveButton);
|
||||
});
|
||||
|
||||
@@ -277,10 +277,10 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
|
||||
// iterate over the form fields and filter for those that are obfuscated
|
||||
// fill those in and assert that they are masked
|
||||
filteredObfuscatedFields.forEach(async (field) => {
|
||||
await fillIn(PAGE.maskedInput(field.name), 'blah');
|
||||
await fillIn(PAGE.inputByAttr(field.name), 'blah');
|
||||
|
||||
assert
|
||||
.dom(PAGE.maskedInput(field.name))
|
||||
.dom(PAGE.inputByAttr(field.name))
|
||||
.hasClass('masked-font', `it renders ${field.name} for ${destination} with masked font`);
|
||||
assert
|
||||
.dom(PAGE.form.enableInput(field.name))
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { clickable, isPresent } from 'ember-cli-page-object';
|
||||
|
||||
export default {
|
||||
textareaIsPresent: isPresent('[data-test-textarea]'),
|
||||
copyButtonIsPresent: isPresent('[data-test-copy-button]'),
|
||||
downloadIconIsPresent: isPresent('[data-test-download-icon]'),
|
||||
downloadButtonIsPresent: isPresent('[data-test-download-button]'),
|
||||
toggleMasked: clickable('[data-test-button="toggle-masked"]'),
|
||||
copyValue: clickable('[data-test-copy-button]'),
|
||||
};
|
||||
Reference in New Issue
Block a user