github.com/hspak/nomad@v0.7.2-0.20180309000617-bc4ae22a39a5/ui/tests/acceptance/client-detail-test.js (about) 1 import $ from 'jquery'; 2 import { click, find, findAll, currentURL, visit } from 'ember-native-dom-helpers'; 3 import { test } from 'qunit'; 4 import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance'; 5 import { formatBytes } from 'nomad-ui/helpers/format-bytes'; 6 import moment from 'moment'; 7 8 let node; 9 10 moduleForAcceptance('Acceptance | client detail', { 11 beforeEach() { 12 server.create('node', 'forceIPv4'); 13 node = server.db.nodes[0]; 14 15 // Related models 16 server.create('agent'); 17 server.create('job', { createAllocations: false }); 18 server.createList('allocation', 3, { nodeId: node.id }); 19 }, 20 }); 21 22 test('/clients/:id should have a breadrcumb trail linking back to clients', function(assert) { 23 visit(`/clients/${node.id}`); 24 25 andThen(() => { 26 assert.equal( 27 find('[data-test-breadcrumb="clients"]').textContent, 28 'Clients', 29 'First breadcrumb says clients' 30 ); 31 assert.equal( 32 find('[data-test-breadcrumb="client"]').textContent, 33 node.id.split('-')[0], 34 'Second breadcrumb says the node short id' 35 ); 36 }); 37 38 andThen(() => { 39 click(find('[data-test-breadcrumb="clients"]')); 40 }); 41 42 andThen(() => { 43 assert.equal(currentURL(), '/clients', 'First breadcrumb links back to clients'); 44 }); 45 }); 46 47 test('/clients/:id should list immediate details for the node in the title', function(assert) { 48 visit(`/clients/${node.id}`); 49 50 andThen(() => { 51 assert.ok(find('[data-test-title]').textContent.includes(node.name), 'Title includes name'); 52 assert.ok(find('[data-test-title]').textContent.includes(node.id), 'Title includes id'); 53 assert.ok(find(`[data-test-node-status="${node.status}"]`), 'Title includes status light'); 54 }); 55 }); 56 57 test('/clients/:id should list additional detail for the node below the title', function(assert) { 58 visit(`/clients/${node.id}`); 59 60 andThen(() => { 61 assert.equal( 62 findAll('.inline-definitions .pair')[0].textContent, 63 `Status ${node.status}`, 64 'Status is in additional details' 65 ); 66 assert.ok( 67 $('[data-test-status-definition] .status-text').hasClass(`node-${node.status}`), 68 'Status is decorated with a status class' 69 ); 70 assert.equal( 71 find('[data-test-address-definition]').textContent, 72 `Address ${node.httpAddr}`, 73 'Address is in additional detals' 74 ); 75 assert.equal( 76 find('[data-test-datacenter-definition]').textContent, 77 `Datacenter ${node.datacenter}`, 78 'Datacenter is in additional details' 79 ); 80 }); 81 }); 82 83 test('/clients/:id should list all allocations on the node', function(assert) { 84 const allocationsCount = server.db.allocations.where({ nodeId: node.id }).length; 85 86 visit(`/clients/${node.id}`); 87 88 andThen(() => { 89 assert.equal( 90 findAll('[data-test-allocation]').length, 91 allocationsCount, 92 `Allocations table lists all ${allocationsCount} associated allocations` 93 ); 94 }); 95 }); 96 97 test('each allocation should have high-level details for the allocation', function(assert) { 98 const allocation = server.db.allocations 99 .where({ nodeId: node.id }) 100 .sortBy('modifyIndex') 101 .reverse()[0]; 102 103 const allocStats = server.db.clientAllocationStats.find(allocation.id); 104 const taskGroup = server.db.taskGroups.findBy({ 105 name: allocation.taskGroup, 106 jobId: allocation.jobId, 107 }); 108 109 const tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); 110 const cpuUsed = tasks.reduce((sum, task) => sum + task.Resources.CPU, 0); 111 const memoryUsed = tasks.reduce((sum, task) => sum + task.Resources.MemoryMB, 0); 112 113 visit(`/clients/${node.id}`); 114 115 andThen(() => { 116 const allocationRow = $(find('[data-test-allocation]')); 117 assert.equal( 118 allocationRow 119 .find('[data-test-short-id]') 120 .text() 121 .trim(), 122 allocation.id.split('-')[0], 123 'Allocation short ID' 124 ); 125 assert.equal( 126 allocationRow 127 .find('[data-test-modify-time]') 128 .text() 129 .trim(), 130 moment(allocation.modifyTime / 1000000).format('MM/DD HH:mm:ss'), 131 'Allocation modify time' 132 ); 133 assert.equal( 134 allocationRow 135 .find('[data-test-name]') 136 .text() 137 .trim(), 138 allocation.name, 139 'Allocation name' 140 ); 141 assert.equal( 142 allocationRow 143 .find('[data-test-client-status]') 144 .text() 145 .trim(), 146 allocation.clientStatus, 147 'Client status' 148 ); 149 assert.equal( 150 allocationRow 151 .find('[data-test-job]') 152 .text() 153 .trim(), 154 server.db.jobs.find(allocation.jobId).name, 155 'Job name' 156 ); 157 assert.ok( 158 allocationRow 159 .find('[data-test-task-group]') 160 .text() 161 .includes(allocation.taskGroup), 162 'Task group name' 163 ); 164 assert.ok( 165 allocationRow 166 .find('[data-test-job-version]') 167 .text() 168 .includes(allocation.jobVersion), 169 'Job Version' 170 ); 171 assert.equal( 172 allocationRow 173 .find('[data-test-cpu]') 174 .text() 175 .trim(), 176 Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks) / cpuUsed, 177 'CPU %' 178 ); 179 assert.equal( 180 allocationRow.find('[data-test-cpu] .tooltip').attr('aria-label'), 181 `${Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks)} / ${cpuUsed} MHz`, 182 'Detailed CPU information is in a tooltip' 183 ); 184 assert.equal( 185 allocationRow 186 .find('[data-test-mem]') 187 .text() 188 .trim(), 189 allocStats.resourceUsage.MemoryStats.RSS / 1024 / 1024 / memoryUsed, 190 'Memory used' 191 ); 192 assert.equal( 193 allocationRow.find('[data-test-mem] .tooltip').attr('aria-label'), 194 `${formatBytes([allocStats.resourceUsage.MemoryStats.RSS])} / ${memoryUsed} MiB`, 195 'Detailed memory information is in a tooltip' 196 ); 197 }); 198 }); 199 200 test('each allocation should show job information even if the job is incomplete and already in the store', function(assert) { 201 // First, visit clients to load the allocations for each visible node. 202 // Don't load the job belongsTo of the allocation! Leave it unfulfilled. 203 204 visit('/clients'); 205 206 // Then, visit jobs to load all jobs, which should implicitly fulfill 207 // the job belongsTo of each allocation pointed at each job. 208 209 visit('/jobs'); 210 211 // Finally, visit a node to assert that the job name and task group name are 212 // present. This will require reloading the job, since task groups aren't a 213 // part of the jobs list response. 214 215 visit(`/clients/${node.id}`); 216 217 andThen(() => { 218 const allocationRow = $(find('[data-test-allocation]')); 219 const allocation = server.db.allocations 220 .where({ nodeId: node.id }) 221 .sortBy('modifyIndex') 222 .reverse()[0]; 223 224 assert.ok( 225 allocationRow 226 .find('[data-test-job]') 227 .text() 228 .includes(server.db.jobs.find(allocation.jobId).name), 229 'Job name' 230 ); 231 assert.ok( 232 allocationRow 233 .find('[data-test-task-group]') 234 .text() 235 .includes(allocation.taskGroup), 236 'Task group name' 237 ); 238 }); 239 }); 240 241 test('each allocation should link to the allocation detail page', function(assert) { 242 const allocation = server.db.allocations 243 .where({ nodeId: node.id }) 244 .sortBy('modifyIndex') 245 .reverse()[0]; 246 247 visit(`/clients/${node.id}`); 248 249 andThen(() => { 250 click('[data-test-short-id] a'); 251 }); 252 253 andThen(() => { 254 assert.equal( 255 currentURL(), 256 `/allocations/${allocation.id}`, 257 'Allocation rows link to allocation detail pages' 258 ); 259 }); 260 }); 261 262 test('each allocation should link to the job the allocation belongs to', function(assert) { 263 visit(`/clients/${node.id}`); 264 265 const allocation = server.db.allocations.where({ nodeId: node.id })[0]; 266 const job = server.db.jobs.find(allocation.jobId); 267 268 andThen(() => { 269 click('[data-test-job]'); 270 }); 271 272 andThen(() => { 273 assert.equal( 274 currentURL(), 275 `/jobs/${job.id}`, 276 'Allocation rows link to the job detail page for the allocation' 277 ); 278 }); 279 }); 280 281 test('/clients/:id should list all attributes for the node', function(assert) { 282 visit(`/clients/${node.id}`); 283 284 andThen(() => { 285 assert.ok(find('[data-test-attributes]'), 'Attributes table is on the page'); 286 }); 287 }); 288 289 test('/clients/:id lists all meta attributes', function(assert) { 290 node = server.create('node', 'forceIPv4', 'withMeta'); 291 292 visit(`/clients/${node.id}`); 293 294 andThen(() => { 295 assert.ok(find('[data-test-meta]'), 'Meta attributes table is on the page'); 296 assert.notOk(find('[data-test-empty-meta-message]'), 'Meta attributes is not empty'); 297 298 const firstMetaKey = Object.keys(node.meta)[0]; 299 assert.equal( 300 find('[data-test-meta] [data-test-key]').textContent.trim(), 301 firstMetaKey, 302 'Meta attributes for the node are bound to the attributes table' 303 ); 304 assert.equal( 305 find('[data-test-meta] [data-test-value]').textContent.trim(), 306 node.meta[firstMetaKey], 307 'Meta attributes for the node are bound to the attributes table' 308 ); 309 }); 310 }); 311 312 test('/clients/:id shows an empty message when there is no meta data', function(assert) { 313 visit(`/clients/${node.id}`); 314 315 andThen(() => { 316 assert.notOk(find('[data-test-meta]'), 'Meta attributes table is not on the page'); 317 assert.ok(find('[data-test-empty-meta-message]'), 'Meta attributes is empty'); 318 }); 319 }); 320 321 test('when the node is not found, an error message is shown, but the URL persists', function(assert) { 322 visit('/clients/not-a-real-node'); 323 324 andThen(() => { 325 assert.equal( 326 server.pretender.handledRequests.findBy('status', 404).url, 327 '/v1/node/not-a-real-node', 328 'A request to the non-existent node is made' 329 ); 330 assert.equal(currentURL(), '/clients/not-a-real-node', 'The URL persists'); 331 assert.ok(find('[data-test-error]'), 'Error message is shown'); 332 assert.equal( 333 find('[data-test-error-title]').textContent, 334 'Not Found', 335 'Error message is for 404' 336 ); 337 }); 338 });