1 parent
8ad4e93ff7
commit
1cb3b708a2
17 files changed
+741
-16
No files matched your search
@@ -0,0 +1,81 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
}}
|
||||
|
||||
<Rose::Form
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
@showEditToggle={{if @model.isNew false true}}
|
||||
as |form|
|
||||
>
|
||||
<Hds::Form::TextInput::Field
|
||||
@isOptional={{true}}
|
||||
@value={{@model.name}}
|
||||
@isInvalid={{@model.errors.name}}
|
||||
@type='text'
|
||||
name='name'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'name')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.name.label'}}</F.Label>
|
||||
<F.HelperText>{{t 'form.name.help'}}</F.HelperText>
|
||||
{{#if @model.errors.name}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.name as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
<Hds::Form::Textarea::Field
|
||||
@isOptional={{true}}
|
||||
@value={{@model.description}}
|
||||
@isInvalid={{@model.errors.description}}
|
||||
name='description'
|
||||
disabled={{form.disabled}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.description.label'}}</F.Label>
|
||||
<F.HelperText>{{t 'form.description.help'}}</F.HelperText>
|
||||
{{#if @model.errors.description}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.description as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::Textarea::Field>
|
||||
|
||||
{{! retention policy }}
|
||||
<Form::Policy::PolicySelection
|
||||
@name='retention_policy'
|
||||
@disabled={{form.disabled}}
|
||||
@model={{@model}}
|
||||
@options={{this.listRententionOptions}}
|
||||
@customInputName='retain_for'
|
||||
@inputValue={{@model.retain_for.days}}
|
||||
@selectedOption={{this.selectRetentionPolicyType}}
|
||||
/>
|
||||
|
||||
{{! deletion policy }}
|
||||
<Form::Policy::PolicySelection
|
||||
@name='deletion_policy'
|
||||
@disabled={{form.disabled}}
|
||||
@model={{@model}}
|
||||
@options={{this.listDeletionOptions}}
|
||||
@customInputName='delete_after'
|
||||
@inputValue={{@model.delete_after.days}}
|
||||
@selectedOption={{this.selectDeletePolicyType}}
|
||||
/>
|
||||
|
||||
{{#if (can 'save model' @model)}}
|
||||
<form.actions
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
{{/if}}
|
||||
</Rose::Form>
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
const RETENTION_POLICY = {
|
||||
forever: -1,
|
||||
custom: 1,
|
||||
do_not_protect: 0,
|
||||
};
|
||||
|
||||
const DELETION_POLICY = { do_not_delete: 0, custom: 1 };
|
||||
|
||||
export default class FormPolicyComponent extends Component {
|
||||
//attributes
|
||||
|
||||
//methods
|
||||
|
||||
/**
|
||||
* Returns retention policy options list
|
||||
* @type {array}
|
||||
*/
|
||||
get listRententionOptions() {
|
||||
return RETENTION_POLICY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns deletion policy options list, if the retain days are -1 (forever)
|
||||
* then we should force the delete option to be do not delete
|
||||
* @type {array}
|
||||
*/
|
||||
get listDeletionOptions() {
|
||||
if (this.args.model.retain_for?.days < 0) {
|
||||
return { do_not_delete: 0 };
|
||||
} else {
|
||||
return DELETION_POLICY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns policy type
|
||||
* @type {string}
|
||||
*/
|
||||
get selectRetentionPolicyType() {
|
||||
if (this.args.model.retain_for?.days < 0) {
|
||||
return 'forever';
|
||||
} else if (this.args.model.retain_for?.days >= 1) {
|
||||
return 'custom';
|
||||
} else {
|
||||
return 'do_not_protect';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns policy type
|
||||
* @type {string}
|
||||
*/
|
||||
get selectDeletePolicyType() {
|
||||
return this.args.model.delete_after?.days > 0 ? 'custom' : 'do_not_delete';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<Hds::Form::Fieldset @isRequired={{true}} class='policy' as |F|>
|
||||
|
||||
<F.Legend>{{t (concat 'resources.policy.form.' @name '.label')}}</F.Legend>
|
||||
<F.HelperText>
|
||||
{{t (concat 'resources.policy.form.' @name '.help')}}
|
||||
</F.HelperText>
|
||||
|
||||
<F.Control>
|
||||
<Hds::Form::Select::Field
|
||||
name={{@name}}
|
||||
disabled={{or @disabled this.isDeleteDisable}}
|
||||
aria-label={{t (concat 'resources.policy.form.' @name '.label')}}
|
||||
@width='80%'
|
||||
{{on 'change' this.handlePolicyTypeSelection}}
|
||||
as |F|
|
||||
>
|
||||
<F.Options>
|
||||
<option disabled hidden selected value=''>
|
||||
{{t 'titles.choose-an-option'}}
|
||||
</option>
|
||||
{{#each-in @options as |key value|}}
|
||||
<option value={{value}} selected={{eq key @selectedOption}}>
|
||||
{{t
|
||||
(concat 'resources.policy.form.' @name '_options.' key '.label')
|
||||
}}
|
||||
</option>
|
||||
{{/each-in}}
|
||||
</F.Options>
|
||||
|
||||
</Hds::Form::Select::Field>
|
||||
|
||||
</F.Control>
|
||||
</Hds::Form::Fieldset>
|
||||
{{#if this.showCustomInput}}
|
||||
<Hds::Form::Field class='policy-custom-input' @layout='vertical' as |F|>
|
||||
<F.Label>{{t (concat 'resources.policy.titles.' @customInputName)}}
|
||||
</F.Label>
|
||||
<F.Control>
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.TextInput
|
||||
@value={{@inputValue}}
|
||||
disabled={{@disabled}}
|
||||
aria-label={{t 'resources.policy.title'}}
|
||||
{{on 'input' this.handleInputChange}}
|
||||
name={{@customInputName}}
|
||||
data-input-type={{@customInputName}}
|
||||
/>
|
||||
<S.Generic>
|
||||
<Hds::Badge
|
||||
class={{if @disabled 'disabled'}}
|
||||
@text={{t 'resources.policy.titles.days'}}
|
||||
@color='neutral'
|
||||
@type='outlined'
|
||||
/>
|
||||
</S.Generic>
|
||||
</Hds::SegmentedGroup>
|
||||
{{#if @model.scope.isGlobal}}
|
||||
<Hds::Form::Toggle::Field
|
||||
checked={{this.isOverridable}}
|
||||
disabled={{@disabled}}
|
||||
name={{@customInputName}}
|
||||
{{on 'change' this.handleOverridableToggle}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'resources.policy.form.overridable.label'}}</F.Label>
|
||||
</Hds::Form::Toggle::Field>
|
||||
{{/if}}
|
||||
</F.Control>
|
||||
|
||||
</Hds::Form::Field>
|
||||
{{/if}}
|
||||
@@ -0,0 +1,109 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class FormPolicySelectionComponent extends Component {
|
||||
//methods
|
||||
/**
|
||||
* This is used to show/hide the custom input field
|
||||
* @type {boolean}
|
||||
*/
|
||||
get showCustomInput() {
|
||||
if (this.args.name === 'retention_policy') {
|
||||
return this.isCustomRetentionSelected;
|
||||
} else if (
|
||||
this.args.name === 'deletion_policy' &&
|
||||
this.args.model.retain_for?.days >= 0
|
||||
) {
|
||||
return this.isCustomDeletionSelected;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if rentention days are greater than 0
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isCustomRetentionSelected() {
|
||||
return this.args.model.retain_for?.days > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if deletion days are greater than 0
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isCustomDeletionSelected() {
|
||||
return this.args.model.delete_after?.days > 0;
|
||||
}
|
||||
/**
|
||||
* Returns true if the toggle is on
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isOverridable() {
|
||||
return this.args.model[this.args.customInputName]?.overridable;
|
||||
}
|
||||
|
||||
get isDeleteDisable() {
|
||||
if (
|
||||
this.args.name === 'deletion_policy' &&
|
||||
this.args.model.retain_for?.days === -1
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//actions
|
||||
/**
|
||||
* Handles custom input changes
|
||||
*/
|
||||
@action
|
||||
handleInputChange({ target: { value, name: field } }) {
|
||||
// Select options return type is string and we want the `days` in integer format
|
||||
const val = Number(value);
|
||||
this.args.model[field] = {
|
||||
...this.args.model[field],
|
||||
days: val,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle retain for & delete after overridables
|
||||
*/
|
||||
@action
|
||||
handleOverridableToggle({ target: { name: field } }) {
|
||||
this.args.model[field] = {
|
||||
...this.args.model[field],
|
||||
overridable: !this.args.model[field].overridable,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Show custom text field when custom option is selected
|
||||
*/
|
||||
@action
|
||||
handlePolicyTypeSelection({ target: { value, name: policy } }) {
|
||||
//Select options return type is string and we want the `days` in integer format
|
||||
const selectedVal = Number(value);
|
||||
if (policy === 'retention_policy') {
|
||||
this.args.model.retain_for = {
|
||||
...this.args.model.retain_for,
|
||||
days: selectedVal,
|
||||
};
|
||||
if (selectedVal < 0) {
|
||||
this.args.model.delete_after = {
|
||||
...this.args.model.delete_after,
|
||||
days: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (policy === 'deletion_policy') {
|
||||
this.args.model.delete_after = {
|
||||
...this.args.model.delete_after,
|
||||
days: selectedVal,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,9 @@ export default class ScopesScopePoliciesRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service can;
|
||||
@service session;
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -35,7 +36,7 @@ export default class ScopesScopePoliciesRoute extends Route {
|
||||
const { id: scope_id } = scope;
|
||||
|
||||
if (
|
||||
this.can.can('list scope', scope, {
|
||||
this.can.can('list model', scope, {
|
||||
collection: 'policies',
|
||||
})
|
||||
) {
|
||||
@@ -43,6 +44,33 @@ export default class ScopesScopePoliciesRoute extends Route {
|
||||
}
|
||||
}
|
||||
|
||||
//actions
|
||||
|
||||
/**
|
||||
* Rollback changes on a policy.
|
||||
* @param {PolicyModel} policy
|
||||
*/
|
||||
@action
|
||||
cancel(policy) {
|
||||
const { isNew } = policy;
|
||||
policy.rollbackAttributes();
|
||||
if (isNew) this.router.transitionTo('scopes.scope.policies');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a policy in current scope.
|
||||
* @param {PolicyModel} policy
|
||||
*/
|
||||
@action
|
||||
@loading
|
||||
@notifyError(({ message }) => message)
|
||||
@notifySuccess('notifications.save-success')
|
||||
async save(policy) {
|
||||
await policy.save();
|
||||
await this.router.transitionTo('scopes.scope.policies.policy', policy);
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
/**
|
||||
* Deletes the policy.
|
||||
* @param {PolicyModel} policy
|
||||
|
||||
@@ -4,7 +4,39 @@
|
||||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopePoliciesNewRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Redirect to parent route when scope does not have create authorized action.
|
||||
*/
|
||||
beforeModel() {
|
||||
const scopeModel = this.modelFor('scopes.scope');
|
||||
if (
|
||||
this.can.cannot('create model', scopeModel, { collection: 'policies' })
|
||||
) {
|
||||
this.router.replaceWith('scopes.scope.policies');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new unsaved policy.
|
||||
* @return {PolicyModel}
|
||||
*/
|
||||
model() {
|
||||
const scopeModel = this.modelFor('scopes.scope');
|
||||
const record = this.store.createRecord('policy', {
|
||||
type: 'storage',
|
||||
});
|
||||
record.scopeModel = scopeModel;
|
||||
return record;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import { inject as service } from '@ember/service';
|
||||
export default class ScopesScopePoliciesPolicyRoute extends Route {
|
||||
@service store;
|
||||
@service can;
|
||||
@service router;
|
||||
|
||||
// =methods
|
||||
|
||||
|
||||
@@ -951,3 +951,37 @@
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
// storage policies
|
||||
.policy {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.policy-custom-input {
|
||||
.hds-form-field__control {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.hds-segmented-group {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.hds-form-field--layout-flag {
|
||||
width: 40%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.hds-badge {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-color: var(--ui-gray-subtler-1);
|
||||
border-left: 0;
|
||||
|
||||
&.disabled {
|
||||
color: var(--token-form-control-disabled-foreground-color);
|
||||
border-color: var(--token-form-control-disabled-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,17 +64,16 @@
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{#if B.data.retain_for.days}}
|
||||
{{! leave blank space when the retain days are 0 }}
|
||||
{{#if (gte B.data.retain_for.days 1)}}
|
||||
{{format-day-year B.data.retain_for.days}}
|
||||
{{else}}
|
||||
{{else if (eq B.data.retain_for.days -1)}}
|
||||
{{t 'resources.policy.titles.forever'}}
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{#if B.data.delete_after.days}}
|
||||
{{format-day-year B.data.delete_after.days}}
|
||||
{{else}}
|
||||
{{t 'resources.policy.titles.do_not_delete'}}
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
|
||||
@@ -7,4 +7,25 @@
|
||||
<Breadcrumbs::Item
|
||||
@text={{t 'resources.policy.titles.new'}}
|
||||
@route='scopes.scope.policies.new'
|
||||
/>
|
||||
/>
|
||||
|
||||
<Rose::Layout::Page as |page|>
|
||||
<page.breadcrumbs>
|
||||
<Breadcrumbs::Container />
|
||||
</page.breadcrumbs>
|
||||
|
||||
<page.header>
|
||||
<h2>
|
||||
{{t 'resources.policy.titles.new'}}
|
||||
</h2>
|
||||
</page.header>
|
||||
|
||||
<page.body>
|
||||
<Form::Policy
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
/>
|
||||
</page.body>
|
||||
|
||||
</Rose::Layout::Page>
|
||||
@@ -1,4 +1,39 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
}}
|
||||
}}
|
||||
|
||||
<Rose::Layout::Page as |page|>
|
||||
<page.breadcrumbs>
|
||||
<Breadcrumbs::Container />
|
||||
</page.breadcrumbs>
|
||||
|
||||
<page.header>
|
||||
<h2>
|
||||
{{t 'resources.policy.title'}}
|
||||
</h2>
|
||||
<Copyable
|
||||
@text={{@model.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<Hds::Text::Code>{{@model.id}}</Hds::Text::Code>
|
||||
</Copyable>
|
||||
</page.header>
|
||||
|
||||
<page.navigation>
|
||||
<Rose::Nav::Tabs as |nav|>
|
||||
<nav.link @route='scopes.scope.policies.policy.index'>
|
||||
{{t 'titles.details'}}
|
||||
</nav.link>
|
||||
</Rose::Nav::Tabs>
|
||||
</page.navigation>
|
||||
|
||||
<page.body>
|
||||
<Form::Policy
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
/>
|
||||
</page.body>
|
||||
</Rose::Layout::Page>
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { visit, currentURL, click, fillIn } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'admin/tests/helpers';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import a11yAudit from 'ember-a11y-testing/test-support/audit';
|
||||
import { Response } from 'miragejs';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
|
||||
module('Acceptance | policies | create', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
let features;
|
||||
let getPolicyCount;
|
||||
|
||||
const SAVE_BTN_SELECTOR = '[type="submit"]';
|
||||
const CANCEL_BTN_SELECTOR = '.rose-form-actions [type="button"]';
|
||||
const NAME_FIELD_SELECTOR = '[name="name"]';
|
||||
const RETAIN_FOR_INPUT = 'input[data-input-type="retain_for"]';
|
||||
const ALERT_TEXT_SELECTOR = '[role="alert"] div';
|
||||
const FIELD_ERROR_TEXT_SELECTOR = '.hds-form-error__message';
|
||||
const NAME_FIELD_TEXT = 'random string';
|
||||
|
||||
const instances = {
|
||||
scopes: {
|
||||
global: null,
|
||||
org: null,
|
||||
},
|
||||
};
|
||||
|
||||
const urls = {
|
||||
globalScope: null,
|
||||
policies: null,
|
||||
newPolicy: null,
|
||||
};
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
instances.scopes.global = this.server.create('scope', { id: 'global' });
|
||||
instances.scopes.org = this.server.create('scope', {
|
||||
type: 'org',
|
||||
scope: { id: 'global', type: 'global' },
|
||||
});
|
||||
urls.globalScope = `/scopes/global`;
|
||||
urls.policies = `${urls.globalScope}/policies`;
|
||||
urls.newPolicy = `${urls.policies}/new`;
|
||||
getPolicyCount = () => this.server.schema.policies.all().models.length;
|
||||
features = this.owner.lookup('service:features');
|
||||
features.enable('ssh-session-recording');
|
||||
authenticateSession({});
|
||||
});
|
||||
|
||||
test('users can create a new policy with global scope', async function (assert) {
|
||||
const policyCount = getPolicyCount();
|
||||
await visit(urls.policies);
|
||||
|
||||
await click(`[href="${urls.newPolicy}"]`);
|
||||
await fillIn(NAME_FIELD_SELECTOR, NAME_FIELD_TEXT);
|
||||
|
||||
await click('.hds-form-select');
|
||||
await fillIn('.hds-form-select', 1);
|
||||
await fillIn(RETAIN_FOR_INPUT, 90);
|
||||
await click(SAVE_BTN_SELECTOR);
|
||||
const policy = this.server.schema.policies.findBy({
|
||||
name: NAME_FIELD_TEXT,
|
||||
});
|
||||
|
||||
assert.strictEqual(policy.name, NAME_FIELD_TEXT);
|
||||
assert.strictEqual(policy.scopeId, 'global');
|
||||
assert.strictEqual(policy.attributes.retain_for.days, 90);
|
||||
assert.strictEqual(getPolicyCount(), policyCount + 1);
|
||||
});
|
||||
|
||||
test('user can cancel new policy creation', async function (assert) {
|
||||
const policyCount = getPolicyCount();
|
||||
await visit(urls.policies);
|
||||
|
||||
await click(`[href="${urls.newPolicy}"]`);
|
||||
await fillIn(NAME_FIELD_SELECTOR, NAME_FIELD_TEXT);
|
||||
await click(CANCEL_BTN_SELECTOR);
|
||||
|
||||
assert.strictEqual(currentURL(), urls.policies);
|
||||
assert.strictEqual(getPolicyCount(), policyCount);
|
||||
});
|
||||
|
||||
test('saving a new policy with invalid fields displays error messages', async function (assert) {
|
||||
this.server.post('/policies', () => {
|
||||
return new Response(
|
||||
400,
|
||||
{},
|
||||
{
|
||||
status: 400,
|
||||
code: 'invalid_argument',
|
||||
message: 'The request was invalid.',
|
||||
details: {
|
||||
request_fields: [
|
||||
{
|
||||
name: 'name',
|
||||
description: 'Name is required.',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
await visit(urls.policies);
|
||||
|
||||
await click(`[href="${urls.newPolicy}"]`);
|
||||
await click(SAVE_BTN_SELECTOR);
|
||||
await a11yAudit();
|
||||
|
||||
assert.dom(ALERT_TEXT_SELECTOR).hasText('The request was invalid.');
|
||||
assert.dom(FIELD_ERROR_TEXT_SELECTOR).hasText('Name is required.');
|
||||
});
|
||||
|
||||
test('users cannot directly navigate to new policy route without proper authorization', async function (assert) {
|
||||
instances.scopes.global.authorized_collection_actions['policies'] =
|
||||
instances.scopes.global.authorized_collection_actions['policies'].filter(
|
||||
(item) => item !== 'create',
|
||||
);
|
||||
|
||||
await visit(urls.newPolicy);
|
||||
|
||||
assert.false(
|
||||
instances.scopes.global.authorized_collection_actions[
|
||||
'policies'
|
||||
].includes('create'),
|
||||
);
|
||||
assert.strictEqual(currentURL(), urls.policies);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { visit, currentURL, click } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'admin/tests/helpers';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import a11yAudit from 'ember-a11y-testing/test-support/audit';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
|
||||
module('Acceptance | policies | read', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
let features;
|
||||
|
||||
const MESSAGE_SELECTOR = '.rose-message-subtitle';
|
||||
const TABLE_LINK_SELECTOR = '.hds-table__tbody tr:first-child a';
|
||||
|
||||
const instances = {
|
||||
scopes: {
|
||||
global: null,
|
||||
org: null,
|
||||
},
|
||||
policy: null,
|
||||
};
|
||||
|
||||
const urls = {
|
||||
globalScope: null,
|
||||
policies: null,
|
||||
policy: null,
|
||||
unknownPolicy: null,
|
||||
};
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
instances.scopes.global = this.server.create('scope', { id: 'global' });
|
||||
instances.scopes.org = this.server.create('scope', {
|
||||
type: 'org',
|
||||
scope: { id: 'global', type: 'global' },
|
||||
});
|
||||
instances.policy = this.server.create('policy', {
|
||||
scope: instances.scopes.global,
|
||||
});
|
||||
urls.globalScope = `/scopes/global`;
|
||||
urls.policies = `${urls.globalScope}/policies`;
|
||||
urls.policy = `${urls.policies}/${instances.policy.id}`;
|
||||
urls.unknownPolicy = `${urls.policies}/foo`;
|
||||
|
||||
features = this.owner.lookup('service:features');
|
||||
features.enable('ssh-session-recording');
|
||||
authenticateSession({});
|
||||
});
|
||||
|
||||
test('visiting a policy', async function (assert) {
|
||||
await visit(urls.globalScope);
|
||||
await a11yAudit();
|
||||
|
||||
await click(`[href="${urls.policies}"]`);
|
||||
await a11yAudit();
|
||||
await click(`[href="${urls.policy}"]`);
|
||||
await a11yAudit();
|
||||
|
||||
assert.strictEqual(currentURL(), urls.policy);
|
||||
});
|
||||
|
||||
test('cannot navigate to a policy without proper authorization', async function (assert) {
|
||||
await visit(urls.globalScope);
|
||||
instances.policy.authorized_actions =
|
||||
instances.policy.authorized_actions.filter((item) => item !== 'read');
|
||||
|
||||
await click(`[href="${urls.policies}"]`);
|
||||
|
||||
assert.dom(TABLE_LINK_SELECTOR).doesNotExist();
|
||||
});
|
||||
|
||||
test('visiting an unknown policy displays 404 message', async function (assert) {
|
||||
await visit(urls.unknownPolicy);
|
||||
await a11yAudit();
|
||||
|
||||
assert.dom(MESSAGE_SELECTOR).hasText('Error 404');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user