navy
Version:
Quick and powerful development environments using Docker and Docker Compose
249 lines (247 loc) • 11.3 kB
JavaScript
;
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);
});
});
});