github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/tests/acceptance/job-detail-test.js (about) 1 /* eslint-disable ember/no-test-module-for */ 2 import { currentURL } from '@ember/test-helpers'; 3 import { module, test } from 'qunit'; 4 import { setupApplicationTest } from 'ember-qunit'; 5 import { selectChoose } from 'ember-power-select/test-support'; 6 import { setupMirage } from 'ember-cli-mirage/test-support'; 7 import moment from 'moment'; 8 import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit'; 9 import moduleForJob from 'nomad-ui/tests/helpers/module-for-job'; 10 import JobDetail from 'nomad-ui/tests/pages/jobs/detail'; 11 import JobsList from 'nomad-ui/tests/pages/jobs/list'; 12 13 moduleForJob('Acceptance | job detail (batch)', 'allocations', () => 14 server.create('job', { type: 'batch', shallow: true }) 15 ); 16 moduleForJob('Acceptance | job detail (system)', 'allocations', () => 17 server.create('job', { type: 'system', shallow: true }) 18 ); 19 moduleForJob( 20 'Acceptance | job detail (periodic)', 21 'children', 22 () => server.create('job', 'periodic', { shallow: true }), 23 { 24 'the default sort is submitTime descending': async function(job, assert) { 25 const mostRecentLaunch = server.db.jobs 26 .where({ parentId: job.id }) 27 .sortBy('submitTime') 28 .reverse()[0]; 29 30 assert.equal( 31 JobDetail.jobs[0].submitTime, 32 moment(mostRecentLaunch.submitTime / 1000000).format('MMM DD HH:mm:ss ZZ') 33 ); 34 }, 35 } 36 ); 37 38 moduleForJob( 39 'Acceptance | job detail (parameterized)', 40 'children', 41 () => server.create('job', 'parameterized', { shallow: true }), 42 { 43 'the default sort is submitTime descending': async (job, assert) => { 44 const mostRecentLaunch = server.db.jobs 45 .where({ parentId: job.id }) 46 .sortBy('submitTime') 47 .reverse()[0]; 48 49 assert.equal( 50 JobDetail.jobs[0].submitTime, 51 moment(mostRecentLaunch.submitTime / 1000000).format('MMM DD HH:mm:ss ZZ') 52 ); 53 }, 54 } 55 ); 56 57 moduleForJob('Acceptance | job detail (periodic child)', 'allocations', () => { 58 const parent = server.create('job', 'periodic', { childrenCount: 1, shallow: true }); 59 return server.db.jobs.where({ parentId: parent.id })[0]; 60 }); 61 62 moduleForJob('Acceptance | job detail (parameterized child)', 'allocations', () => { 63 const parent = server.create('job', 'parameterized', { childrenCount: 1, shallow: true }); 64 return server.db.jobs.where({ parentId: parent.id })[0]; 65 }); 66 67 moduleForJob( 68 'Acceptance | job detail (service)', 69 'allocations', 70 () => server.create('job', { type: 'service' }), 71 { 72 'the subnav links to deployment': async (job, assert) => { 73 await JobDetail.tabFor('deployments').visit(); 74 assert.equal(currentURL(), `/jobs/${job.id}/deployments`); 75 }, 76 'when the job is not found, an error message is shown, but the URL persists': async ( 77 job, 78 assert 79 ) => { 80 await JobDetail.visit({ id: 'not-a-real-job' }); 81 82 assert.equal( 83 server.pretender.handledRequests 84 .filter(request => !request.url.includes('policy')) 85 .findBy('status', 404).url, 86 '/v1/job/not-a-real-job', 87 'A request to the nonexistent job is made' 88 ); 89 assert.equal(currentURL(), '/jobs/not-a-real-job', 'The URL persists'); 90 assert.ok(JobDetail.error.isPresent, 'Error message is shown'); 91 assert.equal(JobDetail.error.title, 'Not Found', 'Error message is for 404'); 92 }, 93 } 94 ); 95 96 module('Acceptance | job detail (with namespaces)', function(hooks) { 97 setupApplicationTest(hooks); 98 setupMirage(hooks); 99 100 let job, managementToken, clientToken; 101 102 hooks.beforeEach(function() { 103 server.createList('namespace', 2); 104 server.create('node'); 105 job = server.create('job', { 106 type: 'service', 107 status: 'running', 108 namespaceId: server.db.namespaces[1].name, 109 }); 110 server.createList('job', 3, { 111 namespaceId: server.db.namespaces[0].name, 112 }); 113 114 managementToken = server.create('token'); 115 clientToken = server.create('token'); 116 }); 117 118 test('it passes an accessibility audit', async function(assert) { 119 const namespace = server.db.namespaces.find(job.namespaceId); 120 await JobDetail.visit({ id: job.id, namespace: namespace.name }); 121 await a11yAudit(assert); 122 }); 123 124 test('when there are namespaces, the job detail page states the namespace for the job', async function(assert) { 125 const namespace = server.db.namespaces.find(job.namespaceId); 126 await JobDetail.visit({ id: job.id, namespace: namespace.name }); 127 128 assert.ok(JobDetail.statFor('namespace').text, 'Namespace included in stats'); 129 }); 130 131 test('when switching namespaces, the app redirects to /jobs with the new namespace', async function(assert) { 132 const namespace = server.db.namespaces.find(job.namespaceId); 133 const otherNamespace = server.db.namespaces.toArray().find(ns => ns !== namespace).name; 134 135 await JobDetail.visit({ id: job.id, namespace: namespace.name }); 136 137 // TODO: Migrate to Page Objects 138 await selectChoose('[data-test-namespace-switcher]', otherNamespace); 139 assert.equal(currentURL().split('?')[0], '/jobs', 'Navigated to /jobs'); 140 141 const jobs = server.db.jobs 142 .where({ namespace: otherNamespace }) 143 .sortBy('modifyIndex') 144 .reverse(); 145 146 assert.equal(JobsList.jobs.length, jobs.length, 'Shows the right number of jobs'); 147 JobsList.jobs.forEach((jobRow, index) => { 148 assert.equal(jobRow.name, jobs[index].name, `Job ${index} is right`); 149 }); 150 }); 151 152 test('the exec button state can change between namespaces', async function(assert) { 153 const job1 = server.create('job', { 154 status: 'running', 155 namespaceId: server.db.namespaces[0].id, 156 }); 157 const job2 = server.create('job', { 158 status: 'running', 159 namespaceId: server.db.namespaces[1].id, 160 }); 161 162 window.localStorage.nomadTokenSecret = clientToken.secretId; 163 164 const policy = server.create('policy', { 165 id: 'something', 166 name: 'something', 167 rulesJSON: { 168 Namespaces: [ 169 { 170 Name: job1.namespaceId, 171 Capabilities: ['list-jobs', 'alloc-exec'], 172 }, 173 { 174 Name: job2.namespaceId, 175 Capabilities: ['list-jobs'], 176 }, 177 ], 178 }, 179 }); 180 181 clientToken.policyIds = [policy.id]; 182 clientToken.save(); 183 184 await JobDetail.visit({ id: job1.id }); 185 assert.notOk(JobDetail.execButton.isDisabled); 186 187 const secondNamespace = server.db.namespaces[1]; 188 await JobDetail.visit({ id: job2.id, namespace: secondNamespace.name }); 189 assert.ok(JobDetail.execButton.isDisabled); 190 }); 191 192 test('the anonymous policy is fetched to check whether to show the exec button', async function(assert) { 193 window.localStorage.removeItem('nomadTokenSecret'); 194 195 server.create('policy', { 196 id: 'anonymous', 197 name: 'anonymous', 198 rulesJSON: { 199 Namespaces: [ 200 { 201 Name: 'default', 202 Capabilities: ['list-jobs', 'alloc-exec'], 203 }, 204 ], 205 }, 206 }); 207 208 await JobDetail.visit({ id: job.id, namespace: server.db.namespaces[1].name }); 209 assert.notOk(JobDetail.execButton.isDisabled); 210 }); 211 212 test('resource recommendations show when they exist and can be expanded, collapsed, and processed', async function(assert) { 213 server.create('feature', { name: 'Dynamic Application Sizing' }); 214 215 job = server.create('job', { 216 type: 'service', 217 status: 'running', 218 namespaceId: server.db.namespaces[1].name, 219 groupsCount: 3, 220 createRecommendations: true, 221 }); 222 223 window.localStorage.nomadTokenSecret = managementToken.secretId; 224 await JobDetail.visit({ id: job.id, namespace: server.db.namespaces[1].name }); 225 226 const groupsWithRecommendations = job.taskGroups.filter(group => 227 group.tasks.models.any(task => task.recommendations.models.length) 228 ); 229 const jobRecommendationCount = groupsWithRecommendations.length; 230 231 const firstRecommendationGroup = groupsWithRecommendations.models[0]; 232 233 assert.equal(JobDetail.recommendations.length, jobRecommendationCount); 234 235 const recommendation = JobDetail.recommendations[0]; 236 237 assert.equal(recommendation.group, firstRecommendationGroup.name); 238 assert.ok(recommendation.card.isHidden); 239 240 const toggle = recommendation.toggleButton; 241 242 assert.equal(toggle.text, 'Show'); 243 244 await toggle.click(); 245 246 assert.ok(recommendation.card.isPresent); 247 assert.equal(toggle.text, 'Collapse'); 248 249 await toggle.click(); 250 251 assert.ok(recommendation.card.isHidden); 252 253 await toggle.click(); 254 255 assert.equal(recommendation.card.slug.groupName, firstRecommendationGroup.name); 256 257 await recommendation.card.acceptButton.click(); 258 259 assert.equal(JobDetail.recommendations.length, jobRecommendationCount - 1); 260 261 await JobDetail.tabFor('definition').visit(); 262 await JobDetail.tabFor('overview').visit(); 263 264 assert.equal(JobDetail.recommendations.length, jobRecommendationCount - 1); 265 }); 266 267 test('resource recommendations are not fetched when the feature doesn’t exist', async function(assert) { 268 window.localStorage.nomadTokenSecret = managementToken.secretId; 269 await JobDetail.visit({ id: job.id, namespace: server.db.namespaces[1].name }); 270 271 assert.equal(JobDetail.recommendations.length, 0); 272 273 assert.equal( 274 server.pretender.handledRequests.filter(request => request.url.includes('recommendations')) 275 .length, 276 0 277 ); 278 }); 279 });