mirror of
https://github.com/kemko/nomad.git
synced 2026-01-06 18:35:44 +03:00
[ui] Tests for Sentinel Policies (#22398)
* Tests for Sentinel Policies UI * Further sentinel tests * job allocations test reinstated
This commit is contained in:
@@ -56,21 +56,21 @@
|
||||
<G.RadioField
|
||||
@id="advisory"
|
||||
checked={{eq @policy.enforcementLevel "advisory"}}
|
||||
data-test-token-type="client"
|
||||
data-test-enforcement-level="advisory"
|
||||
as |F|>
|
||||
<F.Label>Advisory</F.Label>
|
||||
</G.RadioField>
|
||||
<G.RadioField
|
||||
@id="soft-mandatory"
|
||||
checked={{eq @policy.enforcementLevel "soft-mandatory"}}
|
||||
data-test-token-type="soft-mandatory"
|
||||
data-test-enforcement-level="soft-mandatory"
|
||||
as |F|>
|
||||
<F.Label>Soft Mandatory</F.Label>
|
||||
</G.RadioField>
|
||||
<G.RadioField
|
||||
@id="hard-mandatory"
|
||||
checked={{eq @policy.enforcementLevel "hard-mandatory"}}
|
||||
data-test-token-type="hard-mandatory"
|
||||
data-test-enforcement-level="hard-mandatory"
|
||||
as |F|>
|
||||
<F.Label>Hard Mandatory</F.Label>
|
||||
</G.RadioField>
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
<Hds::Form::RadioCard::Group as |G|>
|
||||
<G.Legend>Select a Template</G.Legend>
|
||||
{{#each this.templates as |template|}}
|
||||
<G.RadioCard class="form-container" @layout="fixed" @maxWidth="30%" @checked={{eq template.name
|
||||
this.selectedTemplate}} id={{template.name}} data-test-template-card={{template.name}} {{on "change"
|
||||
this.onChange}} as |R|>
|
||||
<R.Label data-test-template-label>{{template.displayName}}</R.Label>
|
||||
<G.RadioCard class="form-container" @layout="fixed" @maxWidth="30%"
|
||||
@checked={{eq template.name this.selectedTemplate}}
|
||||
id={{template.name}}
|
||||
{{on "change" this.onChange}}
|
||||
as |R|>
|
||||
<R.Label data-test-template-card={{template.name}} data-test-template-label>{{template.displayName}}</R.Label>
|
||||
<R.Description data-test-template-description>{{template.description}}</R.Description>
|
||||
</G.RadioCard>
|
||||
{{/each}}
|
||||
|
||||
@@ -27,7 +27,7 @@ SPDX-License-Identifier: BUSL-1.1
|
||||
label="Create Policy from Template"
|
||||
}}
|
||||
>
|
||||
<Hds::Button @text="Create from Template" @icon="plus" @route="administration.sentinel-policies.gallery" />
|
||||
<Hds::Button @text="Create from Template" @icon="plus" @route="administration.sentinel-policies.gallery" data-test-create-sentinel-policy-from-template />
|
||||
</span>
|
||||
{{else}}
|
||||
<Hds::Button @text="Create Policy" @icon="plus" disabled data-test-disabled-create-sentinel-policy />
|
||||
@@ -44,8 +44,8 @@ SPDX-License-Identifier: BUSL-1.1
|
||||
<LinkTo data-test-sentinel-policy-name={{B.data.name}} @route="administration.sentinel-policies.policy"
|
||||
@model={{B.data.name}}>{{B.data.name}}</LinkTo>
|
||||
</B.Td>
|
||||
<B.Td>{{B.data.description}}</B.Td>
|
||||
<B.Td>{{B.data.enforcementLevel}}</B.Td>
|
||||
<B.Td data-test-sentinel-policy-description>{{B.data.description}}</B.Td>
|
||||
<B.Td data-test-sentinel-policy-enforcement>{{B.data.enforcementLevel}}</B.Td>
|
||||
{{#if (can "destroy sentinel-policy")}}
|
||||
<B.Td>
|
||||
<TwoStepButton
|
||||
@@ -65,8 +65,8 @@ SPDX-License-Identifier: BUSL-1.1
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
{{else}}
|
||||
<div data-test-empty-jobs-list class="empty-message">
|
||||
<h3 data-test-empty-jobs-list-headline class="empty-message-headline">
|
||||
<div data-test-empty-sentinel-policy-list class="empty-message">
|
||||
<h3 data-test-empty-sentinel-policy-list-headline class="empty-message-headline">
|
||||
No Sentinel Policies
|
||||
</h3>
|
||||
<p class="empty-message-body">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
data-test-header-gutter-toggle
|
||||
class="gutter-toggle"
|
||||
aria-label="menu"
|
||||
role="img"
|
||||
onclick={{action this.onHamburgerClick}}
|
||||
>
|
||||
<HamburgerMenu />
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
data-test-gutter-gutter-toggle
|
||||
class="gutter-toggle"
|
||||
aria-label="menu"
|
||||
role="img"
|
||||
onclick={{action this.onHamburgerClick}}
|
||||
>
|
||||
<HamburgerMenu />
|
||||
|
||||
@@ -43,7 +43,7 @@ export default Mixin.create({
|
||||
frameMisses: 0,
|
||||
|
||||
handleResponse(frame) {
|
||||
if (frame.error) {
|
||||
if (!frame || frame.error) {
|
||||
this.incrementProperty('frameMisses');
|
||||
if (this.frameMisses >= this.maxFrameMisses) {
|
||||
// Missing enough data consecutively is effectively a pause
|
||||
|
||||
@@ -936,11 +936,15 @@ export default function () {
|
||||
});
|
||||
|
||||
this.post('/sentinel/policy/:id', function (schema, req) {
|
||||
const { Name, Description, Rules } = JSON.parse(req.requestBody);
|
||||
const { Name, Description, EnforcementLevel, Policy, Scope } = JSON.parse(
|
||||
req.requestBody
|
||||
);
|
||||
return server.create('sentinelPolicy', {
|
||||
name: Name,
|
||||
description: Description,
|
||||
rules: Rules,
|
||||
enforcementLevel: EnforcementLevel,
|
||||
policy: Policy,
|
||||
scope: Scope,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -948,6 +952,16 @@ export default function () {
|
||||
return this.serialize(sentinelPolicies.findBy({ name: req.params.id }));
|
||||
});
|
||||
|
||||
this.delete('/sentinel/policy/:id', function (schema, req) {
|
||||
const { id } = req.params;
|
||||
server.db.sentinelPolicies.remove(id);
|
||||
return '';
|
||||
});
|
||||
|
||||
this.put('/sentinel/policy/:id', function (schema, req) {
|
||||
return new Response(200, {}, {});
|
||||
});
|
||||
|
||||
this.delete('/acl/policy/:id', function (schema, request) {
|
||||
const { id } = request.params;
|
||||
|
||||
|
||||
27
ui/mirage/factories/sentinel-policy.js
Normal file
27
ui/mirage/factories/sentinel-policy.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import faker from 'nomad-ui/mirage/faker';
|
||||
import { pickOne } from '../utils';
|
||||
|
||||
export default Factory.extend({
|
||||
id: () =>
|
||||
`${faker.hacker.verb().replace(/\s/g, '-')}-${faker.random.alphaNumeric(
|
||||
5
|
||||
)}`,
|
||||
name() {
|
||||
return this.id;
|
||||
},
|
||||
description: () =>
|
||||
faker.random.number(10) >= 2 ? faker.lorem.sentence() : null,
|
||||
|
||||
policy: `# This policy will always fail. You can temporarily halt all new job updates using this.
|
||||
|
||||
main = rule { false }`,
|
||||
|
||||
scope: 'submit-job',
|
||||
enforcementLevel: pickOne(['advisory', 'soft-mandatory', 'hard-mandatory']),
|
||||
});
|
||||
@@ -623,8 +623,22 @@ function variableTestCluster(server) {
|
||||
});
|
||||
}
|
||||
|
||||
function policiesTestCluster(server) {
|
||||
server.create('feature', { name: 'Sentinel Policies' });
|
||||
function policiesTestCluster(server, options = { sentinel: false }) {
|
||||
if (options.sentinel) {
|
||||
server.create('feature', { name: 'Sentinel Policies' });
|
||||
server.create('sentinel-policy', {
|
||||
id: 'policy-1',
|
||||
name: 'policy-1',
|
||||
description: 'A sentinel policy generated by Mirage',
|
||||
enforcementLevel: 'soft-mandatory',
|
||||
policy:
|
||||
'import "time"\n\nis_weekday = rule { time.day not in ["friday", "saturday", "sunday"] }\nis_open_hours = rule { time.hour > 8 and time.hour < 16 }\n\nmain = rule { is_open_hours and is_weekday }',
|
||||
scope: 'submit-job',
|
||||
});
|
||||
|
||||
server.createList('sentinel-policy', 5);
|
||||
}
|
||||
|
||||
faker.seed(1);
|
||||
createTokens(server);
|
||||
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { currentURL, triggerKeyEvent } from '@ember/test-helpers';
|
||||
import { currentURL, triggerKeyEvent, click } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import Administration from 'nomad-ui/tests/pages/administration';
|
||||
import Tokens from 'nomad-ui/tests/pages/settings/tokens';
|
||||
import { allScenarios } from '../../mirage/scenarios/default';
|
||||
import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
|
||||
import percySnapshot from '@percy/ember';
|
||||
|
||||
// Several related tests within Access Control are contained in the Tokens, Roles,
|
||||
// and Policies acceptance tests.
|
||||
@@ -71,6 +72,35 @@ module('Acceptance | access control', function (hooks) {
|
||||
);
|
||||
});
|
||||
|
||||
test('Access control does not show Sentinel Policies if they are not present in license', async function (assert) {
|
||||
allScenarios.policiesTestCluster(server);
|
||||
await Tokens.visit();
|
||||
const managementToken = server.db.tokens.findBy(
|
||||
(t) => t.type === 'management'
|
||||
);
|
||||
const { secretId } = managementToken;
|
||||
await Tokens.secret(secretId).submit();
|
||||
await Administration.visit();
|
||||
assert.dom('[data-test-sentinel-policies-card]').doesNotExist();
|
||||
});
|
||||
|
||||
test('Access control shows Sentinel Policies if they are present in license', async function (assert) {
|
||||
assert.expect(2);
|
||||
allScenarios.policiesTestCluster(server, { sentinel: true });
|
||||
await Tokens.visit();
|
||||
const managementToken = server.db.tokens.findBy(
|
||||
(t) => t.type === 'management'
|
||||
);
|
||||
const { secretId } = managementToken;
|
||||
await Tokens.secret(secretId).submit();
|
||||
await Administration.visit();
|
||||
|
||||
assert.dom('[data-test-sentinel-policies-card]').exists();
|
||||
await percySnapshot(assert);
|
||||
await click('[data-test-sentinel-policies-card] a');
|
||||
assert.equal(currentURL(), '/administration/sentinel-policies');
|
||||
});
|
||||
|
||||
test('Access control index content', async function (assert) {
|
||||
await Tokens.visit();
|
||||
const managementToken = server.db.tokens.findBy(
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable qunit/require-expect */
|
||||
import { currentURL, click, find } from '@ember/test-helpers';
|
||||
import { currentURL, click } from '@ember/test-helpers';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
@@ -85,9 +85,8 @@ module('Acceptance | job allocations', function (hooks) {
|
||||
|
||||
await Allocations.visit({ id: job.id });
|
||||
|
||||
const firstAllocation = find('[data-test-allocation]');
|
||||
const firstAllocation = document.querySelector('[data-test-allocation]');
|
||||
await click(firstAllocation);
|
||||
|
||||
const requestToAllocationEndpoint = server.pretender.handledRequests.find(
|
||||
(request) =>
|
||||
request.url.includes(
|
||||
|
||||
177
ui/tests/acceptance/sentinel-policies-test.js
Normal file
177
ui/tests/acceptance/sentinel-policies-test.js
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { findAll, fillIn, find, click, currentURL } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
|
||||
import { allScenarios } from '../../mirage/scenarios/default';
|
||||
import Tokens from 'nomad-ui/tests/pages/settings/tokens';
|
||||
import Administration from 'nomad-ui/tests/pages/administration';
|
||||
import percySnapshot from '@percy/ember';
|
||||
|
||||
module('Acceptance | sentinel policies', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(async function () {
|
||||
window.localStorage.clear();
|
||||
window.sessionStorage.clear();
|
||||
allScenarios.policiesTestCluster(server, { sentinel: true });
|
||||
await Tokens.visit();
|
||||
const managementToken = server.db.tokens.findBy(
|
||||
(t) => t.type === 'management'
|
||||
);
|
||||
const { secretId } = managementToken;
|
||||
await Tokens.secret(secretId).submit();
|
||||
await Administration.visitSentinelPolicies();
|
||||
});
|
||||
|
||||
hooks.afterEach(async function () {
|
||||
await Tokens.visit();
|
||||
await Tokens.clear();
|
||||
});
|
||||
|
||||
test('Sentinel Policies index, general', async function (assert) {
|
||||
assert.expect(3);
|
||||
await a11yAudit(assert);
|
||||
|
||||
assert.equal(currentURL(), '/administration/sentinel-policies');
|
||||
assert
|
||||
.dom('[data-test-sentinel-policy-row]')
|
||||
.exists({ count: server.db.sentinelPolicies.length });
|
||||
|
||||
await percySnapshot(assert);
|
||||
});
|
||||
|
||||
test('Sentinel Policies index: deletion', async function (assert) {
|
||||
// Delete every policy
|
||||
assert
|
||||
.dom('[data-test-empty-sentinel-policy-list-headline]')
|
||||
.doesNotExist('no empty state');
|
||||
const policyRows = findAll('[data-test-sentinel-policy-row]');
|
||||
|
||||
for (const row of policyRows) {
|
||||
const deleteButton = row.querySelector(
|
||||
'[data-test-delete-policy] [data-test-idle-button]'
|
||||
);
|
||||
await click(deleteButton);
|
||||
const yesReallyDeleteButton = row.querySelector(
|
||||
'[data-test-delete-policy] [data-test-confirm-button]'
|
||||
);
|
||||
await click(yesReallyDeleteButton);
|
||||
}
|
||||
// there should be as many success messages as there were policies
|
||||
assert
|
||||
.dom('.flash-message.alert-success')
|
||||
.exists({ count: policyRows.length });
|
||||
|
||||
assert
|
||||
.dom('[data-test-empty-sentinel-policy-list-headline]')
|
||||
.exists('empty state');
|
||||
});
|
||||
|
||||
test('Edit Sentinel Policy: Description and Enforcement Level', async function (assert) {
|
||||
const policy = server.db.sentinelPolicies.findBy(
|
||||
(sp) => sp.name === 'policy-1'
|
||||
);
|
||||
await click('[data-test-sentinel-policy-name="policy-1"]');
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/administration/sentinel-policies/${policy.id}`
|
||||
);
|
||||
|
||||
assert.dom('[data-test-policy-description]').hasValue(policy.description);
|
||||
|
||||
await fillIn('[data-test-policy-description]', 'edited description');
|
||||
await click('[data-test-enforcement-level="hard-mandatory"]');
|
||||
await click('button[data-test-save-policy]');
|
||||
assert.dom('.flash-message.alert-success').exists();
|
||||
|
||||
// Go back to the index
|
||||
await Administration.visitSentinelPolicies();
|
||||
const policyRow = find(
|
||||
'[data-test-sentinel-policy-name="policy-1"]'
|
||||
).closest('[data-test-sentinel-policy-row]');
|
||||
assert.dom(policyRow).exists();
|
||||
let rowDescription = policyRow.querySelector(
|
||||
'[data-test-sentinel-policy-description]'
|
||||
);
|
||||
assert.equal(rowDescription.textContent.trim(), 'edited description');
|
||||
assert
|
||||
.dom(policyRow.querySelector('[data-test-sentinel-policy-enforcement]'))
|
||||
.hasText('hard-mandatory');
|
||||
});
|
||||
|
||||
test('New Sentinel Policy from Scratch', async function (assert) {
|
||||
await click('[data-test-create-sentinel-policy]');
|
||||
assert.equal(currentURL(), '/administration/sentinel-policies/new');
|
||||
await fillIn('[data-test-policy-name-input]', 'new-policy');
|
||||
await fillIn('[data-test-policy-description]', 'new description');
|
||||
await click('[data-test-enforcement-level="hard-mandatory"]');
|
||||
|
||||
await click('[data-test-save-policy]');
|
||||
assert.dom('.flash-message.alert-success').exists('success message shown');
|
||||
|
||||
// Go back to the index
|
||||
await Administration.visitSentinelPolicies();
|
||||
const policyRow = find(
|
||||
'[data-test-sentinel-policy-name="new-policy"]'
|
||||
).closest('[data-test-sentinel-policy-row]');
|
||||
assert.dom(policyRow).exists('new policy row exists');
|
||||
let rowDescription = policyRow.querySelector(
|
||||
'[data-test-sentinel-policy-description]'
|
||||
);
|
||||
assert.equal(
|
||||
rowDescription.textContent.trim(),
|
||||
'new description',
|
||||
'description matches new policy input'
|
||||
);
|
||||
assert
|
||||
.dom(policyRow.querySelector('[data-test-sentinel-policy-enforcement]'))
|
||||
.hasText('hard-mandatory', 'enforcement level matches new policy input');
|
||||
|
||||
await click('[data-test-sentinel-policy-name="new-policy"]');
|
||||
await click('[data-test-delete-policy] [data-test-idle-button]');
|
||||
await click('[data-test-delete-policy] [data-test-confirm-button]');
|
||||
assert.dom('.flash-message.alert-success').exists('success message shown');
|
||||
|
||||
await Administration.visitSentinelPolicies();
|
||||
assert
|
||||
.dom('[data-test-sentinel-policy-name="new-policy"]')
|
||||
.doesNotExist('new policy row is gone');
|
||||
});
|
||||
|
||||
test('New Sentinel Policy from Template', async function (assert) {
|
||||
assert.expect(5);
|
||||
await click('[data-test-create-sentinel-policy-from-template]');
|
||||
assert.equal(currentURL(), '/administration/sentinel-policies/gallery');
|
||||
await percySnapshot(assert);
|
||||
const template = find('[data-test-template-card="no-friday-deploys"]');
|
||||
await click(template);
|
||||
assert.ok(
|
||||
find('[data-test-template-card="no-friday-deploys"]')
|
||||
?.closest('label')
|
||||
.classList.contains(
|
||||
'hds-form-radio-card--checked',
|
||||
'template is selected on click'
|
||||
)
|
||||
);
|
||||
await click('[data-test-apply]');
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
'/administration/sentinel-policies/new?template=no-friday-deploys',
|
||||
'New Policy page has query param'
|
||||
);
|
||||
|
||||
await percySnapshot(assert);
|
||||
|
||||
assert.dom('[data-test-policy-name-input]').hasValue('no-friday-deploys');
|
||||
assert
|
||||
.dom('[data-test-policy-description]')
|
||||
.hasValue('Ensures that no deploys happen on a Friday');
|
||||
});
|
||||
});
|
||||
@@ -11,4 +11,5 @@ export default create({
|
||||
visitPolicies: visitable('/administration/policies'),
|
||||
visitRoles: visitable('/administration/roles'),
|
||||
visitNamespaces: visitable('/administration/namespaces'),
|
||||
visitSentinelPolicies: visitable('/administration/sentinel-policies'),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user