143921948d
refs b6728ecb0f
- The "no-shadow" eslint rune was introduced into ghost's eslint plugin (referenced commmit), which resulted in flood of warning in console output when linting the project codebase.
- This cleanup is aiming to make any new linting issues more visible. Follow up commits will contain similar cleanups in other parts of the codebase
1191 lines
42 KiB
JavaScript
1191 lines
42 KiB
JavaScript
const Promise = require('bluebird');
|
|
const _ = require('lodash');
|
|
const fs = require('fs-extra');
|
|
const path = require('path');
|
|
const os = require('os');
|
|
const express = require('../../core/shared/express');
|
|
const debug = require('ghost-ignition').debug('test');
|
|
const ObjectId = require('bson-objectid');
|
|
const uuid = require('uuid');
|
|
const KnexMigrator = require('knex-migrator');
|
|
const ghost = require('../../core/server');
|
|
const GhostServer = require('../../core/server/ghost-server');
|
|
const {events} = require('../../core/server/lib/common');
|
|
const fixtureUtils = require('../../core/server/data/schema/fixtures/utils');
|
|
const db = require('../../core/server/data/db');
|
|
const schema = require('../../core/server/data/schema').tables;
|
|
const schemaTables = Object.keys(schema);
|
|
const models = require('../../core/server/models');
|
|
const urlUtils = require('../../core/shared/url-utils');
|
|
const urlService = require('../../core/frontend/services/url');
|
|
const routingService = require('../../core/frontend/services/routing');
|
|
const settingsService = require('../../core/server/services/settings');
|
|
const frontendSettingsService = require('../../core/frontend/services/settings');
|
|
const settingsCache = require('../../core/server/services/settings/cache');
|
|
const imageLib = require('../../core/server/lib/image');
|
|
const web = require('../../core/server/web');
|
|
const permissions = require('../../core/server/services/permissions');
|
|
const {sequence} = require('@tryghost/promise');
|
|
const themes = require('../../core/frontend/services/themes');
|
|
const DataGenerator = require('./fixtures/data-generator');
|
|
const configUtils = require('./configUtils');
|
|
const filterData = require('./fixtures/filter-param');
|
|
const APIUtils = require('./api');
|
|
const config = require('../../core/shared/config');
|
|
const knexMigrator = new KnexMigrator();
|
|
let fixtures;
|
|
let toDoList;
|
|
let originalRequireFn;
|
|
let postsInserted = 0;
|
|
|
|
// Require additional assertions which help us keep our tests small and clear
|
|
require('./assertions');
|
|
|
|
/** TEST FIXTURES **/
|
|
fixtures = {
|
|
insertPosts: function insertPosts(posts) {
|
|
return Promise.map(posts, function (post) {
|
|
return models.Post.add(post, module.exports.context.internal);
|
|
});
|
|
},
|
|
|
|
insertPostsAndTags: function insertPostsAndTags() {
|
|
return Promise.map(DataGenerator.forKnex.tags, function (tag) {
|
|
return models.Tag.add(tag, module.exports.context.internal);
|
|
})
|
|
.then(function () {
|
|
return Promise.each(_.cloneDeep(DataGenerator.forKnex.posts), function (post) {
|
|
let postTagRelations = _.filter(DataGenerator.forKnex.posts_tags, {post_id: post.id});
|
|
let postAuthorsRelations = _.filter(DataGenerator.forKnex.posts_authors, {post_id: post.id});
|
|
|
|
postTagRelations = _.map(postTagRelations, function (postTagRelation) {
|
|
return _.find(DataGenerator.forKnex.tags, {id: postTagRelation.tag_id});
|
|
});
|
|
|
|
postAuthorsRelations = _.map(postAuthorsRelations, function (postAuthorsRelation) {
|
|
return _.find(DataGenerator.forKnex.users, {id: postAuthorsRelation.author_id});
|
|
});
|
|
|
|
post.tags = postTagRelations;
|
|
post.authors = postAuthorsRelations;
|
|
|
|
return models.Post.add(post, module.exports.context.internal);
|
|
});
|
|
})
|
|
.then(function () {
|
|
return Promise.map(DataGenerator.forKnex.posts_meta, function (postMeta) {
|
|
return models.PostsMeta.add(postMeta, module.exports.context.internal);
|
|
});
|
|
});
|
|
},
|
|
|
|
insertMultiAuthorPosts: function insertMultiAuthorPosts() {
|
|
let i;
|
|
let j;
|
|
let k = 0;
|
|
let posts = [];
|
|
|
|
// NOTE: this variable should become a parameter as test logic depends on it
|
|
const count = 10;
|
|
|
|
// insert users of different roles
|
|
return Promise.resolve(fixtures.createUsersWithRoles()).then(function () {
|
|
return Promise.map(DataGenerator.forKnex.tags, function (tag) {
|
|
return models.Tag.add(tag, module.exports.context.internal);
|
|
});
|
|
}).then(function () {
|
|
return Promise.all([
|
|
models.User.fetchAll(_.merge({columns: ['id']}, module.exports.context.internal)),
|
|
models.Tag.fetchAll(_.merge({columns: ['id']}, module.exports.context.internal))
|
|
]);
|
|
}).then(function (results) {
|
|
let users = results[0];
|
|
let tags = results[1];
|
|
|
|
tags = tags.toJSON();
|
|
|
|
users = users.toJSON();
|
|
users = _.map(users, 'id');
|
|
|
|
// Let's insert posts with random authors
|
|
for (i = 0; i < count; i += 1) {
|
|
const author = users[i % users.length];
|
|
posts.push(DataGenerator.forKnex.createGenericPost(k, null, null, author));
|
|
k = k + 1;
|
|
}
|
|
|
|
return Promise.map(posts, function (post, index) {
|
|
posts[index].authors = [{id: posts[index].author_id}];
|
|
posts[index].tags = [tags[Math.floor(Math.random() * (tags.length - 1))]];
|
|
return models.Post.add(posts[index], module.exports.context.internal);
|
|
});
|
|
});
|
|
},
|
|
|
|
insertExtraPosts: function insertExtraPosts(max) {
|
|
let lang;
|
|
let status;
|
|
const posts = [];
|
|
let i;
|
|
let j;
|
|
let k = postsInserted;
|
|
|
|
max = max || 50;
|
|
|
|
for (i = 0; i < 2; i += 1) {
|
|
lang = i % 2 ? 'en' : 'fr';
|
|
posts.push(DataGenerator.forKnex.createGenericPost(k, null, lang));
|
|
k = k + 1;
|
|
|
|
for (j = 0; j < max; j += 1) {
|
|
status = j % 2 ? 'draft' : 'published';
|
|
posts.push(DataGenerator.forKnex.createGenericPost(k, status, lang));
|
|
k = k + 1;
|
|
}
|
|
}
|
|
|
|
// Keep track so we can run this function again safely
|
|
postsInserted = k;
|
|
|
|
return models.User.getOwnerUser(module.exports.context.internal)
|
|
.then(function (ownerUser) {
|
|
return Promise.map(posts, function (post, index) {
|
|
posts[index].authors = [ownerUser.toJSON()];
|
|
return models.Post.add(posts[index], module.exports.context.internal);
|
|
});
|
|
});
|
|
},
|
|
|
|
insertTags: function insertTags() {
|
|
return Promise.map(DataGenerator.forKnex.tags, function (tag) {
|
|
return models.Tag.add(tag, module.exports.context.internal);
|
|
});
|
|
},
|
|
|
|
insertExtraTags: function insertExtraTags(max) {
|
|
max = max || 50;
|
|
const tags = [];
|
|
let tagName;
|
|
let i;
|
|
|
|
for (i = 0; i < max; i += 1) {
|
|
tagName = uuid.v4().split('-')[0];
|
|
tags.push(DataGenerator.forKnex.createBasic({name: tagName, slug: tagName}));
|
|
}
|
|
|
|
return Promise.map(tags, function (tag, index) {
|
|
return models.Tag.add(tags[index], module.exports.context.internal);
|
|
});
|
|
},
|
|
|
|
insertExtraPostsTags: function insertExtraPostsTags(max) {
|
|
max = max || 50;
|
|
|
|
return Promise.all([
|
|
models.Post.fetchAll(_.merge({columns: ['id'], withRelated: 'tags'}, module.exports.context.internal)),
|
|
models.Tag.fetchAll(_.merge({columns: ['id', 'name']}, module.exports.context.internal))
|
|
]).then(function (results) {
|
|
let posts = results[0].toJSON();
|
|
let tags = results[1].toJSON();
|
|
|
|
const injectionTagId = _.chain(tags)
|
|
.filter({name: 'injection'})
|
|
.map('id')
|
|
.value()[0];
|
|
|
|
if (max > posts.length) {
|
|
throw new Error('Trying to add more posts_tags than the number of posts.');
|
|
}
|
|
|
|
return Promise.map(posts.slice(0, max), function (post) {
|
|
post.tags = post.tags ? post.tags : [];
|
|
|
|
return models.Post.edit({
|
|
tags: post.tags.concat([_.find(DataGenerator.Content.tags, {id: injectionTagId})])
|
|
}, _.merge({id: post.id}, module.exports.context.internal));
|
|
});
|
|
});
|
|
},
|
|
|
|
insertRoles: function insertRoles() {
|
|
return Promise.map(DataGenerator.forKnex.roles, function (role) {
|
|
return models.Role.add(role, module.exports.context.internal);
|
|
});
|
|
},
|
|
|
|
initOwnerUser: function initOwnerUser() {
|
|
let user = DataGenerator.Content.users[0];
|
|
|
|
user = DataGenerator.forKnex.createBasic(user);
|
|
user = _.extend({}, user, {status: 'inactive'});
|
|
|
|
return Promise.map(DataGenerator.forKnex.roles, function (role) {
|
|
return models.Role.add(role, module.exports.context.internal);
|
|
}).then(function () {
|
|
const userRolesRelation = _.cloneDeep(DataGenerator.forKnex.roles_users[0]);
|
|
user.roles = _.filter(DataGenerator.forKnex.roles, {id: userRolesRelation.role_id});
|
|
return models.User.add(user, module.exports.context.internal);
|
|
});
|
|
},
|
|
|
|
insertOwnerUser: function insertOwnerUser() {
|
|
const user = _.cloneDeep(DataGenerator.forKnex.users[0]);
|
|
user.roles = [DataGenerator.forKnex.roles[3]];
|
|
return models.User.add(user, module.exports.context.internal);
|
|
},
|
|
|
|
overrideOwnerUser: function overrideOwnerUser(slug) {
|
|
return models.User.getOwnerUser(module.exports.context.internal)
|
|
.then(function (ownerUser) {
|
|
const user = DataGenerator.forKnex.createUser(DataGenerator.Content.users[0]);
|
|
|
|
if (slug) {
|
|
user.slug = slug;
|
|
}
|
|
|
|
return models.User.edit(user, _.merge({id: ownerUser.id}, module.exports.context.internal));
|
|
});
|
|
},
|
|
|
|
changeOwnerUserStatus: function changeOwnerUserStatus(options) {
|
|
return models.User.getOwnerUser(module.exports.context.internal)
|
|
.then(function (user) {
|
|
return models.User.edit({status: options.status}, _.merge({id: user.id}, module.exports.context.internal));
|
|
});
|
|
},
|
|
|
|
createUsersWithRoles: function createUsersWithRoles() {
|
|
return Promise.map(DataGenerator.forKnex.roles, function (role) {
|
|
return models.Role.add(role, module.exports.context.internal);
|
|
}).then(function () {
|
|
return Promise.map(_.cloneDeep(DataGenerator.forKnex.users), function (user) {
|
|
let userRolesRelations = _.filter(DataGenerator.forKnex.roles_users, {user_id: user.id});
|
|
|
|
userRolesRelations = _.map(userRolesRelations, function (userRolesRelation) {
|
|
return _.find(DataGenerator.forKnex.roles, {id: userRolesRelation.role_id});
|
|
});
|
|
|
|
user.roles = userRolesRelations;
|
|
return models.User.add(user, module.exports.context.internal);
|
|
});
|
|
});
|
|
},
|
|
|
|
resetRoles: function resetRoles() {
|
|
return Promise.map(_.cloneDeep(DataGenerator.forKnex.users), function (user) {
|
|
let userRolesRelations = _.filter(DataGenerator.forKnex.roles_users, {user_id: user.id});
|
|
|
|
userRolesRelations = _.map(userRolesRelations, function (userRolesRelation) {
|
|
return _.find(DataGenerator.forKnex.roles, {id: userRolesRelation.role_id});
|
|
});
|
|
|
|
user.roles = userRolesRelations;
|
|
return models.User.edit(user, _.merge({id: user.id}, module.exports.context.internal));
|
|
});
|
|
},
|
|
|
|
createUsersWithoutOwner: function createUsersWithoutOwner() {
|
|
const usersWithoutOwner = _.cloneDeep(DataGenerator.forKnex.users.slice(1));
|
|
|
|
return Promise.map(usersWithoutOwner, function (user) {
|
|
let userRolesRelations = _.filter(DataGenerator.forKnex.roles_users, {user_id: user.id});
|
|
|
|
userRolesRelations = _.map(userRolesRelations, function (userRolesRelation) {
|
|
return _.find(DataGenerator.forKnex.roles, {id: userRolesRelation.role_id});
|
|
});
|
|
|
|
user.roles = userRolesRelations;
|
|
return models.User.add(user, module.exports.context.internal);
|
|
});
|
|
},
|
|
|
|
createInactiveUser() {
|
|
const user = DataGenerator.forKnex.createUser({
|
|
email: 'inactive@test.org',
|
|
slug: 'inactive',
|
|
status: 'inactive'
|
|
});
|
|
|
|
return models.User.add(user, module.exports.context.internal);
|
|
},
|
|
|
|
createExtraUsers: function createExtraUsers() {
|
|
// grab 3 more users
|
|
let extraUsers = _.cloneDeep(DataGenerator.Content.users.slice(2, 6));
|
|
extraUsers = _.map(extraUsers, function (user) {
|
|
return DataGenerator.forKnex.createUser(_.extend({}, user, {
|
|
id: ObjectId.generate(),
|
|
email: 'a' + user.email,
|
|
slug: 'a' + user.slug
|
|
}));
|
|
});
|
|
|
|
const roles = {};
|
|
roles[extraUsers[0].id] = DataGenerator.Content.roles[0];
|
|
roles[extraUsers[1].id] = DataGenerator.Content.roles[1];
|
|
roles[extraUsers[2].id] = DataGenerator.Content.roles[2];
|
|
roles[extraUsers[3].id] = DataGenerator.Content.roles[4];
|
|
|
|
// @TODO: remove when overhauling test env
|
|
// tests need access to the extra created users (especially to the created id)
|
|
// replacement for admin2, editor2 etc
|
|
DataGenerator.Content.extraUsers = extraUsers;
|
|
|
|
return Promise.map(extraUsers, function (user) {
|
|
user.roles = roles[user.id];
|
|
return models.User.add(user, module.exports.context.internal);
|
|
});
|
|
},
|
|
|
|
insertOneUser: function insertOneUser(options) {
|
|
options = options || {};
|
|
|
|
return models.User.add({
|
|
name: options.name,
|
|
email: options.email,
|
|
slug: options.slug,
|
|
status: options.status
|
|
}, module.exports.context.internal);
|
|
},
|
|
|
|
insertOne: function insertOne(modelName, tableName, fn, index) {
|
|
const obj = DataGenerator.forKnex[fn](DataGenerator.Content[tableName][index || 0]);
|
|
return models[modelName].add(obj, module.exports.context.internal);
|
|
},
|
|
|
|
getImportFixturePath: function (filename) {
|
|
return path.resolve(__dirname + '/fixtures/import/' + filename);
|
|
},
|
|
|
|
getExportFixturePath: function (filename) {
|
|
const relativePath = '/fixtures/export/';
|
|
return path.resolve(__dirname + relativePath + filename + '.json');
|
|
},
|
|
|
|
loadExportFixture: function loadExportFixture(filename) {
|
|
const filePath = this.getExportFixturePath(filename);
|
|
|
|
return fs.readFile(filePath).then(function (fileContents) {
|
|
let data;
|
|
|
|
// Parse the json data
|
|
try {
|
|
data = JSON.parse(fileContents);
|
|
} catch (e) {
|
|
return new Error('Failed to parse the file');
|
|
}
|
|
|
|
return data;
|
|
});
|
|
},
|
|
|
|
permissionsFor: function permissionsFor(obj) {
|
|
let permsToInsert = _.cloneDeep(fixtureUtils.findModelFixtures('Permission', {object_type: obj}).entries);
|
|
const permsRolesToInsert = fixtureUtils.findPermissionRelationsForObject(obj).entries;
|
|
const actions = [];
|
|
const permissionsRoles = {};
|
|
|
|
const roles = {
|
|
Administrator: DataGenerator.Content.roles[0].id,
|
|
Editor: DataGenerator.Content.roles[1].id,
|
|
Author: DataGenerator.Content.roles[2].id,
|
|
Owner: DataGenerator.Content.roles[3].id,
|
|
Contributor: DataGenerator.Content.roles[4].id,
|
|
'Admin Integration': DataGenerator.Content.roles[5].id
|
|
};
|
|
|
|
// CASE: if empty db will throw SQLITE_MISUSE, hard to debug
|
|
if (_.isEmpty(permsToInsert)) {
|
|
return Promise.reject(new Error('no permission found:' + obj));
|
|
}
|
|
|
|
permsToInsert = _.map(permsToInsert, function (perms) {
|
|
perms.id = ObjectId.generate();
|
|
|
|
actions.push({type: perms.action_type, permissionId: perms.id});
|
|
return DataGenerator.forKnex.createBasic(perms);
|
|
});
|
|
|
|
_.each(permsRolesToInsert, function (perms, role) {
|
|
if (perms[obj]) {
|
|
if (perms[obj] === 'all') {
|
|
_.each(actions, function (action) {
|
|
if (!permissionsRoles[action.permissionId]) {
|
|
permissionsRoles[action.permissionId] = [];
|
|
}
|
|
|
|
permissionsRoles[action.permissionId].push(_.find(DataGenerator.Content.roles, {id: roles[role]}));
|
|
});
|
|
} else {
|
|
_.each(perms[obj], function (action) {
|
|
if (!permissionsRoles[_.find(actions, {type: action}).permissionId]) {
|
|
permissionsRoles[_.find(actions, {type: action}).permissionId] = [];
|
|
}
|
|
|
|
permissionsRoles[_.find(actions, {type: action}).permissionId].push(_.find(DataGenerator.Content.roles, {id: roles[role]}));
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return Promise.map(permsToInsert, function (perm) {
|
|
if (!_.isEmpty(permissionsRoles)) {
|
|
perm.roles = permissionsRoles[perm.id];
|
|
}
|
|
|
|
return models.Permission.add(perm, module.exports.context.internal);
|
|
});
|
|
},
|
|
|
|
insertInvites: function insertInvites() {
|
|
return Promise.map(DataGenerator.forKnex.invites, function (invite) {
|
|
return models.Invite.add(invite, module.exports.context.internal);
|
|
});
|
|
},
|
|
|
|
insertWebhooks: function insertWebhooks() {
|
|
return Promise.map(DataGenerator.forKnex.webhooks, function (webhook) {
|
|
return models.Webhook.add(webhook, module.exports.context.internal);
|
|
});
|
|
},
|
|
|
|
insertIntegrations: function insertIntegrations() {
|
|
return Promise.map(DataGenerator.forKnex.integrations, function (integration) {
|
|
return models.Integration.add(integration, module.exports.context.internal);
|
|
});
|
|
},
|
|
|
|
insertApiKeys: function insertApiKeys() {
|
|
return Promise.map(DataGenerator.forKnex.api_keys, function (api_key) {
|
|
return models.ApiKey.add(api_key, module.exports.context.internal);
|
|
});
|
|
},
|
|
|
|
insertEmails: function insertEmails() {
|
|
return Promise.map(DataGenerator.forKnex.emails, function (email) {
|
|
return models.Email.add(email, module.exports.context.internal);
|
|
});
|
|
},
|
|
|
|
insertMembersAndLabels: function insertMembersAndLabels() {
|
|
return Promise.map(DataGenerator.forKnex.labels, function (label) {
|
|
return models.Label.add(label, module.exports.context.internal);
|
|
}).then(function () {
|
|
return Promise.each(_.cloneDeep(DataGenerator.forKnex.members), function (member) {
|
|
let memberLabelRelations = _.filter(DataGenerator.forKnex.members_labels, {member_id: member.id});
|
|
|
|
memberLabelRelations = _.map(memberLabelRelations, function (memberLabelRelation) {
|
|
return _.find(DataGenerator.forKnex.labels, {id: memberLabelRelation.label_id});
|
|
});
|
|
|
|
member.labels = memberLabelRelations;
|
|
|
|
return models.Member.add(member, module.exports.context.internal);
|
|
});
|
|
}).then(function () {
|
|
return Promise.each(_.cloneDeep(DataGenerator.forKnex.members_stripe_customers), function (customer) {
|
|
return models.MemberStripeCustomer.add(customer, module.exports.context.internal);
|
|
});
|
|
}).then(function () {
|
|
return Promise.each(_.cloneDeep(DataGenerator.forKnex.stripe_customer_subscriptions), function (subscription) {
|
|
return models.StripeCustomerSubscription.add(subscription, module.exports.context.internal);
|
|
});
|
|
});
|
|
},
|
|
|
|
insertSnippets: function insertSnippets() {
|
|
return Promise.map(DataGenerator.forKnex.snippets, function (snippet) {
|
|
return models.Snippet.add(snippet, module.exports.context.internal);
|
|
});
|
|
}
|
|
};
|
|
|
|
/** Test Utility Functions **/
|
|
const initData = function initData() {
|
|
return knexMigrator.init()
|
|
.then(function () {
|
|
events.emit('db.ready');
|
|
|
|
let timeout;
|
|
|
|
return new Promise(function (resolve) {
|
|
(function retry() {
|
|
clearTimeout(timeout);
|
|
|
|
if (urlService.hasFinished()) {
|
|
return resolve();
|
|
}
|
|
|
|
timeout = setTimeout(retry, 50);
|
|
})();
|
|
});
|
|
});
|
|
};
|
|
|
|
const clearBruteData = function clearBruteData() {
|
|
return db.knex('brute').truncate();
|
|
};
|
|
|
|
const truncate = function truncate(tableName) {
|
|
if (config.get('database:client') === 'sqlite3') {
|
|
return db.knex(tableName).truncate();
|
|
}
|
|
|
|
return db.knex.raw('SET FOREIGN_KEY_CHECKS=0;')
|
|
.then(function () {
|
|
return db.knex(tableName).truncate();
|
|
})
|
|
.then(function () {
|
|
return db.knex.raw('SET FOREIGN_KEY_CHECKS=1;');
|
|
});
|
|
};
|
|
|
|
// we must always try to delete all tables
|
|
const clearData = function clearData() {
|
|
debug('Database reset');
|
|
return knexMigrator.reset({force: true})
|
|
.then(function () {
|
|
urlService.softReset();
|
|
});
|
|
};
|
|
|
|
toDoList = {
|
|
permission: function insertPermission() {
|
|
return fixtures.insertOne('Permission', 'permissions', 'createPermission');
|
|
},
|
|
role: function insertRole() {
|
|
return fixtures.insertOne('Role', 'roles', 'createRole');
|
|
},
|
|
roles: function insertRoles() {
|
|
return fixtures.insertRoles();
|
|
},
|
|
tag: function insertTag() {
|
|
return fixtures.insertOne('Tag', 'tags', 'createTag');
|
|
},
|
|
member: function insertMember() {
|
|
return fixtures.insertOne('Member', 'members', 'createMember');
|
|
},
|
|
members: function insertMembersAndLabels() {
|
|
return fixtures.insertMembersAndLabels();
|
|
},
|
|
posts: function insertPostsAndTags() {
|
|
return fixtures.insertPostsAndTags();
|
|
},
|
|
'posts:mu': function insertMultiAuthorPosts() {
|
|
return fixtures.insertMultiAuthorPosts();
|
|
},
|
|
tags: function insertTags() {
|
|
return fixtures.insertTags();
|
|
},
|
|
'tags:extra': function insertExtraTags() {
|
|
return fixtures.insertExtraTags();
|
|
},
|
|
settings: function populateSettings() {
|
|
settingsCache.shutdown();
|
|
return settingsService.init();
|
|
},
|
|
'users:roles': function createUsersWithRoles() {
|
|
return fixtures.createUsersWithRoles();
|
|
},
|
|
'users:no-owner': function createUsersWithoutOwner() {
|
|
return fixtures.createUsersWithoutOwner();
|
|
},
|
|
'user:inactive': function createInactiveUser() {
|
|
return fixtures.createInactiveUser();
|
|
},
|
|
'users:extra': function createExtraUsers() {
|
|
return fixtures.createExtraUsers();
|
|
},
|
|
owner: function insertOwnerUser() {
|
|
return fixtures.insertOwnerUser();
|
|
},
|
|
'owner:pre': function initOwnerUser() {
|
|
return fixtures.initOwnerUser();
|
|
},
|
|
'owner:post': function overrideOwnerUser() {
|
|
return fixtures.overrideOwnerUser();
|
|
},
|
|
'perms:init': function initPermissions() {
|
|
return permissions.init();
|
|
},
|
|
perms: function permissionsFor(obj) {
|
|
return fixtures.permissionsFor(obj);
|
|
},
|
|
filter: function createFilterParamFixtures() {
|
|
return filterData(DataGenerator);
|
|
},
|
|
invites: function insertInvites() {
|
|
return fixtures.insertInvites();
|
|
},
|
|
themes: function loadThemes() {
|
|
return themes.loadAll();
|
|
},
|
|
webhooks: function insertWebhooks() {
|
|
return fixtures.insertWebhooks();
|
|
},
|
|
integrations: function insertIntegrations() {
|
|
return fixtures.insertIntegrations();
|
|
},
|
|
api_keys: function insertApiKeys() {
|
|
return fixtures.insertApiKeys();
|
|
},
|
|
emails: function insertEmails() {
|
|
return fixtures.insertEmails();
|
|
},
|
|
snippets: function insertSnippets() {
|
|
return fixtures.insertSnippets();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* ## getFixtureOps
|
|
*
|
|
* Takes the arguments from a setup function and turns them into an array of promises to fullfil
|
|
*
|
|
* This is effectively a list of instructions with regard to which fixtures should be setup for this test.
|
|
* * `default` - a special option which will cause the full suite of normal fixtures to be initialised
|
|
* * `perms:init` - initialise the permissions object after having added permissions
|
|
* * `perms:obj` - initialise permissions for a particular object type
|
|
* * `users:roles` - create a full suite of users, one per role
|
|
* @param {Object} toDos
|
|
*/
|
|
const getFixtureOps = function getFixtureOps(toDos) {
|
|
// default = default fixtures, if it isn't present, init with tables only
|
|
const tablesOnly = !toDos.default;
|
|
|
|
const fixtureOps = [];
|
|
|
|
// Database initialisation
|
|
if (toDos.init || toDos.default) {
|
|
fixtureOps.push(function initDB() {
|
|
// skip adding all fixtures!
|
|
if (tablesOnly) {
|
|
return knexMigrator.init({skip: 2});
|
|
}
|
|
|
|
return knexMigrator.init();
|
|
});
|
|
|
|
delete toDos.default;
|
|
delete toDos.init;
|
|
}
|
|
|
|
// Go through our list of things to do, and add them to an array
|
|
_.each(toDos, function (value, toDo) {
|
|
let tmp;
|
|
|
|
if ((toDo !== 'perms:init' && toDo.indexOf('perms:') !== -1)) {
|
|
tmp = toDo.split(':');
|
|
|
|
fixtureOps.push(function addCustomFixture() {
|
|
return toDoList[tmp[0]](tmp[1]);
|
|
});
|
|
} else {
|
|
if (!toDoList[toDo]) {
|
|
throw new Error('setup todo does not exist - spell mistake?');
|
|
}
|
|
|
|
fixtureOps.push(toDoList[toDo]);
|
|
}
|
|
});
|
|
|
|
return fixtureOps;
|
|
};
|
|
|
|
// ## Test Setup and Teardown
|
|
|
|
const initFixtures = function initFixtures() {
|
|
const options = _.merge({init: true}, _.transform(arguments, function (result, val) {
|
|
result[val] = true;
|
|
}));
|
|
|
|
const fixtureOps = getFixtureOps(options);
|
|
|
|
return sequence(fixtureOps);
|
|
};
|
|
|
|
/**
|
|
* ## Setup Integration Tests
|
|
* Setup takes a list of arguments like: 'default', 'tag', 'perms:tag', 'perms:init'
|
|
* Setup does 'init' (DB) by default
|
|
* @returns {Function}
|
|
*/
|
|
const setup = function setup() {
|
|
/*eslint no-invalid-this: "off"*/
|
|
const self = this;
|
|
|
|
const args = arguments;
|
|
|
|
return function innerSetup() {
|
|
models.init();
|
|
return initFixtures.apply(self, args);
|
|
};
|
|
};
|
|
|
|
const createUser = function createUser(options) {
|
|
const user = options.user;
|
|
const role = options.role;
|
|
|
|
return models.Role.fetchAll(module.exports.context.internal)
|
|
.then(function (roles) {
|
|
roles = roles.toJSON();
|
|
user.roles = [_.find(roles, {name: role})];
|
|
|
|
return models.User.add(user, module.exports.context.internal)
|
|
.then(function () {
|
|
return user;
|
|
});
|
|
});
|
|
};
|
|
|
|
const createPost = function createPost(options) {
|
|
const post = DataGenerator.forKnex.createPost(options.post);
|
|
|
|
if (options.author) {
|
|
post.author_id = options.author.id;
|
|
}
|
|
|
|
post.authors = [{id: post.author_id}];
|
|
return models.Post.add(post, module.exports.context.internal);
|
|
};
|
|
|
|
/**
|
|
* Has to run in a transaction for MySQL, otherwise the foreign key check does not work.
|
|
* Sqlite3 has no truncate command.
|
|
*/
|
|
const teardownDb = function teardownDb() {
|
|
debug('Database teardown');
|
|
urlService.softReset();
|
|
|
|
const tables = schemaTables.concat(['migrations']);
|
|
|
|
if (config.get('database:client') === 'sqlite3') {
|
|
return Promise
|
|
.mapSeries(tables, function createTable(table) {
|
|
return db.knex.raw('DELETE FROM ' + table + ';');
|
|
})
|
|
.catch(function (err) {
|
|
// CASE: table does not exist
|
|
if (err.errno === 1) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
return db.knex.transaction(function (trx) {
|
|
return db.knex.raw('SET FOREIGN_KEY_CHECKS=0;').transacting(trx)
|
|
.then(function () {
|
|
return Promise
|
|
.each(tables, function createTable(table) {
|
|
return db.knex.raw('TRUNCATE ' + table + ';').transacting(trx);
|
|
});
|
|
})
|
|
.then(function () {
|
|
return db.knex.raw('SET FOREIGN_KEY_CHECKS=1;').transacting(trx);
|
|
})
|
|
.catch(function (err) {
|
|
// CASE: table does not exist
|
|
if (err.errno === 1146) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
throw err;
|
|
});
|
|
});
|
|
};
|
|
|
|
let ghostServer;
|
|
|
|
/**
|
|
* 1. reset & init db
|
|
* 2. start the server once
|
|
*
|
|
* @TODO: tidy up the tmp folders
|
|
*/
|
|
const startGhost = function startGhost(options) {
|
|
console.time('Start Ghost'); // eslint-disable-line no-console
|
|
options = _.merge({
|
|
redirectsFile: true,
|
|
forceStart: false,
|
|
copyThemes: true,
|
|
copySettings: true,
|
|
contentFolder: path.join(os.tmpdir(), uuid.v4(), 'ghost-test'),
|
|
subdir: false
|
|
}, options);
|
|
|
|
const contentFolderForTests = options.contentFolder;
|
|
let parentApp;
|
|
|
|
/**
|
|
* 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) {
|
|
fs.copySync(path.join(__dirname, 'fixtures', 'data', 'redirects.json'), path.join(contentFolderForTests, 'data', 'redirects.json'));
|
|
}
|
|
|
|
if (options.copySettings) {
|
|
fs.copySync(path.join(__dirname, 'fixtures', 'settings', 'routes.yaml'), path.join(contentFolderForTests, 'settings', 'routes.yaml'));
|
|
}
|
|
|
|
// truncate database and re-run fixtures
|
|
// we have to ensure that some components in Ghost are reloaded
|
|
if (ghostServer && ghostServer.httpServer && !options.forceStart) {
|
|
return teardownDb()
|
|
.then(function () {
|
|
return knexMigrator.init({only: 2});
|
|
})
|
|
.then(function () {
|
|
settingsCache.shutdown();
|
|
return settingsService.init();
|
|
})
|
|
.then(function () {
|
|
return frontendSettingsService.init();
|
|
})
|
|
.then(function () {
|
|
return themes.init();
|
|
})
|
|
.then(function () {
|
|
urlService.softReset();
|
|
events.emit('db.ready');
|
|
|
|
let timeout;
|
|
|
|
return new Promise(function (resolve) {
|
|
(function retry() {
|
|
clearTimeout(timeout);
|
|
|
|
if (urlService.hasFinished()) {
|
|
return resolve();
|
|
}
|
|
|
|
timeout = setTimeout(retry, 50);
|
|
})();
|
|
});
|
|
})
|
|
.then(function () {
|
|
web.shared.middlewares.customRedirects.reload();
|
|
|
|
events.emit('server.start');
|
|
|
|
/**
|
|
* @TODO: this is dirty, but makes routing testing a lot easier for now, because the routing test
|
|
* has no easy way to access existing resource id's, which are added from the Ghost fixtures.
|
|
* I can do `testUtils.existingData.roles[0].id`.
|
|
*/
|
|
module.exports.existingData = {};
|
|
return models.Role.findAll({columns: ['id']})
|
|
.then((roles) => {
|
|
module.exports.existingData.roles = roles.toJSON();
|
|
|
|
return models.User.findAll({columns: ['id', 'email']});
|
|
})
|
|
.then((users) => {
|
|
module.exports.existingData.users = users.toJSON(module.exports.context.internal);
|
|
|
|
return models.Tag.findAll({columns: ['id']});
|
|
})
|
|
.then((tags) => {
|
|
module.exports.existingData.tags = tags.toJSON();
|
|
return models.ApiKey.findAll({withRelated: 'integration'});
|
|
})
|
|
.then((keys) => {
|
|
module.exports.existingData.apiKeys = keys.toJSON(module.exports.context.internal);
|
|
console.timeEnd('Start Ghost'); // eslint-disable-line no-console
|
|
})
|
|
.return(ghostServer);
|
|
});
|
|
}
|
|
|
|
return knexMigrator.reset({force: true})
|
|
.then(function () {
|
|
if (ghostServer && ghostServer.httpServer) {
|
|
return ghostServer.stop();
|
|
}
|
|
})
|
|
.then(function initialiseDatabase() {
|
|
settingsCache.shutdown();
|
|
settingsCache.reset();
|
|
return knexMigrator.init();
|
|
})
|
|
.then(function setPragma() {
|
|
if (config.get('database:client') === 'sqlite3') {
|
|
return db.knex.raw('PRAGMA journal_mode = TRUNCATE;');
|
|
} else {
|
|
return Promise.resolve();
|
|
}
|
|
})
|
|
.then(function initializeGhost() {
|
|
urlService.resetGenerators();
|
|
|
|
return ghost();
|
|
})
|
|
.then(function startGhostServer(_ghostServer) {
|
|
ghostServer = _ghostServer;
|
|
|
|
if (options.subdir) {
|
|
parentApp = express('test parent');
|
|
parentApp.use(urlUtils.getSubdir(), ghostServer.rootApp);
|
|
return ghostServer.start(parentApp);
|
|
}
|
|
|
|
return ghostServer.start();
|
|
})
|
|
.then(function () {
|
|
let timeout;
|
|
|
|
GhostServer.announceServerReadiness();
|
|
|
|
return new Promise(function (resolve) {
|
|
(function retry() {
|
|
clearTimeout(timeout);
|
|
|
|
if (urlService.hasFinished()) {
|
|
return resolve();
|
|
}
|
|
|
|
timeout = setTimeout(retry, 50);
|
|
})();
|
|
});
|
|
})
|
|
.then(function returnGhost() {
|
|
/**
|
|
* @TODO: this is dirty, but makes routing testing a lot easier for now, because the routing test
|
|
* has no easy way to access existing resource id's, which are added from the Ghost fixtures.
|
|
* I can do `testUtils.existingData.roles[0].id`.
|
|
*/
|
|
module.exports.existingData = {};
|
|
return models.Role.findAll({columns: ['id']})
|
|
.then((roles) => {
|
|
module.exports.existingData.roles = roles.toJSON();
|
|
|
|
return models.User.findAll({columns: ['id', 'email']});
|
|
})
|
|
.then((users) => {
|
|
module.exports.existingData.users = users.toJSON(module.exports.context.internal);
|
|
|
|
return models.Tag.findAll({columns: ['id']});
|
|
})
|
|
.then((tags) => {
|
|
module.exports.existingData.tags = tags.toJSON();
|
|
|
|
return models.ApiKey.findAll({withRelated: 'integration'});
|
|
})
|
|
.then((keys) => {
|
|
module.exports.existingData.apiKeys = keys.toJSON();
|
|
console.timeEnd('Start Ghost'); // eslint-disable-line no-console
|
|
})
|
|
.return(ghostServer);
|
|
});
|
|
};
|
|
|
|
module.exports = {
|
|
startGhost: startGhost,
|
|
|
|
stopGhost: () => {
|
|
if (ghostServer && ghostServer.httpServer) {
|
|
return ghostServer.stop()
|
|
.then(() => {
|
|
urlService.resetGenerators();
|
|
});
|
|
}
|
|
},
|
|
|
|
integrationTesting: {
|
|
overrideGhostConfig: function overrideGhostConfig(utils) {
|
|
utils.set('paths:contentPath', path.join(__dirname, 'fixtures'));
|
|
utils.set('times:getImageSizeTimeoutInMS', 1);
|
|
},
|
|
|
|
defaultMocks: function defaultMocks(sandbox, options) {
|
|
options = options || {};
|
|
|
|
configUtils.set('paths:contentPath', path.join(__dirname, 'fixtures'));
|
|
|
|
const cacheStub = sandbox.stub(settingsCache, 'get');
|
|
|
|
cacheStub.withArgs('active_theme').returns(options.theme || 'casper');
|
|
cacheStub.withArgs('timezone').returns('Etc/UTC');
|
|
cacheStub.withArgs('permalinks').returns('/:slug/');
|
|
cacheStub.withArgs('ghost_private_key').returns('-----BEGIN RSA PRIVATE KEY-----\nMB8CAQACAgPBAgMBAAECAgMFAgEfAgEfAgEXAgEXAgEA\n-----END RSA PRIVATE KEY-----\n');
|
|
cacheStub.withArgs('ghost_public_key').returns('-----BEGIN RSA PUBLIC KEY-----\nMAkCAgPBAgMBAAE=\n-----END RSA PUBLIC KEY-----\n');
|
|
|
|
if (options.amp) {
|
|
cacheStub.withArgs('amp').returns(true);
|
|
}
|
|
|
|
sandbox.stub(imageLib.imageSize, 'getImageSizeFromUrl').resolves();
|
|
},
|
|
|
|
initGhost: function () {
|
|
models.init();
|
|
|
|
settingsCache.shutdown();
|
|
|
|
return settingsService.init()
|
|
.then(() => {
|
|
return themes.init();
|
|
});
|
|
},
|
|
|
|
routing: {
|
|
reset: function () {
|
|
routingService.registry.resetAll();
|
|
}
|
|
},
|
|
|
|
urlService: {
|
|
waitTillFinished: function (options = {dbIsReady: false}) {
|
|
let timeout;
|
|
|
|
if (!options.dbIsReady) {
|
|
events.emit('db.ready');
|
|
}
|
|
|
|
return new Promise(function (resolve) {
|
|
(function retry() {
|
|
clearTimeout(timeout);
|
|
|
|
if (urlService.hasFinished()) {
|
|
return resolve();
|
|
}
|
|
|
|
timeout = setTimeout(retry, 50);
|
|
})();
|
|
});
|
|
},
|
|
|
|
init: function () {
|
|
const routes = frontendSettingsService.get('routes');
|
|
|
|
const collectionRouter = new routingService.CollectionRouter('/', routes.collections['/']);
|
|
const tagRouter = new routingService.TaxonomyRouter('tag', routes.taxonomies.tag);
|
|
const authorRouter = new routingService.TaxonomyRouter('author', routes.taxonomies.author);
|
|
|
|
events.emit('db.ready');
|
|
|
|
return this.waitTillFinished();
|
|
},
|
|
|
|
reset: function () {
|
|
urlService.softReset();
|
|
},
|
|
|
|
resetGenerators: function () {
|
|
urlService.resetGenerators();
|
|
urlService.resources.reset({ignoreDBReady: true});
|
|
}
|
|
}
|
|
},
|
|
teardownDb: teardownDb,
|
|
truncate: truncate,
|
|
setup: setup,
|
|
createUser: createUser,
|
|
createPost: createPost,
|
|
|
|
/**
|
|
* renderObject: res.render(view, dbResponse)
|
|
* templateOptions: hbs.updateTemplateOptions(...)
|
|
*/
|
|
createHbsResponse: function createHbsResponse(options) {
|
|
const renderObject = options.renderObject || {};
|
|
const templateOptions = options.templateOptions;
|
|
const locals = options.locals || {};
|
|
|
|
const hbsStructure = {
|
|
data: {
|
|
site: {},
|
|
config: {},
|
|
labs: {},
|
|
root: {
|
|
_locals: {}
|
|
}
|
|
}
|
|
};
|
|
|
|
_.merge(hbsStructure.data, templateOptions);
|
|
_.merge(hbsStructure.data.root, renderObject);
|
|
_.merge(hbsStructure.data.root, locals);
|
|
hbsStructure.data.root._locals = locals;
|
|
|
|
return hbsStructure;
|
|
},
|
|
|
|
initFixtures: initFixtures,
|
|
initData: initData,
|
|
clearData: clearData,
|
|
clearBruteData: clearBruteData,
|
|
|
|
fixtures: fixtures,
|
|
|
|
DataGenerator: DataGenerator,
|
|
filterData: filterData,
|
|
API: APIUtils({getFixtureOps: getFixtureOps}),
|
|
|
|
// Helpers to make it easier to write tests which are easy to read
|
|
context: {
|
|
internal: {context: {internal: true}},
|
|
external: {context: {external: true}},
|
|
owner: {context: {user: DataGenerator.Content.users[0].id}},
|
|
admin: {context: {user: DataGenerator.Content.users[1].id}},
|
|
editor: {context: {user: DataGenerator.Content.users[2].id}},
|
|
author: {context: {user: DataGenerator.Content.users[3].id}},
|
|
contributor: {context: {user: DataGenerator.Content.users[7].id}},
|
|
admin_api_key: {context: {api_key: DataGenerator.Content.api_keys[0].id}},
|
|
content_api_key: {context: {api_key: DataGenerator.Content.api_keys[1].id}}
|
|
},
|
|
permissions: {
|
|
owner: {user: {roles: [DataGenerator.Content.roles[3]]}},
|
|
admin: {user: {roles: [DataGenerator.Content.roles[0]]}},
|
|
editor: {user: {roles: [DataGenerator.Content.roles[1]]}},
|
|
author: {user: {roles: [DataGenerator.Content.roles[2]]}},
|
|
contributor: {user: {roles: [DataGenerator.Content.roles[4]]}}
|
|
},
|
|
users: {
|
|
ids: {
|
|
owner: DataGenerator.Content.users[0].id,
|
|
admin: DataGenerator.Content.users[1].id,
|
|
editor: DataGenerator.Content.users[2].id,
|
|
author: DataGenerator.Content.users[3].id,
|
|
admin2: DataGenerator.Content.users[6].id,
|
|
editor2: DataGenerator.Content.users[4].id,
|
|
author2: DataGenerator.Content.users[5].id,
|
|
contributor: DataGenerator.Content.users[7].id,
|
|
contributor2: DataGenerator.Content.users[8].id
|
|
}
|
|
},
|
|
roles: {
|
|
ids: {
|
|
owner: DataGenerator.Content.roles[3].id,
|
|
admin: DataGenerator.Content.roles[0].id,
|
|
editor: DataGenerator.Content.roles[1].id,
|
|
author: DataGenerator.Content.roles[2].id,
|
|
contributor: DataGenerator.Content.roles[4].id
|
|
}
|
|
},
|
|
cacheRules: {
|
|
public: 'public, max-age=0',
|
|
hour: 'public, max-age=' + 3600,
|
|
day: 'public, max-age=' + 86400,
|
|
year: 'public, max-age=' + 31536000,
|
|
private: 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'
|
|
}
|
|
};
|