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.
Files changed (149) hide show
  1. package/lib/__tests__/config-provider.js +75 -0
  2. package/lib/__tests__/config.js +130 -0
  3. package/lib/__tests__/driver-logging.js +148 -0
  4. package/lib/__tests__/driver.js +19 -0
  5. package/lib/__tests__/errors.js +49 -0
  6. package/lib/__tests__/http-proxy.js +214 -0
  7. package/lib/__tests__/index.js +25 -0
  8. package/lib/__tests__/service.js +16 -0
  9. package/lib/cli/__tests__/develop.js +239 -0
  10. package/lib/cli/__tests__/external-ip.js +68 -0
  11. package/lib/cli/__tests__/health.js +257 -0
  12. package/lib/cli/__tests__/https.js +210 -0
  13. package/lib/cli/__tests__/import.js +110 -0
  14. package/lib/cli/__tests__/index.js +118 -0
  15. package/lib/cli/__tests__/lan-ip.js +90 -0
  16. package/lib/cli/__tests__/launch.js +179 -0
  17. package/lib/cli/__tests__/live.js +155 -0
  18. package/lib/cli/__tests__/local-ip.js +72 -0
  19. package/lib/cli/__tests__/logs.js +52 -0
  20. package/lib/cli/__tests__/open.js +65 -0
  21. package/lib/cli/__tests__/program.js +472 -0
  22. package/lib/cli/__tests__/ps.js +345 -0
  23. package/lib/cli/__tests__/refresh-config.js +95 -0
  24. package/lib/cli/__tests__/run.js +54 -0
  25. package/lib/cli/__tests__/status.js +204 -0
  26. package/lib/cli/__tests__/updates.js +243 -0
  27. package/lib/cli/__tests__/wait-for-healthy.js +134 -0
  28. package/lib/cli/config/__tests__/index.js +275 -0
  29. package/lib/cli/config/__tests__/wrapper.js +53 -0
  30. package/lib/cli/config/index.js +19 -37
  31. package/lib/cli/config/wrapper.js +0 -6
  32. package/lib/cli/develop.js +7 -21
  33. package/lib/cli/doctor/__tests__/clean-compose-files.js +78 -0
  34. package/lib/cli/doctor/__tests__/index.js +67 -0
  35. package/lib/cli/doctor/__tests__/invalid-compose-config.js +103 -0
  36. package/lib/cli/doctor/__tests__/invalid-state.js +83 -0
  37. package/lib/cli/doctor/__tests__/util.js +91 -0
  38. package/lib/cli/doctor/clean-compose-files.js +5 -13
  39. package/lib/cli/doctor/index.js +0 -12
  40. package/lib/cli/doctor/invalid-compose-config.js +0 -4
  41. package/lib/cli/doctor/invalid-state.js +0 -6
  42. package/lib/cli/doctor/util.js +0 -10
  43. package/lib/cli/external-ip.js +0 -4
  44. package/lib/cli/health.js +0 -12
  45. package/lib/cli/https.js +9 -25
  46. package/lib/cli/import.js +0 -12
  47. package/lib/cli/index.js +0 -9
  48. package/lib/cli/lan-ip.js +2 -8
  49. package/lib/cli/launch.js +0 -9
  50. package/lib/cli/live.js +6 -16
  51. package/lib/cli/local-ip.js +2 -7
  52. package/lib/cli/logs.js +0 -3
  53. package/lib/cli/open.js +2 -7
  54. package/lib/cli/program.js +73 -101
  55. package/lib/cli/ps.js +1 -21
  56. package/lib/cli/refresh-config.js +0 -7
  57. package/lib/cli/run.js +0 -3
  58. package/lib/cli/status.js +0 -11
  59. package/lib/cli/updates.js +0 -22
  60. package/lib/cli/util/__tests__/get-or-initialise-navy.js +66 -0
  61. package/lib/cli/util/__tests__/import.js +123 -0
  62. package/lib/cli/util/__tests__/index.js +17 -0
  63. package/lib/cli/util/__tests__/merge-action-options.js +47 -0
  64. package/lib/cli/util/__tests__/reconfigure.js +78 -0
  65. package/lib/cli/util/get-or-initialise-navy.js +0 -7
  66. package/lib/cli/util/import.js +0 -9
  67. package/lib/cli/util/index.js +0 -2
  68. package/lib/cli/util/merge-action-options.js +20 -0
  69. package/lib/cli/util/reconfigure.js +0 -4
  70. package/lib/cli/wait-for-healthy.js +0 -21
  71. package/lib/client/registry/__tests__/get-credentials.js +62 -0
  72. package/lib/client/registry/__tests__/get-endpoint.js +124 -0
  73. package/lib/client/registry/__tests__/get-fat-manifest.js +67 -0
  74. package/lib/client/registry/__tests__/get-token.js +66 -0
  75. package/lib/client/registry/__tests__/helpers.js +26 -0
  76. package/lib/client/registry/get-credentials.js +3 -9
  77. package/lib/client/registry/get-endpoint.js +29 -63
  78. package/lib/client/registry/get-fat-manifest.js +2 -9
  79. package/lib/client/registry/get-token.js +2 -13
  80. package/lib/client/registry/helpers.js +0 -4
  81. package/lib/config-provider.js +0 -12
  82. package/lib/config-providers/filesystem/__tests__/index.js +176 -0
  83. package/lib/config-providers/filesystem/index.js +5 -23
  84. package/lib/config-providers/npm/__tests__/index.js +226 -0
  85. package/lib/config-providers/npm/__tests__/util.js +1 -2
  86. package/lib/config-providers/npm/index.js +12 -35
  87. package/lib/config-providers/npm/util.js +0 -3
  88. package/lib/config.js +4 -19
  89. package/lib/domain/__tests__/container-image.js +81 -0
  90. package/lib/domain/__tests__/oci-api-specification.js +23 -0
  91. package/lib/domain/container-image.js +8 -21
  92. package/lib/domain/oci-api-specification.js +3 -5
  93. package/lib/driver-logging.js +0 -19
  94. package/lib/driver.js +0 -4
  95. package/lib/drivers/docker-compose/__tests__/client.js +249 -0
  96. package/lib/drivers/docker-compose/__tests__/index.js +430 -0
  97. package/lib/drivers/docker-compose/client.js +0 -16
  98. package/lib/drivers/docker-compose/index.js +7 -49
  99. package/lib/errors.js +0 -10
  100. package/lib/http-proxy.js +28 -23
  101. package/lib/index.js +1 -9
  102. package/lib/middleware/__tests__/add-service-proxy-config.js +258 -0
  103. package/lib/middleware/__tests__/develop.js +120 -0
  104. package/lib/middleware/__tests__/helpers.js +154 -0
  105. package/lib/middleware/__tests__/port-override.js +125 -0
  106. package/lib/middleware/__tests__/set-env-vars.js +94 -0
  107. package/lib/middleware/__tests__/set-image.js +76 -0
  108. package/lib/middleware/__tests__/set-logging-driver.js +94 -0
  109. package/lib/middleware/__tests__/tag-override.js +92 -0
  110. package/lib/middleware/add-service-proxy-config.js +8 -16
  111. package/lib/middleware/develop.js +2 -5
  112. package/lib/middleware/helpers.js +6 -12
  113. package/lib/middleware/port-override.js +5 -8
  114. package/lib/middleware/set-env-vars.js +6 -6
  115. package/lib/middleware/set-image.js +4 -5
  116. package/lib/middleware/set-logging-driver.js +6 -6
  117. package/lib/middleware/tag-override.js +4 -6
  118. package/lib/navy/__tests__/default-middleware.js +40 -0
  119. package/lib/navy/__tests__/index.js +1612 -0
  120. package/lib/navy/__tests__/middleware.js +71 -0
  121. package/lib/navy/__tests__/plugin-interface.js +121 -0
  122. package/lib/navy/__tests__/state.js +103 -0
  123. package/lib/navy/__tests__/util.js +24 -0
  124. package/lib/navy/default-middleware.js +0 -10
  125. package/lib/navy/index.js +83 -138
  126. package/lib/navy/middleware.js +0 -6
  127. package/lib/navy/plugin-interface.js +2 -10
  128. package/lib/navy/state.js +12 -24
  129. package/lib/navy/util.js +0 -1
  130. package/lib/service.js +2 -3
  131. package/lib/util/__tests__/exec-async.js +83 -0
  132. package/lib/util/__tests__/external-ip.js +97 -2
  133. package/lib/util/__tests__/get-lan-ip.js +46 -0
  134. package/lib/util/__tests__/has-update.js +136 -0
  135. package/lib/util/__tests__/https.js +301 -0
  136. package/lib/util/__tests__/navyrc.js +45 -0
  137. package/lib/util/__tests__/service-host.js +63 -5
  138. package/lib/util/__tests__/table.js +44 -0
  139. package/lib/util/docker-client.js +2 -10
  140. package/lib/util/exec-async.js +0 -4
  141. package/lib/util/external-ip.js +8 -12
  142. package/lib/util/fs.js +1 -6
  143. package/lib/util/get-lan-ip.js +0 -5
  144. package/lib/util/has-update.js +2 -14
  145. package/lib/util/https.js +11 -55
  146. package/lib/util/navyrc.js +0 -5
  147. package/lib/util/service-host.js +0 -17
  148. package/lib/util/table.js +0 -6
  149. package/package.json +14 -13
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ var _chai = require("chai");
4
+ var _service = require("../service");
5
+ /* eslint-env mocha */
6
+
7
+ describe('service', function () {
8
+ describe('Status', function () {
9
+ it('should expose RUNNING and EXITED string constants', function () {
10
+ (0, _chai.expect)(_service.Status).to.eql({
11
+ RUNNING: 'running',
12
+ EXITED: 'exited'
13
+ });
14
+ });
15
+ });
16
+ });
@@ -0,0 +1,239 @@
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
+ describe('cli/develop', function () {
11
+ let sandbox;
12
+ let getNavyStub;
13
+ let navyStub;
14
+ let getNavyRcStub;
15
+ let dockerStub;
16
+ let containerObj;
17
+ let consoleLogStub;
18
+ let originalCwd;
19
+ let developCli;
20
+ beforeEach(function () {
21
+ sandbox = _sinon.default.createSandbox();
22
+ navyStub = {
23
+ name: 'env-1',
24
+ getState: sandbox.stub().resolves({}),
25
+ saveState: sandbox.stub().resolves(),
26
+ emitAsync: sandbox.stub().resolves(),
27
+ kill: sandbox.stub().resolves(),
28
+ launch: sandbox.stub().resolves(),
29
+ ps: sandbox.stub().resolves([{
30
+ name: 'web',
31
+ id: 'abc123'
32
+ }])
33
+ };
34
+ getNavyStub = sandbox.stub().returns(navyStub);
35
+ getNavyRcStub = sandbox.stub().resolves({
36
+ services: ['web'],
37
+ develop: {
38
+ mounts: {
39
+ './src': '/app/src'
40
+ },
41
+ command: 'npm start'
42
+ }
43
+ });
44
+ containerObj = {
45
+ attach: sandbox.stub().resolves('stream'),
46
+ modem: {
47
+ demuxStream: sandbox.stub()
48
+ }
49
+ };
50
+ dockerStub = {
51
+ getContainer: sandbox.stub().returns(containerObj)
52
+ };
53
+ consoleLogStub = sandbox.stub(console, 'log');
54
+ originalCwd = process.cwd;
55
+ sandbox.stub(process, 'cwd').returns('/work');
56
+ developCli = _proxyquire.default.noCallThru()('../develop', {
57
+ '../': {
58
+ getNavy: getNavyStub
59
+ },
60
+ '../errors': {
61
+ NavyError: _errors.NavyError
62
+ },
63
+ '../util/docker-client': dockerStub,
64
+ '../util/navyrc': getNavyRcStub
65
+ });
66
+ });
67
+ afterEach(function () {
68
+ process.cwd = originalCwd;
69
+ sandbox.restore();
70
+ });
71
+ describe('default export', function () {
72
+ it('should throw a NavyError when navyrc is null', async function () {
73
+ getNavyRcStub.resolves(null);
74
+ let caught;
75
+ try {
76
+ await developCli('web', {
77
+ navy: 'env-1'
78
+ });
79
+ } catch (err) {
80
+ caught = err;
81
+ }
82
+ (0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
83
+ (0, _chai.expect)(caught.message).to.contain('No valid .navyrc');
84
+ });
85
+ it('should throw a NavyError when navyrc has no services', async function () {
86
+ getNavyRcStub.resolves({
87
+ services: undefined
88
+ });
89
+ let caught;
90
+ try {
91
+ await developCli('web', {
92
+ navy: 'env-1'
93
+ });
94
+ } catch (err) {
95
+ caught = err;
96
+ }
97
+ (0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
98
+ });
99
+ it('should throw a NavyError when develop key is missing', async function () {
100
+ getNavyRcStub.resolves({
101
+ services: ['web']
102
+ });
103
+ let caught;
104
+ try {
105
+ await developCli('web', {
106
+ navy: 'env-1'
107
+ });
108
+ } catch (err) {
109
+ caught = err;
110
+ }
111
+ (0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
112
+ (0, _chai.expect)(caught.message).to.contain('No develop mounts');
113
+ });
114
+ it('should throw when multiple services are listed and none specified', async function () {
115
+ getNavyRcStub.resolves({
116
+ services: ['web', 'api'],
117
+ develop: {
118
+ mounts: {},
119
+ command: 'x'
120
+ }
121
+ });
122
+ let caught;
123
+ try {
124
+ await developCli(undefined, {
125
+ navy: 'env-1'
126
+ });
127
+ } catch (err) {
128
+ caught = err;
129
+ }
130
+ (0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
131
+ (0, _chai.expect)(caught.message).to.contain('Multiple service mappings');
132
+ });
133
+ it('should infer the only service when one is listed', async function () {
134
+ await developCli(undefined, {
135
+ navy: 'env-1'
136
+ });
137
+ (0, _chai.expect)(navyStub.kill.calledWith(['web'])).to.equal(true);
138
+ });
139
+ it('should throw when the service is not in the navyrc list', async function () {
140
+ let caught;
141
+ try {
142
+ await developCli('unknown', {
143
+ navy: 'env-1'
144
+ });
145
+ } catch (err) {
146
+ caught = err;
147
+ }
148
+ (0, _chai.expect)(caught).to.be.instanceof(_errors.NavyError);
149
+ (0, _chai.expect)(caught.message).to.contain('not a valid development target');
150
+ });
151
+ it('should resolve mount keys to absolute paths and persist them in state', async function () {
152
+ await developCli('web', {
153
+ navy: 'env-1'
154
+ });
155
+ (0, _chai.expect)(navyStub.saveState.calledOnce).to.equal(true);
156
+ const newState = navyStub.saveState.firstCall.args[0];
157
+ const mountKeys = Object.keys(newState.services.web._develop.mounts);
158
+ (0, _chai.expect)(mountKeys[0].endsWith('src')).to.equal(true);
159
+ (0, _chai.expect)(newState.services.web._develop.command).to.equal('npm start');
160
+ });
161
+ it('should preserve existing state.services entries on save', async function () {
162
+ navyStub.getState.resolves({
163
+ services: {
164
+ api: {
165
+ _tag: 'v1'
166
+ }
167
+ }
168
+ });
169
+ await developCli('web', {
170
+ navy: 'env-1'
171
+ });
172
+ const newState = navyStub.saveState.firstCall.args[0];
173
+ (0, _chai.expect)(newState.services.api).to.eql({
174
+ _tag: 'v1'
175
+ });
176
+ });
177
+ it('should default to {} when navy.getState returns null', async function () {
178
+ navyStub.getState.resolves(null);
179
+ await developCli('web', {
180
+ navy: 'env-1'
181
+ });
182
+ const newState = navyStub.saveState.firstCall.args[0];
183
+ // eslint-disable-next-line no-unused-expressions
184
+ (0, _chai.expect)(newState.services.web._develop).to.exist;
185
+ });
186
+ it('should kill, then launch the service in develop mode', async function () {
187
+ await developCli('web', {
188
+ navy: 'env-1'
189
+ });
190
+ (0, _chai.expect)(navyStub.emitAsync.calledWith('cli.develop.beforeLaunch')).to.equal(true);
191
+ (0, _chai.expect)(navyStub.kill.calledOnce).to.equal(true);
192
+ (0, _chai.expect)(navyStub.launch.calledOnce).to.equal(true);
193
+ (0, _chai.expect)(navyStub.launch.firstCall.args[0]).to.eql(['web']);
194
+ (0, _chai.expect)(navyStub.launch.firstCall.args[1]).to.eql({
195
+ noDeps: true
196
+ });
197
+ });
198
+ it('should attach to the container and demux the stream', async function () {
199
+ await developCli('web', {
200
+ navy: 'env-1'
201
+ });
202
+ (0, _chai.expect)(dockerStub.getContainer.calledWith('abc123')).to.equal(true);
203
+ (0, _chai.expect)(containerObj.attach.calledOnce).to.equal(true);
204
+
205
+ // Wait for the .then callback to fire on the attach promise
206
+ await new Promise(resolve => setImmediate(resolve));
207
+ (0, _chai.expect)(containerObj.modem.demuxStream.calledOnce).to.equal(true);
208
+ (0, _chai.expect)(containerObj.modem.demuxStream.firstCall.args[0]).to.equal('stream');
209
+ });
210
+ it('should log a graceful exit message when attach rejects', async function () {
211
+ containerObj.attach.rejects(new Error('attach failed'));
212
+ await developCli('web', {
213
+ navy: 'env-1'
214
+ });
215
+
216
+ // Wait microtask flush
217
+ await new Promise(resolve => setImmediate(resolve));
218
+ await new Promise(resolve => setImmediate(resolve));
219
+ const printed = consoleLogStub.getCalls().map(c => c.args[0] || '').join('\n');
220
+ (0, _chai.expect)(printed).to.contain('exited');
221
+ });
222
+ it('should throw an invariant violation when no container is found for the service', async function () {
223
+ navyStub.ps.resolves([{
224
+ name: 'other',
225
+ id: 'xyz'
226
+ }]);
227
+ let caught;
228
+ try {
229
+ await developCli('web', {
230
+ navy: 'env-1'
231
+ });
232
+ } catch (err) {
233
+ caught = err;
234
+ }
235
+ (0, _chai.expect)(caught).to.be.an('error');
236
+ (0, _chai.expect)(caught.message).to.contain('DEVELOP_NO_CONTAINER_ID');
237
+ });
238
+ });
239
+ });
@@ -0,0 +1,68 @@
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
+ /* eslint-env mocha */
8
+
9
+ describe('cli/external-ip', function () {
10
+ let sandbox;
11
+ let getConfigStub;
12
+ let getExternalIPStub;
13
+ let consoleLogStub;
14
+ let externalIpCli;
15
+ beforeEach(function () {
16
+ sandbox = _sinon.default.createSandbox();
17
+ getConfigStub = sandbox.stub();
18
+ getExternalIPStub = sandbox.stub();
19
+ consoleLogStub = sandbox.stub(console, 'log');
20
+ externalIpCli = _proxyquire.default.noCallThru()('../external-ip', {
21
+ '../config': {
22
+ getConfig: getConfigStub
23
+ },
24
+ '../util/external-ip': {
25
+ getExternalIP: getExternalIPStub
26
+ }
27
+ });
28
+ });
29
+ afterEach(function () {
30
+ sandbox.restore();
31
+ });
32
+ describe('default export', function () {
33
+ it('should pass the externalIP value from config to getExternalIP', async function () {
34
+ getConfigStub.returns({
35
+ externalIP: 'configured.host'
36
+ });
37
+ getExternalIPStub.resolves('1.2.3.4');
38
+ await externalIpCli();
39
+ (0, _chai.expect)(getConfigStub.calledOnce).to.equal(true);
40
+ (0, _chai.expect)(getExternalIPStub.calledOnce).to.equal(true);
41
+ (0, _chai.expect)(getExternalIPStub.firstCall.args[0]).to.equal('configured.host');
42
+ });
43
+ it('should log the resolved external IP', async function () {
44
+ getConfigStub.returns({
45
+ externalIP: null
46
+ });
47
+ getExternalIPStub.resolves('5.6.7.8');
48
+ await externalIpCli();
49
+ (0, _chai.expect)(consoleLogStub.calledOnce).to.equal(true);
50
+ (0, _chai.expect)(consoleLogStub.firstCall.args[0]).to.equal('5.6.7.8');
51
+ });
52
+ it('should pass undefined when config has no externalIP key', async function () {
53
+ getConfigStub.returns({});
54
+ getExternalIPStub.resolves('127.0.0.1');
55
+ await externalIpCli();
56
+ (0, _chai.expect)(getExternalIPStub.firstCall.args[0]).to.equal(undefined);
57
+ });
58
+ it('should support an asynchronous getConfig implementation', async function () {
59
+ getConfigStub.resolves({
60
+ externalIP: 'async.host'
61
+ });
62
+ getExternalIPStub.resolves('9.9.9.9');
63
+ await externalIpCli();
64
+ (0, _chai.expect)(getExternalIPStub.firstCall.args[0]).to.equal('async.host');
65
+ (0, _chai.expect)(consoleLogStub.firstCall.args[0]).to.equal('9.9.9.9');
66
+ });
67
+ });
68
+ });
@@ -0,0 +1,257 @@
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 _stripAnsi = _interopRequireDefault(require("strip-ansi"));
8
+ /* eslint-env mocha */
9
+
10
+ // NOTE (potential source bugs in health.js, not locked in by these tests):
11
+ // 1. `getStatus` and `getHistory` both compute `service.raw && service.raw.State.Health`.
12
+ // If `service.raw` is truthy but `service.raw.State` is undefined, this throws a
13
+ // TypeError reading 'Health' of undefined. The guard chain is incomplete; it
14
+ // should be `service.raw && service.raw.State && service.raw.State.Health`.
15
+ // 2. Both helpers accept a `state` parameter that is never used (dead parameter).
16
+
17
+ describe('cli/health', function () {
18
+ let sandbox;
19
+ let getNavyStub;
20
+ let navyStub;
21
+ let consoleLogStub;
22
+ let healthCli;
23
+ function makeService(overrides = {}) {
24
+ return {
25
+ id: 'svc-1',
26
+ name: 'api',
27
+ raw: undefined,
28
+ ...overrides
29
+ };
30
+ }
31
+ beforeEach(function () {
32
+ sandbox = _sinon.default.createSandbox();
33
+ navyStub = {
34
+ ps: sandbox.stub().resolves([]),
35
+ getState: sandbox.stub().resolves(null)
36
+ };
37
+ getNavyStub = sandbox.stub().returns(navyStub);
38
+ consoleLogStub = sandbox.stub(console, 'log');
39
+ healthCli = _proxyquire.default.noCallThru()('../health', {
40
+ '../': {
41
+ getNavy: getNavyStub
42
+ }
43
+ });
44
+ });
45
+ afterEach(function () {
46
+ sandbox.restore();
47
+ });
48
+ function lastLoggedString() {
49
+ const last = consoleLogStub.lastCall;
50
+ return last ? String(last.args[0]) : '';
51
+ }
52
+ describe('default export', function () {
53
+ it('should resolve the navy instance using opts.navy', async function () {
54
+ await healthCli({
55
+ navy: 'env-1'
56
+ });
57
+ (0, _chai.expect)(getNavyStub.calledOnce).to.equal(true);
58
+ (0, _chai.expect)(getNavyStub.firstCall.args[0]).to.equal('env-1');
59
+ });
60
+ it('should print the table headers even when there are no services', async function () {
61
+ navyStub.ps.resolves([]);
62
+ await healthCli({
63
+ navy: 'env-1'
64
+ });
65
+ (0, _chai.expect)(consoleLogStub.calledOnce).to.equal(true);
66
+ const text = (0, _stripAnsi.default)(consoleLogStub.firstCall.args[0]);
67
+ (0, _chai.expect)(text).to.contain('NAME');
68
+ (0, _chai.expect)(text).to.contain('STATUS');
69
+ (0, _chai.expect)(text).to.contain('HISTORY');
70
+ });
71
+ it('should call ps and getState exactly once each', async function () {
72
+ navyStub.ps.resolves([makeService()]);
73
+ await healthCli({
74
+ navy: 'env-1'
75
+ });
76
+ (0, _chai.expect)(navyStub.ps.calledOnce).to.equal(true);
77
+ (0, _chai.expect)(navyStub.getState.calledOnce).to.equal(true);
78
+ });
79
+ });
80
+ describe('getStatus (via default export)', function () {
81
+ it('should render - when the service has no raw container info', async function () {
82
+ navyStub.ps.resolves([makeService({
83
+ name: 'api',
84
+ raw: undefined
85
+ })]);
86
+ await healthCli({
87
+ navy: 'env-1'
88
+ });
89
+ const text = (0, _stripAnsi.default)(lastLoggedString());
90
+ (0, _chai.expect)(text).to.contain('api');
91
+ (0, _chai.expect)(text).to.contain('-');
92
+ });
93
+ it('should render - when the service has raw but no Health record', async function () {
94
+ navyStub.ps.resolves([makeService({
95
+ name: 'api',
96
+ raw: {
97
+ State: {
98
+ Health: null
99
+ }
100
+ }
101
+ })]);
102
+ await healthCli({
103
+ navy: 'env-1'
104
+ });
105
+ const text = (0, _stripAnsi.default)(lastLoggedString());
106
+ (0, _chai.expect)(text).to.contain('api');
107
+ (0, _chai.expect)(text).to.contain('-');
108
+ });
109
+ it('should render Healthy when health status is "healthy"', async function () {
110
+ navyStub.ps.resolves([makeService({
111
+ raw: {
112
+ State: {
113
+ Health: {
114
+ Status: 'healthy',
115
+ Log: []
116
+ }
117
+ }
118
+ }
119
+ })]);
120
+ await healthCli({
121
+ navy: 'env-1'
122
+ });
123
+ const text = (0, _stripAnsi.default)(lastLoggedString());
124
+ (0, _chai.expect)(text).to.contain('Healthy');
125
+ });
126
+ it('should render Unhealthy when health status is anything other than healthy', async function () {
127
+ navyStub.ps.resolves([makeService({
128
+ raw: {
129
+ State: {
130
+ Health: {
131
+ Status: 'starting',
132
+ Log: []
133
+ }
134
+ }
135
+ }
136
+ })]);
137
+ await healthCli({
138
+ navy: 'env-1'
139
+ });
140
+ const text = (0, _stripAnsi.default)(lastLoggedString());
141
+ (0, _chai.expect)(text).to.contain('Unhealthy');
142
+ });
143
+ });
144
+ describe('getHistory (via default export)', function () {
145
+ it('should render - when the service has no raw container info', async function () {
146
+ navyStub.ps.resolves([makeService({
147
+ name: 'api',
148
+ raw: undefined
149
+ })]);
150
+ await healthCli({
151
+ navy: 'env-1'
152
+ });
153
+ const text = (0, _stripAnsi.default)(lastLoggedString());
154
+ (0, _chai.expect)(text).to.contain('-');
155
+ });
156
+ it('should render - when the service has raw but no Health record', async function () {
157
+ navyStub.ps.resolves([makeService({
158
+ raw: {
159
+ State: {
160
+ Health: null
161
+ }
162
+ }
163
+ })]);
164
+ await healthCli({
165
+ navy: 'env-1'
166
+ });
167
+ const text = (0, _stripAnsi.default)(lastLoggedString());
168
+ (0, _chai.expect)(text).to.contain('-');
169
+ });
170
+ it('should render only success markers when every entry has ExitCode 0', async function () {
171
+ navyStub.ps.resolves([makeService({
172
+ raw: {
173
+ State: {
174
+ Health: {
175
+ Status: 'healthy',
176
+ Log: [{
177
+ ExitCode: 0
178
+ }, {
179
+ ExitCode: 0
180
+ }]
181
+ }
182
+ }
183
+ }
184
+ })]);
185
+ await healthCli({
186
+ navy: 'env-1'
187
+ });
188
+ const text = (0, _stripAnsi.default)(lastLoggedString());
189
+ (0, _chai.expect)(text).to.contain('█ █');
190
+ (0, _chai.expect)(text).not.to.contain('x');
191
+ });
192
+ it('should render only failure markers when every entry has a non-zero ExitCode', async function () {
193
+ navyStub.ps.resolves([makeService({
194
+ raw: {
195
+ State: {
196
+ Health: {
197
+ Status: 'unhealthy',
198
+ Log: [{
199
+ ExitCode: 1
200
+ }, {
201
+ ExitCode: 2
202
+ }]
203
+ }
204
+ }
205
+ }
206
+ })]);
207
+ await healthCli({
208
+ navy: 'env-1'
209
+ });
210
+ const text = (0, _stripAnsi.default)(lastLoggedString());
211
+ (0, _chai.expect)(text).to.contain('x x');
212
+ (0, _chai.expect)(text).not.to.contain('█');
213
+ });
214
+ it('should render mixed success and failure markers based on each entry ExitCode', async function () {
215
+ navyStub.ps.resolves([makeService({
216
+ raw: {
217
+ State: {
218
+ Health: {
219
+ Status: 'healthy',
220
+ Log: [{
221
+ ExitCode: 0
222
+ }, {
223
+ ExitCode: 1
224
+ }, {
225
+ ExitCode: 0
226
+ }]
227
+ }
228
+ }
229
+ }
230
+ })]);
231
+ await healthCli({
232
+ navy: 'env-1'
233
+ });
234
+ const text = (0, _stripAnsi.default)(lastLoggedString());
235
+ (0, _chai.expect)(text).to.contain('█ x █');
236
+ });
237
+ it('should render an empty history string when Log is an empty array', async function () {
238
+ navyStub.ps.resolves([makeService({
239
+ raw: {
240
+ State: {
241
+ Health: {
242
+ Status: 'healthy',
243
+ Log: []
244
+ }
245
+ }
246
+ }
247
+ })]);
248
+ await healthCli({
249
+ navy: 'env-1'
250
+ });
251
+ const text = (0, _stripAnsi.default)(lastLoggedString());
252
+ (0, _chai.expect)(text).to.contain('Healthy');
253
+ (0, _chai.expect)(text).not.to.contain('█');
254
+ (0, _chai.expect)(text).not.to.contain('x');
255
+ });
256
+ });
257
+ });