diff --git a/test/api-acceptance/admin/invites_spec.js b/test/api-acceptance/admin/invites_spec.js index e05522e3e9..47233f5b09 100644 --- a/test/api-acceptance/admin/invites_spec.js +++ b/test/api-acceptance/admin/invites_spec.js @@ -73,7 +73,7 @@ describe('Invites API', function () { .post(localUtils.API.getApiQuery('invites/')) .set('Origin', config.get('url')) .send({ - invites: [{email: 'test@example.com', role_id: testUtils.existingData.roles[1].id}] + invites: [{email: 'test@example.com', role_id: testUtils.getExistingData().roles[1].id}] }) .expect('Content-Type', /json/) .expect('Cache-Control', testUtils.cacheRules.private) @@ -86,7 +86,7 @@ describe('Invites API', function () { jsonResponse.invites.should.have.length(1); localUtils.API.checkResponse(jsonResponse.invites[0], 'invite'); - jsonResponse.invites[0].role_id.should.eql(testUtils.existingData.roles[1].id); + jsonResponse.invites[0].role_id.should.eql(testUtils.getExistingData().roles[1].id); mailService.GhostMailer.prototype.send.called.should.be.true(); diff --git a/test/api-acceptance/admin/tags_spec.js b/test/api-acceptance/admin/tags_spec.js index 490551b184..49e74985c8 100644 --- a/test/api-acceptance/admin/tags_spec.js +++ b/test/api-acceptance/admin/tags_spec.js @@ -59,7 +59,7 @@ describe('Tag API', function () { it('Can read a tag', async function () { const res = await request - .get(localUtils.API.getApiQuery(`tags/${testUtils.existingData.tags[0].id}/?include=count.posts`)) + .get(localUtils.API.getApiQuery(`tags/${testUtils.getExistingData().tags[0].id}/?include=count.posts`)) .set('Origin', config.get('url')) .expect('Content-Type', /json/) .expect('Cache-Control', testUtils.cacheRules.private) @@ -132,10 +132,10 @@ describe('Tag API', function () { it('Can edit a tag', async function () { const res = await request - .put(localUtils.API.getApiQuery(`tags/${testUtils.existingData.tags[0].id}`)) + .put(localUtils.API.getApiQuery(`tags/${testUtils.getExistingData().tags[0].id}`)) .set('Origin', config.get('url')) .send({ - tags: [Object.assign({}, testUtils.existingData.tags[0], {description: 'hey ho ab ins klo'})] + tags: [Object.assign({}, testUtils.getExistingData().tags[0], {description: 'hey ho ab ins klo'})] }) .expect('Content-Type', /json/) .expect('Cache-Control', testUtils.cacheRules.private) @@ -152,7 +152,7 @@ describe('Tag API', function () { it('Can destroy a tag', async function () { const res = await request - .del(localUtils.API.getApiQuery(`tags/${testUtils.existingData.tags[0].id}`)) + .del(localUtils.API.getApiQuery(`tags/${testUtils.getExistingData().tags[0].id}`)) .set('Origin', config.get('url')) .expect('Cache-Control', testUtils.cacheRules.private) .expect(204); diff --git a/test/api-acceptance/admin/users_spec.js b/test/api-acceptance/admin/users_spec.js index 4535a9ac64..e9ad3ddc21 100644 --- a/test/api-acceptance/admin/users_spec.js +++ b/test/api-acceptance/admin/users_spec.js @@ -100,7 +100,7 @@ describe('User API', function () { }); it('Can retrieve a user by id', async function () { - const res = await request.get(localUtils.API.getApiQuery('users/' + testUtils.existingData.users[0].id + '/?include=roles,roles.permissions,count.posts')) + const res = await request.get(localUtils.API.getApiQuery('users/' + testUtils.getExistingData().users[0].id + '/?include=roles,roles.permissions,count.posts')) .set('Origin', config.get('url')) .expect('Content-Type', /json/) .expect('Cache-Control', testUtils.cacheRules.private) @@ -186,7 +186,7 @@ describe('User API', function () { }); it('Can destroy an active user', async function () { - const userId = testUtils.existingData.users[1].id; + const userId = testUtils.getExistingData().users[1].id; const res = await request .get(localUtils.API.getApiQuery(`posts/?filter=author_id:${userId}`)) @@ -257,7 +257,7 @@ describe('User API', function () { newPassword: '1234abcde!!', ne2Password: '1234abcde!!', oldPassword: 'Sl1m3rson99', - user_id: testUtils.existingData.users[0].id + user_id: testUtils.getExistingData().users[0].id }] }) .expect('Content-Type', /json/) @@ -287,7 +287,7 @@ describe('User API', function () { }); it('Can\'t read another user\'s Personal Token', async function () { - const userNotAdmin = testUtils.existingData.users.find(user => user.email === 'ghost-author@example.com'); + const userNotAdmin = testUtils.getExistingData().users.find(user => user.email === 'ghost-author@example.com'); const res = await request .get(localUtils.API.getApiQuery('users/' + userNotAdmin.id + '/token/')) .set('Origin', config.get('url')) diff --git a/test/regression/api/canary/admin/db_spec.js b/test/regression/api/canary/admin/db_spec.js index 82b8bdcf81..39ba0e1b00 100644 --- a/test/regression/api/canary/admin/db_spec.js +++ b/test/regression/api/canary/admin/db_spec.js @@ -28,8 +28,8 @@ describe('DB API', function () { return localUtils.doAuth(request); }) .then(() => { - backupKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-backup'}}); - schedulerKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-scheduler'}}); + backupKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-backup'}}); + schedulerKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-scheduler'}}); }); }); diff --git a/test/regression/api/canary/admin/schedules_spec.js b/test/regression/api/canary/admin/schedules_spec.js index d36eed80a1..056c6dc17e 100644 --- a/test/regression/api/canary/admin/schedules_spec.js +++ b/test/regression/api/canary/admin/schedules_spec.js @@ -33,45 +33,45 @@ describe('Canary Schedules API', function () { request = supertest.agent(config.get('url')); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().add(30, 'seconds').toDate(), status: 'scheduled', slug: 'first' })); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().subtract(30, 'seconds').toDate(), status: 'scheduled', slug: 'second' })); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().add(10, 'minute').toDate(), status: 'scheduled', slug: 'third' })); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().subtract(10, 'minute').toDate(), status: 'scheduled', slug: 'fourth' })); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().add(30, 'seconds').toDate(), status: 'scheduled', slug: 'fifth', @@ -89,7 +89,7 @@ describe('Canary Schedules API', function () { let token; before(function () { - const schedulerKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-scheduler'}}); + const schedulerKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-scheduler'}}); token = localUtils.getValidAdminToken('/canary/admin/', schedulerKey); }); @@ -124,7 +124,7 @@ describe('Canary Schedules API', function () { }); it('no access', function () { - const zapierKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-backup'}}); + const zapierKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-backup'}}); const zapierToken = localUtils.getValidAdminToken('/canary/admin/', zapierKey); return request diff --git a/test/regression/api/canary/admin/users_spec.js b/test/regression/api/canary/admin/users_spec.js index 1f6139221a..589e913916 100644 --- a/test/regression/api/canary/admin/users_spec.js +++ b/test/regression/api/canary/admin/users_spec.js @@ -217,7 +217,7 @@ describe('User API', function () { .set('Origin', config.get('url')) .send({ owner: [{ - id: testUtils.existingData.users[1].id + id: testUtils.getExistingData().users[1].id }] }) .expect('Content-Type', /json/) diff --git a/test/regression/api/v2/admin/db_spec.js b/test/regression/api/v2/admin/db_spec.js index 7958673266..7b881e13db 100644 --- a/test/regression/api/v2/admin/db_spec.js +++ b/test/regression/api/v2/admin/db_spec.js @@ -28,8 +28,8 @@ describe('DB API', function () { return localUtils.doAuth(request); }) .then(() => { - backupKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-backup'}}); - schedulerKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-scheduler'}}); + backupKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-backup'}}); + schedulerKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-scheduler'}}); }); }); diff --git a/test/regression/api/v2/admin/schedules_spec.js b/test/regression/api/v2/admin/schedules_spec.js index c2978fc05d..2d0c5c0e83 100644 --- a/test/regression/api/v2/admin/schedules_spec.js +++ b/test/regression/api/v2/admin/schedules_spec.js @@ -33,45 +33,45 @@ describe('v2 Schedules API', function () { request = supertest.agent(config.get('url')); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().add(30, 'seconds').toDate(), status: 'scheduled', slug: 'first' })); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().subtract(30, 'seconds').toDate(), status: 'scheduled', slug: 'second' })); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().add(10, 'minute').toDate(), status: 'scheduled', slug: 'third' })); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().subtract(10, 'minute').toDate(), status: 'scheduled', slug: 'fourth' })); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().add(30, 'seconds').toDate(), status: 'scheduled', slug: 'fifth', @@ -89,7 +89,7 @@ describe('v2 Schedules API', function () { let token; before(function () { - const schedulerKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-scheduler'}}); + const schedulerKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-scheduler'}}); token = localUtils.getValidAdminToken('/v2/admin/', schedulerKey); }); @@ -124,7 +124,7 @@ describe('v2 Schedules API', function () { }); it('no access', function () { - const zapierKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-backup'}}); + const zapierKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-backup'}}); const zapierToken = localUtils.getValidAdminToken('/v2/admin/', zapierKey); return request diff --git a/test/regression/api/v2/admin/users_spec.js b/test/regression/api/v2/admin/users_spec.js index 1f6139221a..589e913916 100644 --- a/test/regression/api/v2/admin/users_spec.js +++ b/test/regression/api/v2/admin/users_spec.js @@ -217,7 +217,7 @@ describe('User API', function () { .set('Origin', config.get('url')) .send({ owner: [{ - id: testUtils.existingData.users[1].id + id: testUtils.getExistingData().users[1].id }] }) .expect('Content-Type', /json/) diff --git a/test/regression/api/v3/admin/db_spec.js b/test/regression/api/v3/admin/db_spec.js index 323d427a78..43b355e8f8 100644 --- a/test/regression/api/v3/admin/db_spec.js +++ b/test/regression/api/v3/admin/db_spec.js @@ -28,8 +28,8 @@ describe('DB API', function () { return localUtils.doAuth(request); }) .then(() => { - backupKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-backup'}}); - schedulerKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-scheduler'}}); + backupKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-backup'}}); + schedulerKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-scheduler'}}); }); }); diff --git a/test/regression/api/v3/admin/schedules_spec.js b/test/regression/api/v3/admin/schedules_spec.js index 47136aa0a7..569b1fc707 100644 --- a/test/regression/api/v3/admin/schedules_spec.js +++ b/test/regression/api/v3/admin/schedules_spec.js @@ -33,45 +33,45 @@ describe('v3 Schedules API', function () { request = supertest.agent(config.get('url')); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().add(30, 'seconds').toDate(), status: 'scheduled', slug: 'first' })); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().subtract(30, 'seconds').toDate(), status: 'scheduled', slug: 'second' })); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().add(10, 'minute').toDate(), status: 'scheduled', slug: 'third' })); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().subtract(10, 'minute').toDate(), status: 'scheduled', slug: 'fourth' })); resources.push(testUtils.DataGenerator.forKnex.createPost({ - created_by: testUtils.existingData.users[0].id, - author_id: testUtils.existingData.users[0].id, - published_by: testUtils.existingData.users[0].id, + created_by: testUtils.getExistingData().users[0].id, + author_id: testUtils.getExistingData().users[0].id, + published_by: testUtils.getExistingData().users[0].id, published_at: moment().add(30, 'seconds').toDate(), status: 'scheduled', slug: 'fifth', @@ -89,7 +89,7 @@ describe('v3 Schedules API', function () { let token; before(function () { - const schedulerKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-scheduler'}}); + const schedulerKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-scheduler'}}); token = localUtils.getValidAdminToken('/v3/admin/', schedulerKey); }); @@ -124,7 +124,7 @@ describe('v3 Schedules API', function () { }); it('no access', function () { - const zapierKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-backup'}}); + const zapierKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-backup'}}); const zapierToken = localUtils.getValidAdminToken('/v3/admin/', zapierKey); return request diff --git a/test/regression/api/v3/admin/users_spec.js b/test/regression/api/v3/admin/users_spec.js index 1f6139221a..589e913916 100644 --- a/test/regression/api/v3/admin/users_spec.js +++ b/test/regression/api/v3/admin/users_spec.js @@ -217,7 +217,7 @@ describe('User API', function () { .set('Origin', config.get('url')) .send({ owner: [{ - id: testUtils.existingData.users[1].id + id: testUtils.getExistingData().users[1].id }] }) .expect('Content-Type', /json/) diff --git a/test/utils/acceptance-utils.js b/test/utils/acceptance-utils.js new file mode 100644 index 0000000000..82ddf29a19 --- /dev/null +++ b/test/utils/acceptance-utils.js @@ -0,0 +1,223 @@ +require('../../core/server/overrides'); + +// Utility Packages +const Promise = require('bluebird'); +const _ = require('lodash'); +const fs = require('fs-extra'); +const path = require('path'); +const os = require('os'); +const uuid = require('uuid'); +const KnexMigrator = require('knex-migrator'); +const knexMigrator = new KnexMigrator(); + +// Ghost Internals +const config = require('../../core/shared/config'); +const boot = require('../../core/boot'); +const db = require('../../core/server/data/db'); +const models = require('../../core/server/models'); +const urlService = require('../../core/frontend/services/url'); +const settingsService = require('../../core/server/services/settings'); +const frontendSettingsService = require('../../core/frontend/services/settings'); +const settingsCache = require('../../core/server/services/settings/cache'); +const web = require('../../core/server/web'); +const themeService = require('../../core/server/services/themes'); +const limits = require('../../core/server/services/limits'); + +// Other Test Utilities +const configUtils = require('./configUtils'); +const dbUtils = require('./db-utils'); +const urlServiceUtils = require('./url-service-utils'); +const redirects = require('./redirects'); +const context = require('./fixtures/context'); + +let ghostServer; +let existingData = {}; + +/** + * Because we use ObjectID we don't know the ID of fixtures ahead of time + * This function fetches all of our fixtures and exposes them so that tests can use them + * @TODO: Optimise this by making it optional / selective + */ +const exposeFixtures = async () => { + const fixturePromises = { + roles: models.Role.findAll({columns: ['id']}), + users: models.User.findAll({columns: ['id', 'email']}), + tags: models.Tag.findAll({columns: ['id']}), + apiKeys: models.ApiKey.findAll({withRelated: 'integration'}) + }; + const keys = Object.keys(fixturePromises); + existingData = {}; + + return Promise + .all(Object.values(fixturePromises)) + .then((results) => { + for (let i = 0; i < keys.length; i += 1) { + existingData[keys[i]] = results[i].toJSON(context.internal); + } + }) + .catch((err) => { + console.error('Unable to expose fixtures', err); // eslint-disable-line no-console + process.exit(1); + }); +}; + +const prepareContentFolder = (options) => { + const contentFolderForTests = options.contentFolder; + + /** + * We never use the root content folder for testing! + * We use a tmp folder. + */ + configUtils.set('paths:contentPath', contentFolderForTests); + + fs.ensureDirSync(contentFolderForTests); + fs.ensureDirSync(path.join(contentFolderForTests, 'data')); + fs.ensureDirSync(path.join(contentFolderForTests, 'themes')); + fs.ensureDirSync(path.join(contentFolderForTests, 'images')); + fs.ensureDirSync(path.join(contentFolderForTests, 'logs')); + fs.ensureDirSync(path.join(contentFolderForTests, 'adapters')); + fs.ensureDirSync(path.join(contentFolderForTests, 'settings')); + + if (options.copyThemes) { + // Copy all themes into the new test content folder. Default active theme is always casper. If you want to use a different theme, you have to set the active theme (e.g. stub) + fs.copySync(path.join(__dirname, 'fixtures', 'themes'), path.join(contentFolderForTests, 'themes')); + } + + if (options.redirectsFile) { + redirects.setupFile(contentFolderForTests, options.redirectsFileExt); + } + + if (options.copySettings) { + fs.copySync(path.join(__dirname, 'fixtures', 'settings', 'routes.yaml'), path.join(contentFolderForTests, 'settings', 'routes.yaml')); + } +}; + +// CASE: Ghost Server is Running +// In this case we need to reset things so it's as though Ghost just booted: +// - truncate database +// - re-run default fixtures +// - reload affected services +const restartModeGhostStart = async () => { + console.log('Restart Mode'); // eslint-disable-line no-console + + // Teardown truncates all tables and also calls urlServiceUtils.reset(); + await dbUtils.teardown(); + + // The tables have been truncated, this runs the fixture init task (init file 2) to re-add our default fixtures + await knexMigrator.init({only: 2}); + + // Reset the settings cache + // @TODO: Prob A: why/how is this different to using settingsCache.reset() + settingsCache.shutdown(); + await settingsService.init(); + + // Reload the frontend + await frontendSettingsService.init(); + await themeService.init(); + + // Reload the URL service & wait for it to be ready again + // @TODO: Prob B: why/how is this different to urlService.resetGenerators? + urlServiceUtils.reset(); + urlServiceUtils.init(); + await urlServiceUtils.isFinished(); + // @TODO: why does this happen _after_ URL service + web.shared.middlewares.customRedirects.reload(); + + // Trigger themes to load again + themeService.loadInactiveThemes(); + + // Reload limits service + limits.init(); +}; + +const bootGhost = async () => { + ghostServer = await boot(); +}; + +// CASE: Ghost Server needs Starting +// In this case we need to ensure that Ghost is started cleanly: +// - ensure the DB is reset +// - CASE: If we are in force start mode the server is already running so we +// - stop the server (if we are in force start mode it will be running) +// - reload affected services - just settings and not the frontend!? +// - Start Ghost: Uses OLD Boot process +const freshModeGhostStart = async (options) => { + if (options.forceStart) { + console.log('Force Start Mode'); // eslint-disable-line no-console + } else { + console.log('Fresh Start Mode'); // eslint-disable-line no-console + } + + // Reset the DB + await knexMigrator.reset({force: true}); + + // Stop the serve (forceStart Mode) + await stopGhost(); + + // Reset the settings cache + // @TODO: Prob A: why/how is this different to using settingsService.init() and why to do we need this? + settingsCache.shutdown(); + settingsCache.reset(); + + // Do a full database initialisation + await knexMigrator.init(); + + if (config.get('database:client') === 'sqlite3') { + await db.knex.raw('PRAGMA journal_mode = TRUNCATE;'); + } + + // Reset the URL service generators + // @TODO: Prob B: why/how is this different to urlService.reset? + // @TODO: why would we do this on a fresh boot?! + urlService.resetGenerators(); + + // Actually boot Ghost + await bootGhost(options); + + // Wait for the URL service to be ready, which happens after boot + await urlServiceUtils.isFinished(); +}; + +const startGhost = async (options) => { + console.time('Start Ghost'); // eslint-disable-line no-console + options = _.merge({ + redirectsFile: true, + redirectsFileExt: '.json', + forceStart: false, + copyThemes: true, + copySettings: true, + contentFolder: path.join(os.tmpdir(), uuid.v4(), 'ghost-test'), + subdir: false + }, options); + + // Ensure we have tmp content folders populated ready for testing + // @TODO: tidy up the tmp folders after tests + prepareContentFolder(options); + + if (ghostServer && ghostServer.httpServer && !options.forceStart) { + await restartModeGhostStart(options); + } else { + await freshModeGhostStart(options); + } + + // Expose fixture data, wrap-up and return + await exposeFixtures(); + console.timeEnd('Start Ghost'); // eslint-disable-line no-console + return ghostServer; +}; + +const stopGhost = async () => { + if (ghostServer && ghostServer.httpServer) { + await ghostServer.stop(); + delete require.cache[require.resolve('../../core/app')]; + urlService.resetGenerators(); + } +}; + +module.exports = { + startGhost, + stopGhost, + getExistingData: () => { + return existingData; + } +}; diff --git a/test/utils/index.js b/test/utils/index.js index 35e6ae721a..a4a84daee0 100644 --- a/test/utils/index.js +++ b/test/utils/index.js @@ -1,35 +1,17 @@ require('../../core/server/overrides'); // Utility Packages -const Promise = require('bluebird'); const {sequence} = require('@tryghost/promise'); const _ = require('lodash'); -const fs = require('fs-extra'); -const path = require('path'); -const os = require('os'); -const uuid = require('uuid'); -const KnexMigrator = require('knex-migrator'); -const knexMigrator = new KnexMigrator(); // Ghost Internals -const config = require('../../core/shared/config'); -const boot = require('../../core/boot'); -const db = require('../../core/server/data/db'); const models = require('../../core/server/models'); -const urlService = require('../../core/frontend/services/url'); -const settingsService = require('../../core/server/services/settings'); -const frontendSettingsService = require('../../core/frontend/services/settings'); -const settingsCache = require('../../core/server/services/settings/cache'); -const web = require('../../core/server/web'); -const themeService = require('../../core/server/services/themes'); -const limits = require('../../core/server/services/limits'); // Other Test Utilities +const acceptanceUtils = require('./acceptance-utils'); const APIUtils = require('./api'); -const configUtils = require('./configUtils'); const dbUtils = require('./db-utils'); const fixtureUtils = require('./fixture-utils'); -const urlServiceUtils = require('./url-service-utils'); const oldIntegrationUtils = require('./old-integration-utils'); const redirects = require('./redirects'); const cacheRules = require('./fixtures/cache-rules'); @@ -110,192 +92,10 @@ const createEmailedPost = async function createEmailedPost({postOptions, emailOp return {post, email}; }; -let ghostServer; - -/** - * Because we use ObjectID we don't know the ID of fixtures ahead of time - * This function fetches all of our fixtures and exposes them so that tests can use them - * @TODO: Optimise this by making it optional / selective - */ -const exposeFixtures = async () => { - const fixturePromises = { - roles: models.Role.findAll({columns: ['id']}), - users: models.User.findAll({columns: ['id', 'email']}), - tags: models.Tag.findAll({columns: ['id']}), - apiKeys: models.ApiKey.findAll({withRelated: 'integration'}) - }; - const keys = Object.keys(fixturePromises); - module.exports.existingData = {}; - - return Promise - .all(Object.values(fixturePromises)) - .then((results) => { - for (let i = 0; i < keys.length; i += 1) { - module.exports.existingData[keys[i]] = results[i].toJSON(context.internal); - } - }) - .catch((err) => { - console.error('Unable to expose fixtures', err); // eslint-disable-line no-console - process.exit(1); - }); -}; - -const prepareContentFolder = (options) => { - const contentFolderForTests = options.contentFolder; - - /** - * We never use the root content folder for testing! - * We use a tmp folder. - */ - configUtils.set('paths:contentPath', contentFolderForTests); - - fs.ensureDirSync(contentFolderForTests); - fs.ensureDirSync(path.join(contentFolderForTests, 'data')); - fs.ensureDirSync(path.join(contentFolderForTests, 'themes')); - fs.ensureDirSync(path.join(contentFolderForTests, 'images')); - fs.ensureDirSync(path.join(contentFolderForTests, 'logs')); - fs.ensureDirSync(path.join(contentFolderForTests, 'adapters')); - fs.ensureDirSync(path.join(contentFolderForTests, 'settings')); - - if (options.copyThemes) { - // Copy all themes into the new test content folder. Default active theme is always casper. If you want to use a different theme, you have to set the active theme (e.g. stub) - fs.copySync(path.join(__dirname, 'fixtures', 'themes'), path.join(contentFolderForTests, 'themes')); - } - - if (options.redirectsFile) { - redirects.setupFile(contentFolderForTests, options.redirectsFileExt); - } - - if (options.copySettings) { - fs.copySync(path.join(__dirname, 'fixtures', 'settings', 'routes.yaml'), path.join(contentFolderForTests, 'settings', 'routes.yaml')); - } -}; - -// CASE: Ghost Server is Running -// In this case we need to reset things so it's as though Ghost just booted: -// - truncate database -// - re-run default fixtures -// - reload affected services -const restartModeGhostStart = async () => { - console.log('Restart Mode'); // eslint-disable-line no-console - - // Teardown truncates all tables and also calls urlServiceUtils.reset(); - await dbUtils.teardown(); - - // The tables have been truncated, this runs the fixture init task (init file 2) to re-add our default fixtures - await knexMigrator.init({only: 2}); - - // Reset the settings cache - // @TODO: Prob A: why/how is this different to using settingsCache.reset() - settingsCache.shutdown(); - await settingsService.init(); - - // Reload the frontend - await frontendSettingsService.init(); - await themeService.init(); - - // Reload the URL service & wait for it to be ready again - // @TODO: Prob B: why/how is this different to urlService.resetGenerators? - urlServiceUtils.reset(); - urlServiceUtils.init(); - await urlServiceUtils.isFinished(); - // @TODO: why does this happen _after_ URL service - web.shared.middlewares.customRedirects.reload(); - - // Trigger themes to load again - themeService.loadInactiveThemes(); - - // Reload limits service - limits.init(); -}; - -const bootGhost = async () => { - ghostServer = await boot(); -}; - -// CASE: Ghost Server needs Starting -// In this case we need to ensure that Ghost is started cleanly: -// - ensure the DB is reset -// - CASE: If we are in force start mode the server is already running so we -// - stop the server (if we are in force start mode it will be running) -// - reload affected services - just settings and not the frontend!? -// - Start Ghost: Uses OLD Boot process -const freshModeGhostStart = async (options) => { - if (options.forceStart) { - console.log('Force Start Mode'); // eslint-disable-line no-console - } else { - console.log('Fresh Start Mode'); // eslint-disable-line no-console - } - - // Reset the DB - await knexMigrator.reset({force: true}); - - // Stop the serve (forceStart Mode) - await stopGhost(); - - // Reset the settings cache - // @TODO: Prob A: why/how is this different to using settingsService.init() and why to do we need this? - settingsCache.shutdown(); - settingsCache.reset(); - - // Do a full database initialisation - await knexMigrator.init(); - - if (config.get('database:client') === 'sqlite3') { - await db.knex.raw('PRAGMA journal_mode = TRUNCATE;'); - } - - // Reset the URL service generators - // @TODO: Prob B: why/how is this different to urlService.reset? - // @TODO: why would we do this on a fresh boot?! - urlService.resetGenerators(); - - // Actually boot Ghost - await bootGhost(options); - - // Wait for the URL service to be ready, which happens after boot - await urlServiceUtils.isFinished(); -}; - -const startGhost = async (options) => { - console.time('Start Ghost'); // eslint-disable-line no-console - options = _.merge({ - redirectsFile: true, - redirectsFileExt: '.json', - forceStart: false, - copyThemes: true, - copySettings: true, - contentFolder: path.join(os.tmpdir(), uuid.v4(), 'ghost-test'), - subdir: false - }, options); - - // Ensure we have tmp content folders populated ready for testing - // @TODO: tidy up the tmp folders after tests - prepareContentFolder(options); - - if (ghostServer && ghostServer.httpServer && !options.forceStart) { - await restartModeGhostStart(options); - } else { - await freshModeGhostStart(options); - } - - // Expose fixture data, wrap-up and return - await exposeFixtures(); - console.timeEnd('Start Ghost'); // eslint-disable-line no-console - return ghostServer; -}; - -const stopGhost = async () => { - if (ghostServer && ghostServer.httpServer) { - await ghostServer.stop(); - delete require.cache[require.resolve('../../core/app')]; - urlService.resetGenerators(); - } -}; - module.exports = { - startGhost: startGhost, - stopGhost: stopGhost, + startGhost: acceptanceUtils.startGhost, + stopGhost: acceptanceUtils.stopGhost, + getExistingData: acceptanceUtils.getExistingData, teardownDb: dbUtils.teardown, truncate: dbUtils.truncate,