UNPKG

ember-cli-toranb

Version:

Command line tool for developing ambitious ember.js apps

626 lines (562 loc) 18.4 kB
'use strict'; var assert = require('../../../helpers/assert'); var ExpressServer = require('../../../../lib/tasks/server/express-server'); var Promise = require('../../../../lib/ext/promise'); var MockUI = require('../../../helpers/mock-ui'); var MockProject = require('../../../helpers/mock-project'); var MockWatcher = require('../../../helpers/mock-watcher'); var MockServerWatcher = require('../../../helpers/mock-server-watcher'); var ProxyServer = require('../../../helpers/proxy-server'); var chalk = require('chalk'); var request = require('supertest'); var net = require('net'); var EOL = require('os').EOL; describe('express-server', function() { var subject, ui, project, proxy; beforeEach(function() { ui = new MockUI(); project = new MockProject(); proxy = new ProxyServer(); subject = new ExpressServer({ ui: ui, project: project, watcher: new MockWatcher(), serverWatcher: new MockServerWatcher(), serverRestartDelayTime: 5, serverRoot: './server', proxyMiddleware: function() { return proxy.handler.bind(proxy); }, environment: 'development' }); }); afterEach(function() { try { subject.httpServer.close(); } catch(err) { } try { proxy.httpServer.close(); } catch(err) { } }); describe('processAppMiddlewares', function() { it('has a good error message if a file exists, but does not export a function', function() { subject.project = { has: function() { return true; }, require: function() { return {}; } }; assert.throws(function() { subject.processAppMiddlewares(); }, TypeError, 'ember-cli expected ./server/index.js to be the entry for your mock or proxy server'); }); }); describe('output', function() { it('with proxy', function() { return subject.start({ proxy: 'http://localhost:3001/', host: '0.0.0.0', port: '1337', baseURL: '/' }).then(function() { var output = ui.output.trim().split(EOL); assert.deepEqual(output[1], 'Serving on http://0.0.0.0:1337/'); assert.deepEqual(output[0], 'Proxying to http://localhost:3001/'); assert.deepEqual(output.length, 2, 'expected only two lines of output'); }); }); it('without proxy', function() { return subject.start({ host: '0.0.0.0', port: '1337', baseURL: '/' }).then(function() { var output = ui.output.trim().split(EOL); assert.deepEqual(output[0], 'Serving on http://0.0.0.0:1337/'); assert.deepEqual(output.length, 1, 'expected only one line of output'); }); }); it('with baseURL', function() { return subject.start({ host: '0.0.0.0', port: '1337', baseURL: '/foo' }).then(function() { var output = ui.output.trim().split(EOL); assert.deepEqual(output[0], 'Serving on http://0.0.0.0:1337/foo/'); assert.deepEqual(output.length, 1, 'expected only one line of output'); }); }); it('address in use', function(done) { var preexistingServer = net.createServer(); preexistingServer.listen(1337); return subject.start({ host: '0.0.0.0', port: '1337' }) .then(function() { assert(false, 'should have rejected'); }) .catch(function(reason) { assert.equal(reason, 'Could not serve on http://0.0.0.0:1337. It is either in use or you do not have permission.'); }) .finally(function() { preexistingServer.close(done); }); }); }); describe('behaviour', function() { it('app middlewares are processed before the proxy', function(done) { var expected = '/foo was hit'; project.require = function() { return function(app) { app.use('/foo', function(req,res) { res.send(expected); }); }; }; subject.start({ proxy: 'http://localhost:3001/', host: '0.0.0.0', port: '1337', baseURL: '/' }) .then(function() { request(subject.app) .get('/foo') .set('accept', 'application/json, */*') .expect(function(res) { assert.equal(res.text, expected); }) .end(function(err) { if (err) { return done(err); } assert(!proxy.called); done(); }); }); }); describe('with proxy', function() { beforeEach(function() { return subject.start({ proxy: 'http://localhost:3001/', host: '0.0.0.0', port: '1337', baseURL: '/' }); }); function bypassTest(app, url, done, responseCallback) { request(app) .get(url) .set('accept', 'text/html') .end(function(err, response) { if (err) { return done(err); } assert(!proxy.called); if (responseCallback) { responseCallback(response); } done(); }); } it('bypasses proxy for /', function(done) { bypassTest(subject.app, '/', done); }); it('bypasses proxy for files that exist', function(done) { bypassTest(subject.app, '/test-file.txt', done, function(response) { assert.equal(response.text.trim(), 'some contents'); }); }); function apiTest(app, method, url, done) { var req = request(app); return req[method].call(req, url) .set('accept', 'text/json') .end(function(err) { if (err) { return done(err); } assert(proxy.called, 'proxy receives the request'); assert.equal(proxy.lastReq.method, method.toUpperCase()); assert.equal(proxy.lastReq.url, url); done(); }); } it('proxies GET', function(done) { apiTest(subject.app, 'get', '/api/get', done); }); it('proxies PUT', function(done) { apiTest(subject.app, 'put', '/api/put', done); }); it('proxies POST', function(done) { apiTest(subject.app, 'post', '/api/post', done); }); it('proxies DELETE', function(done) { apiTest(subject.app, 'delete', '/api/delete', done); }); // test for #1263 it('proxies when accept contains */*', function(done) { request(subject.app) .get('/api/get') .set('accept', 'application/json, */*') .end(function(err) { if (err) { return done(err); } assert(proxy.called, 'proxy receives the request'); done(); }); }); }); describe('without proxy', function() { function startServer(baseURL) { return subject.start({ host: '0.0.0.0', port: '1337', baseURL: baseURL || '/' }); } it('serves index.html when file not found with auto/history location', function(done) { return startServer() .then(function() { request(subject.app) .get('/someurl.withperiod') .set('accept', 'text/html') .expect(200) .expect('Content-Type', /html/) .end(function(err) { if (err) { return done(err); } done(); }); }); }); it('GET /tests serves tests/index.html for mime of */* (hash location)', function(done) { project._config = { baseURL: '/', locationType: 'hash' }; return startServer() .then(function() { request(subject.app) .get('/tests') .set('accept', '*/*') .expect(200) .expect('Content-Type', /html/) .end(function(err) { if (err) { return done(err); } done(); }); }); }); it('GET /tests serves tests/index.html for mime of */* (auto location)', function(done) { return startServer() .then(function() { request(subject.app) .get('/tests') .set('accept', '*/*') .expect(200) .expect('Content-Type', /html/) .end(function(err) { if (err) { return done(err); } done(); }); }); }); it('serves index.html when file not found (with baseURL) with auto/history location', function(done) { return startServer('/foo') .then(function() { request(subject.app) .get('/foo/someurl') .set('accept', 'text/html') .expect(200) .expect('Content-Type', /html/) .end(function(err) { if (err) { return done(err); } done(); }); }); }); it('returns a 404 when file not found with hash location', function(done) { project._config = { baseURL: '/', locationType: 'hash' }; return startServer() .then(function() { request(subject.app) .get('/someurl.withperiod') .set('accept', 'text/html') .expect(404) .end(done); }); }); it('files that exist in broccoli directory are served up', function(done) { return startServer() .then(function() { request(subject.app) .get('/test-file.txt') .end(function(err, response) { assert.equal(response.text.trim(), 'some contents'); done(); }); }); }); it('serves static asset up from build output without a period in name', function(done) { return startServer() .then(function() { request(subject.app) .get('/someurl-without-period') .expect(200) .end(function(err, response) { if (err) { return done(err); } assert.equal(response.text.trim(), 'some other content'); done(); }); }); }); it('serves static asset up from build output without a period in name (with baseURL)', function(done) { return startServer('/foo') .then(function() { request(subject.app) .get('/foo/someurl-without-period') .expect(200) .end(function(err, response) { if (err) { return done(err); } assert.equal(response.text.trim(), 'some other content'); done(); }); }); }); }); describe('addons', function() { var calls; beforeEach(function() { calls = 0; subject.processAddonMiddlewares = function() { calls++; }; }); it('calls processAddonMiddlewares upon start', function() { return subject.start({ host: '0.0.0.0', port: '1337' }).then(function() { assert.equal(calls, 1); }); }); }); describe('addon middleware', function() { var firstCalls; var secondCalls; beforeEach(function() { firstCalls = 0; secondCalls = 0; project.initializeAddons = function() { }; project.addons = [{ serverMiddleware: function() { firstCalls++; } }, { serverMiddleware: function() { secondCalls++; } }, { doesntGoBoom: null }]; }); it('calls serverMiddleware on the addons on start', function() { return subject.start({ host: '0.0.0.0', port: '1337' }).then(function() { assert.equal(firstCalls, 1); assert.equal(secondCalls, 1); }); }); it('calls serverMiddleware on the addons on restart', function() { return subject.start({ host: '0.0.0.0', port: '1337' }).then(function() { subject.changedFiles = ['bar.js']; return subject.restartHttpServer(); }).then(function() { assert.equal(firstCalls, 2); assert.equal(secondCalls, 2); }); }); }); describe('app middleware', function() { var passedOptions; var calls; beforeEach(function() { passedOptions = null; calls = 0; subject.processAppMiddlewares = function(options) { passedOptions = options; calls++; }; }); it('calls processAppMiddlewares upon start', function() { var realOptions = { host: '0.0.0.0', port: '1337' }; return subject.start(realOptions).then(function() { assert(passedOptions === realOptions); assert.equal(calls, 1); }); }); it('calls processAppMiddlewares upon restart', function() { var realOptions = { host: '0.0.0.0', port: '1337' }; var originalApp; return subject.start(realOptions) .then(function() { originalApp = subject.app; subject.changedFiles = ['bar.js']; return subject.restartHttpServer(); }) .then(function() { assert(subject.app); assert.notEqual(originalApp, subject.app); assert(passedOptions === realOptions); assert.equal(calls, 2); }); }); it('includes httpServer instance in options', function() { var passedOptions; subject.processAppMiddlewares = function(options) { passedOptions = options; }; var realOptions = { host: '0.0.0.0', port: '1337' }; return subject.start(realOptions).then(function() { assert(!!passedOptions.httpServer.listen); }); }); }); describe('serverWatcherDidChange', function() { it('is called on file change', function() { var calls = 0; subject.serverWatcherDidChange = function() { calls++; }; return subject.start({ host: '0.0.0.0', port: '1337' }).then(function() { subject.serverWatcher.emit('change', 'foo.txt'); assert.equal(calls, 1); }); }); it('schedules a server restart', function() { var calls = 0; subject.scheduleServerRestart = function() { calls++; }; return subject.start({ host: '0.0.0.0', port: '1337' }).then(function() { subject.serverWatcher.emit('change', 'foo.txt'); subject.serverWatcher.emit('change', 'bar.txt'); assert.equal(calls, 2); }); }); }); describe('scheduleServerRestart', function() { it('schedules exactly one call of restartHttpServer', function(done) { var calls = 0; subject.restartHttpServer = function() { calls++; }; subject.serverRestartDelayTime = 10; subject.scheduleServerRestart(); assert.equal(calls, 0); setTimeout(function() { assert.equal(calls, 0); subject.scheduleServerRestart(); }, 4); setTimeout(function() { assert.equal(calls, 1); done(); }, 15); }); }); describe('restartHttpServer', function() { it('restarts the server', function() { var originalHttpServer; var originalApp; return subject.start({ host: '0.0.0.0', port: '1337' }).then(function() { ui.output = ''; originalHttpServer = subject.httpServer; originalApp = subject.app; subject.changedFiles = ['bar.js']; return subject.restartHttpServer(); }).then(function() { assert.equal(ui.output, EOL + chalk.green('Server restarted.') + EOL + EOL); assert(subject.httpServer, 'HTTP server exists'); assert.notEqual(subject.httpServer, originalHttpServer, 'HTTP server has changed'); assert(subject.app, 'App exists'); assert.notEqual(subject.app, originalApp, 'App has changed'); }); }); it('restarts the server again if one or more files change during a previous restart', function() { var originalHttpServer; var originalApp; return subject.start({ host: '0.0.0.0', port: '1337' }).then(function() { originalHttpServer = subject.httpServer; originalApp = subject.app; subject.serverRestartPromise = new Promise(function(resolve) { setTimeout(function () { subject.serverRestartPromise = null; resolve(); }, 20); }); subject.changedFiles = ['bar.js']; return subject.restartHttpServer(); }).then(function() { assert(subject.httpServer, 'HTTP server exists'); assert.notEqual(subject.httpServer, originalHttpServer, 'HTTP server has changed'); assert(subject.app, 'App exists'); assert.notEqual(subject.app, originalApp, 'App has changed'); }); }); it('emits the restart event', function() { var calls = 0; subject.on('restart', function() { calls++; }); return subject.start({ host: '0.0.0.0', port: '1337' }).then(function() { subject.changedFiles = ['bar.js']; return subject.restartHttpServer(); }).then(function() { assert.equal(calls, 1); }); }); }); }); });