Skip to content

Commit 4702ce8

Browse files
committed
feat(doctor): allow extensions to provide doctor checks
refs #47 - allow extensions to provide checks - update tests - cleanup filtering logic with `intersection` function
1 parent 208bb15 commit 4702ce8

File tree

2 files changed

+110
-61
lines changed

2 files changed

+110
-61
lines changed

lib/commands/doctor/index.js

+41-36
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,52 @@ const Command = require('../../command');
33

44
class DoctorCommand extends Command {
55
run(argv) {
6-
const includes = require('lodash/includes');
7-
6+
const intersection = require('lodash/intersection');
7+
const flatten = require('lodash/flatten');
88
const checks = require('./checks');
9-
const checkValidInstall = require('../../utils/check-valid-install');
109

11-
const checksToRun = argv.categories && argv.categories.length ?
12-
checks.filter((check) => argv.categories.some((cat) => includes(check.category, cat))) :
13-
checks;
10+
return this.system.hook('doctor').then((extensionChecks) => {
11+
const combinedChecks = checks.concat(flatten(extensionChecks).filter(Boolean));
12+
13+
const checksToRun = argv.categories && argv.categories.length ?
14+
combinedChecks.filter((check) => intersection(argv.categories, check.category || []).length) :
15+
combinedChecks;
16+
17+
if (!checksToRun.length) {
18+
if (!argv.quiet) {
19+
const additional = argv.categories && argv.categories.length ? ` for categories "${argv.categories.join(', ')}"` : '';
20+
this.ui.log(`No checks found to run${additional}.`);
21+
}
22+
23+
return Promise.resolve();
24+
}
25+
26+
let instance;
27+
28+
if (
29+
!argv.skipInstanceCheck &&
30+
!(argv.categories && argv.categories.length === 1 && argv.categories[0] === 'install')
31+
) {
32+
const checkValidInstall = require('../../utils/check-valid-install');
1433

15-
if (!checksToRun.length) {
16-
if (!argv.quiet) {
17-
const additional = argv.categories && argv.categories.length ? ` for categories "${argv.categories.join(', ')}"` : '';
18-
this.ui.log(`No checks found to run${additional}.`);
34+
checkValidInstall('doctor');
35+
instance = this.system.getInstance();
36+
instance.checkEnvironment();
1937
}
2038

21-
return Promise.resolve();
22-
}
23-
24-
let instance;
25-
26-
if (
27-
!argv.skipInstanceCheck &&
28-
!(argv.categories && argv.categories.length === 1 && argv.categories[0] === 'install')
29-
) {
30-
checkValidInstall('doctor');
31-
instance = this.system.getInstance();
32-
instance.checkEnvironment();
33-
}
34-
35-
const context = {
36-
argv: argv,
37-
system: this.system,
38-
instance: instance,
39-
ui: this.ui,
40-
local: argv.local || false,
41-
// This is set to true whenever the command is `ghost doctor` itself,
42-
// rather than something like `ghost start` or `ghost update`
43-
isDoctorCommand: Boolean(argv._ && argv._.length && argv._[0] === 'doctor')
44-
};
45-
46-
return this.ui.listr(checksToRun, context, {exitOnError: false});
39+
const context = {
40+
argv: argv,
41+
system: this.system,
42+
instance: instance,
43+
ui: this.ui,
44+
local: argv.local || false,
45+
// This is set to true whenever the command is `ghost doctor` itself,
46+
// rather than something like `ghost start` or `ghost update`
47+
isDoctorCommand: Boolean(argv._ && argv._.length && argv._[0] === 'doctor')
48+
};
49+
50+
return this.ui.listr(checksToRun, context, {exitOnError: false});
51+
});
4752
}
4853
}
4954

test/unit/commands/doctor/command-spec.js

+69-25
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ describe('Unit: Commands > Doctor', function () {
99
it('doesn\'t do anything if there are no checks to run (with log)', function () {
1010
const listrStub = sinon.stub().resolves();
1111
const logStub = sinon.stub();
12+
const hookStub = sinon.stub().resolves([]);
1213
const DoctorCommand = proxyquire(modulePath, {
1314
'./checks': []
1415
});
15-
const instance = new DoctorCommand({listr: listrStub, log: logStub}, {});
16+
const instance = new DoctorCommand({listr: listrStub, log: logStub}, {hook: hookStub});
1617

1718
return instance.run({}).then(() => {
1819
expect(listrStub.called).to.be.false;
20+
expect(hookStub.calledOnce).to.be.true;
21+
expect(hookStub.calledWithExactly('doctor')).to.be.true;
1922
expect(logStub.calledOnce).to.be.true;
2023
expect(logStub.args[0][0]).to.equal('No checks found to run.');
2124
});
@@ -24,36 +27,48 @@ describe('Unit: Commands > Doctor', function () {
2427
it('doesn\'t do anything if there are no checks to run (with log + specific categories)', function () {
2528
const listrStub = sinon.stub().resolves();
2629
const logStub = sinon.stub();
30+
const hookStub = sinon.stub().resolves([]);
2731
const DoctorCommand = proxyquire(modulePath, {
2832
'./checks': []
2933
});
30-
const instance = new DoctorCommand({listr: listrStub, log: logStub}, {});
34+
const instance = new DoctorCommand({listr: listrStub, log: logStub}, {hook: hookStub});
3135

3236
return instance.run({categories: ['testing', 'validity']}).then(() => {
3337
expect(listrStub.called).to.be.false;
3438
expect(logStub.calledOnce).to.be.true;
39+
expect(hookStub.calledOnce).to.be.true;
40+
expect(hookStub.calledWithExactly('doctor')).to.be.true;
3541
expect(logStub.args[0][0]).to.equal('No checks found to run for categories "testing, validity".');
3642
});
3743
});
3844

3945
it('doesn\'t do anything if there are no checks to run (without log)', function () {
4046
const listrStub = sinon.stub().resolves();
4147
const logStub = sinon.stub();
48+
const hookStub = sinon.stub().resolves([]);
4249
const DoctorCommand = proxyquire(modulePath, {
4350
'./checks': []
4451
});
45-
const instance = new DoctorCommand({listr: listrStub, log: logStub}, {});
52+
const instance = new DoctorCommand({listr: listrStub, log: logStub}, {hook: hookStub});
4653

4754
return instance.run({quiet: true}).then(() => {
4855
expect(listrStub.called).to.be.false;
56+
expect(hookStub.calledOnce).to.be.true;
57+
expect(hookStub.calledWithExactly('doctor')).to.be.true;
4958
expect(logStub.called).to.be.false;
5059
});
5160
});
5261

5362
it('checks instance if skipInstanceCheck not passed, uses correct context', function () {
5463
const ui = {listr: sinon.stub().resolves()};
5564
const instanceStub = {checkEnvironment: sinon.stub()};
56-
const system = {getInstance: sinon.stub().returns(instanceStub)};
65+
const system = {
66+
getInstance: sinon.stub().returns(instanceStub),
67+
hook: sinon.stub().resolves([{
68+
title: 'Extension Task 1',
69+
task: 'someTask'
70+
}])
71+
};
5772
const checkValidStub = sinon.stub();
5873

5974
const DoctorCommand = proxyquire(modulePath, {
@@ -65,9 +80,17 @@ describe('Unit: Commands > Doctor', function () {
6580
return instance.run({test: true, a: 'b'}).then(() => {
6681
expect(checkValidStub.calledOnce).to.be.true;
6782
expect(system.getInstance.calledOnce).to.be.true;
83+
expect(system.hook.calledOnce).to.be.true;
84+
expect(system.hook.calledWithExactly('doctor')).to.be.true;
6885
expect(instanceStub.checkEnvironment.calledOnce).to.be.true;
6986
expect(ui.listr.calledOnce).to.be.true;
70-
expect(ui.listr.args[0][0]).to.deep.equal([{}]);
87+
expect(ui.listr.args[0][0]).to.deep.equal([
88+
{},
89+
{
90+
title: 'Extension Task 1',
91+
task: 'someTask'
92+
}
93+
]);
7194
const context = ui.listr.args[0][1];
7295
expect(context.argv).to.deep.equal({test: true, a: 'b'});
7396
expect(context.system).to.equal(system);
@@ -81,7 +104,13 @@ describe('Unit: Commands > Doctor', function () {
81104
it('skips instance check if skipInstanceCheck is true, uses correct context', function () {
82105
const ui = {listr: sinon.stub().resolves()};
83106
const instanceStub = {checkEnvironment: sinon.stub()};
84-
const system = {getInstance: sinon.stub().returns(instanceStub)};
107+
const system = {
108+
getInstance: sinon.stub().returns(instanceStub),
109+
hook: sinon.stub().resolves([{
110+
title: 'Extension Task 1',
111+
task: 'someTask'
112+
}])
113+
};
85114
const checkValidStub = sinon.stub();
86115

87116
const DoctorCommand = proxyquire(modulePath, {
@@ -93,9 +122,17 @@ describe('Unit: Commands > Doctor', function () {
93122
return instance.run({skipInstanceCheck: true, local: true, argv: true}).then(() => {
94123
expect(checkValidStub.called).to.be.false;
95124
expect(system.getInstance.called).to.be.false;
125+
expect(system.hook.calledOnce).to.be.true;
126+
expect(system.hook.calledWithExactly('doctor')).to.be.true;
96127
expect(instanceStub.checkEnvironment.called).to.be.false;
97128
expect(ui.listr.calledOnce).to.be.true;
98-
expect(ui.listr.args[0][0]).to.deep.equal([{}]);
129+
expect(ui.listr.args[0][0]).to.deep.equal([
130+
{},
131+
{
132+
title: 'Extension Task 1',
133+
task: 'someTask'
134+
}
135+
]);
99136
const context = ui.listr.args[0][1];
100137
expect(context.argv).to.deep.equal({skipInstanceCheck: true, local: true, argv: true});
101138
expect(context.system).to.equal(system);
@@ -109,7 +146,13 @@ describe('Unit: Commands > Doctor', function () {
109146
it('skips instance check if only category is install, uses correct context', function () {
110147
const ui = {listr: sinon.stub().resolves()};
111148
const instanceStub = {checkEnvironment: sinon.stub()};
112-
const system = {getInstance: sinon.stub().returns(instanceStub)};
149+
const system = {
150+
getInstance: sinon.stub().returns(instanceStub),
151+
hook: sinon.stub().resolves([{
152+
title: 'Extension Task 1',
153+
task: 'someTask'
154+
}])
155+
};
113156
const checkValidStub = sinon.stub();
114157

115158
const DoctorCommand = proxyquire(modulePath, {
@@ -127,6 +170,8 @@ describe('Unit: Commands > Doctor', function () {
127170
}).then(() => {
128171
expect(checkValidStub.called).to.be.false;
129172
expect(system.getInstance.called).to.be.false;
173+
expect(system.hook.calledOnce).to.be.true;
174+
expect(system.hook.calledWithExactly('doctor')).to.be.true;
130175
expect(instanceStub.checkEnvironment.called).to.be.false;
131176
expect(ui.listr.calledOnce).to.be.true;
132177
expect(ui.listr.args[0][0]).to.deep.equal([{category: ['install']}]);
@@ -157,33 +202,36 @@ describe('Unit: Commands > Doctor', function () {
157202
title: 'Check 3',
158203
category: ['install', 'start']
159204
}];
205+
const listrStub = sinon.stub().resolves();
206+
const hookStub = sinon.stub().resolves([]);
207+
let instance;
160208

161-
let DoctorCommand;
162-
163-
before(() => {
164-
DoctorCommand = proxyquire(modulePath, {
209+
beforeEach(() => {
210+
const DoctorCommand = proxyquire(modulePath, {
165211
'./checks': testChecks
166212
});
167-
})
213+
instance = new DoctorCommand({listr: listrStub}, {hook: hookStub});
214+
});
168215

169-
it('doesn\'t filter if no categories passed', function () {
170-
const listrStub = sinon.stub().resolves();
171-
const instance = new DoctorCommand({listr: listrStub}, {system: true});
216+
afterEach(() => {
217+
listrStub.resetHistory();
218+
hookStub.resetHistory();
219+
});
172220

221+
it('doesn\'t filter if no categories passed', function () {
173222
return instance.run({skipInstanceCheck: true}).then(() => {
174223
expect(listrStub.calledOnce).to.be.true;
224+
expect(hookStub.calledOnce).to.be.true;
175225
const tasks = listrStub.args[0][0];
176226
expect(tasks).to.be.an('array');
177227
expect(tasks.length).to.equal(3);
178228
});
179229
});
180230

181231
it('filters with one category passed', function () {
182-
const listrStub = sinon.stub().resolves();
183-
const instance = new DoctorCommand({listr: listrStub}, {system: true});
184-
185232
return instance.run({skipInstanceCheck: true, categories: ['install']}).then(() => {
186233
expect(listrStub.calledOnce).to.be.true;
234+
expect(hookStub.calledOnce).to.be.true;
187235
const tasks = listrStub.args[0][0];
188236
expect(tasks).to.be.an('array');
189237
expect(tasks.length).to.equal(2);
@@ -193,11 +241,9 @@ describe('Unit: Commands > Doctor', function () {
193241
});
194242

195243
it('filters with another category passed', function () {
196-
const listrStub = sinon.stub().resolves();
197-
const instance = new DoctorCommand({listr: listrStub}, {system: true});
198-
199244
return instance.run({skipInstanceCheck: true, categories: ['start']}).then(() => {
200245
expect(listrStub.calledOnce).to.be.true;
246+
expect(hookStub.calledOnce).to.be.true;
201247
const tasks = listrStub.args[0][0];
202248
expect(tasks).to.be.an('array');
203249
expect(tasks.length).to.equal(2);
@@ -207,11 +253,9 @@ describe('Unit: Commands > Doctor', function () {
207253
});
208254

209255
it('filters with multiple categories passed', function () {
210-
const listrStub = sinon.stub().resolves();
211-
const instance = new DoctorCommand({listr: listrStub}, {system: true});
212-
213256
return instance.run({skipInstanceCheck: true, categories: ['install', 'start']}).then(() => {
214257
expect(listrStub.calledOnce).to.be.true;
258+
expect(hookStub.calledOnce).to.be.true;
215259
const tasks = listrStub.args[0][0];
216260
expect(tasks).to.be.an('array');
217261
expect(tasks.length).to.equal(3);

0 commit comments

Comments
 (0)