From b72bf6f5a85e7eec2fa2c79eb8f3256ff85ff59a Mon Sep 17 00:00:00 2001 From: Jai Bhagat Date: Wed, 5 Jan 2022 12:50:15 -0500 Subject: [PATCH] test: add tests for not auth behavior for job-client-status-summary --- ui/tests/helpers/module-for-job.js | 211 +++++++++++++++++++---------- 1 file changed, 141 insertions(+), 70 deletions(-) diff --git a/ui/tests/helpers/module-for-job.js b/ui/tests/helpers/module-for-job.js index b929cc383..81dc7fba5 100644 --- a/ui/tests/helpers/module-for-job.js +++ b/ui/tests/helpers/module-for-job.js @@ -1,11 +1,14 @@ /* eslint-disable qunit/require-expect */ /* eslint-disable qunit/no-conditional-assertions */ -import { currentURL } from '@ember/test-helpers'; +import { currentRouteName, currentURL, visit } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { setupMirage } from 'ember-cli-mirage/test-support'; import JobDetail from 'nomad-ui/tests/pages/jobs/detail'; +// moduleFor is an old Ember-QUnit API that is deprected https://guides.emberjs.com/v1.10.0/testing/unit-test-helpers/ +// this is a misnomer in our context, because we're not using this API, however, the linter does not understand this +// the linter warning will go away if we rename this factory function to generateJobDetailsTests // eslint-disable-next-line ember/no-test-module-for export default function moduleForJob( title, @@ -190,6 +193,9 @@ export default function moduleForJob( }); } +// moduleFor is an old Ember-QUnit API that is deprected https://guides.emberjs.com/v1.10.0/testing/unit-test-helpers/ +// this is a misnomer in our context, because we're not using this API, however, the linter does not understand this +// the linter warning will go away if we rename this factory function to generateJobClientStatusTests // eslint-disable-next-line ember/no-test-module-for export function moduleForJobWithClientStatus( title, @@ -211,80 +217,127 @@ export function moduleForJobWithClientStatus( clients.forEach((c) => { server.create('allocation', { jobId: job.id, nodeId: c.id }); }); - if (!job.namespace || job.namespace === 'default') { - await JobDetail.visit({ id: job.id }); - } else { - await JobDetail.visit({ id: job.id, namespace: job.namespace }); + }); + + module('with node:read permissions', function (hooks) { + hooks.beforeEach(async function () { + // Displaying the job status in client requires node:read permission. + setPolicy({ + id: 'node-read', + name: 'node-read', + rulesJSON: { + Node: { + Policy: 'read', + }, + }, + }); + + await visitJobDetailPage(job); + }); + + test('the subnav links to clients', async function (assert) { + await JobDetail.tabFor('clients').visit(); + assert.equal( + currentURL(), + urlWithNamespace( + `/jobs/${encodeURIComponent(job.id)}/clients`, + job.namespace + ) + ); + }); + + test('job status summary is shown in the overview', async function (assert) { + assert.ok( + JobDetail.jobClientStatusSummary.isPresent, + 'Summary bar is displayed in the Job Status in Client summary section' + ); + }); + + test('clicking legend item navigates to a pre-filtered clients table', async function (assert) { + const legendItem = + JobDetail.jobClientStatusSummary.legend.clickableItems[0]; + const status = legendItem.label; + await legendItem.click(); + + const encodedStatus = encodeURIComponent(JSON.stringify([status])); + const expectedURL = new URL( + urlWithNamespace( + `/jobs/${job.name}/clients?status=${encodedStatus}`, + job.namespace + ), + window.location + ); + const gotURL = new URL(currentURL(), window.location); + assert.deepEqual(gotURL.path, expectedURL.path); + assert.deepEqual(gotURL.searchParams, expectedURL.searchParams); + }); + + test('clicking in a slice takes you to a pre-filtered clients table', async function (assert) { + const slice = JobDetail.jobClientStatusSummary.slices[0]; + const status = slice.label; + await slice.click(); + + const encodedStatus = encodeURIComponent(JSON.stringify([status])); + const expectedURL = new URL( + urlWithNamespace( + `/jobs/${job.name}/clients?status=${encodedStatus}`, + job.namespace + ), + window.location + ); + const gotURL = new URL(currentURL(), window.location); + assert.deepEqual(gotURL.pathname, expectedURL.pathname); + + // Sort and compare URL query params. + gotURL.searchParams.sort(); + expectedURL.searchParams.sort(); + assert.equal( + gotURL.searchParams.toString(), + expectedURL.searchParams.toString() + ); + }); + + for (var testName in additionalTests) { + test(testName, async function (assert) { + await additionalTests[testName].call(this, job, assert); + }); } }); - test('the subnav links to clients', async function (assert) { - await JobDetail.tabFor('clients').visit(); - assert.equal( - currentURL(), - urlWithNamespace( - `/jobs/${encodeURIComponent(job.id)}/clients`, - job.namespace - ) - ); - }); + module('without node:read permissions', function (hooks) { + hooks.beforeEach(async function () { + // Test blank Node policy to mock lack of permission. + setPolicy({ + id: 'node', + name: 'node', + rulesJSON: {}, + }); - test('job status summary is shown in the overview', async function (assert) { - assert.ok( - JobDetail.jobClientStatusSummary.isPresent, - 'Summary bar is displayed in the Job Status in Client summary section' - ); - }); - - test('clicking legend item navigates to a pre-filtered clients table', async function (assert) { - const legendItem = - JobDetail.jobClientStatusSummary.legend.clickableItems[0]; - const status = legendItem.label; - await legendItem.click(); - - const encodedStatus = encodeURIComponent(JSON.stringify([status])); - const expectedURL = new URL( - urlWithNamespace( - `/jobs/${job.name}/clients?status=${encodedStatus}`, - job.namespace - ), - window.location - ); - const gotURL = new URL(currentURL(), window.location); - assert.deepEqual(gotURL.path, expectedURL.path); - assert.deepEqual(gotURL.searchParams, expectedURL.searchParams); - }); - - test('clicking in a slice takes you to a pre-filtered clients table', async function (assert) { - const slice = JobDetail.jobClientStatusSummary.slices[0]; - const status = slice.label; - await slice.click(); - - const encodedStatus = encodeURIComponent(JSON.stringify([status])); - const expectedURL = new URL( - urlWithNamespace( - `/jobs/${job.name}/clients?status=${encodedStatus}`, - job.namespace - ), - window.location - ); - const gotURL = new URL(currentURL(), window.location); - assert.deepEqual(gotURL.pathname, expectedURL.pathname); - - // Sort and compare URL query params. - gotURL.searchParams.sort(); - expectedURL.searchParams.sort(); - assert.equal( - gotURL.searchParams.toString(), - expectedURL.searchParams.toString() - ); - }); - - for (var testName in additionalTests) { - test(testName, async function (assert) { - await additionalTests[testName].call(this, job, assert); + await visitJobDetailPage(job); }); - } + + test('the page handles presentations concerns regarding the user not having node:read permissions', async function (assert) { + assert + .dom("[data-test-tab='clients']") + .doesNotExist( + 'Job Detail Sub Navigation should not render Clients tab' + ); + + assert + .dom('[data-test-nodes-not-authorized]') + .exists('Renders Not Authorized message'); + }); + + test('/jobs/job/clients route is protected with authorization logic', async function (assert) { + await visit(`/jobs/${job.id}/clients`); + + assert.equal( + currentRouteName(), + 'jobs.job.index', + 'The clients route cannot be visited unless you have node:read permissions' + ); + }); + }); }); } @@ -299,3 +352,21 @@ function urlWithNamespace(url, namespace) { return `${parts[0]}?${params.toString()}`; } + +function setPolicy(policy) { + const { id: policyId } = server.create('policy', policy); + const clientToken = server.create('token', { type: 'client' }); + clientToken.policyIds = [policyId]; + clientToken.save(); + + window.localStorage.clear(); + window.localStorage.nomadTokenSecret = clientToken.secretId; +} + +async function visitJobDetailPage({ id, namespace }) { + if (!namespace || namespace === 'default') { + await JobDetail.visit({ id }); + } else { + await JobDetail.visit({ id, namespace }); + } +}