github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/ui/tests/integration/allocation-row-test.js (about) 1 import { module, test } from 'qunit'; 2 import { setupRenderingTest } from 'ember-qunit'; 3 import { render, settled } from '@ember/test-helpers'; 4 import hbs from 'htmlbars-inline-precompile'; 5 import generateResources from '../../mirage/data/generate-resources'; 6 import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; 7 import { find } from 'ember-native-dom-helpers'; 8 import Response from 'ember-cli-mirage/response'; 9 import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer'; 10 import { Promise, resolve } from 'rsvp'; 11 12 module('Integration | Component | allocation row', function(hooks) { 13 setupRenderingTest(hooks); 14 15 hooks.beforeEach(function() { 16 fragmentSerializerInitializer(this.owner); 17 this.store = this.owner.lookup('service:store'); 18 this.server = startMirage(); 19 this.server.create('namespace'); 20 this.server.create('node'); 21 this.server.create('job', { createAllocations: false }); 22 }); 23 24 hooks.afterEach(function() { 25 this.server.shutdown(); 26 }); 27 28 test('Allocation row polls for stats, even when it errors or has an invalid response', function(assert) { 29 const component = this; 30 31 let currentFrame = 0; 32 let frames = [ 33 JSON.stringify({ ResourceUsage: generateResources() }), 34 JSON.stringify({ ResourceUsage: generateResources() }), 35 null, 36 '<Not>Valid JSON</Not>', 37 JSON.stringify({ ResourceUsage: generateResources() }), 38 ]; 39 40 this.server.get('/client/allocation/:id/stats', function() { 41 const response = frames[++currentFrame]; 42 43 // Disable polling to stop the EC task in the component 44 if (currentFrame >= frames.length) { 45 component.set('enablePolling', false); 46 } 47 48 if (response) { 49 return response; 50 } 51 return new Response(500, {}, ''); 52 }); 53 54 this.server.create('allocation', { clientStatus: 'running' }); 55 this.store.findAll('allocation'); 56 57 let allocation; 58 59 return settled() 60 .then(async () => { 61 allocation = this.store.peekAll('allocation').get('firstObject'); 62 63 this.setProperties({ 64 allocation, 65 context: 'job', 66 enablePolling: true, 67 }); 68 69 await render(hbs` 70 {{allocation-row 71 allocation=allocation 72 context=context 73 enablePolling=enablePolling}} 74 `); 75 return settled(); 76 }) 77 .then(() => { 78 assert.equal( 79 this.server.pretender.handledRequests.filterBy( 80 'url', 81 `/v1/client/allocation/${allocation.get('id')}/stats` 82 ).length, 83 frames.length, 84 'Requests continue to be made after malformed responses and server errors' 85 ); 86 }); 87 }); 88 89 test('Allocation row shows warning when it requires drivers that are unhealthy on the node it is running on', function(assert) { 90 const node = this.server.schema.nodes.first(); 91 const drivers = node.drivers; 92 Object.values(drivers).forEach(driver => { 93 driver.Healthy = false; 94 driver.Detected = true; 95 }); 96 node.update({ drivers }); 97 98 this.server.create('allocation', { clientStatus: 'running' }); 99 this.store.findAll('job'); 100 this.store.findAll('node'); 101 this.store.findAll('allocation'); 102 103 let allocation; 104 105 return settled() 106 .then(async () => { 107 allocation = this.store.peekAll('allocation').get('firstObject'); 108 109 this.setProperties({ 110 allocation, 111 context: 'job', 112 }); 113 114 await render(hbs` 115 {{allocation-row 116 allocation=allocation 117 context=context}} 118 `); 119 return settled(); 120 }) 121 .then(() => { 122 assert.ok(find('[data-test-icon="unhealthy-driver"]'), 'Unhealthy driver icon is shown'); 123 }); 124 }); 125 126 test('Allocation row shows an icon indicator when it was preempted', async function(assert) { 127 const allocId = this.server.create('allocation', 'preempted').id; 128 129 const allocation = await this.store.findRecord('allocation', allocId); 130 await settled(); 131 132 this.setProperties({ allocation, context: 'job' }); 133 await render(hbs` 134 {{allocation-row 135 allocation=allocation 136 context=context}} 137 `); 138 await settled(); 139 140 assert.ok(find('[data-test-icon="preemption"]'), 'Preempted icon is shown'); 141 }); 142 143 test('when an allocation is not running, the utilization graphs are omitted', function(assert) { 144 this.setProperties({ 145 context: 'job', 146 enablePolling: false, 147 }); 148 149 // All non-running statuses need to be tested 150 ['pending', 'complete', 'failed', 'lost'].forEach(clientStatus => 151 this.server.create('allocation', { clientStatus }) 152 ); 153 154 this.store.findAll('allocation'); 155 156 return settled().then(() => { 157 const allocations = this.store.peekAll('allocation'); 158 return waitForEach( 159 allocations.map(allocation => async () => { 160 this.set('allocation', allocation); 161 await render(hbs` 162 {{allocation-row 163 allocation=allocation 164 context=context 165 enablePolling=enablePolling}} 166 `); 167 return settled().then(() => { 168 const status = allocation.get('clientStatus'); 169 assert.notOk(find('[data-test-cpu] .inline-chart'), `No CPU chart for ${status}`); 170 assert.notOk(find('[data-test-mem] .inline-chart'), `No Mem chart for ${status}`); 171 }); 172 }) 173 ); 174 }); 175 }); 176 177 // A way to loop over asynchronous code. Can be replaced by async/await in the future. 178 const waitForEach = fns => { 179 let i = 0; 180 let done = () => {}; 181 182 // This function is asynchronous and needs to return a promise 183 const pending = new Promise(resolve => { 184 done = resolve; 185 }); 186 187 const step = () => { 188 // The waitForEach promise and this recursive loop are done once 189 // all functions have been called. 190 if (i >= fns.length) { 191 done(); 192 return; 193 } 194 // Call the current function 195 const promise = fns[i]() || resolve(true); 196 // Increment the function position 197 i++; 198 // Wait for async behaviors to settle and repeat 199 promise.then(() => settled()).then(step); 200 }; 201 202 step(); 203 204 return pending; 205 }; 206 });