mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-28 04:10:44 -04:00
* updates oidc scope list view to use api service * updates oidc scope details route to use api service * updates oidc scopes edit and create views to use api service and form class * updates oidc scopes tests Co-authored-by: Jordan Reimer <zofskeez@gmail.com>
This commit is contained in:
parent
be2c46fee5
commit
7d474e2d8c
15 changed files with 237 additions and 221 deletions
|
|
@ -3,7 +3,7 @@
|
|||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Page::Header @title="{{if @model.isNew 'Create' 'Edit'}} Scope">
|
||||
<Page::Header @title="{{if @form.isNew 'Create' 'Edit'}} Scope">
|
||||
<:breadcrumbs>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</:breadcrumbs>
|
||||
|
|
@ -24,13 +24,11 @@
|
|||
Providers may reference a set of scopes to make specific identity information available as claims
|
||||
</p>
|
||||
<MessageError @errorMessage={{this.errorBanner}} />
|
||||
{{#each @model.formFields as |field|}}
|
||||
<FormField @attr={{field}} @model={{@model}} @modelValidations={{this.modelValidations}} />
|
||||
{{/each}}
|
||||
<FormFieldGroups @model={{@form}} @groupName="formFieldGroups" @modelValidations={{this.modelValidations}} />
|
||||
</div>
|
||||
<div class="has-top-margin-l has-bottom-margin-l">
|
||||
<Hds::Button
|
||||
@text={{if @model.isNew "Create" "Update"}}
|
||||
@text={{if @form.isNew "Create" "Update"}}
|
||||
@icon={{if this.save.isRunning "loading"}}
|
||||
type="submit"
|
||||
disabled={{this.save.isRunning}}
|
||||
|
|
@ -41,7 +39,7 @@
|
|||
@color="secondary"
|
||||
class="has-left-margin-s"
|
||||
disabled={{this.save.isRunning}}
|
||||
{{on "click" this.cancel}}
|
||||
{{on "click" @onCancel}}
|
||||
data-test-oidc-scope-cancel
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { task } from 'ember-concurrency';
|
||||
import { service } from '@ember/service';
|
||||
import { waitFor } from '@ember/test-waiters';
|
||||
|
||||
/**
|
||||
* @module OidcScopeForm
|
||||
|
|
@ -15,17 +15,19 @@ import { service } from '@ember/service';
|
|||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <Oidc::ScopeForm @model={{this.model}} />
|
||||
* <Oidc::ScopeForm @form={{this.model}} />
|
||||
* ```
|
||||
* @callback onCancel
|
||||
* @callback onSave
|
||||
* @param {Object} model - oidc scope model
|
||||
* @param {Object} form - oidc scope form
|
||||
* @param {onCancel} onCancel - callback triggered when cancel button is clicked
|
||||
* @param {onSave} onSave - callback triggered on save success
|
||||
*/
|
||||
|
||||
export default class OidcScopeFormComponent extends Component {
|
||||
@service api;
|
||||
@service flashMessages;
|
||||
|
||||
@tracked errorBanner;
|
||||
@tracked invalidFormAlert;
|
||||
@tracked modelValidations;
|
||||
|
|
@ -45,41 +47,38 @@ export default class OidcScopeFormComponent extends Component {
|
|||
{ label: 'OIDC provider: Scopes', route: 'vault.cluster.access.oidc.scopes' },
|
||||
];
|
||||
|
||||
if (!this.args.model.isNew) {
|
||||
if (!this.args.form.isNew) {
|
||||
crumbs.push({
|
||||
label: this.args.model.name,
|
||||
label: this.args.form.data.name,
|
||||
route: 'vault.cluster.access.oidc.scopes.scope.details',
|
||||
model: this.args.model.name,
|
||||
model: this.args.form.data.name,
|
||||
});
|
||||
}
|
||||
|
||||
crumbs.push({ label: this.args.model.isNew ? 'Create scope' : 'Edit scope' });
|
||||
crumbs.push({ label: this.args.form.isNew ? 'Create scope' : 'Edit scope' });
|
||||
return crumbs;
|
||||
}
|
||||
|
||||
@task
|
||||
*save(event) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
const { isValid, state, invalidFormMessage } = this.args.model.validate();
|
||||
this.modelValidations = isValid ? null : state;
|
||||
this.invalidFormAlert = invalidFormMessage;
|
||||
if (isValid) {
|
||||
const { isNew, name } = this.args.model;
|
||||
yield this.args.model.save();
|
||||
this.flashMessages.success(`Successfully ${isNew ? 'created' : 'updated'} the scope ${name}.`);
|
||||
this.args.onSave();
|
||||
save = task(
|
||||
waitFor(async (event) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
const { isNew } = this.args.form;
|
||||
const { isValid, state, invalidFormMessage, data } = this.args.form.toJSON();
|
||||
this.modelValidations = isValid ? null : state;
|
||||
this.invalidFormAlert = invalidFormMessage;
|
||||
|
||||
if (isValid) {
|
||||
const { name, ...payload } = data;
|
||||
await this.api.identity.oidcWriteScope(name, payload);
|
||||
this.flashMessages.success(`Successfully ${isNew ? 'created' : 'updated'} the scope ${name}.`);
|
||||
this.args.onSave();
|
||||
}
|
||||
} catch (error) {
|
||||
const { message } = await this.api.parseError(error);
|
||||
this.errorBanner = message;
|
||||
this.invalidFormAlert = 'There was an error submitting this form.';
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error.errors ? error.errors.join('. ') : error.message;
|
||||
this.errorBanner = message;
|
||||
this.invalidFormAlert = 'There was an error submitting this form.';
|
||||
}
|
||||
}
|
||||
@action
|
||||
cancel() {
|
||||
const method = this.args.model.isNew ? 'unloadRecord' : 'rollbackAttributes';
|
||||
this.args.model[method]();
|
||||
this.args.onCancel();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,18 +8,18 @@ import { action } from '@ember/object';
|
|||
import { service } from '@ember/service';
|
||||
|
||||
export default class OidcScopeDetailsController extends Controller {
|
||||
@service api;
|
||||
@service router;
|
||||
@service flashMessages;
|
||||
|
||||
@action
|
||||
async delete() {
|
||||
try {
|
||||
await this.model.destroyRecord();
|
||||
await this.api.identity.oidcDeleteScope(this.model.scope.name);
|
||||
this.flashMessages.success('Scope deleted successfully');
|
||||
this.router.transitionTo('vault.cluster.access.oidc.scopes');
|
||||
} catch (error) {
|
||||
this.model.rollbackAttributes();
|
||||
const message = error.errors ? error.errors.join('. ') : error.message;
|
||||
const { message } = await this.api.parseError(error);
|
||||
this.flashMessages.danger(message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
ui/app/forms/oidc/scope.ts
Normal file
29
ui/app/forms/oidc/scope.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Form from 'vault/forms/form';
|
||||
import FormField from 'vault/utils/forms/field';
|
||||
import FormFieldGroup from 'vault/utils/forms/field-group';
|
||||
|
||||
import type { Validations } from 'vault/app-types';
|
||||
import type { OidcWriteScopeRequest } from '@hashicorp/vault-client-typescript';
|
||||
|
||||
type OidcScopeFormData = OidcWriteScopeRequest & {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export default class OidcScopeForm extends Form<OidcScopeFormData> {
|
||||
formFieldGroups = [
|
||||
new FormFieldGroup('default', [
|
||||
new FormField('name', 'string', { editDisabled: true }),
|
||||
new FormField('description', 'string', { editType: 'textarea' }),
|
||||
new FormField('template', 'string', { label: 'JSON Template', editType: 'json', mode: 'ruby' }),
|
||||
]),
|
||||
];
|
||||
|
||||
validations: Validations = {
|
||||
name: [{ type: 'presence', message: 'Name is required.' }],
|
||||
};
|
||||
}
|
||||
|
|
@ -4,12 +4,10 @@
|
|||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import OidcScopeForm from 'vault/forms/oidc/scope';
|
||||
|
||||
export default class OidcScopesCreateRoute extends Route {
|
||||
@service store;
|
||||
|
||||
model() {
|
||||
return this.store.createRecord('oidc/scope');
|
||||
return new OidcScopeForm({}, { isNew: true });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,32 @@
|
|||
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { IdentityApiOidcListScopesListEnum } from '@hashicorp/vault-client-typescript';
|
||||
|
||||
export default class OidcScopesRoute extends Route {
|
||||
@service store;
|
||||
@service api;
|
||||
@service capabilities;
|
||||
|
||||
model() {
|
||||
return this.store.query('oidc/scope', {}).catch((err) => {
|
||||
if (err.httpStatus === 404) {
|
||||
return [];
|
||||
async model() {
|
||||
try {
|
||||
const { keys: scopes } = await this.api.identity.oidcListScopes(IdentityApiOidcListScopesListEnum.TRUE);
|
||||
const paths = scopes.map((name) => this.capabilities.pathFor('oidcScope', { name }));
|
||||
const capabilities = paths ? await this.capabilities.fetch(paths) : {};
|
||||
|
||||
return {
|
||||
scopes,
|
||||
capabilities,
|
||||
};
|
||||
} catch (error) {
|
||||
const { status } = await this.api.parseError(error);
|
||||
if (status === 404) {
|
||||
return {
|
||||
scopes: [],
|
||||
capabilities: {},
|
||||
};
|
||||
} else {
|
||||
throw err;
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,15 @@ import Route from '@ember/routing/route';
|
|||
import { service } from '@ember/service';
|
||||
|
||||
export default class OidcScopeRoute extends Route {
|
||||
@service store;
|
||||
@service api;
|
||||
@service capabilities;
|
||||
|
||||
model({ name }) {
|
||||
return this.store.findRecord('oidc/scope', name);
|
||||
async model({ name }) {
|
||||
const { data } = await this.api.identity.oidcReadScope(name);
|
||||
const capabilities = await this.capabilities.for('oidcScope', { name });
|
||||
return {
|
||||
scope: { ...data, name },
|
||||
capabilities,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,5 +4,11 @@
|
|||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import OidcScopeForm from 'vault/forms/oidc/scope';
|
||||
|
||||
export default class OidcScopeEditRoute extends Route {}
|
||||
export default class OidcScopeEditRoute extends Route {
|
||||
model() {
|
||||
const { scope } = this.modelFor('vault.cluster.access.oidc.scopes.scope');
|
||||
return new OidcScopeForm(scope);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
}}
|
||||
|
||||
<Oidc::ScopeForm
|
||||
@model={{this.model}}
|
||||
@form={{this.model}}
|
||||
@onCancel={{transition-to "vault.cluster.access.oidc.scopes"}}
|
||||
@onSave={{transition-to "vault.cluster.access.oidc.scopes.scope.details" this.model.name}}
|
||||
@onSave={{transition-to "vault.cluster.access.oidc.scopes.scope.details" this.model.data.name}}
|
||||
/>
|
||||
|
|
@ -11,19 +11,19 @@
|
|||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
|
||||
{{#if (gt this.model.length 0)}}
|
||||
{{#each this.model as |model|}}
|
||||
{{#if this.model.scopes}}
|
||||
{{#each this.model.scopes as |scope|}}
|
||||
<LinkedBlock
|
||||
class="list-item-row"
|
||||
@params={{array "vault.cluster.access.oidc.scopes.scope.details" model.name}}
|
||||
data-test-oidc-scope-linked-block={{model.name}}
|
||||
@params={{array "vault.cluster.access.oidc.scopes.scope.details" scope}}
|
||||
data-test-oidc-scope-linked-block={{scope}}
|
||||
>
|
||||
<div class="level is-mobile">
|
||||
<div class="level-left">
|
||||
<div>
|
||||
<Icon @name="file" class="has-text-grey-light" />
|
||||
<span class="has-text-weight-semibold is-underline">
|
||||
{{model.name}}
|
||||
{{scope}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -38,16 +38,24 @@
|
|||
/>
|
||||
<dd.Interactive
|
||||
@route="vault.cluster.access.oidc.scopes.scope.details"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canRead false}}
|
||||
@model={{scope}}
|
||||
@disabled={{not
|
||||
(has-capability this.model.capabilities "read" pathKey="oidcScope" params=(hash name=scope))
|
||||
}}
|
||||
data-test-oidc-scope-menu-link="details"
|
||||
>Details</dd.Interactive>
|
||||
>
|
||||
Details
|
||||
</dd.Interactive>
|
||||
<dd.Interactive
|
||||
@route="vault.cluster.access.oidc.scopes.scope.edit"
|
||||
@model={{model.name}}
|
||||
@disabled={{eq model.canEdit false}}
|
||||
@model={{scope}}
|
||||
@disabled={{not
|
||||
(has-capability this.model.capabilities "update" pathKey="oidcScope" params=(hash name=scope))
|
||||
}}
|
||||
data-test-oidc-scope-menu-link="edit"
|
||||
>Edit</dd.Interactive>
|
||||
>
|
||||
Edit
|
||||
</dd.Interactive>
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,56 +3,58 @@
|
|||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Page::Header @title={{this.model.name}}>
|
||||
<:breadcrumbs>
|
||||
<Page::Breadcrumbs
|
||||
@breadcrumbs={{array
|
||||
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
|
||||
(hash label="OIDC provider: Scopes" route="vault.cluster.access.oidc.scopes")
|
||||
(hash label=this.model.name)
|
||||
}}
|
||||
/>
|
||||
</:breadcrumbs>
|
||||
</Page::Header>
|
||||
|
||||
<div class="tabs-container box is-sideless is-fullwidth is-paddingless is-marginless">
|
||||
<nav class="tabs" aria-label="tabs">
|
||||
<ul>
|
||||
<LinkTo @route="vault.cluster.access.oidc.scopes.scope.details" @model={{this.model}} data-test-oidc-scope-details>
|
||||
Details
|
||||
</LinkTo>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
{{#if this.model.canDelete}}
|
||||
<ConfirmAction
|
||||
data-test-oidc-scope-delete
|
||||
@buttonText="Delete scope"
|
||||
class="toolbar-button"
|
||||
@buttonColor="secondary"
|
||||
@onConfirmAction={{this.delete}}
|
||||
@confirmTitle="Delete scope?"
|
||||
@confirmMessage="This scope will be permanently deleted. You will not be able to recover it."
|
||||
{{#let this.model.scope this.model.capabilities as |scope capabilities|}}
|
||||
<Page::Header @title={{scope.name}}>
|
||||
<:breadcrumbs>
|
||||
<Page::Breadcrumbs
|
||||
@breadcrumbs={{array
|
||||
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
|
||||
(hash label="OIDC provider: Scopes" route="vault.cluster.access.oidc.scopes")
|
||||
(hash label=scope.name)
|
||||
}}
|
||||
/>
|
||||
<div class="toolbar-separator"></div>
|
||||
{{/if}}
|
||||
{{#if this.model.canEdit}}
|
||||
<ToolbarLink @route="vault.cluster.access.oidc.scopes.scope.edit" @model={{this.model.name}} data-test-oidc-scope-edit>
|
||||
Edit scope
|
||||
</ToolbarLink>
|
||||
{{/if}}
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
</:breadcrumbs>
|
||||
</Page::Header>
|
||||
|
||||
<div class="box is-fullwidth is-sideless is-paddingless is-marginless">
|
||||
<InfoTableRow @label="Name" @value={{this.model.name}} />
|
||||
<InfoTableRow @label="Description" @value={{this.model.description}} />
|
||||
<Hds::CodeBlock @value={{this.model.template}} @language="ruby" @hasCopyButton={{true}} as |CB|>
|
||||
<CB.Title @tag="h3">
|
||||
JSON Template
|
||||
</CB.Title>
|
||||
</Hds::CodeBlock>
|
||||
</div>
|
||||
<div class="tabs-container box is-sideless is-fullwidth is-paddingless is-marginless">
|
||||
<nav class="tabs" aria-label="tabs">
|
||||
<ul>
|
||||
<LinkTo @route="vault.cluster.access.oidc.scopes.scope.details" @model={{scope.name}} data-test-oidc-scope-details>
|
||||
Details
|
||||
</LinkTo>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<Toolbar>
|
||||
<ToolbarActions>
|
||||
{{#if capabilities.canDelete}}
|
||||
<ConfirmAction
|
||||
data-test-oidc-scope-delete
|
||||
@buttonText="Delete scope"
|
||||
class="toolbar-button"
|
||||
@buttonColor="secondary"
|
||||
@onConfirmAction={{this.delete}}
|
||||
@confirmTitle="Delete scope?"
|
||||
@confirmMessage="This scope will be permanently deleted. You will not be able to recover it."
|
||||
/>
|
||||
<div class="toolbar-separator"></div>
|
||||
{{/if}}
|
||||
{{#if capabilities.canUpdate}}
|
||||
<ToolbarLink @route="vault.cluster.access.oidc.scopes.scope.edit" @model={{scope.name}} data-test-oidc-scope-edit>
|
||||
Edit scope
|
||||
</ToolbarLink>
|
||||
{{/if}}
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
|
||||
<div class="box is-fullwidth is-sideless is-paddingless is-marginless">
|
||||
<InfoTableRow @label="Name" @value={{scope.name}} />
|
||||
<InfoTableRow @label="Description" @value={{scope.description}} />
|
||||
<Hds::CodeBlock @value={{scope.template}} @language="ruby" @hasCopyButton={{true}} as |CB|>
|
||||
<CB.Title @tag="h3">
|
||||
JSON Template
|
||||
</CB.Title>
|
||||
</Hds::CodeBlock>
|
||||
</div>
|
||||
{{/let}}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
}}
|
||||
|
||||
<Oidc::ScopeForm
|
||||
@model={{this.model}}
|
||||
@onCancel={{transition-to "vault.cluster.access.oidc.scopes.scope.details" this.model.name}}
|
||||
@onSave={{transition-to "vault.cluster.access.oidc.scopes.scope.details" this.model.name}}
|
||||
@form={{this.model}}
|
||||
@onCancel={{transition-to "vault.cluster.access.oidc.scopes.scope.details" this.model.data.name}}
|
||||
@onSave={{transition-to "vault.cluster.access.oidc.scopes.scope.details" this.model.data.name}}
|
||||
/>
|
||||
|
|
@ -40,6 +40,7 @@ export const PATH_MAP = {
|
|||
ldapStaticRoleCreds: apiPath`${'backend'}/static-cred/${'name'}`,
|
||||
oidcClient: apiPath`identity/oidc/client/${'name'}`,
|
||||
oidcProvider: apiPath`identity/oidc/provider/${'name'}`,
|
||||
oidcScope: apiPath`identity/oidc/scope/${'name'}`,
|
||||
pkiCertificates: apiPath`${'backend'}/certificates`,
|
||||
pkiConfigAcme: apiPath`${'backend'}/config/acme`,
|
||||
pkiConfigAutoTidy: apiPath`${'backend'}/config/auto-tidy`,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import {
|
|||
SCOPE_DATA_RESPONSE,
|
||||
PROVIDER_LIST_RESPONSE,
|
||||
PROVIDER_DATA_RESPONSE,
|
||||
clearRecord,
|
||||
} from 'vault/tests/helpers/oidc-config';
|
||||
import { capabilitiesStub, overrideResponse } from 'vault/tests/helpers/stubs';
|
||||
import sinon from 'sinon';
|
||||
|
|
@ -39,7 +38,6 @@ module('Acceptance | oidc-config providers and scopes', function (hooks) {
|
|||
|
||||
hooks.beforeEach(function () {
|
||||
oidcConfigHandlers(this.server);
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.api = this.owner.lookup('service:api');
|
||||
// mock client list so OIDC BASE URL does not redirect to landing call-to-action image
|
||||
this.server.get('/identity/oidc/client', () => overrideResponse(null, { data: CLIENT_LIST_RESPONSE }));
|
||||
|
|
@ -49,7 +47,13 @@ module('Acceptance | oidc-config providers and scopes', function (hooks) {
|
|||
// LIST SCOPES EMPTY
|
||||
test('it navigates to scopes list view and renders empty state when no scopes are configured', async function (assert) {
|
||||
assert.expect(4);
|
||||
this.server.get('/identity/oidc/scope', () => overrideResponse(404));
|
||||
this.server.get(
|
||||
'/identity/oidc/scope',
|
||||
() => ({
|
||||
errors: ['Nothing found'],
|
||||
}),
|
||||
404
|
||||
);
|
||||
await visit(OIDC_BASE_URL);
|
||||
await click(GENERAL.tab('scopes'));
|
||||
assert.strictEqual(currentURL(), '/vault/access/oidc/scopes');
|
||||
|
|
@ -168,11 +172,11 @@ module('Acceptance | oidc-config providers and scopes', function (hooks) {
|
|||
test('it creates a scope, and creates a provider with that scope', async function (assert) {
|
||||
assert.expect(28);
|
||||
|
||||
const apiSpy = sinon.spy(this.owner.lookup('service:api').identity, 'oidcWriteProvider');
|
||||
const apiSpy = sinon.spy(this.api.identity, 'oidcWriteProvider');
|
||||
|
||||
//* clear out test state
|
||||
await Promise.allSettled([
|
||||
clearRecord(this.store, 'oidc/scope', 'test-scope'),
|
||||
this.api.identity.oidcDeleteScope('test-scope'),
|
||||
this.api.identity.oidcDeleteProvider('test-provider'),
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,36 +8,38 @@ import { setupRenderingTest } from 'vault/tests/helpers';
|
|||
import { render, fillIn, click } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { SELECTORS, OIDC_BASE_URL } from 'vault/tests/helpers/oidc-config';
|
||||
import { capabilitiesStub } from 'vault/tests/helpers/stubs';
|
||||
import { SELECTORS } from 'vault/tests/helpers/oidc-config';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import sinon from 'sinon';
|
||||
import OidcScopeForm from 'vault/forms/oidc/scope';
|
||||
import { getErrorResponse } from 'vault/tests/helpers/api/error-response';
|
||||
|
||||
module('Integration | Component | oidc/scope-form', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
const api = this.owner.lookup('service:api');
|
||||
this.writeStub = sinon.stub(api.identity, 'oidcWriteScope').resolves();
|
||||
this.onCancel = sinon.spy();
|
||||
this.onSave = sinon.spy();
|
||||
|
||||
this.renderComponent = (scope) => {
|
||||
this.form = new OidcScopeForm(scope || {}, { isNew: !scope });
|
||||
return render(hbs`
|
||||
<Oidc::ScopeForm
|
||||
@form={{this.form}}
|
||||
@onCancel={{this.onCancel}}
|
||||
@onSave={{this.onSave}}
|
||||
/>
|
||||
`);
|
||||
};
|
||||
});
|
||||
|
||||
test('it should save new scope', async function (assert) {
|
||||
assert.expect(8);
|
||||
|
||||
this.server.post('/identity/oidc/scope/test', (schema, req) => {
|
||||
assert.ok(true, 'Request made to save scope');
|
||||
return JSON.parse(req.requestBody);
|
||||
});
|
||||
|
||||
this.model = this.store.createRecord('oidc/scope');
|
||||
this.onSave = () => assert.ok(true, 'onSave callback fires on save success');
|
||||
|
||||
await render(hbs`
|
||||
<Oidc::ScopeForm
|
||||
@model={{this.model}}
|
||||
@onCancel={{this.onCancel}}
|
||||
@onSave={{this.onSave}}
|
||||
/>
|
||||
`);
|
||||
await this.renderComponent();
|
||||
|
||||
assert.dom(GENERAL.hdsPageHeaderTitle).hasText('Create Scope', 'Form title renders');
|
||||
assert.dom(SELECTORS.scopeSaveButton).hasText('Create', 'Save button has correct label');
|
||||
|
|
@ -62,31 +64,18 @@ module('Integration | Component | oidc/scope-form', function (hooks) {
|
|||
await fillIn(GENERAL.inputByAttr('name'), 'test');
|
||||
await fillIn(GENERAL.inputByAttr('description'), 'this is a test');
|
||||
await click(SELECTORS.scopeSaveButton);
|
||||
|
||||
assert.true(this.onSave.calledOnce, 'onSave callback is called on successful save');
|
||||
assert.true(
|
||||
this.writeStub.calledWith('test', { description: 'this is a test' }),
|
||||
'API is called with correct parameters'
|
||||
);
|
||||
});
|
||||
|
||||
test('it should update scope', async function (assert) {
|
||||
assert.expect(9);
|
||||
|
||||
this.server.post('/identity/oidc/scope/test', (schema, req) => {
|
||||
assert.ok(true, 'Request made to save scope');
|
||||
return JSON.parse(req.requestBody);
|
||||
});
|
||||
|
||||
this.store.pushPayload('oidc/scope', {
|
||||
modelName: 'oidc/scope',
|
||||
name: 'test',
|
||||
description: 'this is a test',
|
||||
});
|
||||
this.model = this.store.peekRecord('oidc/scope', 'test');
|
||||
this.onSave = () => assert.ok(true, 'onSave callback fires on save success');
|
||||
|
||||
await render(hbs`
|
||||
<Oidc::ScopeForm
|
||||
@model={{this.model}}
|
||||
@onCancel={{this.onCancel}}
|
||||
@onSave={{this.onSave}}
|
||||
/>
|
||||
`);
|
||||
await this.renderComponent({ name: 'test', description: 'this is a test' });
|
||||
|
||||
assert.dom(GENERAL.hdsPageHeaderTitle).hasText('Edit Scope', 'Form title renders');
|
||||
assert.dom(SELECTORS.scopeSaveButton).hasText('Update', 'Save button has correct label');
|
||||
|
|
@ -105,62 +94,28 @@ module('Integration | Component | oidc/scope-form', function (hooks) {
|
|||
|
||||
await fillIn(GENERAL.inputByAttr('description'), 'this is an edit test');
|
||||
await click(SELECTORS.scopeSaveButton);
|
||||
|
||||
assert.true(this.onSave.calledOnce, 'onSave callback is called on successful save');
|
||||
assert.true(
|
||||
this.writeStub.calledWith('test', { description: 'this is an edit test' }),
|
||||
'API is called with correct parameters'
|
||||
);
|
||||
});
|
||||
|
||||
test('it should rollback attributes or unload record on cancel', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
this.onCancel = () => assert.ok(true, 'onCancel callback fires');
|
||||
|
||||
this.model = this.store.createRecord('oidc/scope');
|
||||
|
||||
await render(hbs`
|
||||
<Oidc::ScopeForm
|
||||
@model={{this.model}}
|
||||
@onCancel={{this.onCancel}}
|
||||
@onSave={{this.onSave}}
|
||||
/>
|
||||
`);
|
||||
test('it should trigger on cancel callback', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
await this.renderComponent();
|
||||
await click(SELECTORS.scopeCancelButton);
|
||||
assert.true(this.model.isDestroyed, 'New model is unloaded on cancel');
|
||||
|
||||
this.store.pushPayload('oidc/scope', {
|
||||
modelName: 'oidc/scope',
|
||||
name: 'test',
|
||||
description: 'this is a test',
|
||||
});
|
||||
this.model = this.store.peekRecord('oidc/scope', 'test');
|
||||
|
||||
await render(hbs`
|
||||
<Oidc::ScopeForm
|
||||
@model={{this.model}}
|
||||
@onCancel={{this.onCancel}}
|
||||
@onSave={{this.onSave}}
|
||||
/>
|
||||
`);
|
||||
|
||||
await fillIn(GENERAL.inputByAttr('description'), 'changed description attribute');
|
||||
await click(SELECTORS.scopeCancelButton);
|
||||
assert.strictEqual(
|
||||
this.model.description,
|
||||
'this is a test',
|
||||
'Model attributes are rolled back on cancel'
|
||||
);
|
||||
assert.true(this.onCancel.calledOnce, 'onCancel callback is called when cancel button is clicked');
|
||||
});
|
||||
|
||||
test('it should show example template modal', async function (assert) {
|
||||
assert.expect(5);
|
||||
const MODAL = (e) => `[data-test-scope-modal="${e}"]`;
|
||||
this.model = this.store.createRecord('oidc/scope');
|
||||
|
||||
await render(hbs`
|
||||
<Oidc::ScopeForm
|
||||
@model={{this.model}}
|
||||
@onCancel={{this.onCancel}}
|
||||
@onSave={{this.onSave}}
|
||||
/>
|
||||
`);
|
||||
const MODAL = (e) => `[data-test-scope-modal="${e}"]`;
|
||||
|
||||
await this.renderComponent();
|
||||
|
||||
await click('[data-test-oidc-scope-example]');
|
||||
assert.dom(MODAL('title')).hasText('Scope template', 'Modal title renders');
|
||||
|
|
@ -173,15 +128,10 @@ module('Integration | Component | oidc/scope-form', function (hooks) {
|
|||
|
||||
test('it should render error alerts when API returns an error', async function (assert) {
|
||||
assert.expect(2);
|
||||
this.model = this.store.createRecord('oidc/scope');
|
||||
this.server.post('/sys/capabilities-self', () => capabilitiesStub(OIDC_BASE_URL + '/scopes'));
|
||||
await render(hbs`
|
||||
<Oidc::ScopeForm
|
||||
@model={{this.model}}
|
||||
@onCancel={{this.onCancel}}
|
||||
@onSave={{this.onSave}}
|
||||
/>
|
||||
`);
|
||||
|
||||
this.writeStub.rejects(getErrorResponse());
|
||||
await this.renderComponent();
|
||||
|
||||
await fillIn(GENERAL.inputByAttr('name'), 'test-scope');
|
||||
await click(SELECTORS.scopeSaveButton);
|
||||
assert
|
||||
|
|
|
|||
Loading…
Reference in a new issue