Kubernetes config state updates (#19074)
* hides roles toolbar actions when k8s is not configured * adds error page component to core addon * moves fetch-config to decorator * updates kubernetes prompt config logic * adds kubernetes error route * fixes tests * adds error handling for kubernetes roles list view * removes unneeded arg to withConfig decorator
This commit is contained in:
parent
074312dde2
commit
bc5a598d70
|
@ -0,0 +1,25 @@
|
|||
<div class="box is-sideless has-background-white-bis has-text-grey has-text-centered has-tall-padding" data-test-page-error>
|
||||
{{#if (eq @error.httpStatus 404)}}
|
||||
<h1 class="title is-3 has-text-grey">
|
||||
404 Not Found
|
||||
</h1>
|
||||
<p>Sorry, we were unable to find any content at <code>{{@error.path}}</code>.</p>
|
||||
{{else if (eq @error.httpStatus 403)}}
|
||||
<h1 class="title is-3 has-text-grey">
|
||||
Not authorized
|
||||
</h1>
|
||||
<p>You are not authorized to access content at <code>{{@error.path}}</code>.</p>
|
||||
{{else}}
|
||||
<h1 class="title is-3 has-text-grey">
|
||||
Error
|
||||
</h1>
|
||||
<p>
|
||||
{{#if @error.message}}
|
||||
<p data-test-page-error-message>{{@error.message}}</p>
|
||||
{{/if}}
|
||||
{{#each @error.errors as |error index|}}
|
||||
<p data-test-page-error-details={{index}}>{{error}}</p>
|
||||
{{/each}}
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -0,0 +1 @@
|
|||
export { default } from 'core/components/page/error';
|
|
@ -2,7 +2,9 @@
|
|||
<ToolbarLink @route="configure">Configure Kubernetes</ToolbarLink>
|
||||
</TabPageHeader>
|
||||
|
||||
{{#if @config}}
|
||||
{{#if @promptConfig}}
|
||||
<ConfigCta />
|
||||
{{else}}
|
||||
<div class="selectable-card-container has-grid has-top-margin-l has-two-col-grid">
|
||||
<OverviewCard
|
||||
@cardTitle="Roles"
|
||||
|
@ -38,6 +40,4 @@
|
|||
</div>
|
||||
</OverviewCard>
|
||||
</div>
|
||||
{{else}}
|
||||
<ConfigCta />
|
||||
{{/if}}
|
|
@ -7,7 +7,7 @@ import { action } from '@ember/object';
|
|||
* @module Overview
|
||||
* OverviewPage component is a child component to overview kubernetes secrets engine.
|
||||
*
|
||||
* @param {object} config - config model that contains kubernetes configuration
|
||||
* @param {boolean} promptConfig - whether or not to display config cta
|
||||
* @param {object} backend - backend model that contains kubernetes configuration
|
||||
* @param {array} roles - array of roles
|
||||
* @param {array} breadcrumbs - breadcrumbs as an array of objects that contain label and route
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
<TabPageHeader @model={{@backend}} @filterRoles={{true}} @rolesFilterValue={{@filterValue}} @breadcrumbs={{@breadcrumbs}}>
|
||||
<ToolbarLink @route="roles.create" @type="add" data-test-toolbar-roles-action>
|
||||
Create role
|
||||
</ToolbarLink>
|
||||
<TabPageHeader
|
||||
@model={{@backend}}
|
||||
@filterRoles={{not @promptConfig}}
|
||||
@rolesFilterValue={{@filterValue}}
|
||||
@breadcrumbs={{@breadcrumbs}}
|
||||
>
|
||||
{{#unless @promptConfig}}
|
||||
<ToolbarLink @route="roles.create" @type="add" data-test-toolbar-roles-action>
|
||||
Create role
|
||||
</ToolbarLink>
|
||||
{{/unless}}
|
||||
</TabPageHeader>
|
||||
|
||||
{{#if (not @config)}}
|
||||
{{#if @promptConfig}}
|
||||
<ConfigCta />
|
||||
{{else if (not @roles)}}
|
||||
{{#if @filterValue}}
|
||||
|
|
|
@ -9,7 +9,7 @@ import errorMessage from 'vault/utils/error-message';
|
|||
* RolesPage component is a child component to show list of roles
|
||||
*
|
||||
* @param {array} roles - array of roles
|
||||
* @param {object} config - config model that contains kubernetes configuration
|
||||
* @param {boolean} promptConfig - whether or not to display config cta
|
||||
* @param {array} pageFilter - array of filtered roles
|
||||
* @param {array} breadcrumbs - breadcrumbs as an array of objects that contain label and route
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import Route from '@ember/routing/route';
|
||||
|
||||
/**
|
||||
* the overview, configure, configuration and roles routes all need to be aware of the config for the engine
|
||||
* if the user has not configured they are prompted to do so in each of the routes
|
||||
* decorate the necessary routes to perform the check in the beforeModel hook since that may change what is returned for the model
|
||||
*/
|
||||
|
||||
export function withConfig() {
|
||||
return function decorator(SuperClass) {
|
||||
if (!Object.prototype.isPrototypeOf.call(Route, SuperClass)) {
|
||||
// eslint-disable-next-line
|
||||
console.error(
|
||||
'withConfig decorator must be used on an instance of ember Route class. Decorator not applied to returned class'
|
||||
);
|
||||
return SuperClass;
|
||||
}
|
||||
return class FetchConfig extends SuperClass {
|
||||
configModel = null;
|
||||
configError = null;
|
||||
promptConfig = false;
|
||||
|
||||
async beforeModel() {
|
||||
super.beforeModel(...arguments);
|
||||
|
||||
const backend = this.secretMountPath.get();
|
||||
// check the store for record first
|
||||
this.configModel = this.store.peekRecord('kubernetes/config', backend);
|
||||
if (!this.configModel) {
|
||||
return this.store
|
||||
.queryRecord('kubernetes/config', { backend })
|
||||
.then((record) => {
|
||||
this.configModel = record;
|
||||
})
|
||||
.catch((error) => {
|
||||
// we need to ignore if the user does not have permission or other failures so as to not block the other operations
|
||||
if (error.httpStatus === 404) {
|
||||
this.promptConfig = true;
|
||||
} else {
|
||||
// not considering 404 an error since it triggers the cta
|
||||
// this error is thrown in the configuration route so we can display the error in the view
|
||||
this.configError = error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,7 +1,17 @@
|
|||
import FetchConfigRoute from './fetch-config';
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { withConfig } from '../decorators/fetch-config';
|
||||
|
||||
@withConfig()
|
||||
export default class KubernetesConfigureRoute extends Route {
|
||||
@service store;
|
||||
@service secretMountPath;
|
||||
|
||||
export default class KubernetesConfigureRoute extends FetchConfigRoute {
|
||||
model() {
|
||||
// in case of any error other than 404 we want to display that to the user
|
||||
if (this.configError) {
|
||||
throw this.configError;
|
||||
}
|
||||
return {
|
||||
backend: this.modelFor('application'),
|
||||
config: this.configModel,
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import FetchConfigRoute from './fetch-config';
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { withConfig } from '../decorators/fetch-config';
|
||||
|
||||
@withConfig()
|
||||
export default class KubernetesConfigureRoute extends Route {
|
||||
@service store;
|
||||
@service secretMountPath;
|
||||
|
||||
export default class KubernetesConfigureRoute extends FetchConfigRoute {
|
||||
async model() {
|
||||
const backend = this.secretMountPath.get();
|
||||
return this.configModel || this.store.createRecord('kubernetes/config', { backend });
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class KubernetesErrorRoute extends Route {
|
||||
@service secretMountPath;
|
||||
|
||||
setupController(controller) {
|
||||
super.setupController(...arguments);
|
||||
controller.breadcrumbs = [
|
||||
{ label: 'secrets', route: 'secrets', linkExternal: true },
|
||||
{ label: this.secretMountPath.currentPath, route: 'overview' },
|
||||
];
|
||||
controller.backend = this.modelFor('application');
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
/**
|
||||
* the overview, configure, configuration and roles routes all need to be aware of the config for the engine
|
||||
* if the user has not configured they are prompted to do so in each of the routes
|
||||
* this route can be extended so the check happens in the beforeModel hook since that may change what is returned from the model hook
|
||||
*/
|
||||
|
||||
export default class KubernetesFetchConfigRoute extends Route {
|
||||
@service store;
|
||||
@service secretMountPath;
|
||||
|
||||
configModel = null;
|
||||
|
||||
async beforeModel() {
|
||||
const backend = this.secretMountPath.get();
|
||||
// check the store for record first
|
||||
this.configModel = this.store.peekRecord('kubernetes/config', backend);
|
||||
if (!this.configModel) {
|
||||
return this.store
|
||||
.queryRecord('kubernetes/config', { backend })
|
||||
.then((record) => {
|
||||
this.configModel = record;
|
||||
})
|
||||
.catch(() => {
|
||||
// it's ok! we don't need to transition to the error route
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,17 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { withConfig } from 'kubernetes/decorators/fetch-config';
|
||||
import { hash } from 'rsvp';
|
||||
import FetchConfigRoute from './fetch-config';
|
||||
|
||||
export default class KubernetesOverviewRoute extends FetchConfigRoute {
|
||||
@withConfig()
|
||||
export default class KubernetesOverviewRoute extends Route {
|
||||
@service store;
|
||||
@service secretMountPath;
|
||||
|
||||
async model() {
|
||||
const backend = this.secretMountPath.get();
|
||||
return hash({
|
||||
config: this.configModel,
|
||||
promptConfig: this.promptConfig,
|
||||
backend: this.modelFor('application'),
|
||||
roles: this.store.query('kubernetes/role', { backend }).catch(() => []),
|
||||
});
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import FetchConfigRoute from '../fetch-config';
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { withConfig } from 'kubernetes/decorators/fetch-config';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
export default class KubernetesRolesRoute extends FetchConfigRoute {
|
||||
@withConfig()
|
||||
export default class KubernetesRolesRoute extends Route {
|
||||
@service store;
|
||||
@service secretMountPath;
|
||||
|
||||
model(params, transition) {
|
||||
// filter roles based on pageFilter value
|
||||
const { pageFilter } = transition.to.queryParams;
|
||||
|
@ -12,10 +18,15 @@ export default class KubernetesRolesRoute extends FetchConfigRoute {
|
|||
? models.filter((model) => model.name.toLowerCase().includes(pageFilter.toLowerCase()))
|
||||
: models
|
||||
)
|
||||
.catch(() => []);
|
||||
.catch((error) => {
|
||||
if (error.httpStatus === 404) {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
return hash({
|
||||
backend: this.modelFor('application'),
|
||||
config: this.configModel,
|
||||
promptConfig: this.promptConfig,
|
||||
roles,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<TabPageHeader @model={{this.backend}} @breadcrumbs={{this.breadcrumbs}} />
|
||||
|
||||
<Page::Error @error={{this.model}} />
|
|
@ -1,5 +1,5 @@
|
|||
<Page::Overview
|
||||
@config={{this.model.config}}
|
||||
@promptConfig={{this.model.promptConfig}}
|
||||
@backend={{this.model.backend}}
|
||||
@roles={{this.model.roles}}
|
||||
@breadcrumbs={{this.breadcrumbs}}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<Page::Roles
|
||||
@roles={{this.model.roles}}
|
||||
@config={{this.model.config}}
|
||||
@promptConfig={{this.model.promptConfig}}
|
||||
@backend={{this.model.backend}}
|
||||
@filterValue={{this.pageFilter}}
|
||||
@breadcrumbs={{this.breadcrumbs}}
|
||||
|
|
|
@ -21,28 +21,4 @@
|
|||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="box is-sideless has-background-white-bis has-text-grey has-text-centered has-tall-padding" data-test-pki-error>
|
||||
{{#if (eq this.model.httpStatus 404)}}
|
||||
<h1 class="title is-3 has-text-grey">
|
||||
404 Not Found
|
||||
</h1>
|
||||
<p>Sorry, we were unable to find any content at <code>{{or this.model.path this.path}}</code>.</p>
|
||||
{{else if (eq this.model.httpStatus 403)}}
|
||||
<h1 class="title is-3 has-text-grey">
|
||||
Not authorized
|
||||
</h1>
|
||||
<p>You are not authorized to access content at <code>{{or this.model.path this.path}}</code>.</p>
|
||||
{{else}}
|
||||
<h1 class="title is-3 has-text-grey">
|
||||
Error
|
||||
</h1>
|
||||
<p>
|
||||
{{#if this.model.message}}
|
||||
<p>{{this.model.message}}</p>
|
||||
{{/if}}
|
||||
{{#each this.model.errors as |error|}}
|
||||
<p>{{error}}</p>
|
||||
{{/each}}
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
<Page::Error @error={{this.model}} />
|
|
@ -5,6 +5,7 @@ import kubernetesScenario from 'vault/mirage/scenarios/kubernetes';
|
|||
import ENV from 'vault/config/environment';
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import { visit, click, currentRouteName } from '@ember/test-helpers';
|
||||
import { Response } from 'miragejs';
|
||||
|
||||
module('Acceptance | kubernetes | configuration', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
@ -13,6 +14,7 @@ module('Acceptance | kubernetes | configuration', function (hooks) {
|
|||
hooks.before(function () {
|
||||
ENV['ember-cli-mirage'].handler = 'kubernetes';
|
||||
});
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
kubernetesScenario(this.server);
|
||||
this.visitConfiguration = () => {
|
||||
|
@ -23,6 +25,7 @@ module('Acceptance | kubernetes | configuration', function (hooks) {
|
|||
};
|
||||
return authPage.login();
|
||||
});
|
||||
|
||||
hooks.after(function () {
|
||||
ENV['ember-cli-mirage'].handler = null;
|
||||
});
|
||||
|
@ -33,6 +36,7 @@ module('Acceptance | kubernetes | configuration', function (hooks) {
|
|||
await click('[data-test-toolbar-config-action]');
|
||||
this.validateRoute(assert, 'configure', 'Transitions to Configure route on click');
|
||||
});
|
||||
|
||||
test('it should transition to the configuration page on Save click in Configure', async function (assert) {
|
||||
assert.expect(1);
|
||||
await this.visitConfiguration();
|
||||
|
@ -41,6 +45,7 @@ module('Acceptance | kubernetes | configuration', function (hooks) {
|
|||
await click('[data-test-config-confirm]');
|
||||
this.validateRoute(assert, 'configuration', 'Transitions to Configuration route on click');
|
||||
});
|
||||
|
||||
test('it should transition to the configuration page on Cancel click in Configure', async function (assert) {
|
||||
assert.expect(1);
|
||||
await this.visitConfiguration();
|
||||
|
@ -48,4 +53,25 @@ module('Acceptance | kubernetes | configuration', function (hooks) {
|
|||
await click('[data-test-config-cancel]');
|
||||
this.validateRoute(assert, 'configuration', 'Transitions to Configuration route on click');
|
||||
});
|
||||
|
||||
test('it should transition to error route on config fetch error other than 404', async function (assert) {
|
||||
this.server.get('/kubernetes/config', () => new Response(403));
|
||||
await this.visitConfiguration();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'vault.cluster.secrets.backend.kubernetes.error',
|
||||
'Transitions to error route on config fetch error'
|
||||
);
|
||||
});
|
||||
|
||||
test('it should not transition to error route on config fetch 404', async function (assert) {
|
||||
this.server.get('/kubernetes/config', () => new Response(404));
|
||||
await this.visitConfiguration();
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'vault.cluster.secrets.backend.kubernetes.configuration',
|
||||
'Transitions to configuration route on fetch 404'
|
||||
);
|
||||
assert.dom('[data-test-empty-state-title]').hasText('Kubernetes not configured', 'Config cta renders');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,11 +22,6 @@ module('Integration | Component | kubernetes | Page::Overview', function (hooks)
|
|||
type: 'kubernetes',
|
||||
},
|
||||
});
|
||||
this.store.pushPayload('kubernetes/config', {
|
||||
modelName: 'kubernetes/config',
|
||||
backend: 'kubernetes-test',
|
||||
...this.server.create('kubernetes-config'),
|
||||
});
|
||||
this.store.pushPayload('kubernetes/role', {
|
||||
modelName: 'kubernetes/role',
|
||||
backend: 'kubernetes-test',
|
||||
|
@ -38,15 +33,15 @@ module('Integration | Component | kubernetes | Page::Overview', function (hooks)
|
|||
...this.server.create('kubernetes-role'),
|
||||
});
|
||||
this.backend = this.store.peekRecord('secret-engine', 'kubernetes-test');
|
||||
this.config = this.store.peekRecord('kubernetes/config', 'kubernetes-test');
|
||||
this.roles = this.store.peekAll('kubernetes/role');
|
||||
this.breadcrumbs = [
|
||||
{ label: 'secrets', route: 'secrets', linkExternal: true },
|
||||
{ label: this.backend.id },
|
||||
];
|
||||
this.promptConfig = false;
|
||||
this.renderComponent = () => {
|
||||
return render(
|
||||
hbs`<Page::Overview @config={{this.config}} @backend={{this.backend}} @roles={{this.roles}} @breadcrumbs={{this.breadcrumbs}} />`,
|
||||
hbs`<Page::Overview @promptConfig={{this.promptConfig}} @backend={{this.backend}} @roles={{this.roles}} @breadcrumbs={{this.breadcrumbs}} />`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
};
|
||||
|
@ -98,7 +93,7 @@ module('Integration | Component | kubernetes | Page::Overview', function (hooks)
|
|||
});
|
||||
|
||||
test('it should show ConfigCta when no config is set up', async function (assert) {
|
||||
this.config = null;
|
||||
this.promptConfig = true;
|
||||
|
||||
await this.renderComponent();
|
||||
assert.dom(SELECTORS.emptyStateTitle).hasText('Kubernetes not configured');
|
||||
|
|
|
@ -20,49 +20,50 @@ module('Integration | Component | kubernetes | Page::Roles', function (hooks) {
|
|||
type: 'kubernetes',
|
||||
},
|
||||
});
|
||||
this.store.pushPayload('kubernetes/config', {
|
||||
modelName: 'kubernetes/config',
|
||||
backend: 'kubernetes-test',
|
||||
...this.server.create('kubernetes-config'),
|
||||
});
|
||||
this.store.pushPayload('kubernetes/role', {
|
||||
modelName: 'kubernetes/role',
|
||||
backend: 'kubernetes-test',
|
||||
...this.server.create('kubernetes-role'),
|
||||
});
|
||||
this.backend = this.store.peekRecord('secret-engine', 'kubernetes-test');
|
||||
this.config = this.store.peekRecord('kubernetes/config', 'kubernetes-test');
|
||||
this.roles = this.store.peekAll('kubernetes/role');
|
||||
this.filterValue = '';
|
||||
this.breadcrumbs = [
|
||||
{ label: 'secrets', route: 'secrets', linkExternal: true },
|
||||
{ label: this.backend.id },
|
||||
];
|
||||
this.promptConfig = false;
|
||||
|
||||
this.renderComponent = () => {
|
||||
return render(
|
||||
hbs`<Page::Roles @config={{this.config}} @backend={{this.backend}} @roles={{this.roles}} @filterValue={{this.filterValue}} @breadcrumbs={{this.breadcrumbs}} />`,
|
||||
hbs`<Page::Roles @promptConfig={{this.promptConfig}} @backend={{this.backend}} @roles={{this.roles}} @filterValue={{this.filterValue}} @breadcrumbs={{this.breadcrumbs}} />`,
|
||||
{ owner: this.engine }
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
test('it should render tab page header and config cta', async function (assert) {
|
||||
this.config = null;
|
||||
this.promptConfig = true;
|
||||
await this.renderComponent();
|
||||
assert.dom('.title svg').hasClass('flight-icon-kubernetes', 'Kubernetes icon renders in title');
|
||||
assert.dom('.title').hasText('kubernetes-test', 'Mount path renders in title');
|
||||
assert.dom('[data-test-toolbar-roles-action]').hasText('Create role', 'Toolbar action has correct text');
|
||||
assert
|
||||
.dom('[data-test-toolbar-roles-action] svg')
|
||||
.hasClass('flight-icon-plus', 'Toolbar action has correct icon');
|
||||
assert.dom('[data-test-nav-input]').exists('Roles filter input renders');
|
||||
.dom('[data-test-toolbar-roles-action]')
|
||||
.doesNotExist('Create role', 'Toolbar action does not render when not configured');
|
||||
assert
|
||||
.dom('[data-test-nav-input]')
|
||||
.doesNotExist('Roles filter input does not render when not configured');
|
||||
assert.dom('[data-test-config-cta]').exists('Config cta renders');
|
||||
});
|
||||
|
||||
test('it should render create roles cta', async function (assert) {
|
||||
this.roles = null;
|
||||
await this.renderComponent();
|
||||
assert.dom('[data-test-toolbar-roles-action]').hasText('Create role', 'Toolbar action has correct text');
|
||||
assert
|
||||
.dom('[data-test-toolbar-roles-action] svg')
|
||||
.hasClass('flight-icon-plus', 'Toolbar action has correct icon');
|
||||
assert.dom('[data-test-nav-input]').exists('Roles filter input renders');
|
||||
assert.dom('[data-test-empty-state-title]').hasText('No roles yet', 'Title renders');
|
||||
assert
|
||||
.dom('[data-test-empty-state-message]')
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'vault/tests/helpers';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | page/error', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it should render 404 error', async function (assert) {
|
||||
this.error = {
|
||||
httpStatus: 404,
|
||||
path: '/v1/kubernetes/config',
|
||||
};
|
||||
|
||||
await render(hbs`<Page::Error @error={{this.error}} />`);
|
||||
|
||||
assert.dom('h1').hasText('404 Not Found', 'Error title renders');
|
||||
assert
|
||||
.dom('p')
|
||||
.hasText(`Sorry, we were unable to find any content at ${this.error.path}.`, 'Error message renders');
|
||||
});
|
||||
|
||||
test('it should render 403 error', async function (assert) {
|
||||
this.error = {
|
||||
httpStatus: 403,
|
||||
path: '/v1/kubernetes/config',
|
||||
};
|
||||
|
||||
await render(hbs`<Page::Error @error={{this.error}} />`);
|
||||
|
||||
assert.dom('h1').hasText('Not authorized', 'Error title renders');
|
||||
assert
|
||||
.dom('p')
|
||||
.hasText(`You are not authorized to access content at ${this.error.path}.`, 'Error message renders');
|
||||
});
|
||||
|
||||
test('it should render general error', async function (assert) {
|
||||
this.error = {
|
||||
message: 'An unexpected error occurred',
|
||||
errors: ['This is one thing that went wrong', 'Unfortunately something else went wrong too'],
|
||||
};
|
||||
|
||||
await render(hbs`<Page::Error @error={{this.error}} />`);
|
||||
|
||||
assert.dom('h1').hasText('Error', 'Error title renders');
|
||||
assert.dom('[data-test-page-error-message]').hasText(this.error.message, 'Error message renders');
|
||||
this.error.errors.forEach((error, index) => {
|
||||
assert
|
||||
.dom(`[data-test-page-error-details="${index}"]`)
|
||||
.hasText(this.error.errors[index], 'Error detail renders');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue