Skip to content

Commit

Permalink
refactor(uninstall): use instance stop instead of runCommand (#1047)
Browse files Browse the repository at this point in the history
  • Loading branch information
acburdine committed Nov 6, 2019
1 parent e58d4c6 commit 83ea37a
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 161 deletions.
69 changes: 32 additions & 37 deletions lib/commands/uninstall.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
'use strict';
const Command = require('../command');

class UninstallCommand extends Command {
run(argv) {
async run(argv) {
const fs = require('fs-extra');
const path = require('path');

const StopCommand = require('./stop');
const ghostUser = require('../utils/use-ghost-user');

if (!argv.force) {
Expand All @@ -16,41 +14,38 @@ class UninstallCommand extends Command {

const instance = this.system.getInstance();

return this.ui.confirm('Are you sure you want to do this?', argv.force).then((confirmed) => {
if (!confirmed) {
return Promise.reject(false);
}

return this.ui.listr([{
title: 'Stopping Ghost',
task: (_, task) => instance.isRunning().then((isRunning) => {
if (!isRunning) {
return task.skip('Instance is not running');
}
const confirmed = await this.ui.confirm('Are you sure you want to do this?');
if (!confirmed) {
return;
}

instance.loadRunningEnvironment(true);
// If the instance is currently running we need to make sure
// it gets stopped and disabled if possible
return this.runCommand(StopCommand, {quiet: true, disable: true});
})
}, {
title: 'Removing content folder',
enabled: () => ghostUser.shouldUseGhostUser(path.join(instance.dir, 'content')),
task: () => this.ui.sudo(`rm -rf ${path.join(instance.dir, 'content')}`)
}, {
title: 'Removing related configuration',
task: () => {
this.system.setEnvironment(!fs.existsSync(path.join(instance.dir, 'config.production.json')));
return this.system.hook('uninstall', instance);
}
}, {
title: 'Removing Ghost installation',
task: () => {
this.system.removeInstance(instance);
return Promise.all(fs.readdirSync('.').map(file => fs.remove(file)));
}
}]);
});
await this.ui.listr([{
title: 'Stopping Ghost',
task: async () => {
instance.loadRunningEnvironment(true);
await instance.stop(true);
},
skip: async () => {
const isRunning = await instance.isRunning();
return !isRunning;
}
}, {
title: 'Removing content folder',
enabled: () => ghostUser.shouldUseGhostUser(path.join(instance.dir, 'content')),
task: () => this.ui.sudo(`rm -rf ${path.join(instance.dir, 'content')}`)
}, {
title: 'Removing related configuration',
task: () => {
this.system.setEnvironment(!fs.existsSync(path.join(instance.dir, 'config.production.json')));
return this.system.hook('uninstall', instance);
}
}, {
title: 'Removing Ghost installation',
task: () => {
this.system.removeInstance(instance);
return Promise.all(fs.readdirSync('.').map(file => fs.remove(file)));
}
}]);
}
}

Expand Down
212 changes: 88 additions & 124 deletions test/unit/commands/uninstall-spec.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
'use strict';
const expect = require('chai').expect;
const sinon = require('sinon');
const proxyquire = require('proxyquire').noCallThru();

const fs = require('fs-extra');
const UI = require('../../../lib/ui');
const System = require('../../../lib/system');
const StopCommand = require('../../../lib/commands/stop');

const modulePath = '../../../lib/commands/uninstall';
const UninstallCommand = require(modulePath);
Expand All @@ -26,174 +24,140 @@ describe('Unit: Commands > Uninstall', function () {
const ui = sinon.createStubInstance(UI);
const system = sinon.createStubInstance(System);

const Klass = proxied ? proxyquire(modulePath, proxied) : UninstallCommand;
const instance = new Klass(ui, system);
const Command = proxied ? proxyquire(modulePath, proxied) : UninstallCommand;

return {
instance: instance,
command: new Command(ui, system),
ui: ui,
system: system
};
}

describe('run', function () {
it('prompts to confirm', function () {
const command = createInstance();
command.ui.confirm.resolves(true);
command.ui.listr.resolves();

return command.instance.run({}).then(() => {
expect(command.ui.confirm.calledOnce).to.be.true;
expect(command.ui.log.calledOnce).to.be.true;
expect(command.ui.listr.calledOnce).to.be.true;
});
it('prompts to confirm', async function () {
const {command, ui} = createInstance();

ui.confirm.resolves(true);
ui.listr.resolves();

await command.run({});
expect(ui.confirm.calledOnce).to.be.true;
expect(ui.log.calledOnce).to.be.true;
expect(ui.listr.calledOnce).to.be.true;
});

it('doesn\'t run if the user backs out', function (done) {
it('doesn\'t run if the user backs out', async function () {
const argv = {force: true};
const command = createInstance();
command.ui.confirm.resolves(false);
command.ui.listr.resolves();

command.instance.run(argv).then(() => {
done(new Error('run should have rejected'));
}).catch(() => {
expect(command.ui.confirm.calledOnce).to.be.true;
expect(command.ui.log.called).to.be.false;
expect(command.ui.listr.called).to.be.false;
done();
}).catch(done);
const {command, ui} = createInstance();
ui.confirm.resolves(false);
ui.listr.resolves();

await command.run(argv);
expect(ui.confirm.calledOnce).to.be.true;
expect(ui.log.called).to.be.false;
expect(ui.listr.called).to.be.false;
});

describe('steps', function () {
function getSteps(instance, ui) {
async function getSteps(command, ui) {
ui.confirm.resolves(true);
ui.listr.resolves();

return instance.run({force: true}).then(() => {
expect(ui.listr.calledOnce).to.be.true;
return ui.listr.args[0][0];
});
await command.run({force: true});
expect(ui.listr.calledOnce).to.be.true;
return ui.listr.args[0][0];
}

it('step 1 (stopping ghost) - skips when ghost is not running', function () {
const command = createInstance();
const instance = {
isRunning: sinon.stub().resolves(false),
loadRunningEnvironment: sinon.stub()
};
const runCommandStub = sinon.stub(command.instance, 'runCommand').resolves();
const skipStub = sinon.stub();
command.system.getInstance.returns(instance);

return getSteps(command.instance, command.ui).then((steps) => {
const task = steps[0];
describe('step 1 (stopping ghost)', function () {
it('skip', async function () {
const {command, ui, system} = createInstance();
const instance = {
isRunning: sinon.stub().resolves(false)
};
system.getInstance.returns(instance);

expect(task.title).to.equal('Stopping Ghost');
const [step1] = await getSteps(command, ui);
const result = await step1.skip();

return task.task(null, {skip: skipStub});
}).then(() => {
expect(result).to.be.true;
expect(instance.isRunning.calledOnce).to.be.true;
expect(skipStub.calledOnce).to.be.true;
expect(instance.loadRunningEnvironment.calledOnce).to.be.false;
expect(runCommandStub.calledOnce).to.be.false;
});
});

it('step 1 (stopping ghost) - doesn\'t skip when ghost is running', function () {
const command = createInstance();
const instance = {
isRunning: sinon.stub().resolves(true),
loadRunningEnvironment: sinon.stub()
};
const runCommandStub = sinon.stub(command.instance, 'runCommand').resolves();
const skipStub = sinon.stub();
command.system.getInstance.returns(instance);
it('task', async function () {
const {command, ui, system} = createInstance();
const instance = {
loadRunningEnvironment: sinon.stub(),
stop: sinon.stub().resolves()
};
system.getInstance.returns(instance);

return getSteps(command.instance, command.ui).then((steps) => {
const task = steps[0];
const [step1] = await getSteps(command, ui);
await step1.task();

expect(task.title).to.equal('Stopping Ghost');

return task.task(null, {skip: skipStub});
}).then(() => {
expect(instance.isRunning.calledOnce).to.be.true;
expect(skipStub.calledOnce).to.be.false;
expect(instance.loadRunningEnvironment.calledOnce).to.be.true;
expect(runCommandStub.calledOnce).to.be.true;
expect(runCommandStub.calledWithExactly(
StopCommand,
{quiet: true, disable: true}
)).to.be.true;
expect(instance.stop.calledOnce).to.be.true;
});
});

it('step 2 (removing content folder)', function () {
it('step 2 (removing content folder)', async function () {
const useGhostUserStub = sinon.stub().returns(false);
const command = createInstance({
const {command, ui, system} = createInstance({
'../utils/use-ghost-user': {shouldUseGhostUser: useGhostUserStub}
});
command.system.getInstance.returns({dir: '/var/www/ghost'});
command.ui.sudo.resolves();
system.getInstance.returns({dir: '/var/www/ghost'});
ui.sudo.resolves();

return getSteps(command.instance, command.ui).then((steps) => {
const task = steps[1];
const [,task] = await getSteps(command, ui);

expect(task.title).to.equal('Removing content folder');
expect(task.enabled()).to.be.false;
expect(useGhostUserStub.calledOnce).to.be.true;
expect(useGhostUserStub.calledWithExactly('/var/www/ghost/content')).to.be.true;
expect(task.title).to.equal('Removing content folder');
expect(task.enabled()).to.be.false;
expect(useGhostUserStub.calledOnce).to.be.true;
expect(useGhostUserStub.calledWithExactly('/var/www/ghost/content')).to.be.true;

return task.task();
}).then(() => {
expect(command.ui.sudo.calledOnce).to.be.true;
expect(command.ui.sudo.calledWithExactly('rm -rf /var/www/ghost/content'));
});
await task.task();
expect(ui.sudo.calledOnce).to.be.true;
expect(ui.sudo.calledWithExactly('rm -rf /var/www/ghost/content'));
});

it('step 3 (removing related configuration)', function () {
const command = createInstance();
it('step 3 (removing related configuration)', async function () {
const {command, ui, system} = createInstance();
const existsStub = sinon.stub(fs, 'existsSync').returns(true);
command.system.getInstance.returns({instance: true, dir: '/var/www/ghost'});
command.system.hook.resolves();

return getSteps(command.instance, command.ui).then((steps) => {
const task = steps[2];

expect(task.title).to.equal('Removing related configuration');
return task.task();
}).then(() => {
expect(existsStub.calledOnce).to.be.true;
expect(command.system.setEnvironment.calledOnce).to.be.true;
expect(command.system.setEnvironment.calledWithExactly(false)).to.be.true;
expect(command.system.hook.calledOnce).to.be.true;
expect(command.system.hook.calledWithExactly(
'uninstall',
{instance: true, dir: '/var/www/ghost'}
)).to.be.true;
});
system.getInstance.returns({instance: true, dir: '/var/www/ghost'});
system.hook.resolves();

const [,,task] = await getSteps(command, ui);

expect(task.title).to.equal('Removing related configuration');
await task.task();

expect(existsStub.calledOnce).to.be.true;
expect(system.setEnvironment.calledOnce).to.be.true;
expect(system.setEnvironment.calledWithExactly(false)).to.be.true;
expect(system.hook.calledOnce).to.be.true;
expect(system.hook.calledWithExactly(
'uninstall',
{instance: true, dir: '/var/www/ghost'}
)).to.be.true;
});

it('step 4 (removing ghost install)', function () {
const command = createInstance();
command.system.getInstance.returns({instance: true, dir: '/var/www/ghost'});
it('step 4 (removing ghost install)', async function () {
const {command, ui, system} = createInstance();
system.getInstance.returns({instance: true, dir: '/var/www/ghost'});
const readdirStub = sinon.stub(fs, 'readdirSync').returns(fileList);
const removeStub = sinon.stub(fs, 'remove').resolves();

return getSteps(command.instance, command.ui).then((steps) => {
const task = steps[3];
const [,,,task] = await getSteps(command, ui);

expect(task.title).to.equal('Removing Ghost installation');
await task.task();

expect(task.title).to.equal('Removing Ghost installation');
return task.task();
}).then(() => {
expect(command.system.removeInstance.calledOnce).to.be.true;
expect(command.system.removeInstance.calledWithExactly({instance: true, dir: '/var/www/ghost'})).to.be.true;
expect(readdirStub.calledOnce).to.be.true;
expect(removeStub.callCount).to.equal(fileList.length);
expect(system.removeInstance.calledOnce).to.be.true;
expect(system.removeInstance.calledWithExactly({instance: true, dir: '/var/www/ghost'})).to.be.true;
expect(readdirStub.calledOnce).to.be.true;
expect(removeStub.callCount).to.equal(fileList.length);

fileList.forEach((f) => {
expect(removeStub.calledWithExactly(f)).to.be.true;
});
fileList.forEach((f) => {
expect(removeStub.calledWithExactly(f)).to.be.true;
});
});
});
Expand Down

0 comments on commit 83ea37a

Please sign in to comment.