navy 6.0.0 → 7.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/__tests__/config-provider.js +75 -0
- package/lib/__tests__/config.js +130 -0
- package/lib/__tests__/driver-logging.js +148 -0
- package/lib/__tests__/driver.js +19 -0
- package/lib/__tests__/errors.js +49 -0
- package/lib/__tests__/http-proxy.js +214 -0
- package/lib/__tests__/index.js +25 -0
- package/lib/__tests__/service.js +16 -0
- package/lib/cli/__tests__/develop.js +239 -0
- package/lib/cli/__tests__/external-ip.js +68 -0
- package/lib/cli/__tests__/health.js +257 -0
- package/lib/cli/__tests__/https.js +210 -0
- package/lib/cli/__tests__/import.js +110 -0
- package/lib/cli/__tests__/index.js +118 -0
- package/lib/cli/__tests__/lan-ip.js +90 -0
- package/lib/cli/__tests__/launch.js +179 -0
- package/lib/cli/__tests__/live.js +155 -0
- package/lib/cli/__tests__/local-ip.js +72 -0
- package/lib/cli/__tests__/logs.js +52 -0
- package/lib/cli/__tests__/open.js +65 -0
- package/lib/cli/__tests__/program.js +472 -0
- package/lib/cli/__tests__/ps.js +345 -0
- package/lib/cli/__tests__/refresh-config.js +95 -0
- package/lib/cli/__tests__/run.js +54 -0
- package/lib/cli/__tests__/status.js +204 -0
- package/lib/cli/__tests__/updates.js +243 -0
- package/lib/cli/__tests__/wait-for-healthy.js +134 -0
- package/lib/cli/config/__tests__/index.js +275 -0
- package/lib/cli/config/__tests__/wrapper.js +53 -0
- package/lib/cli/config/index.js +19 -37
- package/lib/cli/config/wrapper.js +0 -6
- package/lib/cli/develop.js +7 -21
- package/lib/cli/doctor/__tests__/clean-compose-files.js +78 -0
- package/lib/cli/doctor/__tests__/index.js +67 -0
- package/lib/cli/doctor/__tests__/invalid-compose-config.js +103 -0
- package/lib/cli/doctor/__tests__/invalid-state.js +83 -0
- package/lib/cli/doctor/__tests__/util.js +91 -0
- package/lib/cli/doctor/clean-compose-files.js +5 -13
- package/lib/cli/doctor/index.js +0 -12
- package/lib/cli/doctor/invalid-compose-config.js +0 -4
- package/lib/cli/doctor/invalid-state.js +0 -6
- package/lib/cli/doctor/util.js +0 -10
- package/lib/cli/external-ip.js +0 -4
- package/lib/cli/health.js +0 -12
- package/lib/cli/https.js +9 -25
- package/lib/cli/import.js +0 -12
- package/lib/cli/index.js +0 -9
- package/lib/cli/lan-ip.js +2 -8
- package/lib/cli/launch.js +0 -9
- package/lib/cli/live.js +6 -16
- package/lib/cli/local-ip.js +2 -7
- package/lib/cli/logs.js +0 -3
- package/lib/cli/open.js +2 -7
- package/lib/cli/program.js +73 -101
- package/lib/cli/ps.js +1 -21
- package/lib/cli/refresh-config.js +0 -7
- package/lib/cli/run.js +0 -3
- package/lib/cli/status.js +0 -11
- package/lib/cli/updates.js +0 -22
- package/lib/cli/util/__tests__/get-or-initialise-navy.js +66 -0
- package/lib/cli/util/__tests__/import.js +123 -0
- package/lib/cli/util/__tests__/index.js +17 -0
- package/lib/cli/util/__tests__/merge-action-options.js +47 -0
- package/lib/cli/util/__tests__/reconfigure.js +78 -0
- package/lib/cli/util/get-or-initialise-navy.js +0 -7
- package/lib/cli/util/import.js +0 -9
- package/lib/cli/util/index.js +0 -2
- package/lib/cli/util/merge-action-options.js +20 -0
- package/lib/cli/util/reconfigure.js +0 -4
- package/lib/cli/wait-for-healthy.js +0 -21
- package/lib/client/registry/__tests__/get-credentials.js +62 -0
- package/lib/client/registry/__tests__/get-endpoint.js +124 -0
- package/lib/client/registry/__tests__/get-fat-manifest.js +67 -0
- package/lib/client/registry/__tests__/get-token.js +66 -0
- package/lib/client/registry/__tests__/helpers.js +26 -0
- package/lib/client/registry/get-credentials.js +3 -9
- package/lib/client/registry/get-endpoint.js +29 -63
- package/lib/client/registry/get-fat-manifest.js +2 -9
- package/lib/client/registry/get-token.js +2 -13
- package/lib/client/registry/helpers.js +0 -4
- package/lib/config-provider.js +0 -12
- package/lib/config-providers/filesystem/__tests__/index.js +176 -0
- package/lib/config-providers/filesystem/index.js +5 -23
- package/lib/config-providers/npm/__tests__/index.js +226 -0
- package/lib/config-providers/npm/__tests__/util.js +1 -2
- package/lib/config-providers/npm/index.js +12 -35
- package/lib/config-providers/npm/util.js +0 -3
- package/lib/config.js +4 -19
- package/lib/domain/__tests__/container-image.js +81 -0
- package/lib/domain/__tests__/oci-api-specification.js +23 -0
- package/lib/domain/container-image.js +8 -21
- package/lib/domain/oci-api-specification.js +3 -5
- package/lib/driver-logging.js +0 -19
- package/lib/driver.js +0 -4
- package/lib/drivers/docker-compose/__tests__/client.js +249 -0
- package/lib/drivers/docker-compose/__tests__/index.js +430 -0
- package/lib/drivers/docker-compose/client.js +0 -16
- package/lib/drivers/docker-compose/index.js +7 -49
- package/lib/errors.js +0 -10
- package/lib/http-proxy.js +28 -23
- package/lib/index.js +1 -9
- package/lib/middleware/__tests__/add-service-proxy-config.js +258 -0
- package/lib/middleware/__tests__/develop.js +120 -0
- package/lib/middleware/__tests__/helpers.js +154 -0
- package/lib/middleware/__tests__/port-override.js +125 -0
- package/lib/middleware/__tests__/set-env-vars.js +94 -0
- package/lib/middleware/__tests__/set-image.js +76 -0
- package/lib/middleware/__tests__/set-logging-driver.js +94 -0
- package/lib/middleware/__tests__/tag-override.js +92 -0
- package/lib/middleware/add-service-proxy-config.js +8 -16
- package/lib/middleware/develop.js +2 -5
- package/lib/middleware/helpers.js +6 -12
- package/lib/middleware/port-override.js +5 -8
- package/lib/middleware/set-env-vars.js +6 -6
- package/lib/middleware/set-image.js +4 -5
- package/lib/middleware/set-logging-driver.js +6 -6
- package/lib/middleware/tag-override.js +4 -6
- package/lib/navy/__tests__/default-middleware.js +40 -0
- package/lib/navy/__tests__/index.js +1612 -0
- package/lib/navy/__tests__/middleware.js +71 -0
- package/lib/navy/__tests__/plugin-interface.js +121 -0
- package/lib/navy/__tests__/state.js +103 -0
- package/lib/navy/__tests__/util.js +24 -0
- package/lib/navy/default-middleware.js +0 -10
- package/lib/navy/index.js +83 -138
- package/lib/navy/middleware.js +0 -6
- package/lib/navy/plugin-interface.js +2 -10
- package/lib/navy/state.js +12 -24
- package/lib/navy/util.js +0 -1
- package/lib/service.js +2 -3
- package/lib/util/__tests__/exec-async.js +83 -0
- package/lib/util/__tests__/external-ip.js +97 -2
- package/lib/util/__tests__/get-lan-ip.js +46 -0
- package/lib/util/__tests__/has-update.js +136 -0
- package/lib/util/__tests__/https.js +301 -0
- package/lib/util/__tests__/navyrc.js +45 -0
- package/lib/util/__tests__/service-host.js +63 -5
- package/lib/util/__tests__/table.js +44 -0
- package/lib/util/docker-client.js +2 -10
- package/lib/util/exec-async.js +0 -4
- package/lib/util/external-ip.js +8 -12
- package/lib/util/fs.js +1 -6
- package/lib/util/get-lan-ip.js +0 -5
- package/lib/util/has-update.js +2 -14
- package/lib/util/https.js +11 -55
- package/lib/util/navyrc.js +0 -5
- package/lib/util/service-host.js +0 -17
- package/lib/util/table.js +0 -6
- package/package.json +14 -13
|
@@ -0,0 +1,1612 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
var _chai = require("chai");
|
|
5
|
+
var _sinon = _interopRequireDefault(require("sinon"));
|
|
6
|
+
var _proxyquire = _interopRequireDefault(require("proxyquire"));
|
|
7
|
+
var _errors = require("../../errors");
|
|
8
|
+
/* eslint-env mocha */
|
|
9
|
+
|
|
10
|
+
function loadNavyModule(stubs = {}) {
|
|
11
|
+
return _proxyquire.default.noCallThru()('../', {
|
|
12
|
+
'../util/fs': {
|
|
13
|
+
readdirAsync: _sinon.default.stub().resolves([]),
|
|
14
|
+
lstatSync: _sinon.default.stub().returns({
|
|
15
|
+
isDirectory: () => true
|
|
16
|
+
}),
|
|
17
|
+
...stubs.fs
|
|
18
|
+
},
|
|
19
|
+
'./state': {
|
|
20
|
+
getState: _sinon.default.stub().resolves(null),
|
|
21
|
+
saveState: _sinon.default.stub().resolves(),
|
|
22
|
+
deleteState: _sinon.default.stub().resolves(),
|
|
23
|
+
pathToNavys: _sinon.default.stub().returns('/home/test/.navy/navies'),
|
|
24
|
+
pathToNavy: _sinon.default.stub().callsFake(n => `/home/test/.navy/navies/${n}`),
|
|
25
|
+
...stubs.state
|
|
26
|
+
},
|
|
27
|
+
'../driver': {
|
|
28
|
+
resolveDriverFromName: _sinon.default.stub().returns(null),
|
|
29
|
+
...stubs.driver
|
|
30
|
+
},
|
|
31
|
+
'../config-provider': {
|
|
32
|
+
resolveConfigProviderFromName: _sinon.default.stub().returns(null),
|
|
33
|
+
...stubs.configProvider
|
|
34
|
+
},
|
|
35
|
+
'../config': {
|
|
36
|
+
getConfig: _sinon.default.stub().returns({
|
|
37
|
+
externalIP: null
|
|
38
|
+
}),
|
|
39
|
+
...stubs.config
|
|
40
|
+
},
|
|
41
|
+
'./plugin-interface': {
|
|
42
|
+
loadPlugins: _sinon.default.stub().resolves([]),
|
|
43
|
+
...stubs.pluginInterface
|
|
44
|
+
},
|
|
45
|
+
'./middleware': {
|
|
46
|
+
middlewareRunner: _sinon.default.stub().resolves(),
|
|
47
|
+
...stubs.middleware
|
|
48
|
+
},
|
|
49
|
+
'../http-proxy': {
|
|
50
|
+
reconfigureHTTPProxy: _sinon.default.stub().resolves(),
|
|
51
|
+
...stubs.httpProxy
|
|
52
|
+
},
|
|
53
|
+
'../util/external-ip': {
|
|
54
|
+
getExternalIP: _sinon.default.stub().resolves('127.0.0.1'),
|
|
55
|
+
...stubs.externalIP
|
|
56
|
+
},
|
|
57
|
+
'../util/service-host': {
|
|
58
|
+
createUrlForService: _sinon.default.stub().resolves('http://svc.local'),
|
|
59
|
+
getUrlFromService: _sinon.default.stub().returns(null),
|
|
60
|
+
...stubs.serviceHost
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function makeDriver(overrides = {}) {
|
|
65
|
+
return {
|
|
66
|
+
launch: _sinon.default.stub().resolves(),
|
|
67
|
+
destroy: _sinon.default.stub().resolves(),
|
|
68
|
+
ps: _sinon.default.stub().resolves([]),
|
|
69
|
+
start: _sinon.default.stub().resolves(),
|
|
70
|
+
stop: _sinon.default.stub().resolves(),
|
|
71
|
+
restart: _sinon.default.stub().resolves(),
|
|
72
|
+
kill: _sinon.default.stub().resolves(),
|
|
73
|
+
rm: _sinon.default.stub().resolves(),
|
|
74
|
+
update: _sinon.default.stub().resolves(),
|
|
75
|
+
spawnLogStream: _sinon.default.stub().resolves(),
|
|
76
|
+
port: _sinon.default.stub().resolves(null),
|
|
77
|
+
writeConfig: _sinon.default.stub().resolves(),
|
|
78
|
+
getConfig: _sinon.default.stub().resolves({
|
|
79
|
+
services: {}
|
|
80
|
+
}),
|
|
81
|
+
getLaunchedServiceNames: _sinon.default.stub().resolves([]),
|
|
82
|
+
getAvailableServiceNames: _sinon.default.stub().resolves([]),
|
|
83
|
+
...overrides
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
describe('navy/index', function () {
|
|
87
|
+
describe('Navy class', function () {
|
|
88
|
+
it('should normalise the navy name and store it', function () {
|
|
89
|
+
const {
|
|
90
|
+
Navy
|
|
91
|
+
} = loadNavyModule();
|
|
92
|
+
const navy = new Navy('My Env-1');
|
|
93
|
+
(0, _chai.expect)(navy.name).to.equal('My Env-1');
|
|
94
|
+
(0, _chai.expect)(navy.normalisedName).to.equal('MyEnv1');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('getNavy', function () {
|
|
98
|
+
it('should return a Navy instance for the given name', function () {
|
|
99
|
+
const {
|
|
100
|
+
getNavy
|
|
101
|
+
} = loadNavyModule();
|
|
102
|
+
const navy = getNavy('myenv');
|
|
103
|
+
(0, _chai.expect)(navy.name).to.equal('myenv');
|
|
104
|
+
});
|
|
105
|
+
it('should throw when navyName is not provided', function () {
|
|
106
|
+
const {
|
|
107
|
+
getNavy
|
|
108
|
+
} = loadNavyModule();
|
|
109
|
+
(0, _chai.expect)(() => getNavy()).to.throw(/NO_NAVY_PROVIDED/);
|
|
110
|
+
(0, _chai.expect)(() => getNavy(null)).to.throw(/NO_NAVY_PROVIDED/);
|
|
111
|
+
(0, _chai.expect)(() => getNavy('')).to.throw(/NO_NAVY_PROVIDED/);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('isInitialised', function () {
|
|
115
|
+
it('should return true when state exists', async function () {
|
|
116
|
+
const {
|
|
117
|
+
Navy
|
|
118
|
+
} = loadNavyModule({
|
|
119
|
+
state: {
|
|
120
|
+
getState: _sinon.default.stub().resolves({
|
|
121
|
+
driver: 'docker-compose'
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
(0, _chai.expect)(await new Navy('env').isInitialised()).to.equal(true);
|
|
126
|
+
});
|
|
127
|
+
it('should return false when state is null', async function () {
|
|
128
|
+
const {
|
|
129
|
+
Navy
|
|
130
|
+
} = loadNavyModule({
|
|
131
|
+
state: {
|
|
132
|
+
getState: _sinon.default.stub().resolves(null)
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
(0, _chai.expect)(await new Navy('env').isInitialised()).to.equal(false);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
describe('getState/saveState/initialise/delete', function () {
|
|
139
|
+
it('should delegate getState to state module', async function () {
|
|
140
|
+
const stub = _sinon.default.stub().resolves({
|
|
141
|
+
driver: 'docker-compose'
|
|
142
|
+
});
|
|
143
|
+
const {
|
|
144
|
+
Navy
|
|
145
|
+
} = loadNavyModule({
|
|
146
|
+
state: {
|
|
147
|
+
getState: stub
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
const result = await new Navy('env').getState();
|
|
151
|
+
(0, _chai.expect)(result).to.eql({
|
|
152
|
+
driver: 'docker-compose'
|
|
153
|
+
});
|
|
154
|
+
(0, _chai.expect)(stub.firstCall.args[0]).to.equal('env');
|
|
155
|
+
});
|
|
156
|
+
it('saveState should write state then run middleware', async function () {
|
|
157
|
+
const saveStub = _sinon.default.stub().resolves();
|
|
158
|
+
const middlewareStub = _sinon.default.stub().resolves();
|
|
159
|
+
const {
|
|
160
|
+
Navy
|
|
161
|
+
} = loadNavyModule({
|
|
162
|
+
state: {
|
|
163
|
+
saveState: saveStub
|
|
164
|
+
},
|
|
165
|
+
middleware: {
|
|
166
|
+
middlewareRunner: middlewareStub
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
const navy = new Navy('env');
|
|
170
|
+
const state = {
|
|
171
|
+
driver: 'docker-compose'
|
|
172
|
+
};
|
|
173
|
+
await navy.saveState(state);
|
|
174
|
+
(0, _chai.expect)(saveStub.calledWith('env', state)).to.equal(true);
|
|
175
|
+
(0, _chai.expect)(middlewareStub.calledOnce).to.equal(true);
|
|
176
|
+
});
|
|
177
|
+
it('initialise should write state with driver=docker-compose and provided opts', async function () {
|
|
178
|
+
const saveStub = _sinon.default.stub().resolves();
|
|
179
|
+
const {
|
|
180
|
+
Navy
|
|
181
|
+
} = loadNavyModule({
|
|
182
|
+
state: {
|
|
183
|
+
saveState: saveStub
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
await new Navy('env').initialise({
|
|
187
|
+
services: {
|
|
188
|
+
foo: {}
|
|
189
|
+
},
|
|
190
|
+
configProvider: 'filesystem'
|
|
191
|
+
});
|
|
192
|
+
const writtenState = saveStub.firstCall.args[1];
|
|
193
|
+
(0, _chai.expect)(writtenState).to.eql({
|
|
194
|
+
services: {
|
|
195
|
+
foo: {}
|
|
196
|
+
},
|
|
197
|
+
configProvider: 'filesystem',
|
|
198
|
+
driver: 'docker-compose'
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
it('delete should call deleteState', async function () {
|
|
202
|
+
const stub = _sinon.default.stub().resolves();
|
|
203
|
+
const {
|
|
204
|
+
Navy
|
|
205
|
+
} = loadNavyModule({
|
|
206
|
+
state: {
|
|
207
|
+
deleteState: stub
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
await new Navy('env').delete();
|
|
211
|
+
(0, _chai.expect)(stub.calledWith('env')).to.equal(true);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
describe('getDriver', function () {
|
|
215
|
+
it('should return null when state is missing', async function () {
|
|
216
|
+
const {
|
|
217
|
+
Navy
|
|
218
|
+
} = loadNavyModule();
|
|
219
|
+
(0, _chai.expect)(await new Navy('env').getDriver()).to.equal(null);
|
|
220
|
+
});
|
|
221
|
+
it('should return null when state has no driver', async function () {
|
|
222
|
+
const {
|
|
223
|
+
Navy
|
|
224
|
+
} = loadNavyModule({
|
|
225
|
+
state: {
|
|
226
|
+
getState: _sinon.default.stub().resolves({})
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
(0, _chai.expect)(await new Navy('env').getDriver()).to.equal(null);
|
|
230
|
+
});
|
|
231
|
+
it('should return null when the driver name is unknown', async function () {
|
|
232
|
+
const {
|
|
233
|
+
Navy
|
|
234
|
+
} = loadNavyModule({
|
|
235
|
+
state: {
|
|
236
|
+
getState: _sinon.default.stub().resolves({
|
|
237
|
+
driver: 'unknown'
|
|
238
|
+
})
|
|
239
|
+
},
|
|
240
|
+
driver: {
|
|
241
|
+
resolveDriverFromName: _sinon.default.stub().returns(null)
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
(0, _chai.expect)(await new Navy('env').getDriver()).to.equal(null);
|
|
245
|
+
});
|
|
246
|
+
it('should instantiate and return the driver from resolveDriverFromName', async function () {
|
|
247
|
+
const driverInstance = makeDriver();
|
|
248
|
+
const factory = _sinon.default.stub().returns(driverInstance);
|
|
249
|
+
const {
|
|
250
|
+
Navy
|
|
251
|
+
} = loadNavyModule({
|
|
252
|
+
state: {
|
|
253
|
+
getState: _sinon.default.stub().resolves({
|
|
254
|
+
driver: 'docker-compose'
|
|
255
|
+
})
|
|
256
|
+
},
|
|
257
|
+
driver: {
|
|
258
|
+
resolveDriverFromName: _sinon.default.stub().returns(factory)
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
const navy = new Navy('env');
|
|
262
|
+
(0, _chai.expect)(await navy.getDriver()).to.equal(driverInstance);
|
|
263
|
+
(0, _chai.expect)(factory.firstCall.args[0]).to.equal(navy);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
describe('safeGetDriver', function () {
|
|
267
|
+
it('should throw NavyNotInitialisedError when not initialised', async function () {
|
|
268
|
+
const {
|
|
269
|
+
Navy
|
|
270
|
+
} = loadNavyModule();
|
|
271
|
+
let caught;
|
|
272
|
+
try {
|
|
273
|
+
await new Navy('env').safeGetDriver();
|
|
274
|
+
} catch (e) {
|
|
275
|
+
caught = e;
|
|
276
|
+
}
|
|
277
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyNotInitialisedError);
|
|
278
|
+
});
|
|
279
|
+
it('should throw an invariant violation when state exists but no driver could be resolved', async function () {
|
|
280
|
+
const {
|
|
281
|
+
Navy
|
|
282
|
+
} = loadNavyModule({
|
|
283
|
+
state: {
|
|
284
|
+
getState: _sinon.default.stub().resolves({
|
|
285
|
+
driver: 'unknown'
|
|
286
|
+
})
|
|
287
|
+
},
|
|
288
|
+
driver: {
|
|
289
|
+
resolveDriverFromName: _sinon.default.stub().returns(null)
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
let caught;
|
|
293
|
+
try {
|
|
294
|
+
await new Navy('env').safeGetDriver();
|
|
295
|
+
} catch (e) {
|
|
296
|
+
caught = e;
|
|
297
|
+
}
|
|
298
|
+
(0, _chai.expect)(caught.message).to.match(/NO_DRIVER/);
|
|
299
|
+
});
|
|
300
|
+
it('should return the driver when initialised and resolvable', async function () {
|
|
301
|
+
const driverInstance = makeDriver();
|
|
302
|
+
const {
|
|
303
|
+
Navy
|
|
304
|
+
} = loadNavyModule({
|
|
305
|
+
state: {
|
|
306
|
+
getState: _sinon.default.stub().resolves({
|
|
307
|
+
driver: 'docker-compose'
|
|
308
|
+
})
|
|
309
|
+
},
|
|
310
|
+
driver: {
|
|
311
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driverInstance)
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
(0, _chai.expect)(await new Navy('env').safeGetDriver()).to.equal(driverInstance);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
describe('getConfigProvider', function () {
|
|
318
|
+
it('should return null when state has no configProvider', async function () {
|
|
319
|
+
const {
|
|
320
|
+
Navy
|
|
321
|
+
} = loadNavyModule({
|
|
322
|
+
state: {
|
|
323
|
+
getState: _sinon.default.stub().resolves({})
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
(0, _chai.expect)(await new Navy('env').getConfigProvider()).to.equal(null);
|
|
327
|
+
});
|
|
328
|
+
it('should return null when state is missing', async function () {
|
|
329
|
+
const {
|
|
330
|
+
Navy
|
|
331
|
+
} = loadNavyModule();
|
|
332
|
+
(0, _chai.expect)(await new Navy('env').getConfigProvider()).to.equal(null);
|
|
333
|
+
});
|
|
334
|
+
it('should return null when configProvider is unknown', async function () {
|
|
335
|
+
const {
|
|
336
|
+
Navy
|
|
337
|
+
} = loadNavyModule({
|
|
338
|
+
state: {
|
|
339
|
+
getState: _sinon.default.stub().resolves({
|
|
340
|
+
configProvider: 'unknown'
|
|
341
|
+
})
|
|
342
|
+
},
|
|
343
|
+
configProvider: {
|
|
344
|
+
resolveConfigProviderFromName: _sinon.default.stub().returns(null)
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
(0, _chai.expect)(await new Navy('env').getConfigProvider()).to.equal(null);
|
|
348
|
+
});
|
|
349
|
+
it('should instantiate the config provider from resolveConfigProviderFromName', async function () {
|
|
350
|
+
const provider = {
|
|
351
|
+
getNavyPath: async () => '/path'
|
|
352
|
+
};
|
|
353
|
+
const factory = _sinon.default.stub().returns(provider);
|
|
354
|
+
const {
|
|
355
|
+
Navy
|
|
356
|
+
} = loadNavyModule({
|
|
357
|
+
state: {
|
|
358
|
+
getState: _sinon.default.stub().resolves({
|
|
359
|
+
configProvider: 'filesystem'
|
|
360
|
+
})
|
|
361
|
+
},
|
|
362
|
+
configProvider: {
|
|
363
|
+
resolveConfigProviderFromName: _sinon.default.stub().returns(factory)
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
(0, _chai.expect)(await new Navy('env').getConfigProvider()).to.equal(provider);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
describe('getNavyFile', function () {
|
|
370
|
+
it('should throw NavyNotInitialisedError when not initialised', async function () {
|
|
371
|
+
const {
|
|
372
|
+
Navy
|
|
373
|
+
} = loadNavyModule();
|
|
374
|
+
let caught;
|
|
375
|
+
try {
|
|
376
|
+
await new Navy('env').getNavyFile();
|
|
377
|
+
} catch (e) {
|
|
378
|
+
caught = e;
|
|
379
|
+
}
|
|
380
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyNotInitialisedError);
|
|
381
|
+
});
|
|
382
|
+
it('should throw NO_CONFIG_PROVIDER when initialised but no provider', async function () {
|
|
383
|
+
const {
|
|
384
|
+
Navy
|
|
385
|
+
} = loadNavyModule({
|
|
386
|
+
state: {
|
|
387
|
+
getState: _sinon.default.stub().resolves({})
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
let caught;
|
|
391
|
+
try {
|
|
392
|
+
await new Navy('env').getNavyFile();
|
|
393
|
+
} catch (e) {
|
|
394
|
+
caught = e;
|
|
395
|
+
}
|
|
396
|
+
(0, _chai.expect)(caught.message).to.match(/NO_CONFIG_PROVIDER/);
|
|
397
|
+
});
|
|
398
|
+
it('should require the navy file path and return its module export', async function () {
|
|
399
|
+
const fakePath = '/abs/Navyfile.js';
|
|
400
|
+
const navyFile = {
|
|
401
|
+
plugins: ['p']
|
|
402
|
+
};
|
|
403
|
+
const provider = {
|
|
404
|
+
getNavyFilePath: async () => fakePath
|
|
405
|
+
};
|
|
406
|
+
const {
|
|
407
|
+
Navy
|
|
408
|
+
} = loadNavyModule({
|
|
409
|
+
state: {
|
|
410
|
+
getState: _sinon.default.stub().resolves({
|
|
411
|
+
configProvider: 'filesystem'
|
|
412
|
+
})
|
|
413
|
+
},
|
|
414
|
+
configProvider: {
|
|
415
|
+
resolveConfigProviderFromName: _sinon.default.stub().returns(() => provider)
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
const navy = new Navy('env');
|
|
419
|
+
const proxiedRequire = require('proxyquire').noCallThru();
|
|
420
|
+
const NavyMod = proxiedRequire('../', {
|
|
421
|
+
'../util/fs': {
|
|
422
|
+
readdirAsync: _sinon.default.stub().resolves([]),
|
|
423
|
+
lstatSync: _sinon.default.stub().returns({
|
|
424
|
+
isDirectory: () => true
|
|
425
|
+
})
|
|
426
|
+
},
|
|
427
|
+
'./state': {
|
|
428
|
+
getState: _sinon.default.stub().resolves({
|
|
429
|
+
configProvider: 'filesystem'
|
|
430
|
+
}),
|
|
431
|
+
saveState: _sinon.default.stub(),
|
|
432
|
+
deleteState: _sinon.default.stub(),
|
|
433
|
+
pathToNavys: _sinon.default.stub().returns('/'),
|
|
434
|
+
pathToNavy: _sinon.default.stub()
|
|
435
|
+
},
|
|
436
|
+
'../driver': {
|
|
437
|
+
resolveDriverFromName: _sinon.default.stub().returns(null)
|
|
438
|
+
},
|
|
439
|
+
'../config-provider': {
|
|
440
|
+
resolveConfigProviderFromName: _sinon.default.stub().returns(() => provider)
|
|
441
|
+
},
|
|
442
|
+
'../config': {
|
|
443
|
+
getConfig: _sinon.default.stub().returns({
|
|
444
|
+
externalIP: null
|
|
445
|
+
})
|
|
446
|
+
},
|
|
447
|
+
'./plugin-interface': {
|
|
448
|
+
loadPlugins: _sinon.default.stub().resolves([])
|
|
449
|
+
},
|
|
450
|
+
'./middleware': {
|
|
451
|
+
middlewareRunner: _sinon.default.stub().resolves()
|
|
452
|
+
},
|
|
453
|
+
'../http-proxy': {
|
|
454
|
+
reconfigureHTTPProxy: _sinon.default.stub().resolves()
|
|
455
|
+
},
|
|
456
|
+
'../util/external-ip': {
|
|
457
|
+
getExternalIP: _sinon.default.stub().resolves('127.0.0.1')
|
|
458
|
+
},
|
|
459
|
+
'../util/service-host': {
|
|
460
|
+
createUrlForService: _sinon.default.stub(),
|
|
461
|
+
getUrlFromService: _sinon.default.stub()
|
|
462
|
+
},
|
|
463
|
+
[fakePath]: navyFile
|
|
464
|
+
});
|
|
465
|
+
const fresh = new NavyMod.Navy('env');
|
|
466
|
+
(0, _chai.expect)(await fresh.getNavyFile()).to.equal(navyFile);
|
|
467
|
+
|
|
468
|
+
// also check that not-required path returns null
|
|
469
|
+
provider.getNavyFilePath = async () => '/does-not-exist/Navyfile.js';
|
|
470
|
+
(0, _chai.expect)(await navy.getNavyFile()).to.equal(null);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
describe('ensurePluginsLoaded', function () {
|
|
474
|
+
it('should be a no-op the second time when plugins are already loaded', async function () {
|
|
475
|
+
const loadStub = _sinon.default.stub().resolves([]);
|
|
476
|
+
const {
|
|
477
|
+
Navy
|
|
478
|
+
} = loadNavyModule({
|
|
479
|
+
pluginInterface: {
|
|
480
|
+
loadPlugins: loadStub
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
const navy = new Navy('env');
|
|
484
|
+
navy.getNavyFile = _sinon.default.stub().resolves({
|
|
485
|
+
plugins: []
|
|
486
|
+
});
|
|
487
|
+
await navy.ensurePluginsLoaded();
|
|
488
|
+
await navy.ensurePluginsLoaded();
|
|
489
|
+
(0, _chai.expect)(loadStub.calledOnce).to.equal(true);
|
|
490
|
+
(0, _chai.expect)(navy._pluginsLoaded).to.equal(true);
|
|
491
|
+
});
|
|
492
|
+
it('should not load plugins when navyFile is null', async function () {
|
|
493
|
+
const loadStub = _sinon.default.stub().resolves([]);
|
|
494
|
+
const provider = {
|
|
495
|
+
getNavyFilePath: async () => '/missing/Navyfile.js'
|
|
496
|
+
};
|
|
497
|
+
const {
|
|
498
|
+
Navy
|
|
499
|
+
} = loadNavyModule({
|
|
500
|
+
state: {
|
|
501
|
+
getState: _sinon.default.stub().resolves({
|
|
502
|
+
configProvider: 'filesystem'
|
|
503
|
+
})
|
|
504
|
+
},
|
|
505
|
+
configProvider: {
|
|
506
|
+
resolveConfigProviderFromName: _sinon.default.stub().returns(() => provider)
|
|
507
|
+
},
|
|
508
|
+
pluginInterface: {
|
|
509
|
+
loadPlugins: loadStub
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
await new Navy('env').ensurePluginsLoaded();
|
|
513
|
+
(0, _chai.expect)(loadStub.called).to.equal(false);
|
|
514
|
+
});
|
|
515
|
+
it('should call loadPlugins and mark _pluginsLoaded when navyFile is returned', async function () {
|
|
516
|
+
const loadStub = _sinon.default.stub().resolves([]);
|
|
517
|
+
const {
|
|
518
|
+
Navy
|
|
519
|
+
} = loadNavyModule({
|
|
520
|
+
pluginInterface: {
|
|
521
|
+
loadPlugins: loadStub
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
const navy = new Navy('env');
|
|
525
|
+
const navyFile = {
|
|
526
|
+
plugins: ['my-plugin']
|
|
527
|
+
};
|
|
528
|
+
navy.getNavyFile = _sinon.default.stub().resolves(navyFile);
|
|
529
|
+
await navy.ensurePluginsLoaded();
|
|
530
|
+
(0, _chai.expect)(loadStub.calledOnce).to.equal(true);
|
|
531
|
+
(0, _chai.expect)(loadStub.firstCall.args[0]).to.equal(navy);
|
|
532
|
+
(0, _chai.expect)(loadStub.firstCall.args[1]).to.equal(navyFile);
|
|
533
|
+
(0, _chai.expect)(navy._pluginsLoaded).to.equal(true);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
describe('registerCommand / invokeCommand', function () {
|
|
537
|
+
it('should run the registered action when invoked', async function () {
|
|
538
|
+
const {
|
|
539
|
+
Navy
|
|
540
|
+
} = loadNavyModule();
|
|
541
|
+
const navy = new Navy('env');
|
|
542
|
+
const action = _sinon.default.stub().resolves();
|
|
543
|
+
navy.registerCommand('foo', action);
|
|
544
|
+
await navy.invokeCommand('foo', ['a', 'b']);
|
|
545
|
+
(0, _chai.expect)(action.calledOnce).to.equal(true);
|
|
546
|
+
(0, _chai.expect)(action.firstCall.args[0]).to.eql(['a', 'b']);
|
|
547
|
+
});
|
|
548
|
+
it('should throw a NavyError for unknown commands', async function () {
|
|
549
|
+
const {
|
|
550
|
+
Navy
|
|
551
|
+
} = loadNavyModule();
|
|
552
|
+
let caught;
|
|
553
|
+
try {
|
|
554
|
+
await new Navy('env').invokeCommand('missing', []);
|
|
555
|
+
} catch (e) {
|
|
556
|
+
caught = e;
|
|
557
|
+
}
|
|
558
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
|
|
559
|
+
(0, _chai.expect)(caught.message).to.contain('Unknown command "missing"');
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
describe('registerMiddleware', function () {
|
|
563
|
+
it('should append the middleware to _registeredMiddleware', function () {
|
|
564
|
+
const {
|
|
565
|
+
Navy
|
|
566
|
+
} = loadNavyModule();
|
|
567
|
+
const navy = new Navy('env');
|
|
568
|
+
const fn = () => null;
|
|
569
|
+
navy.registerMiddleware(fn);
|
|
570
|
+
(0, _chai.expect)(navy._registeredMiddleware).to.eql([fn]);
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
describe('launch', function () {
|
|
574
|
+
function setupForLaunch({
|
|
575
|
+
available = ['api', 'web'],
|
|
576
|
+
launched = []
|
|
577
|
+
} = {}) {
|
|
578
|
+
const driver = makeDriver({
|
|
579
|
+
getAvailableServiceNames: _sinon.default.stub().resolves(available),
|
|
580
|
+
getLaunchedServiceNames: _sinon.default.stub().resolves(launched)
|
|
581
|
+
});
|
|
582
|
+
const provider = {
|
|
583
|
+
getNavyFilePath: async () => '/missing'
|
|
584
|
+
};
|
|
585
|
+
const reconfigureHTTPProxy = _sinon.default.stub().resolves();
|
|
586
|
+
const {
|
|
587
|
+
Navy
|
|
588
|
+
} = loadNavyModule({
|
|
589
|
+
state: {
|
|
590
|
+
getState: _sinon.default.stub().resolves({
|
|
591
|
+
driver: 'docker-compose',
|
|
592
|
+
configProvider: 'filesystem'
|
|
593
|
+
})
|
|
594
|
+
},
|
|
595
|
+
driver: {
|
|
596
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
597
|
+
},
|
|
598
|
+
configProvider: {
|
|
599
|
+
resolveConfigProviderFromName: _sinon.default.stub().returns(() => provider)
|
|
600
|
+
},
|
|
601
|
+
httpProxy: {
|
|
602
|
+
reconfigureHTTPProxy
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
return {
|
|
606
|
+
driver,
|
|
607
|
+
Navy,
|
|
608
|
+
reconfigureHTTPProxy
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
it('should default to launching the currently launched services when no list is given', async function () {
|
|
612
|
+
const {
|
|
613
|
+
driver,
|
|
614
|
+
Navy
|
|
615
|
+
} = setupForLaunch({
|
|
616
|
+
available: ['api'],
|
|
617
|
+
launched: ['api']
|
|
618
|
+
});
|
|
619
|
+
await new Navy('env').launch();
|
|
620
|
+
(0, _chai.expect)(driver.launch.calledOnce).to.equal(true);
|
|
621
|
+
(0, _chai.expect)(driver.launch.firstCall.args[0]).to.eql(['api']);
|
|
622
|
+
});
|
|
623
|
+
it('should filter out unknown service names with a warning', async function () {
|
|
624
|
+
const log = _sinon.default.stub(console, 'log');
|
|
625
|
+
try {
|
|
626
|
+
const {
|
|
627
|
+
driver,
|
|
628
|
+
Navy
|
|
629
|
+
} = setupForLaunch({
|
|
630
|
+
available: ['api']
|
|
631
|
+
});
|
|
632
|
+
await new Navy('env').launch(['api', 'unknown']);
|
|
633
|
+
(0, _chai.expect)(driver.launch.firstCall.args[0]).to.eql(['api']);
|
|
634
|
+
(0, _chai.expect)(log.calledWith('Unknown service name: unknown')).to.equal(true);
|
|
635
|
+
} finally {
|
|
636
|
+
log.restore();
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
it('should return early without launching when all requested services are invalid', async function () {
|
|
640
|
+
const log = _sinon.default.stub(console, 'log');
|
|
641
|
+
try {
|
|
642
|
+
const {
|
|
643
|
+
driver,
|
|
644
|
+
Navy
|
|
645
|
+
} = setupForLaunch({
|
|
646
|
+
available: ['api']
|
|
647
|
+
});
|
|
648
|
+
await new Navy('env').launch(['unknown1', 'unknown2']);
|
|
649
|
+
(0, _chai.expect)(driver.launch.called).to.equal(false);
|
|
650
|
+
} finally {
|
|
651
|
+
log.restore();
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
it('should reconfigure the HTTP proxy after launch', async function () {
|
|
655
|
+
const {
|
|
656
|
+
Navy,
|
|
657
|
+
reconfigureHTTPProxy
|
|
658
|
+
} = setupForLaunch({
|
|
659
|
+
available: ['api'],
|
|
660
|
+
launched: ['api']
|
|
661
|
+
});
|
|
662
|
+
await new Navy('env').launch(['api']);
|
|
663
|
+
(0, _chai.expect)(reconfigureHTTPProxy.calledOnce).to.equal(true);
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
describe('reconfigure / relaunch', function () {
|
|
667
|
+
it('reconfigure should be a no-op when no services are launched', async function () {
|
|
668
|
+
const {
|
|
669
|
+
Navy
|
|
670
|
+
} = loadNavyModule({
|
|
671
|
+
state: {
|
|
672
|
+
getState: _sinon.default.stub().resolves({
|
|
673
|
+
driver: 'docker-compose'
|
|
674
|
+
})
|
|
675
|
+
},
|
|
676
|
+
driver: {
|
|
677
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => makeDriver({
|
|
678
|
+
getLaunchedServiceNames: _sinon.default.stub().resolves([])
|
|
679
|
+
}))
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
await new Navy('env').reconfigure();
|
|
683
|
+
});
|
|
684
|
+
it('reconfigure should call launch when there are running services', async function () {
|
|
685
|
+
const driver = makeDriver({
|
|
686
|
+
getLaunchedServiceNames: _sinon.default.stub().resolves(['api']),
|
|
687
|
+
getAvailableServiceNames: _sinon.default.stub().resolves(['api'])
|
|
688
|
+
});
|
|
689
|
+
const provider = {
|
|
690
|
+
getNavyFilePath: async () => '/missing'
|
|
691
|
+
};
|
|
692
|
+
const {
|
|
693
|
+
Navy
|
|
694
|
+
} = loadNavyModule({
|
|
695
|
+
state: {
|
|
696
|
+
getState: _sinon.default.stub().resolves({
|
|
697
|
+
driver: 'docker-compose',
|
|
698
|
+
configProvider: 'filesystem'
|
|
699
|
+
})
|
|
700
|
+
},
|
|
701
|
+
driver: {
|
|
702
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
703
|
+
},
|
|
704
|
+
configProvider: {
|
|
705
|
+
resolveConfigProviderFromName: _sinon.default.stub().returns(() => provider)
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
await new Navy('env').reconfigure();
|
|
709
|
+
(0, _chai.expect)(driver.launch.calledOnce).to.equal(true);
|
|
710
|
+
});
|
|
711
|
+
it('relaunch should delegate to reconfigure', async function () {
|
|
712
|
+
const driver = makeDriver({
|
|
713
|
+
getLaunchedServiceNames: _sinon.default.stub().resolves([])
|
|
714
|
+
});
|
|
715
|
+
const {
|
|
716
|
+
Navy
|
|
717
|
+
} = loadNavyModule({
|
|
718
|
+
state: {
|
|
719
|
+
getState: _sinon.default.stub().resolves({
|
|
720
|
+
driver: 'docker-compose'
|
|
721
|
+
})
|
|
722
|
+
},
|
|
723
|
+
driver: {
|
|
724
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
await new Navy('env').relaunch();
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
describe('destroy', function () {
|
|
731
|
+
it('should throw NavyNotInitialisedError when not initialised', async function () {
|
|
732
|
+
const {
|
|
733
|
+
Navy
|
|
734
|
+
} = loadNavyModule();
|
|
735
|
+
let caught;
|
|
736
|
+
try {
|
|
737
|
+
await new Navy('env').destroy();
|
|
738
|
+
} catch (e) {
|
|
739
|
+
caught = e;
|
|
740
|
+
}
|
|
741
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyNotInitialisedError);
|
|
742
|
+
});
|
|
743
|
+
it('should reconfigure proxy excluding self, destroy the driver, and delete state', async function () {
|
|
744
|
+
const driver = makeDriver();
|
|
745
|
+
const provider = {
|
|
746
|
+
getNavyFilePath: async () => '/missing'
|
|
747
|
+
};
|
|
748
|
+
const reconfigureHTTPProxy = _sinon.default.stub().resolves();
|
|
749
|
+
const deleteState = _sinon.default.stub().resolves();
|
|
750
|
+
const {
|
|
751
|
+
Navy
|
|
752
|
+
} = loadNavyModule({
|
|
753
|
+
state: {
|
|
754
|
+
getState: _sinon.default.stub().resolves({
|
|
755
|
+
driver: 'docker-compose',
|
|
756
|
+
configProvider: 'filesystem'
|
|
757
|
+
}),
|
|
758
|
+
deleteState
|
|
759
|
+
},
|
|
760
|
+
driver: {
|
|
761
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
762
|
+
},
|
|
763
|
+
configProvider: {
|
|
764
|
+
resolveConfigProviderFromName: _sinon.default.stub().returns(() => provider)
|
|
765
|
+
},
|
|
766
|
+
httpProxy: {
|
|
767
|
+
reconfigureHTTPProxy
|
|
768
|
+
},
|
|
769
|
+
fs: {
|
|
770
|
+
readdirAsync: _sinon.default.stub().resolves(['env', 'other'])
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
await new Navy('env').destroy();
|
|
774
|
+
(0, _chai.expect)(reconfigureHTTPProxy.calledOnce).to.equal(true);
|
|
775
|
+
(0, _chai.expect)(reconfigureHTTPProxy.firstCall.args[0].navies).to.eql(['other']);
|
|
776
|
+
(0, _chai.expect)(driver.destroy.calledOnce).to.equal(true);
|
|
777
|
+
(0, _chai.expect)(deleteState.calledOnce).to.equal(true);
|
|
778
|
+
});
|
|
779
|
+
it('should still delete state even when driver.destroy throws', async function () {
|
|
780
|
+
const driver = makeDriver({
|
|
781
|
+
destroy: _sinon.default.stub().rejects(new Error('boom'))
|
|
782
|
+
});
|
|
783
|
+
const provider = {
|
|
784
|
+
getNavyFilePath: async () => '/missing'
|
|
785
|
+
};
|
|
786
|
+
const deleteState = _sinon.default.stub().resolves();
|
|
787
|
+
const {
|
|
788
|
+
Navy
|
|
789
|
+
} = loadNavyModule({
|
|
790
|
+
state: {
|
|
791
|
+
getState: _sinon.default.stub().resolves({
|
|
792
|
+
driver: 'docker-compose',
|
|
793
|
+
configProvider: 'filesystem'
|
|
794
|
+
}),
|
|
795
|
+
deleteState
|
|
796
|
+
},
|
|
797
|
+
driver: {
|
|
798
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
799
|
+
},
|
|
800
|
+
configProvider: {
|
|
801
|
+
resolveConfigProviderFromName: _sinon.default.stub().returns(() => provider)
|
|
802
|
+
},
|
|
803
|
+
httpProxy: {
|
|
804
|
+
reconfigureHTTPProxy: _sinon.default.stub().resolves()
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
await new Navy('env').destroy();
|
|
808
|
+
(0, _chai.expect)(deleteState.calledOnce).to.equal(true);
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
describe('start/stop/restart/kill/rm/update/spawnLogStream', function () {
|
|
812
|
+
function setup(driverOverrides = {}) {
|
|
813
|
+
const driver = makeDriver(driverOverrides);
|
|
814
|
+
const provider = {
|
|
815
|
+
getNavyFilePath: async () => '/missing'
|
|
816
|
+
};
|
|
817
|
+
const {
|
|
818
|
+
Navy
|
|
819
|
+
} = loadNavyModule({
|
|
820
|
+
state: {
|
|
821
|
+
getState: _sinon.default.stub().resolves({
|
|
822
|
+
driver: 'docker-compose',
|
|
823
|
+
configProvider: 'filesystem'
|
|
824
|
+
})
|
|
825
|
+
},
|
|
826
|
+
driver: {
|
|
827
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
828
|
+
},
|
|
829
|
+
configProvider: {
|
|
830
|
+
resolveConfigProviderFromName: _sinon.default.stub().returns(() => provider)
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
return {
|
|
834
|
+
driver,
|
|
835
|
+
navy: new Navy('env')
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
;
|
|
839
|
+
[{
|
|
840
|
+
method: 'start',
|
|
841
|
+
explicit: ['api']
|
|
842
|
+
}, {
|
|
843
|
+
method: 'stop',
|
|
844
|
+
explicit: ['api']
|
|
845
|
+
}, {
|
|
846
|
+
method: 'restart',
|
|
847
|
+
explicit: ['api']
|
|
848
|
+
}, {
|
|
849
|
+
method: 'kill',
|
|
850
|
+
explicit: ['api']
|
|
851
|
+
}].forEach(({
|
|
852
|
+
method,
|
|
853
|
+
explicit
|
|
854
|
+
}) => {
|
|
855
|
+
it(`${method} should pass the explicit list through to the driver`, async function () {
|
|
856
|
+
const {
|
|
857
|
+
driver,
|
|
858
|
+
navy
|
|
859
|
+
} = setup();
|
|
860
|
+
await navy[method](explicit);
|
|
861
|
+
(0, _chai.expect)(driver[method].calledOnce).to.equal(true);
|
|
862
|
+
(0, _chai.expect)(driver[method].firstCall.args[0]).to.eql(explicit);
|
|
863
|
+
});
|
|
864
|
+
it(`${method} should default to currently launched services when none provided`, async function () {
|
|
865
|
+
const {
|
|
866
|
+
driver,
|
|
867
|
+
navy
|
|
868
|
+
} = setup({
|
|
869
|
+
getLaunchedServiceNames: _sinon.default.stub().resolves(['api', 'web'])
|
|
870
|
+
});
|
|
871
|
+
await navy[method]();
|
|
872
|
+
(0, _chai.expect)(driver[method].firstCall.args[0]).to.eql(['api', 'web']);
|
|
873
|
+
});
|
|
874
|
+
});
|
|
875
|
+
it('rm should default to currently launched services when none provided', async function () {
|
|
876
|
+
const {
|
|
877
|
+
driver,
|
|
878
|
+
navy
|
|
879
|
+
} = setup({
|
|
880
|
+
getLaunchedServiceNames: _sinon.default.stub().resolves(['x'])
|
|
881
|
+
});
|
|
882
|
+
await navy.rm();
|
|
883
|
+
(0, _chai.expect)(driver.rm.firstCall.args[0]).to.eql(['x']);
|
|
884
|
+
});
|
|
885
|
+
it('rm should accept an explicit list', async function () {
|
|
886
|
+
const {
|
|
887
|
+
driver,
|
|
888
|
+
navy
|
|
889
|
+
} = setup();
|
|
890
|
+
await navy.rm(['x']);
|
|
891
|
+
(0, _chai.expect)(driver.rm.firstCall.args[0]).to.eql(['x']);
|
|
892
|
+
});
|
|
893
|
+
it('update should default to all available services', async function () {
|
|
894
|
+
const {
|
|
895
|
+
driver,
|
|
896
|
+
navy
|
|
897
|
+
} = setup({
|
|
898
|
+
getAvailableServiceNames: _sinon.default.stub().resolves(['a', 'b'])
|
|
899
|
+
});
|
|
900
|
+
await navy.update();
|
|
901
|
+
(0, _chai.expect)(driver.update.firstCall.args[0]).to.eql(['a', 'b']);
|
|
902
|
+
});
|
|
903
|
+
it('update should accept an explicit list', async function () {
|
|
904
|
+
const {
|
|
905
|
+
driver,
|
|
906
|
+
navy
|
|
907
|
+
} = setup();
|
|
908
|
+
await navy.update(['x']);
|
|
909
|
+
(0, _chai.expect)(driver.update.firstCall.args[0]).to.eql(['x']);
|
|
910
|
+
});
|
|
911
|
+
it('spawnLogStream should default to currently launched services when none provided', async function () {
|
|
912
|
+
const {
|
|
913
|
+
driver,
|
|
914
|
+
navy
|
|
915
|
+
} = setup({
|
|
916
|
+
getLaunchedServiceNames: _sinon.default.stub().resolves(['l'])
|
|
917
|
+
});
|
|
918
|
+
await navy.spawnLogStream();
|
|
919
|
+
(0, _chai.expect)(driver.spawnLogStream.firstCall.args[0]).to.eql(['l']);
|
|
920
|
+
});
|
|
921
|
+
it('spawnLogStream should accept an explicit list', async function () {
|
|
922
|
+
const {
|
|
923
|
+
driver,
|
|
924
|
+
navy
|
|
925
|
+
} = setup();
|
|
926
|
+
await navy.spawnLogStream(['l']);
|
|
927
|
+
(0, _chai.expect)(driver.spawnLogStream.firstCall.args[0]).to.eql(['l']);
|
|
928
|
+
});
|
|
929
|
+
it('start should reconfigure the HTTP proxy', async function () {
|
|
930
|
+
const driver = makeDriver({
|
|
931
|
+
getLaunchedServiceNames: _sinon.default.stub().resolves(['api'])
|
|
932
|
+
});
|
|
933
|
+
const provider = {
|
|
934
|
+
getNavyFilePath: async () => '/missing'
|
|
935
|
+
};
|
|
936
|
+
const reconfigure = _sinon.default.stub().resolves();
|
|
937
|
+
const {
|
|
938
|
+
Navy
|
|
939
|
+
} = loadNavyModule({
|
|
940
|
+
state: {
|
|
941
|
+
getState: _sinon.default.stub().resolves({
|
|
942
|
+
driver: 'docker-compose',
|
|
943
|
+
configProvider: 'filesystem'
|
|
944
|
+
})
|
|
945
|
+
},
|
|
946
|
+
driver: {
|
|
947
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
948
|
+
},
|
|
949
|
+
configProvider: {
|
|
950
|
+
resolveConfigProviderFromName: _sinon.default.stub().returns(() => provider)
|
|
951
|
+
},
|
|
952
|
+
httpProxy: {
|
|
953
|
+
reconfigureHTTPProxy: reconfigure
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
await new Navy('env').start(['api']);
|
|
957
|
+
(0, _chai.expect)(reconfigure.calledOnce).to.equal(true);
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
describe('useTag/resetTag/usePort/resetPort', function () {
|
|
961
|
+
function setupTagPort({
|
|
962
|
+
initialState = {
|
|
963
|
+
driver: 'docker-compose',
|
|
964
|
+
configProvider: 'filesystem',
|
|
965
|
+
services: {}
|
|
966
|
+
}
|
|
967
|
+
} = {}) {
|
|
968
|
+
const driver = makeDriver({
|
|
969
|
+
getAvailableServiceNames: _sinon.default.stub().resolves(['api']),
|
|
970
|
+
getLaunchedServiceNames: _sinon.default.stub().resolves([])
|
|
971
|
+
});
|
|
972
|
+
let currentState = initialState;
|
|
973
|
+
const getStateStub = _sinon.default.stub().callsFake(async () => currentState);
|
|
974
|
+
const saveStub = _sinon.default.stub().callsFake(async (envName, state) => {
|
|
975
|
+
currentState = state;
|
|
976
|
+
});
|
|
977
|
+
const provider = {
|
|
978
|
+
getNavyFilePath: async () => '/missing'
|
|
979
|
+
};
|
|
980
|
+
const {
|
|
981
|
+
Navy
|
|
982
|
+
} = loadNavyModule({
|
|
983
|
+
state: {
|
|
984
|
+
getState: getStateStub,
|
|
985
|
+
saveState: saveStub
|
|
986
|
+
},
|
|
987
|
+
driver: {
|
|
988
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
989
|
+
},
|
|
990
|
+
configProvider: {
|
|
991
|
+
resolveConfigProviderFromName: _sinon.default.stub().returns(() => provider)
|
|
992
|
+
}
|
|
993
|
+
});
|
|
994
|
+
return {
|
|
995
|
+
Navy,
|
|
996
|
+
driver,
|
|
997
|
+
saveStub
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
it('useTag should record the tag in state, kill the service, and update it', async function () {
|
|
1001
|
+
const {
|
|
1002
|
+
Navy,
|
|
1003
|
+
driver,
|
|
1004
|
+
saveStub
|
|
1005
|
+
} = setupTagPort();
|
|
1006
|
+
await new Navy('env').useTag('api', 'v2');
|
|
1007
|
+
const written = saveStub.firstCall.args[1];
|
|
1008
|
+
(0, _chai.expect)(written.services.api._tag).to.equal('v2');
|
|
1009
|
+
(0, _chai.expect)(driver.kill.firstCall.args[0]).to.eql(['api']);
|
|
1010
|
+
(0, _chai.expect)(driver.update.firstCall.args[0]).to.eql(['api']);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
// NOTE: useTag/resetTag/usePort/resetPort all start with
|
|
1014
|
+
// `(await this.getState()) || {}` and write the result back as the new
|
|
1015
|
+
// state. If state was never initialised, the saved state has no `driver`
|
|
1016
|
+
// key, and the subsequent kill/launch then fails with a NO_DRIVER invariant.
|
|
1017
|
+
// We therefore exercise these methods only with a properly initialised state.
|
|
1018
|
+
it('useTag should preserve other services in state.services when modifying one', async function () {
|
|
1019
|
+
const initial = {
|
|
1020
|
+
driver: 'docker-compose',
|
|
1021
|
+
configProvider: 'filesystem',
|
|
1022
|
+
services: {
|
|
1023
|
+
other: {
|
|
1024
|
+
_tag: 'keep-me'
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
const {
|
|
1029
|
+
Navy,
|
|
1030
|
+
saveStub
|
|
1031
|
+
} = setupTagPort({
|
|
1032
|
+
initialState: initial
|
|
1033
|
+
});
|
|
1034
|
+
await new Navy('env').useTag('api', 'v2');
|
|
1035
|
+
const written = saveStub.firstCall.args[1];
|
|
1036
|
+
(0, _chai.expect)(written.services.other).to.eql({
|
|
1037
|
+
_tag: 'keep-me'
|
|
1038
|
+
});
|
|
1039
|
+
(0, _chai.expect)(written.services.api._tag).to.equal('v2');
|
|
1040
|
+
});
|
|
1041
|
+
it('resetTag should clear the tag and relaunch the service with noDeps', async function () {
|
|
1042
|
+
const {
|
|
1043
|
+
Navy,
|
|
1044
|
+
driver,
|
|
1045
|
+
saveStub
|
|
1046
|
+
} = setupTagPort({
|
|
1047
|
+
initialState: {
|
|
1048
|
+
driver: 'docker-compose',
|
|
1049
|
+
configProvider: 'filesystem',
|
|
1050
|
+
services: {
|
|
1051
|
+
api: {
|
|
1052
|
+
_tag: 'old'
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
await new Navy('env').resetTag('api');
|
|
1058
|
+
(0, _chai.expect)(saveStub.firstCall.args[1].services.api._tag).to.equal(undefined);
|
|
1059
|
+
(0, _chai.expect)(driver.launch.firstCall.args[1]).to.eql({
|
|
1060
|
+
noDeps: true
|
|
1061
|
+
});
|
|
1062
|
+
});
|
|
1063
|
+
it('resetTag should preserve services other than the one being reset', async function () {
|
|
1064
|
+
const {
|
|
1065
|
+
Navy,
|
|
1066
|
+
saveStub
|
|
1067
|
+
} = setupTagPort({
|
|
1068
|
+
initialState: {
|
|
1069
|
+
driver: 'docker-compose',
|
|
1070
|
+
configProvider: 'filesystem',
|
|
1071
|
+
services: {
|
|
1072
|
+
api: {
|
|
1073
|
+
_tag: 'old'
|
|
1074
|
+
},
|
|
1075
|
+
other: {
|
|
1076
|
+
_tag: 'keep'
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
await new Navy('env').resetTag('api');
|
|
1082
|
+
const written = saveStub.firstCall.args[1];
|
|
1083
|
+
(0, _chai.expect)(written.services.other).to.eql({
|
|
1084
|
+
_tag: 'keep'
|
|
1085
|
+
});
|
|
1086
|
+
(0, _chai.expect)(written.services.api._tag).to.equal(undefined);
|
|
1087
|
+
});
|
|
1088
|
+
it('usePort should record port mapping in state', async function () {
|
|
1089
|
+
const {
|
|
1090
|
+
Navy,
|
|
1091
|
+
saveStub
|
|
1092
|
+
} = setupTagPort();
|
|
1093
|
+
await new Navy('env').usePort('api', 80, 8080);
|
|
1094
|
+
(0, _chai.expect)(saveStub.firstCall.args[1].services.api._ports).to.eql({
|
|
1095
|
+
80: 8080
|
|
1096
|
+
});
|
|
1097
|
+
});
|
|
1098
|
+
it('usePort should merge the new port mapping with existing ports for the service', async function () {
|
|
1099
|
+
const {
|
|
1100
|
+
Navy,
|
|
1101
|
+
saveStub
|
|
1102
|
+
} = setupTagPort({
|
|
1103
|
+
initialState: {
|
|
1104
|
+
driver: 'docker-compose',
|
|
1105
|
+
configProvider: 'filesystem',
|
|
1106
|
+
services: {
|
|
1107
|
+
api: {
|
|
1108
|
+
_ports: {
|
|
1109
|
+
443: 4433
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
await new Navy('env').usePort('api', 80, 8080);
|
|
1116
|
+
const ports = saveStub.firstCall.args[1].services.api._ports;
|
|
1117
|
+
(0, _chai.expect)(ports).to.eql({
|
|
1118
|
+
80: 8080,
|
|
1119
|
+
443: 4433
|
|
1120
|
+
});
|
|
1121
|
+
});
|
|
1122
|
+
it('resetPort should set the port to undefined', async function () {
|
|
1123
|
+
const {
|
|
1124
|
+
Navy,
|
|
1125
|
+
saveStub
|
|
1126
|
+
} = setupTagPort({
|
|
1127
|
+
initialState: {
|
|
1128
|
+
driver: 'docker-compose',
|
|
1129
|
+
configProvider: 'filesystem',
|
|
1130
|
+
services: {
|
|
1131
|
+
api: {
|
|
1132
|
+
_ports: {
|
|
1133
|
+
80: 8080
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
await new Navy('env').resetPort('api', 80);
|
|
1140
|
+
(0, _chai.expect)(saveStub.firstCall.args[1].services.api._ports[80]).to.equal(undefined);
|
|
1141
|
+
});
|
|
1142
|
+
it('resetPort should preserve other ports on the same service', async function () {
|
|
1143
|
+
const {
|
|
1144
|
+
Navy,
|
|
1145
|
+
saveStub
|
|
1146
|
+
} = setupTagPort({
|
|
1147
|
+
initialState: {
|
|
1148
|
+
driver: 'docker-compose',
|
|
1149
|
+
configProvider: 'filesystem',
|
|
1150
|
+
services: {
|
|
1151
|
+
api: {
|
|
1152
|
+
_ports: {
|
|
1153
|
+
80: 8080,
|
|
1154
|
+
443: 4433
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
await new Navy('env').resetPort('api', 80);
|
|
1161
|
+
const ports = saveStub.firstCall.args[1].services.api._ports;
|
|
1162
|
+
(0, _chai.expect)(ports[80]).to.equal(undefined);
|
|
1163
|
+
(0, _chai.expect)(ports[443]).to.equal(4433);
|
|
1164
|
+
});
|
|
1165
|
+
it('useTag should throw NavyNotInitialisedError when state is missing', async function () {
|
|
1166
|
+
const {
|
|
1167
|
+
Navy
|
|
1168
|
+
} = loadNavyModule();
|
|
1169
|
+
let caught;
|
|
1170
|
+
try {
|
|
1171
|
+
await new Navy('env').useTag('api', 'v1');
|
|
1172
|
+
} catch (e) {
|
|
1173
|
+
caught = e;
|
|
1174
|
+
}
|
|
1175
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyNotInitialisedError);
|
|
1176
|
+
});
|
|
1177
|
+
it('resetTag should throw NavyNotInitialisedError when state is missing', async function () {
|
|
1178
|
+
const {
|
|
1179
|
+
Navy
|
|
1180
|
+
} = loadNavyModule();
|
|
1181
|
+
let caught;
|
|
1182
|
+
try {
|
|
1183
|
+
await new Navy('env').resetTag('api');
|
|
1184
|
+
} catch (e) {
|
|
1185
|
+
caught = e;
|
|
1186
|
+
}
|
|
1187
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyNotInitialisedError);
|
|
1188
|
+
});
|
|
1189
|
+
it('usePort should throw NavyNotInitialisedError when state is missing', async function () {
|
|
1190
|
+
const {
|
|
1191
|
+
Navy
|
|
1192
|
+
} = loadNavyModule();
|
|
1193
|
+
let caught;
|
|
1194
|
+
try {
|
|
1195
|
+
await new Navy('env').usePort('api', 80, 8080);
|
|
1196
|
+
} catch (e) {
|
|
1197
|
+
caught = e;
|
|
1198
|
+
}
|
|
1199
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyNotInitialisedError);
|
|
1200
|
+
});
|
|
1201
|
+
it('resetPort should throw NavyNotInitialisedError when state is missing', async function () {
|
|
1202
|
+
const {
|
|
1203
|
+
Navy
|
|
1204
|
+
} = loadNavyModule();
|
|
1205
|
+
let caught;
|
|
1206
|
+
try {
|
|
1207
|
+
await new Navy('env').resetPort('api', 80);
|
|
1208
|
+
} catch (e) {
|
|
1209
|
+
caught = e;
|
|
1210
|
+
}
|
|
1211
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyNotInitialisedError);
|
|
1212
|
+
});
|
|
1213
|
+
it('useTag should treat missing state.services as an empty services map', async function () {
|
|
1214
|
+
const {
|
|
1215
|
+
Navy,
|
|
1216
|
+
saveStub
|
|
1217
|
+
} = setupTagPort({
|
|
1218
|
+
initialState: {
|
|
1219
|
+
driver: 'docker-compose',
|
|
1220
|
+
configProvider: 'filesystem'
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
await new Navy('env').useTag('api', 'v2');
|
|
1224
|
+
(0, _chai.expect)(saveStub.firstCall.args[1].services.api._tag).to.equal('v2');
|
|
1225
|
+
});
|
|
1226
|
+
it('resetTag should treat missing state.services as an empty services map', async function () {
|
|
1227
|
+
const {
|
|
1228
|
+
Navy,
|
|
1229
|
+
saveStub
|
|
1230
|
+
} = setupTagPort({
|
|
1231
|
+
initialState: {
|
|
1232
|
+
driver: 'docker-compose',
|
|
1233
|
+
configProvider: 'filesystem'
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
await new Navy('env').resetTag('api');
|
|
1237
|
+
(0, _chai.expect)(saveStub.firstCall.args[1].services.api._tag).to.equal(undefined);
|
|
1238
|
+
});
|
|
1239
|
+
it('usePort should treat missing state.services as an empty services map', async function () {
|
|
1240
|
+
const {
|
|
1241
|
+
Navy,
|
|
1242
|
+
saveStub
|
|
1243
|
+
} = setupTagPort({
|
|
1244
|
+
initialState: {
|
|
1245
|
+
driver: 'docker-compose',
|
|
1246
|
+
configProvider: 'filesystem'
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
await new Navy('env').usePort('api', 80, 8080);
|
|
1250
|
+
(0, _chai.expect)(saveStub.firstCall.args[1].services.api._ports).to.eql({
|
|
1251
|
+
80: 8080
|
|
1252
|
+
});
|
|
1253
|
+
});
|
|
1254
|
+
it('resetPort should treat missing state.services as an empty services map', async function () {
|
|
1255
|
+
const {
|
|
1256
|
+
Navy,
|
|
1257
|
+
saveStub
|
|
1258
|
+
} = setupTagPort({
|
|
1259
|
+
initialState: {
|
|
1260
|
+
driver: 'docker-compose',
|
|
1261
|
+
configProvider: 'filesystem'
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
await new Navy('env').resetPort('api', 80);
|
|
1265
|
+
(0, _chai.expect)(saveStub.firstCall.args[1].services.api._ports[80]).to.equal(undefined);
|
|
1266
|
+
});
|
|
1267
|
+
});
|
|
1268
|
+
describe('externalIP', function () {
|
|
1269
|
+
it('should return the IP from getExternalIP using config.externalIP', async function () {
|
|
1270
|
+
const {
|
|
1271
|
+
Navy
|
|
1272
|
+
} = loadNavyModule({
|
|
1273
|
+
config: {
|
|
1274
|
+
getConfig: _sinon.default.stub().returns({
|
|
1275
|
+
externalIP: 'configured.host'
|
|
1276
|
+
})
|
|
1277
|
+
},
|
|
1278
|
+
externalIP: {
|
|
1279
|
+
getExternalIP: _sinon.default.stub().resolves('1.2.3.4')
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
(0, _chai.expect)(await new Navy('env').externalIP()).to.equal('1.2.3.4');
|
|
1283
|
+
});
|
|
1284
|
+
});
|
|
1285
|
+
describe('port', function () {
|
|
1286
|
+
it('should delegate to driver.port with the privatePort and index defaulting to 1', async function () {
|
|
1287
|
+
const driver = makeDriver({
|
|
1288
|
+
port: _sinon.default.stub().resolves(8080)
|
|
1289
|
+
});
|
|
1290
|
+
const {
|
|
1291
|
+
Navy
|
|
1292
|
+
} = loadNavyModule({
|
|
1293
|
+
state: {
|
|
1294
|
+
getState: _sinon.default.stub().resolves({
|
|
1295
|
+
driver: 'docker-compose'
|
|
1296
|
+
})
|
|
1297
|
+
},
|
|
1298
|
+
driver: {
|
|
1299
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
(0, _chai.expect)(await new Navy('env').port('api', 80)).to.equal(8080);
|
|
1303
|
+
(0, _chai.expect)(driver.port.firstCall.args).to.eql(['api', 80, 1]);
|
|
1304
|
+
});
|
|
1305
|
+
});
|
|
1306
|
+
describe('url', function () {
|
|
1307
|
+
it('should return getUrlFromService when there is a running service URL', async function () {
|
|
1308
|
+
const driver = makeDriver({
|
|
1309
|
+
ps: _sinon.default.stub().resolves([{
|
|
1310
|
+
name: 'api'
|
|
1311
|
+
}])
|
|
1312
|
+
});
|
|
1313
|
+
const {
|
|
1314
|
+
Navy
|
|
1315
|
+
} = loadNavyModule({
|
|
1316
|
+
state: {
|
|
1317
|
+
getState: _sinon.default.stub().resolves({
|
|
1318
|
+
driver: 'docker-compose'
|
|
1319
|
+
})
|
|
1320
|
+
},
|
|
1321
|
+
driver: {
|
|
1322
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
1323
|
+
},
|
|
1324
|
+
serviceHost: {
|
|
1325
|
+
getUrlFromService: _sinon.default.stub().returns('http://from-service'),
|
|
1326
|
+
createUrlForService: _sinon.default.stub().resolves('http://fallback')
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1329
|
+
(0, _chai.expect)(await new Navy('env').url('api')).to.equal('http://from-service');
|
|
1330
|
+
});
|
|
1331
|
+
it('should fall back to createUrlForService when getUrlFromService returns null', async function () {
|
|
1332
|
+
const driver = makeDriver({
|
|
1333
|
+
ps: _sinon.default.stub().resolves([{
|
|
1334
|
+
name: 'api'
|
|
1335
|
+
}])
|
|
1336
|
+
});
|
|
1337
|
+
const {
|
|
1338
|
+
Navy
|
|
1339
|
+
} = loadNavyModule({
|
|
1340
|
+
state: {
|
|
1341
|
+
getState: _sinon.default.stub().resolves({
|
|
1342
|
+
driver: 'docker-compose'
|
|
1343
|
+
})
|
|
1344
|
+
},
|
|
1345
|
+
driver: {
|
|
1346
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
1347
|
+
},
|
|
1348
|
+
serviceHost: {
|
|
1349
|
+
getUrlFromService: _sinon.default.stub().returns(null),
|
|
1350
|
+
createUrlForService: _sinon.default.stub().resolves('http://fallback')
|
|
1351
|
+
},
|
|
1352
|
+
externalIP: {
|
|
1353
|
+
getExternalIP: _sinon.default.stub().resolves('1.2.3.4')
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
(0, _chai.expect)(await new Navy('env').url('api')).to.equal('http://fallback');
|
|
1357
|
+
});
|
|
1358
|
+
});
|
|
1359
|
+
describe('ps / getLaunchedServiceNames / getAvailableServiceNames', function () {
|
|
1360
|
+
it('ps should delegate to the driver', async function () {
|
|
1361
|
+
const driver = makeDriver({
|
|
1362
|
+
ps: _sinon.default.stub().resolves([{
|
|
1363
|
+
name: 'api'
|
|
1364
|
+
}])
|
|
1365
|
+
});
|
|
1366
|
+
const {
|
|
1367
|
+
Navy
|
|
1368
|
+
} = loadNavyModule({
|
|
1369
|
+
state: {
|
|
1370
|
+
getState: _sinon.default.stub().resolves({
|
|
1371
|
+
driver: 'docker-compose'
|
|
1372
|
+
})
|
|
1373
|
+
},
|
|
1374
|
+
driver: {
|
|
1375
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
(0, _chai.expect)(await new Navy('env').ps()).to.eql([{
|
|
1379
|
+
name: 'api'
|
|
1380
|
+
}]);
|
|
1381
|
+
});
|
|
1382
|
+
it('getLaunchedServiceNames should delegate to driver', async function () {
|
|
1383
|
+
const driver = makeDriver({
|
|
1384
|
+
getLaunchedServiceNames: _sinon.default.stub().resolves(['x'])
|
|
1385
|
+
});
|
|
1386
|
+
const {
|
|
1387
|
+
Navy
|
|
1388
|
+
} = loadNavyModule({
|
|
1389
|
+
state: {
|
|
1390
|
+
getState: _sinon.default.stub().resolves({
|
|
1391
|
+
driver: 'docker-compose'
|
|
1392
|
+
})
|
|
1393
|
+
},
|
|
1394
|
+
driver: {
|
|
1395
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1398
|
+
(0, _chai.expect)(await new Navy('env').getLaunchedServiceNames()).to.eql(['x']);
|
|
1399
|
+
});
|
|
1400
|
+
it('getAvailableServiceNames should delegate to driver', async function () {
|
|
1401
|
+
const driver = makeDriver({
|
|
1402
|
+
getAvailableServiceNames: _sinon.default.stub().resolves(['x', 'y'])
|
|
1403
|
+
});
|
|
1404
|
+
const {
|
|
1405
|
+
Navy
|
|
1406
|
+
} = loadNavyModule({
|
|
1407
|
+
state: {
|
|
1408
|
+
getState: _sinon.default.stub().resolves({
|
|
1409
|
+
driver: 'docker-compose'
|
|
1410
|
+
})
|
|
1411
|
+
},
|
|
1412
|
+
driver: {
|
|
1413
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
(0, _chai.expect)(await new Navy('env').getAvailableServiceNames()).to.eql(['x', 'y']);
|
|
1417
|
+
});
|
|
1418
|
+
});
|
|
1419
|
+
describe('waitForHealthy', function () {
|
|
1420
|
+
function setupHealth({
|
|
1421
|
+
ps = []
|
|
1422
|
+
} = {}) {
|
|
1423
|
+
const driver = makeDriver({
|
|
1424
|
+
ps: _sinon.default.stub().resolves(ps)
|
|
1425
|
+
});
|
|
1426
|
+
const {
|
|
1427
|
+
Navy
|
|
1428
|
+
} = loadNavyModule({
|
|
1429
|
+
state: {
|
|
1430
|
+
getState: _sinon.default.stub().resolves({
|
|
1431
|
+
driver: 'docker-compose'
|
|
1432
|
+
})
|
|
1433
|
+
},
|
|
1434
|
+
driver: {
|
|
1435
|
+
resolveDriverFromName: _sinon.default.stub().returns(() => driver)
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
return new Navy('env');
|
|
1439
|
+
}
|
|
1440
|
+
it('should resolve true when all specified services are healthy', async function () {
|
|
1441
|
+
const navy = setupHealth({
|
|
1442
|
+
ps: [{
|
|
1443
|
+
name: 'api',
|
|
1444
|
+
raw: {
|
|
1445
|
+
State: {
|
|
1446
|
+
Health: {
|
|
1447
|
+
Status: 'healthy'
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}]
|
|
1452
|
+
});
|
|
1453
|
+
const result = await navy.waitForHealthy(['api'], null, {
|
|
1454
|
+
factor: 1,
|
|
1455
|
+
retries: 1,
|
|
1456
|
+
minTimeout: 1
|
|
1457
|
+
});
|
|
1458
|
+
(0, _chai.expect)(result).to.equal(true);
|
|
1459
|
+
});
|
|
1460
|
+
it('should default services to all containers with health checks when none provided', async function () {
|
|
1461
|
+
const navy = setupHealth({
|
|
1462
|
+
ps: [{
|
|
1463
|
+
name: 'api',
|
|
1464
|
+
raw: {
|
|
1465
|
+
State: {
|
|
1466
|
+
Health: {
|
|
1467
|
+
Status: 'healthy'
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
}, {
|
|
1472
|
+
name: 'noh',
|
|
1473
|
+
raw: {
|
|
1474
|
+
State: {}
|
|
1475
|
+
}
|
|
1476
|
+
}]
|
|
1477
|
+
});
|
|
1478
|
+
const result = await navy.waitForHealthy(undefined, null, {
|
|
1479
|
+
factor: 1,
|
|
1480
|
+
retries: 1,
|
|
1481
|
+
minTimeout: 1
|
|
1482
|
+
});
|
|
1483
|
+
(0, _chai.expect)(result).to.equal(true);
|
|
1484
|
+
});
|
|
1485
|
+
it('should call progressCallback with health status of each service', async function () {
|
|
1486
|
+
const navy = setupHealth({
|
|
1487
|
+
ps: [{
|
|
1488
|
+
name: 'api',
|
|
1489
|
+
raw: {
|
|
1490
|
+
State: {
|
|
1491
|
+
Health: {
|
|
1492
|
+
Status: 'healthy'
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}]
|
|
1497
|
+
});
|
|
1498
|
+
const cb = _sinon.default.spy();
|
|
1499
|
+
await navy.waitForHealthy(['api'], cb, {
|
|
1500
|
+
factor: 1,
|
|
1501
|
+
retries: 1,
|
|
1502
|
+
minTimeout: 1
|
|
1503
|
+
});
|
|
1504
|
+
(0, _chai.expect)(cb.calledOnce).to.equal(true);
|
|
1505
|
+
(0, _chai.expect)(cb.firstCall.args[0]).to.eql([{
|
|
1506
|
+
service: 'api',
|
|
1507
|
+
health: 'healthy'
|
|
1508
|
+
}]);
|
|
1509
|
+
});
|
|
1510
|
+
it('should throw a NavyError describing services missing health checks', async function () {
|
|
1511
|
+
const navy = setupHealth({
|
|
1512
|
+
ps: [{
|
|
1513
|
+
name: 'api',
|
|
1514
|
+
raw: {
|
|
1515
|
+
State: {}
|
|
1516
|
+
}
|
|
1517
|
+
}]
|
|
1518
|
+
});
|
|
1519
|
+
let caught;
|
|
1520
|
+
try {
|
|
1521
|
+
await navy.waitForHealthy(['api'], null, {
|
|
1522
|
+
factor: 1,
|
|
1523
|
+
retries: 1,
|
|
1524
|
+
minTimeout: 1
|
|
1525
|
+
});
|
|
1526
|
+
} catch (e) {
|
|
1527
|
+
caught = e;
|
|
1528
|
+
}
|
|
1529
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
|
|
1530
|
+
(0, _chai.expect)(caught.message).to.contain("don't have health checks");
|
|
1531
|
+
(0, _chai.expect)(caught.message).to.contain('api');
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
// NOTE: Currently `waitForHealthy` calls `retry('Unhealthy services: ...')`
|
|
1535
|
+
// with a string. After retries exhaust, that string is what is thrown, then
|
|
1536
|
+
// `catch (e) { throw new NavyError(e.message) }` reads `.message` on the
|
|
1537
|
+
// string, which is undefined. The NavyError that surfaces therefore has no
|
|
1538
|
+
// message. This test asserts the type only and documents the lost message.
|
|
1539
|
+
it('should throw a NavyError after retries exhausted with unhealthy services', async function () {
|
|
1540
|
+
this.timeout(10000);
|
|
1541
|
+
const navy = setupHealth({
|
|
1542
|
+
ps: [{
|
|
1543
|
+
name: 'api',
|
|
1544
|
+
raw: {
|
|
1545
|
+
State: {
|
|
1546
|
+
Health: {
|
|
1547
|
+
Status: 'starting'
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
}]
|
|
1552
|
+
});
|
|
1553
|
+
let caught;
|
|
1554
|
+
try {
|
|
1555
|
+
await navy.waitForHealthy(['api'], null, {
|
|
1556
|
+
factor: 1,
|
|
1557
|
+
retries: 2,
|
|
1558
|
+
minTimeout: 1
|
|
1559
|
+
});
|
|
1560
|
+
} catch (e) {
|
|
1561
|
+
caught = e;
|
|
1562
|
+
}
|
|
1563
|
+
(0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
|
|
1564
|
+
});
|
|
1565
|
+
});
|
|
1566
|
+
describe('getLaunchedNavies / getLaunchedNavyNames', function () {
|
|
1567
|
+
it('getLaunchedNavyNames should return readdir entries', async function () {
|
|
1568
|
+
const {
|
|
1569
|
+
getLaunchedNavyNames
|
|
1570
|
+
} = loadNavyModule({
|
|
1571
|
+
fs: {
|
|
1572
|
+
readdirAsync: _sinon.default.stub().resolves(['env1', 'env2'])
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
(0, _chai.expect)(await getLaunchedNavyNames()).to.eql(['env1', 'env2']);
|
|
1576
|
+
});
|
|
1577
|
+
it('getLaunchedNavyNames should return [] when readdir fails', async function () {
|
|
1578
|
+
const {
|
|
1579
|
+
getLaunchedNavyNames
|
|
1580
|
+
} = loadNavyModule({
|
|
1581
|
+
fs: {
|
|
1582
|
+
readdirAsync: _sinon.default.stub().rejects(new Error('ENOENT'))
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
(0, _chai.expect)(await getLaunchedNavyNames()).to.eql([]);
|
|
1586
|
+
});
|
|
1587
|
+
it('getLaunchedNavies should return Navy instances for each navy directory', async function () {
|
|
1588
|
+
const {
|
|
1589
|
+
getLaunchedNavies
|
|
1590
|
+
} = loadNavyModule({
|
|
1591
|
+
fs: {
|
|
1592
|
+
readdirAsync: _sinon.default.stub().resolves(['env1', 'env2', 'file.txt']),
|
|
1593
|
+
lstatSync: _sinon.default.stub().callsFake(p => ({
|
|
1594
|
+
isDirectory: () => !p.endsWith('file.txt')
|
|
1595
|
+
}))
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
const navies = await getLaunchedNavies();
|
|
1599
|
+
(0, _chai.expect)(navies.map(n => n.name)).to.eql(['env1', 'env2']);
|
|
1600
|
+
});
|
|
1601
|
+
it('getLaunchedNavies should return [] when readdir fails', async function () {
|
|
1602
|
+
const {
|
|
1603
|
+
getLaunchedNavies
|
|
1604
|
+
} = loadNavyModule({
|
|
1605
|
+
fs: {
|
|
1606
|
+
readdirAsync: _sinon.default.stub().rejects(new Error('ENOENT'))
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
(0, _chai.expect)(await getLaunchedNavies()).to.eql([]);
|
|
1610
|
+
});
|
|
1611
|
+
});
|
|
1612
|
+
});
|