import EmberObject from '@ember/object'; import RSVP from 'rsvp'; import { run } from '@ember/runloop'; import sinon from 'sinon'; import { module, test } from 'qunit'; import _Log from 'nomad-ui/utils/classes/log'; import { settled } from '@ember/test-helpers'; let startSpy, stopSpy, initSpy, fetchSpy; const MockStreamer = EmberObject.extend({ init() { this.poll = { isRunning: false, }; initSpy(...arguments); }, start() { this.poll.isRunning = true; startSpy(...arguments); }, stop() { this.poll.isRunning = true; stopSpy(...arguments); }, step(chunk) { if (this.poll.isRunning) { this.write(chunk); } }, }); const Log = _Log.extend({ init() { this._super(); const props = this.logStreamer.getProperties('url', 'params', 'logFetch', 'write'); this.set('logStreamer', MockStreamer.create(props)); }, }); module('Unit | Util | Log', function(hooks) { hooks.beforeEach(function() { initSpy = sinon.spy(); startSpy = sinon.spy(); stopSpy = sinon.spy(); fetchSpy = sinon.spy(); }); const makeMocks = output => ({ url: '/test-url/', params: { a: 'param', another: 'one', }, logFetch: function() { fetchSpy(...arguments); return RSVP.Promise.resolve({ text() { return output; }, }); }, }); test('logStreamer is created on init', async function(assert) { const log = Log.create(makeMocks('')); assert.ok(log.get('logStreamer'), 'logStreamer property is defined'); assert.ok(initSpy.calledOnce, 'logStreamer init was called'); }); test('gotoHead builds the correct URL', async function(assert) { const mocks = makeMocks(''); const expectedUrl = `${mocks.url}?a=param&another=one&offset=0&origin=start`; const log = Log.create(mocks); run(() => { log.get('gotoHead').perform(); assert.ok(fetchSpy.calledWith(expectedUrl), `gotoHead URL was ${expectedUrl}`); }); }); test('When gotoHead returns too large of a log, the log is truncated', async function(assert) { const longLog = Array(50001) .fill('a') .join(''); const encodedLongLog = `{"Offset":0,"Data":"${window.btoa(longLog)}"}`; const truncationMessage = '\n\n---------- TRUNCATED: Click "tail" to view the bottom of the log ----------'; const mocks = makeMocks(encodedLongLog); const log = Log.create(mocks); run(() => { log.get('gotoHead').perform(); }); await settled(); assert.ok( log .get('output') .toString() .endsWith(truncationMessage), 'Truncation message is shown' ); assert.equal( log.get('output').toString().length, 50000 + truncationMessage.length, 'Output is truncated the appropriate amount' ); }); test('gotoTail builds the correct URL', async function(assert) { const mocks = makeMocks(''); const expectedUrl = `${mocks.url}?a=param&another=one&offset=50000&origin=end`; const log = Log.create(mocks); run(() => { log.get('gotoTail').perform(); assert.ok(fetchSpy.calledWith(expectedUrl), `gotoTail URL was ${expectedUrl}`); }); }); test('startStreaming starts the log streamer', async function(assert) { const log = Log.create(makeMocks('')); log.startStreaming(); assert.ok(startSpy.calledOnce, 'Streaming started'); assert.equal(log.get('logPointer'), 'tail', 'Streaming points the log to the tail'); }); test('When the log streamer calls `write`, the output is appended', async function(assert) { const log = Log.create(makeMocks('')); const chunk1 = 'Hello'; const chunk2 = ' World'; const chunk3 = '\n\nEOF'; log.startStreaming(); assert.equal(log.get('output'), '', 'No output yet'); log.get('logStreamer').step(chunk1); assert.equal(log.get('output'), chunk1, 'First chunk written'); log.get('logStreamer').step(chunk2); assert.equal(log.get('output'), chunk1 + chunk2, 'Second chunk written'); log.get('logStreamer').step(chunk3); assert.equal(log.get('output'), chunk1 + chunk2 + chunk3, 'Third chunk written'); }); test('stop stops the log streamer', async function(assert) { const log = Log.create(makeMocks('')); log.stop(); assert.ok(stopSpy.calledOnce, 'Streaming stopped'); }); });