[ui] Tests for Sentinel Policies (#22398)

* Tests for Sentinel Policies UI

* Further sentinel tests

* job allocations test reinstated
This commit is contained in:
Phil Renaud
2024-05-31 10:38:54 -04:00
committed by GitHub
parent 2054e87158
commit 36c2439503
13 changed files with 287 additions and 21 deletions

View File

@@ -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>

View File

@@ -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}}

View File

@@ -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">

View File

@@ -10,6 +10,7 @@
data-test-header-gutter-toggle
class="gutter-toggle"
aria-label="menu"
role="img"
onclick={{action this.onHamburgerClick}}
>
<HamburgerMenu />

View File

@@ -14,6 +14,7 @@
data-test-gutter-gutter-toggle
class="gutter-toggle"
aria-label="menu"
role="img"
onclick={{action this.onHamburgerClick}}
>
<HamburgerMenu />

View File

@@ -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

View File

@@ -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;

View 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']),
});

View File

@@ -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');

View File

@@ -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(

View File

@@ -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(

View 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');
});
});

View File

@@ -11,4 +11,5 @@ export default create({
visitPolicies: visitable('/administration/policies'),
visitRoles: visitable('/administration/roles'),
visitNamespaces: visitable('/administration/namespaces'),
visitSentinelPolicies: visitable('/administration/sentinel-policies'),
});