Improve bootstrap flow of a Ghost application

addresses #1789, #1364

- Moves ./core/server/loader -> ./core/bootstrap.
The bootstrap file is only accessed once during startup,
and it’s sole job is to ensure a config.js file exists
(creating one if it doesn’t) and then validates
the contents of the config file.

Since this is directly related to the initializing 
the application is is appropriate to have 
it in the ./core folder, named bootstrap as that
is what it does.

This also improves the dependency graph, as now
the bootstrap file require’s the ./core/server/config
module and is responsible for passing in the validated
config file.

Whereas before we had ./core/server/config
require’ing ./core/server/loader and running its
init code and then passing that value back to itself,
the flow is now more straight forward of
./core/bootstrap handling initialization and then
instatiation of config module

- Merges ./core/server/config/paths into 
./core/server/config
This flow was always confusing me to that some config
options were on the config object, and some were on
the paths object.

This change now incorporates all of the variables
previously defined in config/paths directly
into the config module, and in extension,
the config.js file.

This means that you now have the option of deciding
at startup where the content directory for ghost
should reside.

- broke out loader tests in config_spec to bootstrap_spec

- updated all relevant files to now use config().paths

- moved urlFor and urlForPost function into 
 ./server/config/url.js
This commit is contained in:
Harry Wolff 2014-01-05 01:40:53 -05:00
parent cf86334aed
commit f16dc290b7
25 changed files with 690 additions and 649 deletions

View File

@ -10,7 +10,7 @@ var path = require('path'),
spawn = require('child_process').spawn,
buildDirectory = path.resolve(process.cwd(), '.build'),
distDirectory = path.resolve(process.cwd(), '.dist'),
config = require('./core/server/config'),
bootstrap = require('./core/bootstrap'),
// ## Build File Patterns
@ -501,7 +501,7 @@ var path = require('path'),
grunt.registerTask('loadConfig', function () {
var done = this.async();
config.load().then(function () {
bootstrap().then(function () {
done();
});
});

View File

@ -38,6 +38,9 @@ config = {
host: '127.0.0.1',
// Port to be passed to node's `net.Server#listen()`, for iisnode set this to `process.env.PORT`
port: '2368'
},
paths: {
contentPath: path.join(__dirname, '/content/')
}
},

View File

@ -7,13 +7,13 @@
var fs = require('fs'),
url = require('url'),
when = require('when'),
errors = require('../errorHandling'),
path = require('path'),
paths = require('./paths'),
errors = require('./server/errorHandling'),
config = require('./server/config'),
appRoot = paths().appRoot,
configExample = paths().configExample,
configFile = process.env.GHOST_CONFIG || paths().config,
appRoot = config().paths.appRoot,
configExample = config().paths.configExample,
configFile = process.env.GHOST_CONFIG || config().paths.config,
rejectMessage = 'Unable to load config';
function readConfigFile(envVal) {
@ -112,8 +112,11 @@ function loadConfig() {
if (!configExists) {
pendingConfig = writeConfigFile();
}
when(pendingConfig).then(validateConfigEnvironment).then(loaded.resolve).otherwise(loaded.reject);
when(pendingConfig).then(validateConfigEnvironment).then(function (rawConfig) {
return config.init(rawConfig).then(loaded.resolve);
}).otherwise(loaded.reject);
});
return loaded.promise;
}

View File

