Skip to content

Commit

Permalink
chore(import): reorganize code & add downloadExport code
Browse files Browse the repository at this point in the history
  • Loading branch information
acburdine committed Nov 6, 2019
1 parent 0d07841 commit 7e1db00
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 50 deletions.
2 changes: 1 addition & 1 deletion lib/commands/import.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const Command = require('../command');
class ImportCommand extends Command {
async run(argv) {
const semver = require('semver');
const importTask = require('../tasks/import');
const {importTask} = require('../tasks/import');
const {SystemError} = require('../errors');

const instance = this.system.getInstance();
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class SetupCommand extends Command {
const linux = require('../tasks/linux');
const migrator = require('../tasks/migrator');
const configure = require('../tasks/configure');
const importTask = require('../tasks/import');
const {importTask} = require('../tasks/import');

// This is used so we can denote built-in setup steps
// and disable the "do you wish to set up x?" prompt
Expand Down
16 changes: 15 additions & 1 deletion lib/tasks/import/api.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
const fs = require('fs-extra');
const got = require('got');
const get = require('lodash/get');
const util = require('util');
const stream = require('stream');
const semver = require('semver');
const FormData = require('form-data');
const {Cookie} = require('tough-cookie');

const finished = util.promisify(stream.finished);

const {SystemError} = require('../../errors');

const bases = {
Expand Down Expand Up @@ -96,9 +100,19 @@ async function runImport(version, url, auth, exportFile) {
await got.post('/db/', {...authOpts, body});
}

async function downloadExport(version, url, auth, outputFile) {
const authOpts = await getAuthOpts(version, url, auth);

const ws = fs.createWriteStream(outputFile);
const resp = got.stream('/db/', {...authOpts}).pipe(ws);

await finished(resp);
}

module.exports = {
getBaseUrl,
isSetup,
setup,
runImport
runImport,
downloadExport
};
44 changes: 5 additions & 39 deletions lib/tasks/import/index.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,7 @@
const validator = require('validator');

const {importTask} = require('./tasks');
const parseExport = require('./parse-export');
const {isSetup, setup, runImport} = require('./api');

async function task(ui, instance, exportFile) {
const {data} = parseExport(exportFile);
const url = instance.config.get('url');

const prompts = [{
type: 'password',
name: 'password',
message: 'Enter your Ghost administrator password',
validate: val => validator.isLength(`${val}`, {min: 10}) || 'Password must be at least 10 characters long'
}];

const blogIsSetup = await isSetup(instance.version, url);
if (blogIsSetup) {
prompts.unshift({
type: 'string',
name: 'username',
message: 'Enter your Ghost administrator email address',
validate: val => validator.isEmail(`${val}`) || 'You must specify a valid email'
});
}

const {username, password} = await ui.prompt(prompts);
const importUsername = username || data.email;

return ui.listr([{
title: 'Running blog setup',
task: () => setup(instance.version, url, {...data, password}),
enabled: () => !blogIsSetup
}, {
title: 'Running blog import',
task: () => runImport(instance.version, url, {username: importUsername, password}, exportFile)
}], false);
}

module.exports = task;
module.exports.parseExport = parseExport;
module.exports = {
importTask,
parseExport
};
42 changes: 42 additions & 0 deletions lib/tasks/import/tasks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const validator = require('validator');

const parseExport = require('./parse-export');
const {isSetup, setup, runImport} = require('./api');

async function importTask(ui, instance, exportFile) {
const {data} = parseExport(exportFile);
const url = instance.config.get('url');

const prompts = [{
type: 'password',
name: 'password',
message: 'Enter your Ghost administrator password',
validate: val => validator.isLength(`${val}`, {min: 10}) || 'Password must be at least 10 characters long'
}];

const blogIsSetup = await isSetup(instance.version, url);
if (blogIsSetup) {
prompts.unshift({
type: 'string',
name: 'username',
message: 'Enter your Ghost administrator email address',
validate: val => validator.isEmail(`${val}`) || 'You must specify a valid email'
});
}

const {username, password} = await ui.prompt(prompts);
const importUsername = username || data.email;

return ui.listr([{
title: 'Running blog setup',
task: () => setup(instance.version, url, {...data, password}),
enabled: () => !blogIsSetup
}, {
title: 'Running blog import',
task: () => runImport(instance.version, url, {username: importUsername, password}, exportFile)
}], false);
}

module.exports = {
importTask
};
2 changes: 1 addition & 1 deletion test/unit/commands/setup-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ describe('Unit: Commands > Setup', function () {

it('import', function () {
const importTaskStub = sinon.stub();
const {tasks} = getTasks({'../tasks/import': importTaskStub});
const {tasks} = getTasks({'../tasks/import': {importTask: importTaskStub}});
const [,,,,,importTask] = tasks;

expect(importTask.id).to.equal('import');
Expand Down
153 changes: 152 additions & 1 deletion test/unit/tasks/import/api-spec.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const {expect} = require('chai');
const nock = require('nock');
const path = require('path');
const tmp = require('tmp');
const fs = require('fs-extra');

const {SystemError} = require('../../../../lib/errors');
const {getBaseUrl, isSetup, setup, runImport} = require('../../../../lib/tasks/import/api');
const {getBaseUrl, isSetup, setup, runImport, downloadExport} = require('../../../../lib/tasks/import/api');

const testUrl = 'http://localhost:2368';

Expand Down Expand Up @@ -169,4 +171,153 @@ describe('Unit > Tasks > Import > setup', function () {
expect(importScope.isDone()).to.be.true;
});
});

describe('downloadExport', function () {
it('1.x', async function () {
const clientId = 'client-id';
const clientSecret = 'client-secret';
const configBody = {
configuration: [{
clientId, clientSecret
}]
};

const tokenRequestBody = {
grant_type: 'password',
client_id: clientId,
client_secret: clientSecret,
username: '[email protected]',
password: 'password'
};

const tokenResponseBody = {
access_token: 'access-token'
};

const configScope = nock(testUrl)
.get('/ghost/api/v0.1/configuration/')
.reply(200, configBody);

const tokenScope = nock(testUrl)
.post('/ghost/api/v0.1/authentication/token/', tokenRequestBody)
.reply(201, tokenResponseBody);

const exportData = {
db: [{
meta: {
version: '1.0.0'
},
data: {
users: []
}
}]
};
const exportScope = nock(testUrl, {
reqheaders: {
Authorization: 'Bearer access-token'
}
}).get('/ghost/api/v0.1/db/').reply(200, exportData);

const tmpDir = tmp.dirSync();
const outputFile = path.join(tmpDir.name, '1.x.json');

await downloadExport('1.0.0', testUrl, {
username: '[email protected]',
password: 'password'
}, outputFile);

expect(configScope.isDone()).to.be.true;
expect(tokenScope.isDone()).to.be.true;
expect(exportScope.isDone()).to.be.true;
expect(fs.readJsonSync(outputFile)).to.deep.equal(exportData);
});

it('2.x', async function () {
const sessionScope = nock(testUrl, {
reqheaders: {
Origin: testUrl
}
}).post('/ghost/api/v2/admin/session/', {
username: '[email protected]',
password: 'password'
}).reply(201, 'Success', {
'Set-Cookie': 'ghost-admin-api-session=test-session-data; Path=/ghost; HttpOnly; Secure; Expires=Tue, 31 Dec 2099 23:59:59 GMT;'
});

const exportData = {
db: [{
meta: {
version: '2.0.0'
},
data: {
users: []
}
}]
};
const exportScope = nock(testUrl, {
reqheaders: {
cookie: [
'ghost-admin-api-session=test-session-data'
],
origin: testUrl
}
}).get('/ghost/api/v2/admin/db/').reply(200, exportData);

const tmpDir = tmp.dirSync();
const outputFile = path.join(tmpDir.name, '2.x.json');

await downloadExport('2.0.0', 'http://localhost:2368', {
username: '[email protected]',
password: 'password'
}, outputFile);

expect(sessionScope.isDone()).to.be.true;
expect(exportScope.isDone()).to.be.true;
expect(fs.readJsonSync(outputFile)).to.deep.equal(exportData);
});

it('3.x', async function () {
const sessionScope = nock(testUrl, {
reqheaders: {
Origin: testUrl
}
}).post('/ghost/api/v3/admin/session/', {
username: '[email protected]',
password: 'password'
}).reply(201, 'Success', {
'Set-Cookie': 'ghost-admin-api-session=test-session-data; Path=/ghost; HttpOnly; Secure; Expires=Tue, 31 Dec 2099 23:59:59 GMT;'
});

const exportData = {
db: [{
meta: {
version: '3.0.0'
},
data: {
users: []
}
}]
};
const exportScope = nock(testUrl, {
reqheaders: {
cookie: [
'ghost-admin-api-session=test-session-data'
],
origin: testUrl
}
}).get('/ghost/api/v3/admin/db/').reply(200, exportData);

const tmpDir = tmp.dirSync();
const outputFile = path.join(tmpDir.name, '3.x.json');

await downloadExport('3.0.0', 'http://localhost:2368', {
username: '[email protected]',
password: 'password'
}, outputFile);

expect(sessionScope.isDone()).to.be.true;
expect(exportScope.isDone()).to.be.true;
expect(fs.readJsonSync(outputFile)).to.deep.equal(exportData);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ const proxyquire = require('proxyquire').noCallThru();
const Promise = require('bluebird');
const createConfigStub = require('../../../utils/config-stub');

const modulePath = '../../../../lib/tasks/import';
const modulePath = '../../../../lib/tasks/import/tasks';

describe('Unit: Tasks > Import', function () {
describe('Unit: Tasks > Import > Import task', function () {
it('works with already set up blog', async function () {
const parseExport = sinon.stub().returns({data: {name: 'test', email: '[email protected]', blogTitle: 'test'}});
const isSetup = sinon.stub().resolves(true);
const setup = sinon.stub().resolves();
const runImport = sinon.stub().resolves();

const task = proxyquire(modulePath, {
const {importTask} = proxyquire(modulePath, {
'./parse-export': parseExport,
'./api': {isSetup, setup, runImport}
});
Expand All @@ -29,7 +29,7 @@ describe('Unit: Tasks > Import', function () {
const config = createConfigStub();
config.get.withArgs('url').returns('http://localhost:2368');

await task({prompt, listr}, {config, version: '1.0.0'}, 'test-export.json');
await importTask({prompt, listr}, {config, version: '1.0.0'}, 'test-export.json');

expect(parseExport.calledOnceWithExactly('test-export.json')).to.be.true;
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
Expand Down Expand Up @@ -58,7 +58,7 @@ describe('Unit: Tasks > Import', function () {
const setup = sinon.stub().resolves();
const runImport = sinon.stub().resolves();

const task = proxyquire(modulePath, {
const {importTask} = proxyquire(modulePath, {
'./parse-export': parseExport,
'./api': {isSetup, setup, runImport}
});
Expand All @@ -74,7 +74,7 @@ describe('Unit: Tasks > Import', function () {
const config = createConfigStub();
config.get.withArgs('url').returns('http://localhost:2368');

await task({prompt, listr}, {config, version: '1.0.0'}, 'test-export.json');
await importTask({prompt, listr}, {config, version: '1.0.0'}, 'test-export.json');

expect(parseExport.calledOnceWithExactly('test-export.json')).to.be.true;
expect(isSetup.calledOnceWithExactly('1.0.0', 'http://localhost:2368')).to.be.true;
Expand Down

0 comments on commit 7e1db00

Please sign in to comment.