UNPKG

navy

Version:

Quick and powerful development environments using Docker and Docker Compose

249 lines (247 loc) 11.3 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _chai = require("chai"); var _sinon = _interopRequireDefault(require("sinon")); var _path = _interopRequireDefault(require("path")); var _events = require("events"); var _fs = _interopRequireDefault(require("../../../util/fs")); var execAsyncModule = _interopRequireWildcard(require("../../../util/exec-async")); var driverLogging = _interopRequireWildcard(require("../../../driver-logging")); var _client = require("../client"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /* eslint-env mocha */ function makeNavy({ name = 'env', normalisedName = 'env', configProvider = null } = {}) { return { name, normalisedName, getConfigProvider: _sinon.default.stub().resolves(configProvider) }; } describe('docker-compose client', function () { let sandbox; let originalHome; let execStub; beforeEach(function () { sandbox = _sinon.default.createSandbox(); originalHome = process.env.HOME; process.env.HOME = '/home/test'; execStub = sandbox.stub(execAsyncModule, 'execAsync').resolves(''); sandbox.stub(driverLogging, 'log'); }); afterEach(function () { sandbox.restore(); if (originalHome === undefined) { delete process.env.HOME; } else { process.env.HOME = originalHome; } }); describe('createComposeClient', function () { it('should expose exec, getCompiledDockerComposePath, getDockerComposeFilePath, getOriginalDockerComposeDirectory', function () { const client = (0, _client.createComposeClient)(makeNavy()); (0, _chai.expect)(client).to.have.property('exec').that.is.a('function'); (0, _chai.expect)(client).to.have.property('getCompiledDockerComposePath').that.is.a('function'); (0, _chai.expect)(client).to.have.property('getDockerComposeFilePath').that.is.a('function'); (0, _chai.expect)(client).to.have.property('getOriginalDockerComposeDirectory').that.is.a('function'); }); }); describe('getCompiledDockerComposePath', function () { it('should return $HOME/.navy/navies/<env>/docker-compose.tmp.yml', function () { const client = (0, _client.createComposeClient)(makeNavy({ normalisedName: 'envname' })); (0, _chai.expect)(client.getCompiledDockerComposePath()).to.equal(_path.default.join('/home/test', '.navy', 'navies', 'envname', 'docker-compose.tmp.yml')); }); }); describe('getDockerComposeFilePath', function () { it('should return the compiled path when the file exists', async function () { const client = (0, _client.createComposeClient)(makeNavy()); sandbox.stub(_fs.default, 'statAsync').resolves({}); const result = await client.getDockerComposeFilePath(); (0, _chai.expect)(result).to.contain('docker-compose.tmp.yml'); }); it('should return null when the compiled file does not exist', async function () { const client = (0, _client.createComposeClient)(makeNavy()); sandbox.stub(_fs.default, 'statAsync').rejects(new Error('ENOENT')); (0, _chai.expect)(await client.getDockerComposeFilePath()).to.equal(null); }); }); describe('getOriginalDockerComposeDirectory', function () { it('should return the navy path from the config provider', async function () { const client = (0, _client.createComposeClient)(makeNavy({ configProvider: { getNavyPath: async () => '/some/path' } })); (0, _chai.expect)(await client.getOriginalDockerComposeDirectory()).to.equal('/some/path'); }); it('should throw an invariant violation when there is no config provider', async function () { const client = (0, _client.createComposeClient)(makeNavy({ configProvider: null })); let caught; try { await client.getOriginalDockerComposeDirectory(); } catch (err) { caught = err; } (0, _chai.expect)(caught).to.be.an('error'); (0, _chai.expect)(caught.message).to.match(/NO_CONFIG_PROVIDER/); }); }); describe('exec', function () { it('should call docker compose with -p <name> -f <compiled path> when compiled file exists', async function () { const client = (0, _client.createComposeClient)(makeNavy({ normalisedName: 'env' })); sandbox.stub(_fs.default, 'statAsync').resolves({}); await client.exec('up', ['-d']); (0, _chai.expect)(execStub.calledOnce).to.equal(true); const [cmd, args,, opts] = execStub.firstCall.args; (0, _chai.expect)(cmd).to.equal('docker compose'); (0, _chai.expect)(args[0]).to.equal('-p'); (0, _chai.expect)(args[1]).to.equal('env'); (0, _chai.expect)(args[2]).to.equal('-f'); (0, _chai.expect)(args[3]).to.contain('docker-compose.tmp.yml'); (0, _chai.expect)(args.slice(4)).to.eql(['up', '-d']); (0, _chai.expect)(opts.maxBuffer).to.equal(Infinity); }); it('should set cwd to the original docker-compose directory when the compiled file is missing', async function () { const client = (0, _client.createComposeClient)(makeNavy({ configProvider: { getNavyPath: async () => '/orig/path' } })); sandbox.stub(_fs.default, 'statAsync').rejects(new Error('ENOENT')); await client.exec('config'); const [, args,, opts] = execStub.firstCall.args; (0, _chai.expect)(args).to.not.include('-f'); (0, _chai.expect)(opts.cwd).to.equal('/orig/path'); }); it('should pass useOriginalDockerComposeFiles to bypass the compiled file', async function () { const client = (0, _client.createComposeClient)(makeNavy({ configProvider: { getNavyPath: async () => '/orig/path' } })); sandbox.stub(_fs.default, 'statAsync').resolves({}); await client.exec('config', [], { useOriginalDockerComposeFiles: true }); const [, args,, opts] = execStub.firstCall.args; (0, _chai.expect)(args).to.not.include('-f'); (0, _chai.expect)(opts.cwd).to.equal('/orig/path'); }); it('should respect a custom maxBuffer', async function () { const client = (0, _client.createComposeClient)(makeNavy()); sandbox.stub(_fs.default, 'statAsync').resolves({}); await client.exec('up', [], { maxBuffer: 1024 }); const [,,, opts] = execStub.firstCall.args; (0, _chai.expect)(opts.maxBuffer).to.equal(1024); }); it('should default args to an empty array', async function () { const client = (0, _client.createComposeClient)(makeNavy()); sandbox.stub(_fs.default, 'statAsync').resolves({}); await client.exec('config'); (0, _chai.expect)(execStub.calledOnce).to.equal(true); }); it('should pipe child stdout/stderr through the spinner log when noLog and pipeLog are not set', async function () { const client = (0, _client.createComposeClient)(makeNavy()); sandbox.stub(_fs.default, 'statAsync').resolves({}); const child = new _events.EventEmitter(); child.stdout = new _events.EventEmitter(); child.stderr = new _events.EventEmitter(); execStub.callsFake(async (cmd, args, callback) => { callback(child); child.stdout.emit('data', 'out-line'); child.stderr.emit('data', 'err-line'); return ''; }); await client.exec('up'); (0, _chai.expect)(driverLogging.log.callCount).to.equal(2); (0, _chai.expect)(driverLogging.log.firstCall.args[0]).to.equal('out-line'); (0, _chai.expect)(driverLogging.log.secondCall.args[0]).to.equal('err-line'); }); it('should pipe child stdout/stderr to process.stdout/stderr when pipeLog is true', async function () { const client = (0, _client.createComposeClient)(makeNavy()); sandbox.stub(_fs.default, 'statAsync').resolves({}); const child = new _events.EventEmitter(); child.stdout = new _events.EventEmitter(); child.stderr = new _events.EventEmitter(); const stdoutWrite = sandbox.stub(process.stdout, 'write').returns(true); const stderrWrite = sandbox.stub(process.stderr, 'write').returns(true); execStub.callsFake(async (cmd, args, callback) => { callback(child); child.stdout.emit('data', 'out'); child.stderr.emit('data', 'err'); return ''; }); await client.exec('logs', [], { pipeLog: true }); (0, _chai.expect)(stdoutWrite.calledWith('out')).to.equal(true); (0, _chai.expect)(stderrWrite.calledWith('err')).to.equal(true); }); it('should NOT attach stdout/stderr listeners when noLog is true', async function () { const client = (0, _client.createComposeClient)(makeNavy()); sandbox.stub(_fs.default, 'statAsync').resolves({}); const child = new _events.EventEmitter(); child.stdout = new _events.EventEmitter(); child.stderr = new _events.EventEmitter(); execStub.callsFake(async (cmd, args, callback) => { callback(child); return ''; }); await client.exec('config', [], { noLog: true }); (0, _chai.expect)(child.stdout.listenerCount('data')).to.equal(0); (0, _chai.expect)(child.stderr.listenerCount('data')).to.equal(0); }); it('should map "Can\'t find a suitable configuration file" errors to NO_DOCKER_COMPOSE_FILE invariant', async function () { const client = (0, _client.createComposeClient)(makeNavy()); sandbox.stub(_fs.default, 'statAsync').resolves({}); execStub.rejects(new Error("Can't find a suitable configuration file")); let caught; try { await client.exec('config'); } catch (err) { caught = err; } (0, _chai.expect)(caught).to.be.an('error'); (0, _chai.expect)(caught.message).to.match(/NO_DOCKER_COMPOSE_FILE/); }); it('should rethrow other exec errors as-is', async function () { const client = (0, _client.createComposeClient)(makeNavy()); sandbox.stub(_fs.default, 'statAsync').resolves({}); const err = new Error('some other failure'); execStub.rejects(err); let caught; try { await client.exec('up'); } catch (e) { caught = e; } (0, _chai.expect)(caught).to.equal(err); }); it('should rethrow errors that have no message', async function () { const client = (0, _client.createComposeClient)(makeNavy()); sandbox.stub(_fs.default, 'statAsync').resolves({}); const err = {}; execStub.rejects(err); let caught; try { await client.exec('up'); } catch (e) { caught = e; } (0, _chai.expect)(caught).to.equal(err); }); }); });