@ -2,13 +2,13 @@
// Orchestrates the loading of Ghost
// When run from command line.
var config = require('./server/config'),
errors = require('./server/errorHandling');
var bootstrap = require('./bootstrap'),
errors = require('./server/errorHandling');
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
function startGhost(app) {
config.load().then(function () {
bootstrap().then(function () {
var ghost = require('./server');
ghost(app);
}).otherwise(errors.logAndThrowError);

View File

@ -7,7 +7,7 @@ var dataExport = require('../data/export'),
nodefn = require('when/node/function'),
_ = require('lodash'),
schema = require('../data/schema').tables,
configPaths = require('../config/paths'),
config = require('../config'),
api = {},
db;
@ -20,7 +20,7 @@ db = {
/*jslint unparam:true*/
return dataExport().then(function (exportedData) {
// Save the exported data to the file system for download
var fileName = path.join(configPaths().exportPath, 'exported-' + (new Date().getTime()) + '.json');
var fileName = path.join(config().paths.exportPath, 'exported-' + (new Date().getTime()) + '.json');
return nodefn.call(fs.writeFile, fileName, JSON.stringify(exportedData)).then(function () {
return when(fileName);
@ -39,7 +39,7 @@ db = {
};
return api.notifications.add(notification).then(function () {
res.redirect(configPaths().debugPath);
res.redirect(config().paths.debugPath);
});
});
});

View File

@ -31,7 +31,7 @@ function cacheInvalidationHeader(req, result) {
} else if (endpoint === 'posts') {
cacheInvalidate = "/, /page/*, /rss/, /rss/*";
if (id && jsonResult.slug) {
return config.paths.urlForPost(settings, jsonResult).then(function (postUrl) {
return config.urlForPost(settings, jsonResult).then(function (postUrl) {
return cacheInvalidate + ', ' + postUrl;
});
}

View File

@ -77,7 +77,7 @@ readSettingsResult = function (result) {
settings[member.attributes.key] = val;
}
})).then(function () {
return when(config.paths().availableThemes).then(function (themes) {
return when(config().paths.availableThemes).then(function (themes) {
var themeKeys = Object.keys(themes),
res = [],
i,

View File

@ -12,7 +12,7 @@ var path = require('path'),
function getAppRelativePath(name, relativeTo) {
relativeTo = relativeTo || __dirname;
return path.relative(relativeTo, path.join(config.paths().appPath, name));
return path.relative(relativeTo, path.join(config().paths.appPath, name));
}
// Load apps through a psuedo sandbox

View File

@ -3,10 +3,93 @@
// This file itself is a wrapper for the root level config.js file.
// All other files that need to reference config.js should use this file.
var loader = require('./loader'),
paths = require('./paths'),
var path = require('path'),
when = require('when'),
url = require('url'),
_ = require('lodash'),
requireTree = require('../require-tree'),
theme = require('./theme'),
ghostConfig;
configUrl = require('./url'),
ghostConfig = {},
appRoot = path.resolve(__dirname, '../../../'),
corePath = path.resolve(appRoot, 'core/');
function updateConfig(config) {
var localPath,
contentPath,
subdir;
// Merge passed in config object onto
// the cached ghostConfig object
_.merge(ghostConfig, config);
// Protect against accessing a non-existant object.
// This ensures there's always at least a paths object
// because it's referenced in multiple places.
ghostConfig.paths = ghostConfig.paths || {};
// Parse local path location
if (ghostConfig.url) {
localPath = url.parse(ghostConfig.url).path;
// Remove trailing slash
if (localPath !== '/') {
localPath = localPath.replace(/\/$/, '');
}
}
subdir = localPath === '/' ? '' : localPath;
// Allow contentPath to be over-written by passed in config object
// Otherwise default to default content path location
contentPath = ghostConfig.paths.contentPath || path.resolve(appRoot, 'content');
_.merge(ghostConfig, {
paths: {
'appRoot': appRoot,
'subdir': subdir,
'config': path.join(appRoot, 'config.js'),
'configExample': path.join(appRoot, 'config.example.js'),
'corePath': corePath,
'contentPath': contentPath,
'themePath': path.resolve(contentPath, 'themes'),
'appPath': path.resolve(contentPath, 'apps'),
'imagesPath': path.resolve(contentPath, 'images'),
'imagesRelPath': 'content/images',
'adminViews': path.join(corePath, '/server/views/'),
'helperTemplates': path.join(corePath, '/server/helpers/tpl/'),
'exportPath': path.join(corePath, '/server/data/export/'),
'lang': path.join(corePath, '/shared/lang/'),
'debugPath': subdir + '/ghost/debug/',
'availableThemes': ghostConfig.paths.availableThemes || [],
'availableApps': ghostConfig.paths.availableApps || [],
'builtScriptPath': path.join(corePath, 'built/scripts/')
}
});
// Also pass config object to
// configUrl object to maintain
// clean depedency tree
configUrl.setConfig(ghostConfig);
return ghostConfig;
}
function initConfig(rawConfig) {
// Cache the config.js object's environment
// object so we can later refer to it.
// Note: this is not the entirety of config.js,
// just the object appropriate for this NODE_ENV
ghostConfig = updateConfig(rawConfig);
return when.all([requireTree(ghostConfig.paths.themePath), requireTree(ghostConfig.paths.appPath)]).then(function (paths) {
ghostConfig.paths.availableThemes = paths[0];
ghostConfig.paths.availableApps = paths[1];
return ghostConfig;
});
}
// Returns NODE_ENV config object
function config() {
@ -14,25 +97,18 @@ function config() {
// This is currently needed for tests to load config file
// successfully. While running application we should never
// have to directly delegate to the config.js file.
return ghostConfig || require(paths().config)[process.env.NODE_ENV];
if (_.isEmpty(ghostConfig)) {
try {
ghostConfig = require(path.resolve(__dirname, '../../../', 'config.js'))[process.env.NODE_ENV] || {};
} catch (ignore) {/*jslint sloppy: true */}
ghostConfig = updateConfig(ghostConfig);
}
return ghostConfig;
}
function loadConfig() {
return loader().then(function (config) {
// Cache the config.js object's environment
// object so we can later refer to it.
// Note: this is not the entirety of config.js,
// just the object appropriate for this NODE_ENV
ghostConfig = config;
// can't load theme settings yet as we don't have the API,
// but we can load the paths
return paths.update(config.url);
});
}
config.load = loadConfig;
config.paths = paths;
config.theme = theme;
module.exports = config;
module.exports = config;
module.exports.init = initConfig;
module.exports.theme = theme;
module.exports.urlFor = configUrl.urlFor;
module.exports.urlForPost = configUrl.urlForPost;

View File

@ -2,66 +2,17 @@
// the codebase.
var moment = require('moment'),
path = require('path'),
when = require('when'),
url = require('url'),
_ = require('lodash'),
requireTree = require('../require-tree'),
appRoot = path.resolve(__dirname, '../../../'),
corePath = path.resolve(appRoot, 'core/'),
contentPath = path.resolve(appRoot, 'content/'),
themePath = path.resolve(contentPath + '/themes'),
appPath = path.resolve(contentPath + '/apps'),
themeDirectories = requireTree(themePath),
appDirectories = requireTree(appPath),
localPath = '',
configUrl = '',
ghostConfig = '';
availableThemes,
availableApps;
function paths() {
var subdir = localPath === '/' ? '' : localPath;
return {
'appRoot': appRoot,
'subdir': subdir,
'config': path.join(appRoot, 'config.js'),
'configExample': path.join(appRoot, 'config.example.js'),
'contentPath': contentPath,
'corePath': corePath,
'themePath': themePath,
'appPath': appPath,
'imagesPath': path.resolve(contentPath, 'images/'),
'imagesRelPath': 'content/images',
'adminViews': path.join(corePath, '/server/views/'),
'helperTemplates': path.join(corePath, '/server/helpers/tpl/'),
'exportPath': path.join(corePath, '/server/data/export/'),
'lang': path.join(corePath, '/shared/lang/'),
'debugPath': subdir + '/ghost/debug/',
'availableThemes': availableThemes,
'availableApps': availableApps,
'builtScriptPath': path.join(corePath, 'built/scripts/')
};
}
// TODO: remove configURL and give direct access to config object?
// TODO: not called when executing tests
function update(configURL) {
configUrl = configURL;
localPath = url.parse(configURL).path;
// Remove trailing slash
if (localPath !== '/') {
localPath = localPath.replace(/\/$/, '');
}
return when.all([themeDirectories, appDirectories]).then(function (paths) {
availableThemes = paths[0];
availableApps = paths[1];
return;
});
// ## setConfig
// Simple utility function to allow
// passing of the ghostConfig
// object here to be used locally
// to ensure clean depedency graph
// (i.e. no circular dependencies).
function setConfig(config) {
ghostConfig = config;
}
// ## createUrl
@ -85,9 +36,9 @@ function createUrl(urlPath, absolute) {
// create base of url, always ends without a slash
if (absolute) {
output += configUrl.replace(/\/$/, '');
output += ghostConfig.url.replace(/\/$/, '');
} else {
output += paths().subdir;
output += ghostConfig.paths.subdir;
}
// append the path, always starts and ends with a slash
@ -186,7 +137,6 @@ function urlForPost(settings, post, absolute) {
});
}
module.exports = paths;
module.exports.update = update;
module.exports.setConfig = setConfig;
module.exports.urlFor = urlFor;
module.exports.urlForPost = urlForPost;

View File

@ -88,7 +88,7 @@ adminControllers = {
req.session.regenerate(function (err) {
if (!err) {
req.session.user = user.id;
var redirect = config.paths().subdir + '/ghost/';
var redirect = config().paths.subdir + '/ghost/';
if (req.body.redirect) {
redirect += decodeURIComponent(req.body.redirect);
}
@ -164,7 +164,7 @@ adminControllers = {
if (req.session.user === undefined) {
req.session.user = user.id;
}
res.json(200, {redirect: config.paths().subdir + '/ghost/'});
res.json(200, {redirect: config().paths.subdir + '/ghost/'});
}
});
});
@ -206,7 +206,7 @@ adminControllers = {
};
return api.notifications.add(notification).then(function () {
res.json(200, {redirect: config.paths().subdir + '/ghost/signin/'});
res.json(200, {redirect: config().paths.subdir + '/ghost/signin/'});
});
}, function failure(error) {
@ -241,7 +241,7 @@ adminControllers = {
errors.logError(err, 'admin.js', "Please check the provided token for validity and expiration.");
return api.notifications.add(notification).then(function () {
res.redirect(config.paths().subdir + '/ghost/forgotten');
res.redirect(config().paths.subdir + '/ghost/forgotten');
});
});
},
@ -259,7 +259,7 @@ adminControllers = {
};
return api.notifications.add(notification).then(function () {
res.json(200, {redirect: config.paths().subdir + '/ghost/signin/'});
res.json(200, {redirect: config().paths.subdir + '/ghost/signin/'});
});
}).otherwise(function (err) {
res.json(401, {error: err.message});
@ -276,7 +276,7 @@ adminControllers = {
};
return api.notifications.add(notification).then(function () {
res.redirect(config.paths().subdir + '/ghost/signin/');
res.redirect(config().paths.subdir + '/ghost/signin/');
});
},
'index': function (req, res) {

View File

@ -26,7 +26,7 @@ frontendControllers = {
// No negative pages, or page 1
if (isNaN(pageParam) || pageParam < 1 || (pageParam === 1 && req.route.path === '/page/:page/')) {
return res.redirect(config.paths().subdir + '/');
return res.redirect(config().paths.subdir + '/');
}
return api.settings.read('postsPerPage').then(function (postPP) {
@ -51,7 +51,7 @@ frontendControllers = {
// If page is greater than number of pages we have, redirect to last page
if (pageParam > maxPage) {
return res.redirect(maxPage === 1 ? config.paths().subdir + '/' : (config.paths().subdir + '/page/' + maxPage + '/'));
return res.redirect(maxPage === 1 ? config().paths.subdir + '/' : (config().paths.subdir + '/page/' + maxPage + '/'));
}
// Render the page of posts
@ -78,12 +78,12 @@ frontendControllers = {
function render() {
// If we're ready to render the page but the last param is 'edit' then we'll send you to the edit page.
if (req.params[2] && req.params[2] === 'edit') {
return res.redirect(config.paths().subdir + '/ghost/editor/' + post.id + '/');
return res.redirect(config().paths.subdir + '/ghost/editor/' + post.id + '/');
}
filters.doFilter('prePostsRender', post).then(function (post) {
api.settings.read('activeTheme').then(function (activeTheme) {
var paths = config.paths().availableThemes[activeTheme.value],
var paths = config().paths.availableThemes[activeTheme.value],
view = post.page && paths.hasOwnProperty('page') ? 'page' : 'post';
res.render(view, {post: post});
});
@ -134,7 +134,7 @@ frontendControllers = {
// No negative pages, or page 1
if (isNaN(pageParam) || pageParam < 1 || (pageParam === 1 && req.route.path === '/rss/:page/')) {
return res.redirect(config.paths().subdir + '/rss/');
return res.redirect(config().paths.subdir + '/rss/');
}
// TODO: needs refactor for multi user to not use first user as default
@ -148,8 +148,8 @@ frontendControllers = {
title = result[1].value.value,
description = result[2].value.value,
permalinks = result[3].value,
siteUrl = config.paths.urlFor('home', null, true),
feedUrl = config.paths.urlFor('rss', null, true);
siteUrl = config.urlFor('home', null, true),
feedUrl = config.urlFor('rss', null, true);
feed = new RSS({
title: title,
@ -173,7 +173,7 @@ frontendControllers = {
// If page is greater than number of pages we have, redirect to last page
if (pageParam > maxPage) {
return res.redirect(config.paths().subdir + '/rss/' + maxPage + '/');
return res.redirect(config().paths.subdir + '/rss/' + maxPage + '/');
}
filters.doFilter('prePostsRender', page.posts).then(function (posts) {
@ -182,7 +182,7 @@ frontendControllers = {
item = {
title: _.escape(post.title),
guid: post.uuid,
url: config.paths.urlFor('post', {post: post, permalinks: permalinks}, true),
url: config.urlFor('post', {post: post, permalinks: permalinks}, true),
date: post.published_at,
categories: _.pluck(post.tags, 'name')
},

View File

@ -2,15 +2,15 @@
var _ = require('lodash'),
colors = require('colors'),
fs = require('fs'),
configPaths = require('./config/paths'),
config = require('./config'),
path = require('path'),
when = require('when'),
hbs = require('express-hbs'),
errors,
// Paths for views
defaultErrorTemplatePath = path.resolve(configPaths().adminViews, 'user-error.hbs'),
userErrorTemplatePath = path.resolve(configPaths().themePath, 'error.hbs'),
defaultErrorTemplatePath = path.resolve(config().paths.adminViews, 'user-error.hbs'),
userErrorTemplatePath = path.resolve(config().paths.themePath, 'error.hbs'),
userErrorTemplateExists = false,
ONE_HOUR_S = 60 * 60;
@ -20,7 +20,7 @@ var _ = require('lodash'),
*/
errors = {
updateActiveTheme: function (activeTheme, hasErrorTemplate) {
userErrorTemplatePath = path.resolve(configPaths().themePath, activeTheme, 'error.hbs');
userErrorTemplatePath = path.resolve(config().paths.themePath, activeTheme, 'error.hbs');
userErrorTemplateExists = hasErrorTemplate;
},

View File

@ -92,7 +92,7 @@ coreHelpers.encode = function (context, str) {
//
coreHelpers.pageUrl = function (context, block) {
/*jslint unparam:true*/
return config.paths().subdir + (context === 1 ? '/' : ('/page/' + context + '/'));
return config().paths.subdir + (context === 1 ? '/' : ('/page/' + context + '/'));
};
// ### URL helper
@ -108,10 +108,10 @@ coreHelpers.url = function (options) {
var absolute = options && options.hash.absolute;
if (schema.isPost(this)) {
return config.paths.urlForPost(api.settings, this, absolute);
return config.urlForPost(api.settings, this, absolute);
}
return when(config.paths.urlFor(this, absolute));
return when(config.urlFor(this, absolute));
};
// ### Asset helper
@ -125,7 +125,7 @@ coreHelpers.asset = function (context, options) {
var output = '',
isAdmin = options && options.hash && options.hash.ghost;
output += config.paths().subdir + '/';
output += config().paths.subdir + '/';
if (!context.match(/^favicon\.ico$/) && !context.match(/^shared/) && !context.match(/^asset/)) {
if (isAdmin) {
@ -278,7 +278,7 @@ coreHelpers.ghostScriptTags = function () {
scriptList = _.map(scriptList, function (fileName) {
return scriptTemplate({
source: config.paths().subdir + '/ghost/scripts/' + fileName,
source: config().paths.subdir + '/ghost/scripts/' + fileName,
version: coreHelpers.assetHash
});
});
@ -356,7 +356,7 @@ coreHelpers.ghost_head = function (options) {
head.push('<meta name="generator" content="Ghost ' + trimmedVersion + '" />');
head.push('<link rel="alternate" type="application/rss+xml" title="'
+ _.escape(blog.title) + '" href="' + config.paths.urlFor('rss') + '">');
+ _.escape(blog.title) + '" href="' + config.urlFor('rss') + '">');
return coreHelpers.url.call(self, {hash: {absolute: true}}).then(function (url) {
head.push('<link rel="canonical" href="' + url + '" />');
@ -373,7 +373,7 @@ coreHelpers.ghost_foot = function (options) {
var foot = [];
foot.push(scriptTemplate({
source: config.paths().subdir + '/shared/vendor/jquery/jquery.js',
source: config().paths.subdir + '/shared/vendor/jquery/jquery.js',
version: coreHelpers.assetHash
}));
@ -559,7 +559,7 @@ coreHelpers.adminUrl = function (options) {
// Ghost isn't a named route as currently it violates the must start-and-end with slash rule
context = !options || !options.hash || !options.hash.frontend ? {relativeUrl: '/ghost'} : 'home';
return config.paths.urlFor(context, absolute);
return config.urlFor(context, absolute);
};
coreHelpers.updateNotification = function (options) {

View File

@ -75,7 +75,7 @@ function initDbHashAndFirstRun() {
// any are missing.
function builtFilesExist() {
var deferreds = [],
location = config.paths().builtScriptPath,
location = config().paths.builtScriptPath,
fileNames = process.env.NODE_ENV === 'production' ?
helpers.scriptFiles.production : helpers.scriptFiles.development;
@ -158,7 +158,7 @@ function setup(server) {
server.set('view engine', 'hbs');
// Create a hbs instance for admin and init view engine
server.set('admin view engine', adminHbs.express3({partialsDir: config.paths().adminViews + 'partials'}));
server.set('admin view engine', adminHbs.express3({partialsDir: config().paths.adminViews + 'partials'}));
// Load helpers
helpers.loadCoreHelpers(adminHbs, assetHash);

View File

@ -35,7 +35,7 @@ function ghostLocals(req, res, next) {
res.locals = res.locals || {};
res.locals.version = packageInfo.version;
// relative path from the URL, not including subdir
res.locals.relativeUrl = req.path.replace(config.paths().subdir, '');
res.locals.relativeUrl = req.path.replace(config().paths.subdir, '');
if (res.isAdmin) {
res.locals.csrfToken = req.csrfToken();
@ -79,10 +79,10 @@ function initViews(req, res, next) {
if (!res.isAdmin) {
hbs.updateTemplateOptions({ data: {blog: config.theme()} });
expressServer.engine('hbs', expressServer.get('theme view engine'));
expressServer.set('views', path.join(config.paths().themePath, expressServer.get('activeTheme')));
expressServer.set('views', path.join(config().paths.themePath, expressServer.get('activeTheme')));
} else {
expressServer.engine('hbs', expressServer.get('admin view engine'));
expressServer.set('views', config.paths().adminViews);
expressServer.set('views', config().paths.adminViews);
}
next();
@ -92,9 +92,9 @@ function initViews(req, res, next) {
// Helper for manageAdminAndTheme
function activateTheme(activeTheme) {
var hbsOptions,
themePartials = path.join(config.paths().themePath, activeTheme, 'partials'),
themePartials = path.join(config().paths.themePath, activeTheme, 'partials'),
stackLocation = _.indexOf(expressServer.stack, _.find(expressServer.stack, function (stackItem) {
return stackItem.route === config.paths().subdir && stackItem.handle.name === 'settingEnabled';
return stackItem.route === config().paths.subdir && stackItem.handle.name === 'settingEnabled';
}));
// clear the view cache
@ -107,7 +107,7 @@ function activateTheme(activeTheme) {
}
// set view engine
hbsOptions = { partialsDir: [ config.paths().helperTemplates ] };
hbsOptions = { partialsDir: [ config().paths.helperTemplates ] };
fs.stat(themePartials, function (err, stats) {
// Check that the theme has a partials directory before trying to use it
@ -119,14 +119,14 @@ function activateTheme(activeTheme) {
expressServer.set('theme view engine', hbs.express3(hbsOptions));
// Update user error template
errors.updateActiveTheme(activeTheme, config.paths().availableThemes[activeTheme].hasOwnProperty('error'));
errors.updateActiveTheme(activeTheme, config().paths.availableThemes[activeTheme].hasOwnProperty('error'));
}
// ### ManageAdminAndTheme Middleware
// Uses the URL to detect whether this response should be an admin response
// This is used to ensure the right content is served, and is not for security purposes
function manageAdminAndTheme(req, res, next) {
res.isAdmin = req.url.lastIndexOf(config.paths().subdir + '/ghost/', 0) === 0;
res.isAdmin = req.url.lastIndexOf(config().paths.subdir + '/ghost/', 0) === 0;
if (res.isAdmin) {
expressServer.enable('admin');
@ -139,7 +139,7 @@ function manageAdminAndTheme(req, res, next) {
// Check if the theme changed
if (activeTheme.value !== expressServer.get('activeTheme')) {
// Change theme
if (!config.paths().availableThemes.hasOwnProperty(activeTheme.value)) {
if (!config().paths.availableThemes.hasOwnProperty(activeTheme.value)) {
if (!res.isAdmin) {
// Throw an error if the theme is not available, but not on the admin UI
return errors.throwError('The currently active theme ' + activeTheme.value + ' is missing.');
@ -162,7 +162,7 @@ function redirectToSignup(req, res, next) {
/*jslint unparam:true*/
api.users.browse().then(function (users) {
if (users.length === 0) {
return res.redirect(config.paths().subdir + '/ghost/signup/');
return res.redirect(config().paths.subdir + '/ghost/signup/');
}
next();
}).otherwise(function (err) {
@ -196,8 +196,8 @@ function checkSSL(req, res, next) {
}
module.exports = function (server, dbHash) {
var subdir = config.paths().subdir,
corePath = config.paths().corePath,
var subdir = config().paths.subdir,
corePath = config().paths.corePath,
cookie;
// Cache express server instance

View File

@ -47,7 +47,7 @@ var middleware = {
}
redirect = '?r=' + encodeURIComponent(reqPath);
}
return res.redirect(config.paths().subdir + '/ghost/signin/' + redirect);
return res.redirect(config().paths.subdir + '/ghost/signin/' + redirect);
});
}
next();
@ -68,7 +68,7 @@ var middleware = {
// Login and signup forms in particular
redirectToDashboard: function (req, res, next) {
if (req.session.user) {
return res.redirect(config.paths().subdir + '/ghost/');
return res.redirect(config().paths.subdir + '/ghost/');
}
next();
@ -140,7 +140,7 @@ var middleware = {
forwardToExpressStatic: function (req, res, next) {
api.settings.read('activeTheme').then(function (activeTheme) {
// For some reason send divides the max age number by 1000
express['static'](path.join(config.paths().themePath, activeTheme.value), {maxAge: ONE_HOUR_MS})(req, res, next);
express['static'](path.join(config().paths.themePath, activeTheme.value), {maxAge: ONE_HOUR_MS})(req, res, next);
});
},

View File

@ -3,7 +3,7 @@ var admin = require('../controllers/admin'),
middleware = require('../middleware').middleware;
module.exports = function (server) {
var subdir = config.paths().subdir;
var subdir = config().paths.subdir;
// ### Admin routes
server.get('/logout/', function redirect(req, res) {
/*jslint unparam:true*/

View File

@ -8,7 +8,7 @@ var _ = require('lodash'),
path = require('path'),
when = require('when'),
errors = require('../errorHandling'),
configPaths = require('../config/paths'),
config = require('../config'),
baseStore = require('./base'),
localFileStore;
@ -20,7 +20,7 @@ localFileStore = _.extend(baseStore, {
// - returns a promise which ultimately returns the full url to the uploaded image
'save': function (image) {
var saved = when.defer(),
targetDir = this.getTargetDir(configPaths().imagesPath),
targetDir = this.getTargetDir(config().paths.imagesPath),
targetFilename;
this.getUniqueFileName(this, image, targetDir).then(function (filename) {
@ -33,7 +33,7 @@ localFileStore = _.extend(baseStore, {
}).then(function () {
// The src for the image must be in URI format, not a file system path, which in Windows uses \
// For local file system storage can use relative path so add a slash
var fullUrl = (configPaths().subdir + '/' + path.relative(configPaths().appRoot, targetFilename)).replace(new RegExp('\\' + path.sep, 'g'), '/');
var fullUrl = (config().paths.subdir + '/' + path.relative(config().paths.appRoot, targetFilename)).replace(new RegExp('\\' + path.sep, 'g'), '/');
return saved.resolve(fullUrl);
}).otherwise(function (e) {
errors.logError(e);
@ -60,7 +60,7 @@ localFileStore = _.extend(baseStore, {
ONE_YEAR_MS = 365 * 24 * ONE_HOUR_MS;
// For some reason send divides the max age number by 1000
return express['static'](configPaths().imagesPath, {maxAge: ONE_YEAR_MS});
return express['static'](config().paths.imagesPath, {maxAge: ONE_YEAR_MS});
}
});

View File

@ -10,7 +10,7 @@ I18n = function (ghost) {
// TODO: validate
var lang = ghost.settings('defaultLang'),
path = config.paths().lang,
path = config().paths.lang,
langFilePath = path + lang + '.json';
return function (req, res, next) {

214
core/test/unit/bootstrap_spec.js vendored Normal file
View File

@ -0,0 +1,214 @@
/*globals describe, it, beforeEach, afterEach */
var should = require('should'),
sinon = require('sinon'),
when = require('when'),
path = require('path'),
fs = require('fs'),
_ = require('lodash'),
rewire = require("rewire"),
// Thing we are testing
defaultConfig = require('../../../config.example')[process.env.NODE_ENV],
bootstrap = rewire('../../bootstrap'),
config = rewire('../../server/config');
describe('Bootstrap', function () {
var sandbox,
rejectMessage = bootstrap.__get__('rejectMessage'),
overrideConfig = function (newConfig) {
bootstrap.__set__("readConfigFile", sandbox.stub().returns(
_.extend({}, defaultConfig, newConfig)
));
};
beforeEach(function () {
sandbox = sinon.sandbox.create();
});
afterEach(function () {
bootstrap = rewire('../../bootstrap');
sandbox.restore();
});
it('loads the config file if one exists', function (done) {
// the test infrastructure is setup so that there is always config present,
// but we want to overwrite the test to actually load config.example.js, so that any local changes
// don't break the tests
bootstrap.__set__("configFile", path.join(config().paths.appRoot, 'config.example.js'));
bootstrap().then(function (config) {
config.url.should.equal(defaultConfig.url);
config.database.client.should.equal(defaultConfig.database.client);
config.database.connection.should.eql(defaultConfig.database.connection);
config.server.host.should.equal(defaultConfig.server.host);
config.server.port.should.equal(defaultConfig.server.port);
done();
}).then(null, done);
});
it('creates the config file if one does not exist', function (done) {
var deferred = when.defer(),
// trick bootstrap into thinking that the config file doesn't exist yet
existsStub = sandbox.stub(fs, 'exists', function (file, cb) { return cb(false); }),
// create a method which will return a pre-resolved promise
resolvedPromise = sandbox.stub().returns(deferred.promise);
deferred.resolve();
// ensure that the file creation is a stub, the tests shouldn't really create a file
bootstrap.__set__("writeConfigFile", resolvedPromise);
bootstrap.__set__("validateConfigEnvironment", resolvedPromise);
bootstrap().then(function () {
existsStub.calledOnce.should.be.true;
resolvedPromise.calledTwice.should.be.true;
done();
}).then(null, done);
});
it('accepts valid urls', function (done) {
// replace the config file with invalid data
overrideConfig({url: 'http://testurl.com'});
bootstrap().then(function (localConfig) {
localConfig.url.should.equal('http://testurl.com');
// Next test
overrideConfig({url: 'https://testurl.com'});
return bootstrap();
}).then(function (localConfig) {
localConfig.url.should.equal('https://testurl.com');
// Next test
overrideConfig({url: 'http://testurl.com/blog/'});
return bootstrap();
}).then(function (localConfig) {
localConfig.url.should.equal('http://testurl.com/blog/');
// Next test
overrideConfig({url: 'http://testurl.com/ghostly/'});
return bootstrap();
}).then(function (localConfig) {
localConfig.url.should.equal('http://testurl.com/ghostly/');
// Next test
overrideConfig({url: '//testurl.com'});
return bootstrap();
}).then(function (localConfig) {
localConfig.url.should.equal('//testurl.com');
done();
}).then(null, done);
});
it('rejects invalid urls', function (done) {
// replace the config file with invalid data
overrideConfig({url: 'notvalid'});
bootstrap().otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({url: 'something.com'});
return bootstrap();
}).otherwise(function (error) {
error.should.include(rejectMessage);
done();
}).then(function () {
should.fail('no error was thrown when it should have been');
done();
}).then(done, null);
});
it('does not permit subdirectories named ghost', function (done) {
// replace the config file with invalid data
overrideConfig({url: 'http://testurl.com/ghost/'});
bootstrap().otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({url: 'http://testurl.com/ghost/blog/'});
return bootstrap();
}).otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({url: 'http://testurl.com/blog/ghost'});
return bootstrap();
}).otherwise(function (error) {
error.should.include(rejectMessage);
done();
}).then(function () {
should.fail('no error was thrown when it should have been');
done();
}).then(done, null);
});
it('requires a database config', function (done) {
// replace the config file with invalid data
overrideConfig({database: null});
bootstrap().otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({database: {}});
return bootstrap();
}).otherwise(function (error) {
error.should.include(rejectMessage);
done();
}).then(function () {
should.fail('no error was thrown when it should have been');
done();
}).then(done, null);
});
it('requires a socket or a host and port', function (done) {
// replace the config file with invalid data
overrideConfig({server: {socket: 'test'}});
bootstrap().then(function (localConfig) {
localConfig.server.socket.should.equal('test');
// Next test
overrideConfig({server: null});
return bootstrap();
}).otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({server: {host: null}});
return bootstrap();
}).otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({server: {port: null}});
return bootstrap();
}).otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({server: {host: null, port: null}});
return bootstrap();
}).otherwise(function (error) {
error.should.include(rejectMessage);
done();
}).then(function () {
should.fail('no error was thrown when it should have been');
done();
}).then(done, null);
});
});

View File

@ -12,212 +12,12 @@ var should = require('should'),
// Thing we are testing
defaultConfig = require('../../../config.example')[process.env.NODE_ENV],
loader = rewire('../../server/config/loader'),
theme = rewire('../../server/config/theme'),
paths = rewire('../../server/config/paths');
config = rewire('../../server/config'),
configUpdate = config.__get__('updateConfig');
describe('Config', function () {
describe('Loader', function () {
var sandbox,
rejectMessage = loader.__get__('rejectMessage'),
overrideConfig = function (newConfig) {
loader.__set__("readConfigFile", sandbox.stub().returns(
_.extend({}, defaultConfig, newConfig)
));
};
beforeEach(function () {
sandbox = sinon.sandbox.create();
});
afterEach(function () {
loader = rewire('../../server/config/loader');
sandbox.restore();
});
it('loads the config file if one exists', function (done) {
// the test infrastructure is setup so that there is always config present,
// but we want to overwrite the test to actually load config.example.js, so that any local changes
// don't break the tests
loader.__set__("configFile", path.join(paths().appRoot, 'config.example.js'));
loader().then(function (config) {
config.url.should.equal(defaultConfig.url);
config.database.client.should.equal(defaultConfig.database.client);
config.database.connection.should.eql(defaultConfig.database.connection);
config.server.host.should.equal(defaultConfig.server.host);
config.server.port.should.equal(defaultConfig.server.port);
done();
}).then(null, done);
});
it('creates the config file if one does not exist', function (done) {
var deferred = when.defer(),
// trick loader into thinking that the config file doesn't exist yet
existsStub = sandbox.stub(fs, 'exists', function (file, cb) { return cb(false); }),
// create a method which will return a pre-resolved promise
resolvedPromise = sandbox.stub().returns(deferred.promise);
deferred.resolve();
// ensure that the file creation is a stub, the tests shouldn't really create a file
loader.__set__("writeConfigFile", resolvedPromise);
loader.__set__("validateConfigEnvironment", resolvedPromise);
loader().then(function () {
existsStub.calledOnce.should.be.true;
resolvedPromise.calledTwice.should.be.true;
done();
}).then(null, done);
});
it('accepts valid urls', function (done) {
// replace the config file with invalid data
overrideConfig({url: 'http://testurl.com'});
loader().then(function (localConfig) {
localConfig.url.should.equal('http://testurl.com');
// Next test
overrideConfig({url: 'https://testurl.com'});
return loader();
}).then(function (localConfig) {
localConfig.url.should.equal('https://testurl.com');
// Next test
overrideConfig({url: 'http://testurl.com/blog/'});
return loader();
}).then(function (localConfig) {
localConfig.url.should.equal('http://testurl.com/blog/');
// Next test
overrideConfig({url: 'http://testurl.com/ghostly/'});
return loader();
}).then(function (localConfig) {
localConfig.url.should.equal('http://testurl.com/ghostly/');
// Next test
overrideConfig({url: '//testurl.com'});
return loader();
}).then(function (localConfig) {
localConfig.url.should.equal('//testurl.com');
done();
}).then(null, done);
});
it('rejects invalid urls', function (done) {
// replace the config file with invalid data
overrideConfig({url: 'notvalid'});
loader().otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({url: 'something.com'});
return loader();
}).otherwise(function (error) {
error.should.include(rejectMessage);
done();
}).then(function () {
should.fail('no error was thrown when it should have been');
done();
}).then(done, null);
});
it('does not permit subdirectories named ghost', function (done) {
// replace the config file with invalid data
overrideConfig({url: 'http://testurl.com/ghost/'});
loader().otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({url: 'http://testurl.com/ghost/blog/'});
return loader();
}).otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({url: 'http://testurl.com/blog/ghost'});
return loader();
}).otherwise(function (error) {
error.should.include(rejectMessage);
done();
}).then(function () {
should.fail('no error was thrown when it should have been');
done();
}).then(done, null);
});
it('requires a database config', function (done) {
// replace the config file with invalid data
overrideConfig({database: null});
loader().otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({database: {}});
return loader();
}).otherwise(function (error) {
error.should.include(rejectMessage);
done();
}).then(function () {
should.fail('no error was thrown when it should have been');
done();
}).then(done, null);
});
it('requires a socket or a host and port', function (done) {
// replace the config file with invalid data
overrideConfig({server: {socket: 'test'}});
loader().then(function (localConfig) {
localConfig.server.socket.should.equal('test');
// Next test
overrideConfig({server: null});
return loader();
}).otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({server: {host: null}});
return loader();
}).otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({server: {port: null}});
return loader();
}).otherwise(function (error) {
error.should.include(rejectMessage);
// Next test
overrideConfig({server: {host: null, port: null}});
return loader();
}).otherwise(function (error) {
error.should.include(rejectMessage);
done();
}).then(function () {
should.fail('no error was thrown when it should have been');
done();
}).then(done, null);
});
});
describe('Theme', function () {
var sandbox,
@ -268,22 +68,20 @@ describe('Config', function () {
});
});
describe('Paths', function () {
var sandbox;
describe('Index', function () {
var defaultContentPath = config().paths.contentPath;
beforeEach(function () {
sandbox = sinon.sandbox.create();
});
afterEach(function (done) {
sandbox.restore();
paths.update(defaultConfig.url)
.then(done)
.then(null, done);
afterEach(function () {
configUpdate({
url: defaultConfig.url,
paths: {
contentPath: defaultContentPath
}
});
});
it('should have exactly the right keys', function () {
var pathConfig = paths();
var pathConfig = config().paths;
// This will fail if there are any extra keys
pathConfig.should.have.keys(
@ -309,128 +107,113 @@ describe('Config', function () {
});
it('should have the correct values for each key', function () {
var pathConfig = paths(),
var pathConfig = config().paths,
appRoot = path.resolve(__dirname, '../../../');
pathConfig.should.have.property('appRoot', appRoot);
pathConfig.should.have.property('subdir', '');
});
it('should not return a slash for subdir', function (done) {
paths.update('http://my-ghost-blog.com').then(function () {
paths().should.have.property('subdir', '');
it('should not return a slash for subdir', function () {
configUpdate({url: 'http://my-ghost-blog.com'});
config().paths.should.have.property('subdir', '');
return paths.update('http://my-ghost-blog.com/');
}).then(function () {
paths().should.have.property('subdir', '');
done();
}).otherwise(done);
configUpdate({url: 'http://my-ghost-blog.com/'});
config().paths.should.have.property('subdir', '');
});
it('should handle subdirectories properly', function (done) {
paths.update('http://my-ghost-blog.com/blog').then(function () {
paths().should.have.property('subdir', '/blog');
it('should handle subdirectories properly', function () {
configUpdate({url: 'http://my-ghost-blog.com/blog'});
config().paths.should.have.property('subdir', '/blog');
return paths.update('http://my-ghost-blog.com/blog/');
}).then(function () {
paths().should.have.property('subdir', '/blog');
configUpdate({url: 'http://my-ghost-blog.com/blog/'});
config().paths.should.have.property('subdir', '/blog');
return paths.update('http://my-ghost-blog.com/my/blog');
}).then(function () {
paths().should.have.property('subdir', '/my/blog');
configUpdate({url: 'http://my-ghost-blog.com/my/blog'});
config().paths.should.have.property('subdir', '/my/blog');
return paths.update('http://my-ghost-blog.com/my/blog/');
}).then(function () {
paths().should.have.property('subdir', '/my/blog');
configUpdate({url: 'http://my-ghost-blog.com/my/blog/'});
config().paths.should.have.property('subdir', '/my/blog');
});
done();
}).otherwise(done);
it('should set contentPath and sub-directories correctly', function () {
var contentPath = config().paths.appRoot + '/otherContent/';
configUpdate({
paths: {
contentPath: contentPath
}
});
config().paths.should.have.property('contentPath', contentPath);
config().paths.should.have.property('themePath', contentPath + 'themes');
config().paths.should.have.property('appPath', contentPath + 'apps');
config().paths.should.have.property('imagesPath', contentPath + 'images');
});
});
describe('urlFor', function () {
afterEach(function (done) {
paths.update(defaultConfig.url)
.then(done)
.then(null, done);
afterEach(function () {
configUpdate({url: defaultConfig.url});
});
it('should return the home url with no options', function (done) {
paths.urlFor().should.equal('/');
paths.update('http://my-ghost-blog.com/blog').then(function () {
paths.urlFor().should.equal('/blog/');
done();
});
it('should return the home url with no options', function () {
config.urlFor().should.equal('/');
configUpdate({url: 'http://my-ghost-blog.com/blog'});
config.urlFor().should.equal('/blog/');
});
it('should return home url when asked for', function (done) {
it('should return home url when asked for', function () {
var testContext = 'home';
paths.update('http://my-ghost-blog.com').then(function () {
paths.urlFor(testContext).should.equal('/');
paths.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/');
configUpdate({url: 'http://my-ghost-blog.com'});
config.urlFor(testContext).should.equal('/');
config.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/');
return paths.update('http://my-ghost-blog.com/blog');
}).then(function () {
paths.urlFor(testContext).should.equal('/blog/');
paths.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/');
done();
});
configUpdate({url: 'http://my-ghost-blog.com/blog'});
config.urlFor(testContext).should.equal('/blog/');
config.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/');
});
it('should return rss url when asked for', function (done) {
it('should return rss url when asked for', function () {
var testContext = 'rss';
paths.update('http://my-ghost-blog.com').then(function () {
paths.urlFor(testContext).should.equal('/rss/');
paths.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/rss/');
return paths.update('http://my-ghost-blog.com/blog');
}).then(function () {
paths.urlFor(testContext).should.equal('/blog/rss/');
paths.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/rss/');
configUpdate({url: 'http://my-ghost-blog.com'});
config.urlFor(testContext).should.equal('/rss/');
config.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/rss/');
done();
});
configUpdate({url: 'http://my-ghost-blog.com/blog'});
config.urlFor(testContext).should.equal('/blog/rss/');
config.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/rss/');
});
it('should return url for a random path when asked for', function (done) {
it('should return url for a random path when asked for', function () {
var testContext = {relativeUrl: '/about/'};
paths.update('http://my-ghost-blog.com').then(function () {
paths.urlFor(testContext).should.equal('/about/');
paths.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/about/');
configUpdate({url: 'http://my-ghost-blog.com'});
config.urlFor(testContext).should.equal('/about/');
config.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/about/');
return paths.update('http://my-ghost-blog.com/blog');
}).then(function () {
paths.urlFor(testContext).should.equal('/blog/about/');
paths.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/about/');
done();
});
configUpdate({url: 'http://my-ghost-blog.com/blog'});
config.urlFor(testContext).should.equal('/blog/about/');
config.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/about/');
});
it('should return url for a post when asked for', function (done) {
it('should return url for a post when asked for', function () {
var testContext = 'post',
testData = {post: testUtils.DataGenerator.Content.posts[2], permalinks: {value: '/:slug/'}};
paths.update('http://my-ghost-blog.com').then(function () {
paths.urlFor(testContext, testData).should.equal('/short-and-sweet/');
paths.urlFor(testContext, testData, true).should.equal('http://my-ghost-blog.com/short-and-sweet/');
configUpdate({url: 'http://my-ghost-blog.com'});
config.urlFor(testContext, testData).should.equal('/short-and-sweet/');
config.urlFor(testContext, testData, true).should.equal('http://my-ghost-blog.com/short-and-sweet/');
return paths.update('http://my-ghost-blog.com/blog');
}).then(function () {
paths.urlFor(testContext, testData).should.equal('/blog/short-and-sweet/');
paths.urlFor(testContext, testData, true).should.equal('http://my-ghost-blog.com/blog/short-and-sweet/');
done();
}).then(null, done);
configUpdate({url: 'http://my-ghost-blog.com/blog'});
config.urlFor(testContext, testData).should.equal('/blog/short-and-sweet/');
config.urlFor(testContext, testData, true).should.equal('http://my-ghost-blog.com/blog/short-and-sweet/');
});
it('should return url for a dated post when asked for', function (done) {
it('should return url for a dated post when asked for', function () {
var testContext = 'post',
testData = {
post: testUtils.DataGenerator.Content.posts[2],
@ -442,17 +225,13 @@ describe('Config', function () {
yyyy = today.getFullYear(),
postLink = '/' + yyyy + '/' + mm + '/' + dd + '/short-and-sweet/';
paths.update('http://my-ghost-blog.com').then(function () {
paths.urlFor(testContext, testData).should.equal(postLink);
paths.urlFor(testContext, testData, true).should.equal('http://my-ghost-blog.com' + postLink);
configUpdate({url: 'http://my-ghost-blog.com'});
config.urlFor(testContext, testData).should.equal(postLink);
config.urlFor(testContext, testData, true).should.equal('http://my-ghost-blog.com' + postLink);
return paths.update('http://my-ghost-blog.com/blog');
}).then(function () {
paths.urlFor(testContext, testData).should.equal('/blog' + postLink);
paths.urlFor(testContext, testData, true).should.equal('http://my-ghost-blog.com/blog' + postLink);
done();
}).then(null, done);
configUpdate({url: 'http://my-ghost-blog.com/blog'});
config.urlFor(testContext, testData).should.equal('/blog' + postLink);
config.urlFor(testContext, testData, true).should.equal('http://my-ghost-blog.com/blog' + postLink);
});
});
@ -464,43 +243,40 @@ describe('Config', function () {
sandbox = sinon.sandbox.create();
});
afterEach(function (done) {
afterEach(function () {
sandbox.restore();
paths.update(defaultConfig.url)
.then(done)
.then(null, done);
configUpdate({url: defaultConfig.url});
});
it('should output correct url for post', function (done) {
var settings = {'read': function read() {}},
var settings = {'read': function read() {}},
settingsStub = sandbox.stub(settings, 'read', function () {
return when({value: '/:slug/'});
}),
testData = testUtils.DataGenerator.Content.posts[2],
postLink = '/short-and-sweet/';
paths.update('http://my-ghost-blog.com').then(function () {
configUpdate({url: 'http://my-ghost-blog.com'});
// next test
return paths.urlForPost(settings, testData);
}).then(function (url) {
// next test
config.urlForPost(settings, testData).then(function (url) {
url.should.equal(postLink);
// next test
return paths.urlForPost(settings, testData, true);
return config.urlForPost(settings, testData, true);
}).then(function (url) {
url.should.equal('http://my-ghost-blog.com' + postLink);
return paths.update('http://my-ghost-blog.com/blog');
return configUpdate({url: 'http://my-ghost-blog.com/blog'});
}).then(function () {
// next test
return paths.urlForPost(settings, testData);
return config.urlForPost(settings, testData);
}).then(function (url) {
url.should.equal('/blog' + postLink);
// next test
return paths.urlForPost(settings, testData, true);
return config.urlForPost(settings, testData, true);
}).then(function (url) {
url.should.equal('http://my-ghost-blog.com/blog' + postLink);
@ -521,28 +297,27 @@ describe('Config', function () {
yyyy = today.getFullYear(),
postLink = '/' + yyyy + '/' + mm + '/' + dd + '/short-and-sweet/';
paths.update('http://my-ghost-blog.com').then(function () {
configUpdate({url: 'http://my-ghost-blog.com'});
// next test
return paths.urlForPost(settings, testData);
}).then(function (url) {
// next test
config.urlForPost(settings, testData).then(function (url) {
url.should.equal(postLink);
// next test
return paths.urlForPost(settings, testData, true);
return config.urlForPost(settings, testData, true);
}).then(function (url) {
url.should.equal('http://my-ghost-blog.com' + postLink);
return paths.update('http://my-ghost-blog.com/blog');
return configUpdate({url: 'http://my-ghost-blog.com/blog'});
}).then(function () {
// next test
return paths.urlForPost(settings, testData);
return config.urlForPost(settings, testData);
}).then(function (url) {
url.should.equal('/blog' + postLink);
// next test
return paths.urlForPost(settings, testData, true);
return config.urlForPost(settings, testData, true);
}).then(function (url) {
url.should.equal('http://my-ghost-blog.com/blog' + postLink);
@ -558,28 +333,27 @@ describe('Config', function () {
testData = testUtils.DataGenerator.Content.posts[5],
postLink = '/static-page-test/';
paths.update('http://my-ghost-blog.com').then(function () {
configUpdate({url: 'http://my-ghost-blog.com'});
// next test
return paths.urlForPost(settings, testData);
}).then(function (url) {
// next test
config.urlForPost(settings, testData).then(function (url) {
url.should.equal(postLink);
// next test
return paths.urlForPost(settings, testData, true);
return config.urlForPost(settings, testData, true);
}).then(function (url) {
url.should.equal('http://my-ghost-blog.com' + postLink);
return paths.update('http://my-ghost-blog.com/blog');
return configUpdate({url: 'http://my-ghost-blog.com/blog'});
}).then(function () {
// next test
return paths.urlForPost(settings, testData);
return config.urlForPost(settings, testData);
}).then(function (url) {
url.should.equal('/blog' + postLink);
// next test
return paths.urlForPost(settings, testData, true);
return config.urlForPost(settings, testData, true);
}).then(function (url) {
url.should.equal('http://my-ghost-blog.com/blog' + postLink);

View File

@ -4,11 +4,11 @@ var assert = require('assert'),
should = require('should'),
sinon = require('sinon'),
when = require('when'),
rewire = require("rewire"),
// Stuff we are testing
config = require('../../server/config'),
api = require('../../server/api'),
frontend = require('../../server/controllers/frontend');
frontend = rewire('../../server/controllers/frontend');
describe('Frontend Controller', function () {
@ -19,6 +19,9 @@ describe('Frontend Controller', function () {
beforeEach(function () {
sandbox = sinon.sandbox.create();
// Reset frontend controller for next test
frontend = rewire('../../server/controllers/frontend');
});
afterEach(function () {
@ -79,7 +82,11 @@ describe('Frontend Controller', function () {
});
it('Redirects to home if page number is 0 with subdirectory', function () {
sandbox.stub(config, 'paths', function () { return {subdir: '/blog'}; });
frontend.__set__('config', function() {
return {
paths: {subdir: '/blog'}
};
});
var req = {params: {page: 0}, route: {path: '/page/:page/'}};
@ -91,7 +98,11 @@ describe('Frontend Controller', function () {
});
it('Redirects to home if page number is 1 with subdirectory', function () {
sandbox.stub(config, 'paths', function () { return {subdir: '/blog'}; });
frontend.__set__('config', function() {
return {
paths: {subdir: '/blog'}
};
});
var req = {params: {page: 1}, route: {path: '/page/:page/'}};
@ -114,7 +125,11 @@ describe('Frontend Controller', function () {
});
it('Redirects to last page if page number too big with subdirectory', function (done) {
sandbox.stub(config, 'paths', function () { return {subdir: '/blog'}; });
frontend.__set__('config', function() {
return {
paths: {subdir: '/blog'}
};
});
var req = {params: {page: 4}, route: {path: '/page/:page/'}};
@ -160,8 +175,8 @@ describe('Frontend Controller', function () {
'value': 'casper'
}));
sandbox.stub(config, 'paths', function () {
return {
frontend.__set__('config', sandbox.stub().returns({
'paths': {
'subdir': '',
'availableThemes': {
'casper': {
@ -172,8 +187,8 @@ describe('Frontend Controller', function () {
'post': '/content/themes/casper/post.hbs'
}
}
};
});
}
}));
});
describe('permalink set to slug', function () {
@ -459,7 +474,15 @@ describe('Frontend Controller', function () {
describe('rss redirects', function () {
var res,
apiUsersStub;
apiUsersStub,
overwriteConfig = function(newConfig) {
var existingConfig = frontend.__get__('config');
var newConfigModule = function() {
return newConfig;
};
newConfigModule.urlFor = existingConfig.urlFor;
frontend.__set__('config', newConfigModule);
};
beforeEach(function () {
res = {
@ -522,7 +545,7 @@ describe('Frontend Controller', function () {
});
it('Redirects to home if page number is 0 with subdirectory', function () {
sandbox.stub(config, 'paths', function () { return {subdir: '/blog'}; });
overwriteConfig({paths: {subdir: '/blog'}});
var req = {params: {page: 0}, route: {path: '/rss/:page/'}};
@ -534,7 +557,7 @@ describe('Frontend Controller', function () {
});
it('Redirects to home if page number is 1 with subdirectory', function () {
sandbox.stub(config, 'paths', function () { return {subdir: '/blog'}; });
overwriteConfig({paths: {subdir: '/blog'}});
var req = {params: {page: 1}, route: {path: '/rss/:page/'}};
@ -557,7 +580,7 @@ describe('Frontend Controller', function () {
});
it('Redirects to last page if page number too big with subdirectory', function (done) {
sandbox.stub(config, 'paths', function () { return {subdir: '/blog'}; });
overwriteConfig({paths: {subdir: '/blog'}});
var req = {params: {page: 4}, route: {path: '/rss/:page/'}};

View File

@ -1,15 +1,15 @@
/*globals describe, beforeEach, afterEach, it*/
var testUtils = require('../utils'),
should = require('should'),
sinon = require('sinon'),
when = require('when'),
_ = require("lodash"),
cp = require('child_process'),
var should = require('should'),
sinon = require('sinon'),
when = require('when'),
_ = require("lodash"),
cp = require('child_process'),
rewire = require("rewire"),
testUtils = require('../utils'),
// Stuff we are testing
defaultConfig = require('../../../config'),
mailer = require('../../server/mail'),
mailer = rewire('../../server/mail'),
defaultConfig = require('../../../config'),
SMTP,
SENDMAIL,
fakeConfig,
@ -39,6 +39,11 @@ SENDMAIL = {
};
describe("Mail", function () {
var overrideConfig = function (newConfig) {
mailer.__set__('config', sandbox.stub().returns(
_.extend({}, defaultConfig, newConfig)
));
};
beforeEach(function () {
// Mock config and settings
@ -72,7 +77,7 @@ describe("Mail", function () {
});
it('should setup SMTP transport on initialization', function (done) {
fakeConfig[process.env.NODE_ENV].mail = SMTP;
overrideConfig({mail: SMTP});
mailer.init().then(function () {
mailer.should.have.property('transport');
mailer.transport.transportType.should.eql('SMTP');
@ -82,7 +87,7 @@ describe("Mail", function () {
});
it('should setup sendmail transport on initialization', function (done) {
fakeConfig[process.env.NODE_ENV].mail = SENDMAIL;
overrideConfig({mail: SENDMAIL});
mailer.init().then(function () {
mailer.should.have.property('transport');
mailer.transport.transportType.should.eql('SENDMAIL');
@ -92,7 +97,7 @@ describe("Mail", function () {
});
it('should fallback to sendmail if no config set', function (done) {
fakeConfig[process.env.NODE_ENV].mail = null;
overrideConfig({mail: null});
mailer.init().then(function () {
mailer.should.have.property('transport');
mailer.transport.transportType.should.eql('SENDMAIL');
@ -102,7 +107,7 @@ describe("Mail", function () {
});
it('should fallback to sendmail if config is empty', function (done) {
fakeConfig[process.env.NODE_ENV].mail = {};
overrideConfig({mail: {}});
mailer.init().then(function () {
mailer.should.have.property('transport');
mailer.transport.transportType.should.eql('SENDMAIL');
@ -112,7 +117,7 @@ describe("Mail", function () {
});
it('should disable transport if config is empty & sendmail not found', function (done) {
fakeConfig[process.env.NODE_ENV].mail = {};
overrideConfig({mail: {}});
mailer.detectSendmail.restore();
sandbox.stub(mailer, "detectSendmail", when.reject);
mailer.init().then(function () {
@ -122,7 +127,7 @@ describe("Mail", function () {
});
it('should disable transport if config is empty & platform is win32', function (done) {
fakeConfig[process.env.NODE_ENV].mail = {};
overrideConfig({mail: {}});
mailer.detectSendmail.restore();
mailer.isWindows.restore();
sandbox.stub(mailer, 'isWindows', function () {

View File

@ -1,24 +1,30 @@
/*globals describe, beforeEach, afterEach, it*/
var testUtils = require('../utils'),
should = require('should'),
sinon = require('sinon'),
when = require('when'),
_ = require('lodash'),
path = require('path'),
rewire = require('rewire'),
api = require('../../server/api'),
hbs = require('express-hbs'),
packageInfo = require('../../../package'),
var testUtils = require('../utils'),
should = require('should'),
sinon = require('sinon'),
when = require('when'),
_ = require('lodash'),
path = require('path'),
rewire = require('rewire'),
api = require('../../server/api'),
hbs = require('express-hbs'),
packageInfo = require('../../../package'),
// Stuff we are testing
handlebars = hbs.handlebars,
helpers = rewire('../../server/helpers'),
config = require('../../server/config');
handlebars = hbs.handlebars,
helpers = rewire('../../server/helpers'),
config = rewire('../../server/config'),
configUpdate = config.__get__('updateConfig');
describe('Core Helpers', function () {
var sandbox,
apiStub;
apiStub,
overrideConfig = function (newConfig) {
helpers.__set__('config', function() {
return newConfig;
});
};
beforeEach(function (done) {
var adminHbs = hbs.create();
@ -28,6 +34,7 @@ describe('Core Helpers', function () {
return when({value: 'casper'});
});
config = helpers.__get__('config');
config.theme = sandbox.stub(config, 'theme', function () {
return {
title: 'Ghost',
@ -38,7 +45,7 @@ describe('Core Helpers', function () {
helpers.loadCoreHelpers(adminHbs);
// Load template helpers in handlebars
hbs.express3({ partialsDir: [config.paths().helperTemplates] });
hbs.express3({ partialsDir: [config().paths.helperTemplates] });
hbs.cachePartials(function () {
done();
});
@ -317,10 +324,8 @@ describe('Core Helpers', function () {
// TODO: these tests should be easier to do!
var configUrl = config().url;
afterEach(function (done) {
config.paths.update(configUrl).then(function () {
done();
}).then(null, done);
afterEach(function () {
configUpdate({url: configUrl});
});
it('has loaded ghost_head helper', function () {
@ -328,54 +333,50 @@ describe('Core Helpers', function () {
});
it('returns meta tag string', function (done) {
config.paths.update('http://testurl.com/').then(function () {
helpers.ghost_head.call({version: "0.3.0"}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
'<link rel="canonical" href="http://testurl.com/" />');
configUpdate({url: 'http://testurl.com/'});
helpers.ghost_head.call({version: "0.3.0"}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
'<link rel="canonical" href="http://testurl.com/" />');
done();
});
done();
}).then(null, done);
});
it('returns meta tag string even if version is invalid', function (done) {
config.paths.update('http://testurl.com/').then(function () {
return helpers.ghost_head.call({version: "0.9"}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.9" />\n' +
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
'<link rel="canonical" href="http://testurl.com/" />');
configUpdate({url: 'http://testurl.com/'});
helpers.ghost_head.call({version: "0.9"}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.9" />\n' +
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
'<link rel="canonical" href="http://testurl.com/" />');
done();
});
done();
}).then(null, done);
});
it('returns correct rss url with subdirectory', function (done) {
config.paths.update('http://testurl.com/blog/').then(function () {
return helpers.ghost_head.call({version: "0.3.0"}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/blog/rss/">\n' +
'<link rel="canonical" href="http://testurl.com/blog/" />');
configUpdate({url: 'http://testurl.com/blog/'});
helpers.ghost_head.call({version: "0.3.0"}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/blog/rss/">\n' +
'<link rel="canonical" href="http://testurl.com/blog/" />');
done();
});
done();
}).then(null, done);
});
it('returns canonical URL', function (done) {
config.paths.update('http://testurl.com').then(function () {
return helpers.ghost_head.call({version: "0.3.0", relativeUrl: '/about/'}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
'<link rel="canonical" href="http://testurl.com/about/" />');
configUpdate({url: 'http://testurl.com'});
helpers.ghost_head.call({version: "0.3.0", relativeUrl: '/about/'}).then(function (rendered) {
should.exist(rendered);
rendered.string.should.equal('<meta name="generator" content="Ghost 0.3" />\n' +
'<link rel="alternate" type="application/rss+xml" title="Ghost" href="/rss/">\n' +
'<link rel="canonical" href="http://testurl.com/about/" />');
done();
});
done();
}).then(null, done);
});
});
@ -448,7 +449,11 @@ describe('Core Helpers', function () {
});
it('can return a valid url with subdirectory', function () {
sandbox.stub(config, 'paths', function () { return {'subdir': '/blog'}; });
helpers.__set__('config', function() {
return {
paths: {'subdir': '/blog'}
};
});
helpers.pageUrl(1).should.equal('/blog/');
helpers.pageUrl(2).should.equal('/blog/page/2/');
helpers.pageUrl(50).should.equal('/blog/page/50/');
@ -674,17 +679,19 @@ describe('Core Helpers', function () {
describe("asset helper", function () {
var rendered,
configStub;
configOriginal;
beforeEach(function () {
// set the asset hash
helpers.assetHash = 'abc';
before(function() {
configOriginal = helpers.__get__('config');
});
afterEach(function () {
if (configStub) {
configStub.restore();
}
after(function() {
helpers.__set__('config', configOriginal);
});
beforeEach(function () {
helpers.assetHash = 'abc';
helpers.__set__('config', configOriginal);
});
it('has loaded asset helper', function () {
@ -702,8 +709,8 @@ describe('Core Helpers', function () {
should.exist(rendered);
String(rendered).should.equal('/favicon.ico');
configStub = sinon.stub(config, 'paths', function () {
return {'subdir': '/blog'};
overrideConfig({
paths: {'subdir': '/blog'}
});
// with subdirectory
@ -728,8 +735,8 @@ describe('Core Helpers', function () {
should.exist(rendered);
String(rendered).should.equal('/shared/asset.js?v=abc');
configStub = sinon.stub(config, 'paths', function () {
return {'subdir': '/blog'};
overrideConfig({
paths: {'subdir': '/blog'}
});
// with subdirectory
@ -749,8 +756,8 @@ describe('Core Helpers', function () {
should.exist(rendered);
String(rendered).should.equal('/ghost/js/asset.js?v=abc');
configStub = sinon.stub(config, 'paths', function () {
return {'subdir': '/blog'};
overrideConfig({
paths: {'subdir': '/blog'}
});
// with subdirectory
@ -765,8 +772,8 @@ describe('Core Helpers', function () {
should.exist(rendered);
String(rendered).should.equal('/assets/js/asset.js?v=abc');
configStub = sinon.stub(config, 'paths', function () {
return {'subdir': '/blog'};
overrideConfig({
paths: {'subdir': '/blog'}
});
// with subdirectory
@ -780,18 +787,21 @@ describe('Core Helpers', function () {
// ## Admin only helpers
describe("ghostScriptTags helper", function () {
var rendered,
configStub;
configOriginal;
before(function() {
configOriginal = helpers.__get__('config');
});
after(function() {
helpers.__set__('config', configOriginal);
});
beforeEach(function () {
// set the asset hash
helpers = rewire('../../server/helpers');
helpers.assetHash = 'abc';
});
afterEach(function () {
if (configStub) {
configStub.restore();
}
helpers.__set__('config', configOriginal);
});
it('has loaded ghostScriptTags helper', function () {
@ -809,8 +819,8 @@ describe('Core Helpers', function () {
'<script src="/ghost/scripts/views.js?v=abc"></script>'
);
configStub = sinon.stub(config, 'paths', function () {
return {'subdir': '/blog'};
overrideConfig({
paths: {'subdir': '/blog'}
});
// with subdirectory
@ -833,8 +843,8 @@ describe('Core Helpers', function () {
should.exist(rendered);
String(rendered).should.equal('<script src="/ghost/scripts/ghost.min.js?v=abc"></script>');
configStub = sinon.stub(config, 'paths', function () {
return {'subdir': '/blog'};
overrideConfig({
paths: {'subdir': '/blog'}
});
// with subdirectory
@ -848,10 +858,8 @@ describe('Core Helpers', function () {
var rendered,
configUrl = config().url;
afterEach(function (done) {
config.paths.update(configUrl).then(function () {
done();
}).then(null, done);
afterEach(function () {
configUpdate({url: configUrl});
});
@ -861,40 +869,33 @@ describe('Core Helpers', function () {
rendered.should.equal('/ghost');
});
it('should output the path to admin with subdirectory', function (done) {
config.paths.update('http://testurl.com/blog/').then(function () {
rendered = helpers.adminUrl();
should.exist(rendered);
rendered.should.equal('/blog/ghost');
done();
});
it('should output the path to admin with subdirectory', function () {
configUpdate({url: 'http://testurl.com/blog/'});
rendered = helpers.adminUrl();
should.exist(rendered);
rendered.should.equal('/blog/ghost');
});
it('should output absolute path if absolute is set', function (done) {
it('should output absolute path if absolute is set', function () {
// no trailing slash
config.paths.update('http://testurl.com').then(function () {
configUpdate({url: 'http://testurl.com'});
rendered = helpers.adminUrl({"hash": {absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/ghost');
rendered = helpers.adminUrl({"hash": {absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/ghost');
// test trailing slash
return config.paths.update('http://testurl.com/');
}).then(function () {
rendered = helpers.adminUrl({"hash": {absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/ghost');
done();
});
// test trailing slash
configUpdate({url: 'http://testurl.com/'});
rendered = helpers.adminUrl({"hash": {absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/ghost');
});
it('should output absolute path with subdirectory', function (done) {
config.paths.update('http://testurl.com/blog').then(function () {
rendered = helpers.adminUrl({"hash": {absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/blog/ghost');
done();
});
it('should output absolute path with subdirectory', function () {
configUpdate({url: 'http://testurl.com/blog'});
rendered = helpers.adminUrl({"hash": {absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/blog/ghost');
});
it('should output the path to frontend if frontend is set', function () {
@ -903,39 +904,31 @@ describe('Core Helpers', function () {
rendered.should.equal('/');
});
it('should output the absolute path to frontend if both are set', function (done) {
config.paths.update('http://testurl.com').then(function () {
it('should output the absolute path to frontend if both are set', function () {
configUpdate({url: 'http://testurl.com'});
rendered = helpers.adminUrl({"hash": {frontend: true, absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/');
rendered = helpers.adminUrl({"hash": {frontend: true, absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/');
return config.paths.update('http://testurl.com/');
}).then(function () {
rendered = helpers.adminUrl({"hash": {frontend: true, absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/');
done();
});
configUpdate({url: 'http://testurl.com/'});
rendered = helpers.adminUrl({"hash": {frontend: true, absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/');
});
it('should output the path to frontend with subdirectory', function (done) {
config.paths.update('http://testurl.com/blog/').then(function () {
rendered = helpers.adminUrl({"hash": {frontend: true}});
should.exist(rendered);
rendered.should.equal('/blog/');
done();
});
it('should output the path to frontend with subdirectory', function () {
configUpdate({url: 'http://testurl.com/blog/'});
rendered = helpers.adminUrl({"hash": {frontend: true}});
should.exist(rendered);
rendered.should.equal('/blog/');
});
it('should output the absolute path to frontend with subdirectory', function (done) {
config.paths.update('http://testurl.com/blog/').then(function () {
rendered = helpers.adminUrl({"hash": {frontend: true, absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/blog/');
done();
});
it('should output the absolute path to frontend with subdirectory', function () {
configUpdate({url: 'http://testurl.com/blog/'});
rendered = helpers.adminUrl({"hash": {frontend: true, absolute: true}});
should.exist(rendered);
rendered.should.equal('http://testurl.com/blog/');
});
});
describe('updateNotification', function () {