From a68592a6b976e9e39246ef645877a93fe7a3e3d8 Mon Sep 17 00:00:00 2001 From: Katharina Irrgang Date: Fri, 3 Feb 2017 19:13:22 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A5=E2=9C=A8=20remove=20forceAdminSSL?= =?UTF-8?q?=20and=20urlSSL,=20add=20admin=20url=20(#7937)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🔥 kill apiUrl helper, use urlFor helper instead More consistency of creating urls. Creates an easier ability to add config changes. Attention: urlFor function is getting a little nesty, BUT that is for now wanted to make easier and centralised changes to the configs. The url util need's refactoring anyway. * 🔥 urlSSL Remove all urlSSL usages. Add TODO's for the next commit to re-add logic for deleted logic. e.g. - cors helper generated an array of url's to allow requests from the defined config url's -> will be replaced by the admin url if available - theme handler prefered the urlSSL in case it was defined -> will be replaced by using the urlFor helper to get the blog url (based on the request secure flag) The changes in this commit doesn't have to be right, but it helped going step by step. The next commit is the more interesting one. * 🔥 ✨ remove forceAdminSSL, add new admin url and adapt logic I wanted to remove the forceAdminSSL as separate commit, but was hard to realise. That's why both changes are in one commit: 1. remove forceAdminSSL 2. add admin.url option - fix TODO's from last commits - rewrite the ssl middleware! - create some private helper functions in the url helper to realise the changes - rename some wordings and functions e.g. base === blog (we have so much different wordings) - i would like to do more, but this would end in a non readable PR - this commit contains the most important changes to offer admin.url option * 🤖 adapt tests IMPORTANT - all changes in the routing tests were needed, because each routing test did not start the ghost server - they just required the ghost application, which resulted in a random server port - having a random server port results in a redirect, caused by the ssl/redirect middleware * 😎 rename check-ssl middleware * 🎨 fix theme-handler because of master rebase --- core/server/admin/app.js | 4 +- core/server/api/app.js | 4 +- core/server/blog/app.js | 4 +- core/server/config/defaults.json | 2 - core/server/index.js | 2 +- core/server/middleware/api/cors.js | 11 +- core/server/middleware/check-ssl.js | 61 - core/server/middleware/serve-shared-file.js | 2 +- core/server/middleware/theme-handler.js | 10 +- core/server/middleware/url-redirects.js | 68 + core/server/utils/url.js | 162 +- core/test/functional/routes/admin_spec.js | 327 ++--- .../routes/api/authentication_spec.js | 18 +- .../routes/api/configuration_spec.js | 21 +- core/test/functional/routes/api/db_spec.js | 19 +- core/test/functional/routes/api/error_spec.js | 40 +- .../routes/api/notifications_spec.js | 19 +- core/test/functional/routes/api/posts_spec.js | 19 +- .../functional/routes/api/public_api_spec.js | 19 +- .../functional/routes/api/settings_spec.js | 19 +- core/test/functional/routes/api/slugs_spec.js | 19 +- .../routes/api/spam_prevention_spec.js | 50 +- core/test/functional/routes/api/tags_spec.js | 19 +- .../test/functional/routes/api/themes_spec.js | 17 +- .../functional/routes/api/upload_icon_spec.js | 18 +- .../test/functional/routes/api/upload_spec.js | 19 +- core/test/functional/routes/api/users_spec.js | 19 +- core/test/functional/routes/channel_spec.js | 16 +- core/test/functional/routes/frontend_spec.js | 1304 +++++++++-------- core/test/unit/ghost_url_spec.js | 17 +- core/test/unit/middleware/api/cors_spec.js | 11 +- core/test/unit/middleware/check-ssl_spec.js | 226 --- .../unit/middleware/theme-handler_spec.js | 17 - .../unit/middleware/url-redirects_spec.js | 196 +++ core/test/unit/slack_spec.js | 2 +- core/test/unit/utils/url_spec.js | 202 +-- core/test/utils/configUtils.js | 6 + 37 files changed, 1527 insertions(+), 1462 deletions(-) delete mode 100644 core/server/middleware/check-ssl.js create mode 100644 core/server/middleware/url-redirects.js delete mode 100644 core/test/unit/middleware/check-ssl_spec.js create mode 100644 core/test/unit/middleware/url-redirects_spec.js diff --git a/core/server/admin/app.js b/core/server/admin/app.js index 121827048d..9093b952b2 100644 --- a/core/server/admin/app.js +++ b/core/server/admin/app.js @@ -8,7 +8,7 @@ var debug = require('debug')('ghost:admin'), // Global/shared middleware? cacheControl = require('../middleware/cache-control'), - checkSSL = require('../middleware/check-ssl'), + urlRedirects = require('../middleware/url-redirects'), errorHandler = require('../middleware//error-handler'), maintenance = require('../middleware/maintenance'), prettyURLs = require('../middleware//pretty-urls'), @@ -46,7 +46,7 @@ module.exports = function setupAdminApp() { // Force SSL if required // must happen AFTER asset loading and BEFORE routing - adminApp.use(checkSSL); + adminApp.use(urlRedirects); // Add in all trailing slashes & remove uppercase // must happen AFTER asset loading and BEFORE routing diff --git a/core/server/api/app.js b/core/server/api/app.js index 6bfe72465c..5e581abe74 100644 --- a/core/server/api/app.js +++ b/core/server/api/app.js @@ -21,7 +21,7 @@ var debug = require('debug')('ghost:api'), // Shared bodyParser = require('body-parser'), // global, shared cacheControl = require('../middleware/cache-control'), // global, shared - checkSSL = require('../middleware/check-ssl'), + urlRedirects = require('../middleware/url-redirects'), prettyURLs = require('../middleware/pretty-urls'), maintenance = require('../middleware/maintenance'), // global, shared errorHandler = require('../middleware/error-handler'), // global, shared @@ -235,7 +235,7 @@ module.exports = function setupApiApp() { // Force SSL if required // must happen AFTER asset loading and BEFORE routing - apiApp.use(checkSSL); + apiApp.use(urlRedirects); // Add in all trailing slashes & remove uppercase // must happen AFTER asset loading and BEFORE routing diff --git a/core/server/blog/app.js b/core/server/blog/app.js index f104e9f41f..d83e1792c5 100644 --- a/core/server/blog/app.js +++ b/core/server/blog/app.js @@ -14,7 +14,7 @@ var debug = require('debug')('ghost:blog'), // local middleware cacheControl = require('../middleware/cache-control'), - checkSSL = require('../middleware/check-ssl'), + urlRedirects = require('../middleware/url-redirects'), errorHandler = require('../middleware/error-handler'), maintenance = require('../middleware/maintenance'), prettyURLs = require('../middleware/pretty-urls'), @@ -75,7 +75,7 @@ module.exports = function setupBlogApp() { // Force SSL if required // must happen AFTER asset loading and BEFORE routing - blogApp.use(checkSSL); + blogApp.use(urlRedirects); // Add in all trailing slashes & remove uppercase // must happen AFTER asset loading and BEFORE routing diff --git a/core/server/config/defaults.json b/core/server/config/defaults.json index f34f8d535f..456828f9a8 100644 --- a/core/server/config/defaults.json +++ b/core/server/config/defaults.json @@ -1,7 +1,5 @@ { "url": "http://localhost:2368", - "urlSSL": false, - "forceAdminSSL": false, "server": { "host": "127.0.0.1", "port": 2368 diff --git a/core/server/index.js b/core/server/index.js index f021b2611d..18d4ffb757 100644 --- a/core/server/index.js +++ b/core/server/index.js @@ -170,7 +170,7 @@ function init(options) { return scheduling.init({ schedulerUrl: config.get('scheduling').schedulerUrl, active: config.get('scheduling').active, - apiUrl: utils.url.apiUrl(), + apiUrl: utils.url.urlFor('api', true), internalPath: config.get('paths').internalSchedulingPath, contentPath: config.getContentPath('scheduling') }); diff --git a/core/server/middleware/api/cors.js b/core/server/middleware/api/cors.js index 77173523b0..07a4a602fe 100644 --- a/core/server/middleware/api/cors.js +++ b/core/server/middleware/api/cors.js @@ -3,7 +3,6 @@ var cors = require('cors'), url = require('url'), os = require('os'), utils = require('../../utils'), - config = require('../../config'), whitelist = [], ENABLE_CORS = {origin: true, maxAge: 86400}, DISABLE_CORS = {origin: false}; @@ -33,10 +32,14 @@ function getIPs() { } function getUrls() { - var urls = [url.parse(utils.url.urlFor('home', true)).hostname]; + var blogHost = url.parse(utils.url.urlFor('home', true)).hostname, + adminHost = url.parse(utils.url.urlFor('admin', true)).hostname, + urls = []; - if (config.get('urlSSL')) { - urls.push(url.parse(config.get('urlSSL')).hostname); + urls.push(blogHost); + + if (adminHost !== blogHost) { + urls.push(adminHost); } return urls; diff --git a/core/server/middleware/check-ssl.js b/core/server/middleware/check-ssl.js deleted file mode 100644 index c07db32727..0000000000 --- a/core/server/middleware/check-ssl.js +++ /dev/null @@ -1,61 +0,0 @@ -var config = require('../config'), - url = require('url'), - utils = require('../utils'), - checkSSL; - -function isSSLrequired(isAdmin, configUrl, forceAdminSSL) { - var forceSSL = url.parse(configUrl).protocol === 'https:' ? true : false; - if (forceSSL || (isAdmin && forceAdminSSL)) { - return true; - } - return false; -} - -// The guts of checkSSL. Indicate forbidden or redirect according to configuration. -// Required args: forceAdminSSL, url and urlSSL should be passed from config. reqURL from req.url -function sslForbiddenOrRedirect(opt) { - var forceAdminSSL = opt.forceAdminSSL, - reqUrl = url.parse(opt.reqUrl), // expected to be relative-to-root - baseUrl = url.parse(opt.configUrlSSL || opt.configUrl), - response = { - // Check if forceAdminSSL: { redirect: false } is set, which means - // we should just deny non-SSL access rather than redirect - isForbidden: (forceAdminSSL && forceAdminSSL.redirect !== undefined && !forceAdminSSL.redirect), - - redirectUrl: function redirectUrl(query) { - return url.format({ - protocol: 'https:', - hostname: baseUrl.hostname, - port: baseUrl.port, - pathname: reqUrl.pathname, - query: query - }); - } - }; - - return response; -} - -// Check to see if we should use SSL -// and redirect if needed -checkSSL = function checkSSL(req, res, next) { - if (isSSLrequired(res.isAdmin, utils.url.urlFor('home', true), config.get('forceAdminSSL'))) { - if (!req.secure) { - var response = sslForbiddenOrRedirect({ - forceAdminSSL: config.get('forceAdminSSL'), - configUrlSSL: config.get('urlSSL'), - configUrl: utils.url.urlFor('home', true), - reqUrl: req.originalUrl || req.url - }); - - if (response.isForbidden) { - return res.sendStatus(403); - } else { - return res.redirect(301, response.redirectUrl(req.query)); - } - } - } - next(); -}; - -module.exports = checkSSL; diff --git a/core/server/middleware/serve-shared-file.js b/core/server/middleware/serve-shared-file.js index 6c91166cfd..0ce8abb97a 100644 --- a/core/server/middleware/serve-shared-file.js +++ b/core/server/middleware/serve-shared-file.js @@ -28,7 +28,7 @@ function serveSharedFile(file, type, maxAge) { if (type === 'text/xsl' || type === 'text/plain' || type === 'application/javascript') { buf = buf.toString().replace(blogRegex, utils.url.urlFor('home', true).replace(/\/$/, '')); - buf = buf.toString().replace(apiRegex, utils.url.apiUrl({cors: true})); + buf = buf.toString().replace(apiRegex, utils.url.urlFor('api', {cors: true}, true)); } content = { headers: { diff --git a/core/server/middleware/theme-handler.js b/core/server/middleware/theme-handler.js index 8c7161058b..f87e49cdfa 100644 --- a/core/server/middleware/theme-handler.js +++ b/core/server/middleware/theme-handler.js @@ -8,6 +8,7 @@ var _ = require('lodash'), utils = require('../utils'), logging = require('../logging'), errors = require('../errors'), + utils = require('../utils'), i18n = require('../i18n'), themeHandler; @@ -18,7 +19,7 @@ themeHandler = { var themeData = { title: settingsCache.get('title'), description: settingsCache.get('description'), - url: utils.url.urlFor('home', true), + url: utils.url.urlFor('home', {secure: req.secure}, true), facebook: settingsCache.get('facebook'), twitter: settingsCache.get('twitter'), timezone: settingsCache.get('activeTimezone'), @@ -32,11 +33,6 @@ themeHandler = { labsData = _.cloneDeep(settingsCache.get('labs')), blogApp = req.app; - if (req.secure && config.get('urlSSL')) { - // For secure requests override .url property with the SSL version - themeData.url = config.get('urlSSL').replace(/\/$/, ''); - } - hbs.updateTemplateOptions({ data: { blog: themeData, @@ -49,7 +45,7 @@ themeHandler = { } // Pass 'secure' flag to the view engine - // so that templates can choose 'url' vs 'urlSSL' + // so that templates can choose to render https or http 'url', see url utility res.locals.secure = req.secure; next(); diff --git a/core/server/middleware/url-redirects.js b/core/server/middleware/url-redirects.js new file mode 100644 index 0000000000..d4e6952832 --- /dev/null +++ b/core/server/middleware/url-redirects.js @@ -0,0 +1,68 @@ +var url = require('url'), + debug = require('debug')('ghost:redirects'), + utils = require('../utils'), + urlRedirects; + +function redirectUrl(options) { + var redirectTo = options.redirectTo, + path = options.path, + query = options.query, + parts = url.parse(redirectTo); + + return url.format({ + protocol: parts.protocol, + hostname: parts.hostname, + port: parts.port, + pathname: path, + query: query + }); +} + +/** + * SSL AND REDIRECTS + */ +urlRedirects = function urlRedirects(req, res, next) { + var requestedUrl = req.originalUrl || req.url, + requestedHost = req.get('host'), + targetHostWithProtocol, + targetHostWithoutProtocol; + + if (res.isAdmin) { + targetHostWithProtocol = utils.url.urlFor('admin', true); + targetHostWithoutProtocol = utils.url.urlFor('admin', {cors: true}, true); + } else { + targetHostWithProtocol = utils.url.urlFor('home', true); + targetHostWithoutProtocol = utils.url.urlFor('home', {cors: true}, true); + } + + debug('requestedUrl', requestedUrl); + debug('requestedHost', requestedHost); + debug('targetHost', targetHostWithoutProtocol); + + // CASE: custom admin url is configured, but user requests blog domain + // CASE: exception: localhost is always allowed + if (!targetHostWithoutProtocol.match(new RegExp(requestedHost))) { + debug('redirect because host does not match'); + + return res.redirect(301, redirectUrl({ + redirectTo: targetHostWithProtocol, + path: requestedUrl, + query: req.query + })); + } + + // CASE: correct admin url, but not the correct protocol + if (utils.url.isSSL(targetHostWithProtocol) && !req.secure) { + debug('redirect because protocol does not match'); + + return res.redirect(301, redirectUrl({ + redirectTo: targetHostWithProtocol, + path: requestedUrl, + query: req.query + })); + } + + next(); +}; + +module.exports = urlRedirects; diff --git a/core/server/utils/url.js b/core/server/utils/url.js index 4647965d29..3c23c3acb8 100644 --- a/core/server/utils/url.js +++ b/core/server/utils/url.js @@ -6,39 +6,37 @@ var moment = require('moment-timezone'), url = require('url'), config = require('./../config'), settingsCache = require('./../api/settings').cache, - // @TODO: unify this with routes.apiBaseUrl - apiPath = '/ghost/api/v0.1', + // @TODO: unify this with the path in server/app.js + API_PATH = '/ghost/api/v0.1/', STATIC_IMAGE_URL_PREFIX = 'content/images'; -/** getBaseUrl - * Returns the base URL of the blog as set in the config. If called with secure options, returns the ssl URL. +/** + * Returns the base URL of the blog as set in the config. + * + * Secure: + * If the request is secure, we want to force returning the blog url as https. + * Imagine Ghost runs with http, but nginx allows SSL connections. + * * @param {boolean} secure * @return {string} URL returns the url as defined in config, but always with a trailing `/` */ -function getBaseUrl(secure) { - var base; +function getBlogUrl(secure) { + var blogUrl; - // CASE: a specified SSL URL is configured (e. g. https://secure.blog.org/) - // see: https://github.com/TryGhost/Ghost/issues/6270#issuecomment-168939865 - if (secure && config.get('urlSSL')) { - base = config.get('urlSSL'); + if (secure) { + blogUrl = config.get('url').replace('http://', 'https://'); } else { - // CASE: no specified SSL URL configured, but user request is secure. In this case we force SSL - // and therefore replace the protocol. - if (secure) { - base = config.get('url').replace('http://', 'https://'); - } else { - base = config.get('url'); - } + blogUrl = config.get('url'); } - if (!base.match(/\/$/)) { - base += '/'; + if (!blogUrl.match(/\/$/)) { + blogUrl += '/'; } - return base; + + return blogUrl; } -/** getSubdir +/** * Returns a subdirectory URL, if defined so in the config. * @return {string} URL a subdirectory if configured. */ @@ -59,11 +57,25 @@ function getSubdir() { return subdir; } -function getProtectedSlugs() { - var subdir = getSubdir(); +function deduplicateSubDir(url) { + var subDir = getSubdir(), + subDirRegex; - if (!_.isEmpty(subdir)) { - return config.get('slugs').protected.concat([subdir.split('/').pop()]); + if (!subDir) { + return url; + } + + subDir = subDir.replace(/^\/|\/+$/, ''); + subDirRegex = new RegExp(subDir + '\/' + subDir + '\/'); + + return url.replace(subDirRegex, subDir + '/'); +} + +function getProtectedSlugs() { + var subDir = getSubdir(); + + if (!_.isEmpty(subDir)) { + return config.get('slugs').protected.concat([subDir.split('/').pop()]); } else { return config.get('slugs').protected; } @@ -77,8 +89,6 @@ function getProtectedSlugs() { function urlJoin() { var args = Array.prototype.slice.call(arguments), prefixDoubleSlash = false, - subdir = getSubdir().replace(/^\/|\/+$/, ''), - subdirRegex, url; // Remove empty item at the beginning @@ -102,13 +112,28 @@ function urlJoin() { url = url.replace(/^\//, '//'); } - // Deduplicate subdirectory - if (subdir) { - subdirRegex = new RegExp(subdir + '\/' + subdir + '\/'); - url = url.replace(subdirRegex, subdir + '/'); + url = deduplicateSubDir(url); + return url; +} + +/** + * admin:url is optional + */ +function getAdminUrl() { + var adminUrl = config.get('admin:url'), + subDir = getSubdir(); + + if (!adminUrl) { + return; } - return url; + if (!adminUrl.match(/\/$/)) { + adminUrl += '/'; + } + + adminUrl = urlJoin(adminUrl, subDir, '/'); + adminUrl = deduplicateSubDir(adminUrl); + return adminUrl; } // ## createUrl @@ -122,7 +147,7 @@ function urlJoin() { // Parameters: // - urlPath - string which must start and end with a slash // - absolute (optional, default:false) - boolean whether or not the url should be absolute -// - secure (optional, default:false) - boolean whether or not to use urlSSL or url config +// - secure (optional, default:false) - boolean whether or not to force SSL // Returns: // - a URL which always ends with a slash function createUrl(urlPath, absolute, secure) { @@ -132,7 +157,7 @@ function createUrl(urlPath, absolute, secure) { // create base of url, always ends without a slash if (absolute) { - base = getBaseUrl(secure); + base = getBlogUrl(secure); } else { base = getSubdir(); } @@ -193,7 +218,7 @@ function urlPathForPost(post) { // - data (optional) - a json object containing data needed to generate a url // - absolute (optional, default:false) - boolean whether or not the url should be absolute // This is probably not the right place for this, but it's the best place for now -// @TODO: rewrite, very hard to read! +// @TODO: rewrite, very hard to read, create private functions! function urlFor(context, data, absolute) { var urlPath = '/', secure, imagePathRe, @@ -204,7 +229,7 @@ function urlFor(context, data, absolute) { knownPaths = { home: '/', rss: '/rss/', - api: apiPath, + api: API_PATH, sitemap_xsl: '/sitemap.xsl' }; @@ -239,7 +264,7 @@ function urlFor(context, data, absolute) { if (absolute) { // Remove the sub-directory from the URL because ghostConfig will add it back. urlPath = urlPath.replace(new RegExp('^' + getSubdir()), ''); - baseUrl = getBaseUrl(secure).replace(/\/$/, ''); + baseUrl = getBlogUrl(secure).replace(/\/$/, ''); urlPath = baseUrl + urlPath; } @@ -247,7 +272,7 @@ function urlFor(context, data, absolute) { } else if (context === 'nav' && data.nav) { urlPath = data.nav.url; secure = data.nav.secure || secure; - baseUrl = getBaseUrl(secure); + baseUrl = getBlogUrl(secure); hostname = baseUrl.split('//')[1] + getSubdir(); if (urlPath.indexOf(hostname) > -1 @@ -266,20 +291,35 @@ function urlFor(context, data, absolute) { } } } else if (context === 'home' && absolute) { - urlPath = getBaseUrl(secure); - // other objects are recognised but not yet supported - } else if (context === 'admin') { - if (config.get('forceAdminSSL')) { - urlPath = getBaseUrl(true); - } else { - urlPath = getBaseUrl(); + urlPath = getBlogUrl(secure); + + // CASE: with or without protocol? + // @TODO: rename cors + if (data && data.cors) { + urlPath = urlPath.replace(/^.*?:\/\//g, '//'); } + } else if (context === 'admin') { + urlPath = getAdminUrl() || getBlogUrl(); if (absolute) { urlPath += 'ghost/'; } else { urlPath = '/ghost/'; } + } else if (context === 'api') { + urlPath = getAdminUrl() || getBlogUrl(); + + // CASE: with or without protocol? + // @TODO: rename cors + if (data && data.cors) { + urlPath = urlPath.replace(/^.*?:\/\//g, '//'); + } + + if (absolute) { + urlPath = urlPath.replace(/\/$/, '') + API_PATH; + } else { + urlPath = API_PATH; + } } else if (_.isString(context) && _.indexOf(_.keys(knownPaths), context) !== -1) { // trying to create a url for a named path urlPath = knownPaths[context] || '/'; @@ -294,41 +334,17 @@ function urlFor(context, data, absolute) { return createUrl(urlPath, absolute, secure); } -/** - * CASE: generate api url for CORS - * - we delete the http protocol if your blog runs with http and https (configured by nginx) - * - in that case your config.js configures Ghost with http and no admin ssl force - * - the browser then reads the protocol dynamically - */ -function apiUrl(options) { - options = options || {cors: false}; - - // @TODO unify this with urlFor - var url; - - if (config.get('forceAdminSSL')) { - url = (config.get('urlSSL') || getBaseUrl(true)).replace(/^.*?:\/\//g, 'https://'); - } else if (config.get('urlSSL')) { - url = config.get('urlSSL').replace(/^.*?:\/\//g, 'https://'); - } else if (config.get('url').match(/^https:/)) { - url = config.get('url'); - } else { - if (options.cors === false) { - url = config.get('url'); - } else { - url = config.get('url').replace(/^.*?:\/\//g, '//'); - } - } - - return url.replace(/\/$/, '') + apiPath + '/'; +function isSSL(urlToParse) { + var protocol = url.parse(urlToParse).protocol; + return protocol === 'https:'; } module.exports.getProtectedSlugs = getProtectedSlugs; module.exports.getSubdir = getSubdir; module.exports.urlJoin = urlJoin; module.exports.urlFor = urlFor; +module.exports.isSSL = isSSL; module.exports.urlPathForPost = urlPathForPost; -module.exports.apiUrl = apiUrl; /** * If you request **any** image in Ghost, it get's served via diff --git a/core/test/functional/routes/admin_spec.js b/core/test/functional/routes/admin_spec.js index a960557ff4..9d7eedb630 100644 --- a/core/test/functional/routes/admin_spec.js +++ b/core/test/functional/routes/admin_spec.js @@ -1,15 +1,14 @@ - // # Frontend Route tests // As it stands, these tests depend on the database, and as such are integration tests. // Mocking out the models to not touch the DB would turn these into unit tests, and should probably be done in future, // But then again testing real code, rather than mock code, might be more useful... -var request = require('supertest'), - should = require('should'), - testUtils = require('../../utils'), - ghost = testUtils.startGhost, - i18n = require('../../../../core/server/i18n'), - config = require('../../../../core/server/config'); +var request = require('supertest'), + should = require('should'), + testUtils = require('../../utils'), + ghost = testUtils.startGhost, + i18n = require('../../../../core/server/i18n'), + config = require('../../../../core/server/config'); i18n.init(); @@ -42,192 +41,164 @@ describe('Admin Routing', function () { before(testUtils.teardown); - before(function (done) { - ghost().then(function (ghostServer) { - // Setup the request object with the ghost express app - request = request(ghostServer.rootApp); - - done(); - }).catch(function (e) { - console.log('Ghost Error: ', e); - console.log(e.stack); - done(e); - }); - }); - - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); - }); - - describe('Assets', function () { - it('should return 404 for unknown assets', function (done) { - request.get('/ghost/assets/not-found.js') - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(404) - .end(doEnd(done)); - }); - - it('should retrieve built assets', function (done) { - request.get('/ghost/assets/vendor.js') - .expect('Cache-Control', testUtils.cacheRules.year) - .expect(200) - .end(doEnd(done)); - }); - }); - - describe('Legacy Redirects', function () { - it('should redirect /logout/ to /ghost/signout/', function (done) { - request.get('/logout/') - .expect('Location', '/ghost/signout/') - .expect('Cache-Control', testUtils.cacheRules.year) - .expect(301) - .end(doEndNoAuth(done)); - }); - - it('should redirect /signout/ to /ghost/signout/', function (done) { - request.get('/signout/') - .expect('Location', '/ghost/signout/') - .expect('Cache-Control', testUtils.cacheRules.year) - .expect(301) - .end(doEndNoAuth(done)); - }); - - it('should redirect /signup/ to /ghost/signup/', function (done) { - request.get('/signup/') - .expect('Location', '/ghost/signup/') - .expect('Cache-Control', testUtils.cacheRules.year) - .expect(301) - .end(doEndNoAuth(done)); - }); - - // Admin aliases - it('should redirect /signin/ to /ghost/', function (done) { - request.get('/signin/') - .expect('Location', '/ghost/') - .expect('Cache-Control', testUtils.cacheRules.year) - .expect(301) - .end(doEndNoAuth(done)); - }); - - it('should redirect /admin/ to /ghost/', function (done) { - request.get('/admin/') - .expect('Location', '/ghost/') - .expect('Cache-Control', testUtils.cacheRules.year) - .expect(301) - .end(doEndNoAuth(done)); - }); - - it('should redirect /GHOST/ to /ghost/', function (done) { - request.get('/GHOST/') - .expect('Location', '/ghost/') - .expect(301) - .end(doEndNoAuth(done)); - }); - }); - - // we'll use X-Forwarded-Proto: https to simulate an 'https://' request behind a proxy - describe('Require HTTPS - no redirect', function () { - var forkedGhost, request; + describe('NO FORK', function () { + var ghostServer; before(function (done) { - testUtils.fork.ghost({ - forceAdminSSL: {redirect: false}, - urlSSL: 'https://localhost/' - }, 'testhttps') - .then(function (child) { - forkedGhost = child; - request = require('supertest'); - request = request(config.get('url').replace(/\/$/, '')); - }) - .then(done) - .catch(done); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = request(config.get('url')); + done(); + }).catch(function (e) { + console.log('Ghost Error: ', e); + console.log(e.stack); + done(e); + }); }); - after(function (done) { - if (forkedGhost) { - forkedGhost.kill(done); - } else { - done(new Error('No forked ghost process exists, test setup must have failed.')); - } + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); - it('should block admin access over non-HTTPS', function (done) { - request.get('/ghost/') - .expect(403) - .end(doEnd(done)); + describe('Assets', function () { + it('should return 404 for unknown assets', function (done) { + request.get('/ghost/assets/not-found.js') + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(404) + .end(doEnd(done)); + }); + + it('should retrieve built assets', function (done) { + request.get('/ghost/assets/vendor.js') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(200) + .end(doEnd(done)); + }); }); - it('should allow admin access over HTTPS', function (done) { - request.get('/ghost/setup/') - .set('X-Forwarded-Proto', 'https') - .expect(200) - .end(doEnd(done)); + describe('Legacy Redirects', function () { + it('should redirect /logout/ to /ghost/signout/', function (done) { + request.get('/logout/') + .expect('Location', '/ghost/signout/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEndNoAuth(done)); + }); + + it('should redirect /signout/ to /ghost/signout/', function (done) { + request.get('/signout/') + .expect('Location', '/ghost/signout/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEndNoAuth(done)); + }); + + it('should redirect /signup/ to /ghost/signup/', function (done) { + request.get('/signup/') + .expect('Location', '/ghost/signup/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEndNoAuth(done)); + }); + + // Admin aliases + it('should redirect /signin/ to /ghost/', function (done) { + request.get('/signin/') + .expect('Location', '/ghost/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEndNoAuth(done)); + }); + + it('should redirect /admin/ to /ghost/', function (done) { + request.get('/admin/') + .expect('Location', '/ghost/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEndNoAuth(done)); + }); + + it('should redirect /GHOST/ to /ghost/', function (done) { + request.get('/GHOST/') + .expect('Location', '/ghost/') + .expect(301) + .end(doEndNoAuth(done)); + }); + }); + + describe('Ghost Admin Setup', function () { + it('should redirect from /ghost/ to /ghost/setup/ when no user/not installed yet', function (done) { + request.get('/ghost/') + .expect('Location', /ghost\/setup/) + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(302) + .end(doEnd(done)); + }); + + it('should redirect from /ghost/signin/ to /ghost/setup/ when no user', function (done) { + request.get('/ghost/signin/') + .expect('Location', /ghost\/setup/) + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(302) + .end(doEnd(done)); + }); + + it('should respond with html for /ghost/setup/', function (done) { + request.get('/ghost/setup/') + .expect('Content-Type', /html/) + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(200) + .end(doEnd(done)); + }); }); }); - describe('Require HTTPS - redirect', function () { - var forkedGhost, request; - before(function (done) { - testUtils.fork.ghost({ - forceAdminSSL: {redirect: true}, - urlSSL: 'https://localhost/' - }, 'testhttps') - .then(function (child) { - forkedGhost = child; - request = require('supertest'); - request = request(config.get('url').replace(/\/$/, '')); - }).then(done) - .catch(done); - }); + describe('FORK', function () { + // we'll use X-Forwarded-Proto: https to simulate an 'https://' request behind a proxy + describe('Require HTTPS - redirect', function () { + var forkedGhost, request; - after(function (done) { - if (forkedGhost) { - forkedGhost.kill(done); - } else { - done(new Error('No forked ghost process exists, test setup must have failed.')); - } - }); + before(function (done) { + testUtils.fork.ghost({ + url: 'https://localhost/', + server: { + port: 2390 + } + }, 'testhttps') + .then(function (child) { + forkedGhost = child; + request = require('supertest'); + request = request('http://localhost:2390'); + }).then(done) + .catch(done); + }); - it('should redirect admin access over non-HTTPS', function (done) { - request.get('/ghost/') - .expect('Location', /^https:\/\/localhost\/ghost\//) - .expect(301) - .end(doEnd(done)); - }); + after(function (done) { + if (forkedGhost) { + forkedGhost.kill(done); + } else { + done(new Error('No forked ghost process exists, test setup must have failed.')); + } + }); - it('should allow admin access over HTTPS', function (done) { - request.get('/ghost/setup/') - .set('X-Forwarded-Proto', 'https') - .expect(200) - .end(doEnd(done)); - }); - }); + it('should redirect admin access over non-HTTPS', function (done) { + request.get('/ghost/') + .expect('Location', /^https:\/\/localhost:2390\/ghost\//) + .expect(301) + .end(doEnd(done)); + }); - describe('Ghost Admin Setup', function () { - it('should redirect from /ghost/ to /ghost/setup/ when no user/not installed yet', function (done) { - request.get('/ghost/') - .expect('Location', /ghost\/setup/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(302) - .end(doEnd(done)); - }); - - it('should redirect from /ghost/signin/ to /ghost/setup/ when no user', function (done) { - request.get('/ghost/signin/') - .expect('Location', /ghost\/setup/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(302) - .end(doEnd(done)); - }); - - it('should respond with html for /ghost/setup/', function (done) { - request.get('/ghost/setup/') - .expect('Content-Type', /html/) - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(200) - .end(doEnd(done)); + it('should allow admin access over HTTPS', function (done) { + request.get('/ghost/setup/') + .set('X-Forwarded-Proto', 'https') + .expect(200) + .end(doEnd(done)); + }); }); }); }); diff --git a/core/test/functional/routes/api/authentication_spec.js b/core/test/functional/routes/api/authentication_spec.js index 97ecacbdd7..81adc646c1 100644 --- a/core/test/functional/routes/api/authentication_spec.js +++ b/core/test/functional/routes/api/authentication_spec.js @@ -10,13 +10,16 @@ var supertest = require('supertest'), request; describe('Authentication API', function () { - var accesstoken = ''; + var accesstoken = '', ghostServer; before(function (done) { // starting ghost automatically populates the db // TODO: prevent db init, and manage bringing up the DB with fixtures ourselves - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); }).then(function () { return testUtils.doAuth(request); }).then(function (token) { @@ -31,10 +34,11 @@ describe('Authentication API', function () { }); }); - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); it('can authenticate', function (done) { diff --git a/core/test/functional/routes/api/configuration_spec.js b/core/test/functional/routes/api/configuration_spec.js index 4ad557c33b..d27b9b4fa7 100644 --- a/core/test/functional/routes/api/configuration_spec.js +++ b/core/test/functional/routes/api/configuration_spec.js @@ -1,29 +1,34 @@ var testUtils = require('../../../utils'), should = require('should'), supertest = require('supertest'), + config = require('../../../../../core/server/config'), ghost = testUtils.startGhost, request; describe('Configuration API', function () { - var accesstoken = ''; + var accesstoken = '', ghostServer; before(function (done) { // starting ghost automatically populates the db // TODO: prevent db init, and manage bringing up the DB with fixtures ourselves - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); }).then(function () { - return testUtils.doAuth(request, 'posts'); + request = supertest.agent(config.get('url')); + }).then(function () { + return testUtils.doAuth(request); }).then(function (token) { accesstoken = token; done(); }).catch(done); }); - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); describe('success', function () { diff --git a/core/test/functional/routes/api/db_spec.js b/core/test/functional/routes/api/db_spec.js index 911fac0fc3..69a54638f4 100644 --- a/core/test/functional/routes/api/db_spec.js +++ b/core/test/functional/routes/api/db_spec.js @@ -2,17 +2,21 @@ var supertest = require('supertest'), should = require('should'), path = require('path'), testUtils = require('../../../utils'), + config = require('../../../../../core/server/config'), ghost = testUtils.startGhost, request; describe('DB API', function () { - var accesstoken = ''; + var accesstoken = '', ghostServer; before(function (done) { // starting ghost automatically populates the db // TODO: prevent db init, and manage bringing up the DB with fixtures ourselves - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); }).then(function () { return testUtils.doAuth(request); }).then(function (token) { @@ -21,10 +25,11 @@ describe('DB API', function () { }).catch(done); }); - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); it('attaches the Content-Disposition header on export', function (done) { diff --git a/core/test/functional/routes/api/error_spec.js b/core/test/functional/routes/api/error_spec.js index 14ead4c165..e47cdf3702 100644 --- a/core/test/functional/routes/api/error_spec.js +++ b/core/test/functional/routes/api/error_spec.js @@ -6,24 +6,31 @@ var supertest = require('supertest'), should = require('should'), testUtils = require('../../../utils'), + config = require('../../../../../core/server/config'), ghost = testUtils.startGhost, request; require('should-http'); describe('Unauthorized', function () { - before(function (done) { - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + var ghostServer; + before(function (done) { + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); done(); }).catch(done); }); - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }) + .catch(); }); it('returns 401 error for known endpoint', function (done) { @@ -65,12 +72,16 @@ describe('Unauthorized', function () { }); describe('Authorized API', function () { - var accesstoken; + var accesstoken, ghostServer; + before(function (done) { // starting ghost automatically populates the db // TODO: prevent db init, and manage bringing up the DB with fixtures ourselves - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); }).then(function () { return testUtils.doAuth(request); }).then(function (token) { @@ -79,10 +90,11 @@ describe('Authorized API', function () { }).catch(done); }); - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); it('serves a JSON 404 for an unknown endpoint', function (done) { diff --git a/core/test/functional/routes/api/notifications_spec.js b/core/test/functional/routes/api/notifications_spec.js index 85296a8d49..a63505c050 100644 --- a/core/test/functional/routes/api/notifications_spec.js +++ b/core/test/functional/routes/api/notifications_spec.js @@ -1,17 +1,21 @@ var testUtils = require('../../../utils'), supertest = require('supertest'), should = require('should'), + config = require('../../../../../core/server/config'), ghost = testUtils.startGhost, request; describe('Notifications API', function () { - var accesstoken = ''; + var accesstoken = '', ghostServer; before(function (done) { // starting ghost automatically populates the db // TODO: prevent db init, and manage bringing up the DB with fixtures ourselves - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); }).then(function () { return testUtils.doAuth(request); }).then(function (token) { @@ -20,10 +24,11 @@ describe('Notifications API', function () { }).catch(done); }); - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); describe('Add', function () { diff --git a/core/test/functional/routes/api/posts_spec.js b/core/test/functional/routes/api/posts_spec.js index 9233afea16..047d71088c 100644 --- a/core/test/functional/routes/api/posts_spec.js +++ b/core/test/functional/routes/api/posts_spec.js @@ -3,17 +3,21 @@ var testUtils = require('../../../utils'), supertest = require('supertest'), _ = require('lodash'), ObjectId = require('bson-objectid'), + config = require('../../../../../core/server/config'), ghost = testUtils.startGhost, request; describe('Post API', function () { - var accesstoken = ''; + var accesstoken = '', ghostServer; before(function (done) { // starting ghost automatically populates the db // TODO: prevent db init, and manage bringing up the DB with fixtures ourselves - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); }).then(function () { return testUtils.doAuth(request, 'posts'); }).then(function (token) { @@ -22,10 +26,11 @@ describe('Post API', function () { }).catch(done); }); - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); describe('Browse', function () { diff --git a/core/test/functional/routes/api/public_api_spec.js b/core/test/functional/routes/api/public_api_spec.js index fe7cb3854f..004eee9f73 100644 --- a/core/test/functional/routes/api/public_api_spec.js +++ b/core/test/functional/routes/api/public_api_spec.js @@ -2,6 +2,7 @@ var testUtils = require('../../../utils'), should = require('should'), supertest = require('supertest'), _ = require('lodash'), + config = require('../../../../../core/server/config'), ghost = testUtils.startGhost, request; @@ -10,13 +11,16 @@ describe('Public API', function () { settings: [ {key: 'labs', value: {publicAPI: true}} ] - }; + }, ghostServer; before(function (done) { // starting ghost automatically populates the db // TODO: prevent db init, and manage bringing up the DB with fixtures ourselves - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); }).then(function () { return testUtils.doAuth(request, 'posts', 'tags'); }).then(function (token) { @@ -36,10 +40,11 @@ describe('Public API', function () { }).catch(done); }); - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); it('browse posts', function (done) { diff --git a/core/test/functional/routes/api/settings_spec.js b/core/test/functional/routes/api/settings_spec.js index 6114ee50d3..48c55d05c4 100644 --- a/core/test/functional/routes/api/settings_spec.js +++ b/core/test/functional/routes/api/settings_spec.js @@ -1,17 +1,21 @@ var testUtils = require('../../../utils'), should = require('should'), supertest = require('supertest'), + config = require('../../../../../core/server/config'), ghost = testUtils.startGhost, request; describe('Settings API', function () { - var accesstoken = ''; + var accesstoken = '', ghostServer; before(function (done) { // starting ghost automatically populates the db // TODO: prevent db init, and manage bringing up the DB with fixtures ourselves - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); }).then(function () { return testUtils.doAuth(request); }).then(function (token) { @@ -20,10 +24,11 @@ describe('Settings API', function () { }).catch(done); }); - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); // TODO: currently includes values of type=core diff --git a/core/test/functional/routes/api/slugs_spec.js b/core/test/functional/routes/api/slugs_spec.js index 70e9de327f..52180fadaf 100644 --- a/core/test/functional/routes/api/slugs_spec.js +++ b/core/test/functional/routes/api/slugs_spec.js @@ -1,17 +1,21 @@ var testUtils = require('../../../utils'), should = require('should'), supertest = require('supertest'), + config = require('../../../../../core/server/config'), ghost = testUtils.startGhost, request; describe('Slug API', function () { - var accesstoken = ''; + var accesstoken = '', ghostServer; before(function (done) { // starting ghost automatically populates the db // TODO: prevent db init, and manage bringing up the DB with fixtures ourselves - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); }).then(function () { return testUtils.doAuth(request); }).then(function (token) { @@ -20,10 +24,11 @@ describe('Slug API', function () { }).catch(done); }); - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); it('should be able to get a post slug', function (done) { diff --git a/core/test/functional/routes/api/spam_prevention_spec.js b/core/test/functional/routes/api/spam_prevention_spec.js index eefc349ab8..c2bf8d07e0 100644 --- a/core/test/functional/routes/api/spam_prevention_spec.js +++ b/core/test/functional/routes/api/spam_prevention_spec.js @@ -13,33 +13,41 @@ var supertest = require('supertest'), describe('Spam Prevention API', function () { var author, - owner = testUtils.DataGenerator.Content.users[0]; + owner = testUtils.DataGenerator.Content.users[0], + ghostServer; before(function (done) { ghost() - .then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + .then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }) + .then(function () { + request = supertest.agent(config.get('url')); - // in functional tests we start Ghost and the database get's migrated/seeded - // no need to add or care about any missing data (settings, permissions) except of extra data we would like to add or override - // override Ghost fixture owner and add some extra users - return testUtils.setup('owner:post')(); - }).then(function () { - return testUtils.createUser({ - user: testUtils.DataGenerator.forKnex.createUser({email: 'test+1@ghost.org'}), - role: testUtils.DataGenerator.Content.roles[1] - }); - }).then(function (user) { - author = user; - done(); - }) - .catch(done); + // in functional tests we start Ghost and the database get's migrated/seeded + // no need to add or care about any missing data (settings, permissions) except of extra data we would like to add or override + // override Ghost fixture owner and add some extra users + return testUtils.setup('owner:post')(); + }) + .then(function () { + return testUtils.createUser({ + user: testUtils.DataGenerator.forKnex.createUser({email: 'test+1@ghost.org'}), + role: testUtils.DataGenerator.Content.roles[1] + }); + }) + .then(function (user) { + author = user; + done(); + }) + .catch(done); }); - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); afterEach(function (done) { diff --git a/core/test/functional/routes/api/tags_spec.js b/core/test/functional/routes/api/tags_spec.js index ea4df8ad67..24ac307b31 100644 --- a/core/test/functional/routes/api/tags_spec.js +++ b/core/test/functional/routes/api/tags_spec.js @@ -1,17 +1,21 @@ var testUtils = require('../../../utils'), should = require('should'), supertest = require('supertest'), + config = require('../../../../../core/server/config'), ghost = testUtils.startGhost, request; describe('Tag API', function () { - var accesstoken = ''; + var accesstoken = '', ghostServer; before(function (done) { // starting ghost automatically populates the db // TODO: prevent db init, and manage bringing up the DB with fixtures ourselves - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); }).then(function () { return testUtils.doAuth(request, 'posts'); }).then(function (token) { @@ -20,10 +24,11 @@ describe('Tag API', function () { }).catch(done); }); - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); it('can retrieve all tags', function (done) { diff --git a/core/test/functional/routes/api/themes_spec.js b/core/test/functional/routes/api/themes_spec.js index 247ae753c3..5c501fc7a5 100644 --- a/core/test/functional/routes/api/themes_spec.js +++ b/core/test/functional/routes/api/themes_spec.js @@ -22,11 +22,14 @@ describe('Themes API', function () { .attach(fieldName, themePath); }, editor: null - }; + }, ghostServer; before(function (done) { - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); }).then(function () { return testUtils.doAuth(request); }).then(function (token) { @@ -47,7 +50,7 @@ describe('Themes API', function () { }).catch(done); }); - after(function (done) { + after(function () { // clean successful uploaded themes fs.removeSync(config.getContentPath('themes') + '/valid'); fs.removeSync(config.getContentPath('themes') + '/casper.zip'); @@ -55,10 +58,10 @@ describe('Themes API', function () { // gscan creates /test/tmp in test mode fs.removeSync(config.get('paths').appRoot + '/test'); - testUtils.clearData() + return testUtils.clearData() .then(function () { - done(); - }).catch(done); + return ghostServer.stop(); + }); }); describe('success cases', function () { diff --git a/core/test/functional/routes/api/upload_icon_spec.js b/core/test/functional/routes/api/upload_icon_spec.js index bc292e7809..74c94b0d4f 100644 --- a/core/test/functional/routes/api/upload_icon_spec.js +++ b/core/test/functional/routes/api/upload_icon_spec.js @@ -12,13 +12,16 @@ var testUtils = require('../../../utils'), describe('Upload Icon API', function () { var accesstoken = '', getIconDimensions, - icons = []; + icons = [], ghostServer; before(function (done) { // starting ghost automatically populates the db // TODO: prevent db init, and manage bringing up the DB with fixtures ourselves - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); }).then(function () { return testUtils.doAuth(request); }).then(function (token) { @@ -27,14 +30,15 @@ describe('Upload Icon API', function () { }).catch(done); }); - after(function (done) { + after(function () { icons.forEach(function (icon) { fs.removeSync(config.get('paths').appRoot + icon); }); - testUtils.clearData().then(function () { - done(); - }).catch(done); + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); describe('success cases for icons', function () { diff --git a/core/test/functional/routes/api/upload_spec.js b/core/test/functional/routes/api/upload_spec.js index ac6c7f5efc..6b92f079ed 100644 --- a/core/test/functional/routes/api/upload_spec.js +++ b/core/test/functional/routes/api/upload_spec.js @@ -10,13 +10,17 @@ var testUtils = require('../../../utils'), describe('Upload API', function () { var accesstoken = '', - images = []; + images = [], + ghostServer; before(function (done) { // starting ghost automatically populates the db // TODO: prevent db init, and manage bringing up the DB with fixtures ourselves - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); }).then(function () { return testUtils.doAuth(request); }).then(function (token) { @@ -25,14 +29,15 @@ describe('Upload API', function () { }).catch(done); }); - after(function (done) { + after(function () { images.forEach(function (image) { fs.removeSync(config.get('paths').appRoot + image); }); - testUtils.clearData().then(function () { - done(); - }).catch(done); + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); describe('success cases', function () { diff --git a/core/test/functional/routes/api/users_spec.js b/core/test/functional/routes/api/users_spec.js index d2a62d224e..494f4321f2 100644 --- a/core/test/functional/routes/api/users_spec.js +++ b/core/test/functional/routes/api/users_spec.js @@ -2,6 +2,7 @@ var testUtils = require('../../../utils'), should = require('should'), supertest = require('supertest'), ObjectId = require('bson-objectid'), + config = require('../../../../../core/server/config'), ghost = testUtils.startGhost, request; @@ -9,13 +10,16 @@ describe('User API', function () { var ownerAccessToken = '', editorAccessToken = '', authorAccessToken = '', - editor, author; + editor, author, ghostServer; before(function (done) { // starting ghost automatically populates the db // TODO: prevent db init, and manage bringing up the DB with fixtures ourselves - ghost().then(function (ghostServer) { - request = supertest.agent(ghostServer.rootApp); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = supertest.agent(config.get('url')); }).then(function () { // create editor return testUtils.createUser({ @@ -51,10 +55,11 @@ describe('User API', function () { }).catch(done); }); - after(function (done) { - testUtils.clearData().then(function () { - done(); - }).catch(done); + after(function () { + return testUtils.clearData() + .then(function () { + return ghostServer.stop(); + }); }); describe('As Owner', function () { diff --git a/core/test/functional/routes/channel_spec.js b/core/test/functional/routes/channel_spec.js index 94722bcea7..05bfa555c0 100644 --- a/core/test/functional/routes/channel_spec.js +++ b/core/test/functional/routes/channel_spec.js @@ -7,9 +7,12 @@ var request = require('supertest'), should = require('should'), cheerio = require('cheerio'), testUtils = require('../../utils'), + config = require('../../../../core/server/config'), ghost = testUtils.startGhost; describe('Channel Routes', function () { + var ghostServer; + function doEnd(done) { return function (err, res) { if (err) { @@ -26,10 +29,11 @@ describe('Channel Routes', function () { } before(function (done) { - ghost().then(function (ghostServer) { - // Setup the request object with the ghost express app - request = request(ghostServer.rootApp); - + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = request(config.get('url')); done(); }).catch(function (e) { console.log('Ghost Error: ', e); @@ -40,6 +44,10 @@ describe('Channel Routes', function () { after(testUtils.teardown); + after(function () { + return ghostServer.stop(); + }); + describe('Index', function () { it('should respond with html', function (done) { request.get('/') diff --git a/core/test/functional/routes/frontend_spec.js b/core/test/functional/routes/frontend_spec.js index 705e37eef8..d0a53171cc 100644 --- a/core/test/functional/routes/frontend_spec.js +++ b/core/test/functional/routes/frontend_spec.js @@ -9,11 +9,14 @@ var request = require('supertest'), sinon = require('sinon'), cheerio = require('cheerio'), testUtils = require('../../utils'), + config = require('../../../server/config'), settingsCache = require('../../../server/api/settings').cache, sandbox = sinon.sandbox.create(), ghost = testUtils.startGhost; describe('Frontend Routing', function () { + var ghostServer; + function doEnd(done) { return function (err, res) { if (err) { @@ -37,253 +40,408 @@ describe('Frontend Routing', function () { }); } - before(function (done) { - ghost().then(function (ghostServer) { - // Setup the request object with the ghost express app - request = request(ghostServer.rootApp); - - done(); - }).catch(function (e) { - console.log('Ghost Error: ', e); - console.log(e.stack); - done(e); - }); - }); - afterEach(function () { sandbox.restore(); }); - describe('Date permalinks', function () { + describe('NO FORK', function () { before(function (done) { - // Only way to swap permalinks setting is to login and visit the URL because - // poking the database doesn't work as settings are cached - testUtils.togglePermalinks(request, 'date') - .then(function () { - done(); - }) - .catch(done); - }); - - after(function (done) { - testUtils.togglePermalinks(request) - .then(function () { - done(); - }) - .catch(done); - }); - - it('should load a post with date permalink', function (done) { - var date = moment().format('YYYY/MM/DD'); - - request.get('/' + date + '/welcome-to-ghost/') - .expect(200) - .expect('Content-Type', /html/) - .end(doEnd(done)); - }); - - it('expect redirect because of wrong/old permalink prefix', function (done) { - var date = moment().format('YYYY/MM/DD'); - - request.get('/2016/04/01/welcome-to-ghost/') - .expect('Content-Type', /html/) - .end(function (err, res) { - res.status.should.eql(301); - request.get('/' + date + '/welcome-to-ghost/') - .expect(200) - .expect('Content-Type', /html/) - .end(doEnd(done)); - }); - }); - - it('should serve RSS with date permalink', function (done) { - request.get('/rss/') - .expect('Content-Type', 'text/xml; charset=utf-8') - .expect('Cache-Control', testUtils.cacheRules.public) - .expect(200) - .end(function (err, res) { - if (err) { - return done(err); - } - - should.not.exist(res.headers['x-cache-invalidate']); - should.not.exist(res.headers['X-CSRF-Token']); - should.not.exist(res.headers['set-cookie']); - should.exist(res.headers.date); - - var content = res.text, - todayMoment = moment(), - dd = todayMoment.format('DD'), - mm = todayMoment.format('MM'), - yyyy = todayMoment.format('YYYY'), - postLink = '/' + yyyy + '/' + mm + '/' + dd + '/welcome-to-ghost/'; - - content.indexOf(postLink).should.be.above(0); - done(); - }); - }); - }); - - describe('Test with Initial Fixtures', function () { - after(testUtils.teardown); - - describe('Error', function () { - it('should 404 for unknown post', function (done) { - request.get('/spectacular/') - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(404) - .expect(/Page not found/) - .end(doEnd(done)); - }); - - it('should 404 for unknown post with invalid characters', function (done) { - request.get('/$pec+acular~/') - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(404) - .expect(/Page not found/) - .end(doEnd(done)); - }); - - it('should 404 for unknown frontend route', function (done) { - request.get('/spectacular/marvellous/') - .set('Accept', 'application/json') - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(404) - .expect(/Page not found/) - .end(doEnd(done)); - }); - - it('should 404 for encoded char not 301 from uncapitalise', function (done) { - request.get('/|/') - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(404) - .expect(/Page not found/) - .end(doEnd(done)); - }); - - it('should 404 for unknown file', function (done) { - request.get('/content/images/some/file/that/doesnt-exist.jpg') - .expect('Cache-Control', testUtils.cacheRules['private']) - .expect(404) - .expect(/Page not found/) - .end(doEnd(done)); + ghost().then(function (_ghostServer) { + ghostServer = _ghostServer; + return ghostServer.start(); + }).then(function () { + request = request(config.get('url')); + done(); + }).catch(function (e) { + console.log('Ghost Error: ', e); + console.log(e.stack); + done(e); }); }); - describe('Single post', function () { - it('should redirect without slash', function (done) { - request.get('/welcome-to-ghost') - .expect('Location', '/welcome-to-ghost/') - .expect('Cache-Control', testUtils.cacheRules.year) - .expect(301) - .end(doEnd(done)); - }); - - it('should redirect uppercase', function (done) { - request.get('/Welcome-To-Ghost/') - .expect('Location', '/welcome-to-ghost/') - .expect('Cache-Control', testUtils.cacheRules.year) - .expect(301) - .end(doEnd(done)); - }); - - it('should sanitize double slashes when redirecting uppercase', function (done) { - request.get('///Google.com/') - .expect('Location', '/google.com/') - .expect('Cache-Control', testUtils.cacheRules.year) - .expect(301) - .end(doEnd(done)); - }); - - it('should respond with html for valid url', function (done) { - request.get('/welcome-to-ghost/') - .expect('Content-Type', /html/) - .expect('Cache-Control', testUtils.cacheRules.public) - .expect(200) - .end(function (err, res) { - if (err) { - return done(err); - } - - var $ = cheerio.load(res.text); - - should.not.exist(res.headers['x-cache-invalidate']); - should.not.exist(res.headers['X-CSRF-Token']); - should.not.exist(res.headers['set-cookie']); - should.exist(res.headers.date); - - $('title').text().should.equal('Welcome to Ghost'); - $('.content .post').length.should.equal(1); - $('.poweredby').text().should.equal('Proudly published with Ghost'); - $('body.post-template').length.should.equal(1); - $('body.tag-getting-started').length.should.equal(1); - $('article.post').length.should.equal(1); - $('article.tag-getting-started').length.should.equal(1); + after(function () { + return ghostServer.stop(); + }); + describe('Date permalinks', function () { + before(function (done) { + // Only way to swap permalinks setting is to login and visit the URL because + // poking the database doesn't work as settings are cached + testUtils.togglePermalinks(request, 'date') + .then(function () { done(); - }); + }) + .catch(done); }); - it('should not work with date permalinks', function (done) { - // get today's date + after(function (done) { + testUtils.togglePermalinks(request) + .then(function () { + done(); + }) + .catch(done); + }); + + it('should load a post with date permalink', function (done) { var date = moment().format('YYYY/MM/DD'); request.get('/' + date + '/welcome-to-ghost/') - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(404) - .expect(/Page not found/) - .end(doEnd(done)); - }); - }); - - describe('Post edit', function () { - it('should redirect without slash', function (done) { - request.get('/welcome-to-ghost/edit') - .expect('Location', '/welcome-to-ghost/edit/') - .expect('Cache-Control', testUtils.cacheRules.year) - .expect(301) + .expect(200) + .expect('Content-Type', /html/) .end(doEnd(done)); }); - it('should redirect to editor', function (done) { - request.get('/welcome-to-ghost/edit/') - .expect('Location', /ghost\/editor\/\w+/) + it('expect redirect because of wrong/old permalink prefix', function (done) { + var date = moment().format('YYYY/MM/DD'); + + request.get('/2016/04/01/welcome-to-ghost/') + .expect('Content-Type', /html/) + .end(function (err, res) { + res.status.should.eql(301); + request.get('/' + date + '/welcome-to-ghost/') + .expect(200) + .expect('Content-Type', /html/) + .end(doEnd(done)); + }); + }); + + it('should serve RSS with date permalink', function (done) { + request.get('/rss/') + .expect('Content-Type', 'text/xml; charset=utf-8') .expect('Cache-Control', testUtils.cacheRules.public) - .expect(302) - .end(doEnd(done)); - }); + .expect(200) + .end(function (err, res) { + if (err) { + return done(err); + } - it('should 404 for non-edit parameter', function (done) { - request.get('/welcome-to-ghost/notedit/') - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(404) - .expect(/Page not found/) - .end(doEnd(done)); + should.not.exist(res.headers['x-cache-invalidate']); + should.not.exist(res.headers['X-CSRF-Token']); + should.not.exist(res.headers['set-cookie']); + should.exist(res.headers.date); + + var content = res.text, + todayMoment = moment(), + dd = todayMoment.format('DD'), + mm = todayMoment.format('MM'), + yyyy = todayMoment.format('YYYY'), + postLink = '/' + yyyy + '/' + mm + '/' + dd + '/welcome-to-ghost/'; + + content.indexOf(postLink).should.be.above(0); + done(); + }); }); }); - describe('AMP post', function () { + describe('Test with Initial Fixtures', function () { + after(testUtils.teardown); + + describe('Error', function () { + it('should 404 for unknown post', function (done) { + request.get('/spectacular/') + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(404) + .expect(/Page not found/) + .end(doEnd(done)); + }); + + it('should 404 for unknown post with invalid characters', function (done) { + request.get('/$pec+acular~/') + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(404) + .expect(/Page not found/) + .end(doEnd(done)); + }); + + it('should 404 for unknown frontend route', function (done) { + request.get('/spectacular/marvellous/') + .set('Accept', 'application/json') + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(404) + .expect(/Page not found/) + .end(doEnd(done)); + }); + + it('should 404 for encoded char not 301 from uncapitalise', function (done) { + request.get('/|/') + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(404) + .expect(/Page not found/) + .end(doEnd(done)); + }); + + it('should 404 for unknown file', function (done) { + request.get('/content/images/some/file/that/doesnt-exist.jpg') + .expect('Cache-Control', testUtils.cacheRules['private']) + .expect(404) + .expect(/Page not found/) + .end(doEnd(done)); + }); + }); + + describe('Single post', function () { + it('should redirect without slash', function (done) { + request.get('/welcome-to-ghost') + .expect('Location', '/welcome-to-ghost/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEnd(done)); + }); + + it('should redirect uppercase', function (done) { + request.get('/Welcome-To-Ghost/') + .expect('Location', '/welcome-to-ghost/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEnd(done)); + }); + + it('should sanitize double slashes when redirecting uppercase', function (done) { + request.get('///Google.com/') + .expect('Location', '/google.com/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEnd(done)); + }); + + it('should respond with html for valid url', function (done) { + request.get('/welcome-to-ghost/') + .expect('Content-Type', /html/) + .expect('Cache-Control', testUtils.cacheRules.public) + .expect(200) + .end(function (err, res) { + if (err) { + return done(err); + } + + var $ = cheerio.load(res.text); + + should.not.exist(res.headers['x-cache-invalidate']); + should.not.exist(res.headers['X-CSRF-Token']); + should.not.exist(res.headers['set-cookie']); + should.exist(res.headers.date); + + $('title').text().should.equal('Welcome to Ghost'); + $('.content .post').length.should.equal(1); + $('.poweredby').text().should.equal('Proudly published with Ghost'); + $('body.post-template').length.should.equal(1); + $('body.tag-getting-started').length.should.equal(1); + $('article.post').length.should.equal(1); + $('article.tag-getting-started').length.should.equal(1); + + done(); + }); + }); + + it('should not work with date permalinks', function (done) { + // get today's date + var date = moment().format('YYYY/MM/DD'); + + request.get('/' + date + '/welcome-to-ghost/') + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(404) + .expect(/Page not found/) + .end(doEnd(done)); + }); + }); + + describe('Post edit', function () { + it('should redirect without slash', function (done) { + request.get('/welcome-to-ghost/edit') + .expect('Location', '/welcome-to-ghost/edit/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEnd(done)); + }); + + it('should redirect to editor', function (done) { + request.get('/welcome-to-ghost/edit/') + .expect('Location', /ghost\/editor\/\w+/) + .expect('Cache-Control', testUtils.cacheRules.public) + .expect(302) + .end(doEnd(done)); + }); + + it('should 404 for non-edit parameter', function (done) { + request.get('/welcome-to-ghost/notedit/') + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(404) + .expect(/Page not found/) + .end(doEnd(done)); + }); + }); + + describe('AMP post', function () { + it('should redirect without slash', function (done) { + request.get('/welcome-to-ghost/amp') + .expect('Location', '/welcome-to-ghost/amp/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEnd(done)); + }); + + it('should redirect uppercase', function (done) { + request.get('/Welcome-To-Ghost/AMP/') + .expect('Location', '/welcome-to-ghost/amp/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEnd(done)); + }); + + it('should respond with html for valid url', function (done) { + request.get('/welcome-to-ghost/amp/') + .expect('Content-Type', /html/) + .expect('Cache-Control', testUtils.cacheRules.public) + .expect(200) + .end(function (err, res) { + if (err) { + return done(err); + } + + var $ = cheerio.load(res.text); + + should.not.exist(res.headers['x-cache-invalidate']); + should.not.exist(res.headers['X-CSRF-Token']); + should.not.exist(res.headers['set-cookie']); + should.exist(res.headers.date); + + $('title').text().should.equal('Welcome to Ghost'); + $('.content .post').length.should.equal(1); + $('.poweredby').text().should.equal('Proudly published with Ghost'); + $('body.amp-template').length.should.equal(1); + $('article.post').length.should.equal(1); + + done(); + }); + }); + + it('should not work with date permalinks', function (done) { + // get today's date + var date = moment().format('YYYY/MM/DD'); + + request.get('/' + date + '/welcome-to-ghost/amp/') + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(404) + .expect(/Page not found/) + .end(doEnd(done)); + }); + + it('should not render AMP, when AMP is disabled', function (done) { + var originalFn = settingsCache.get; + + sandbox.stub(settingsCache, 'get', function (key) { + if (key !== 'amp') { + return originalFn(key); + } + + return false; + }); + + request.get('/welcome-to-ghost/amp/') + .expect(404) + .expect(/Page not found/) + .end(doEnd(done)); + }); + }); + + describe('Static assets', function () { + it('should retrieve theme assets', function (done) { + request.get('/assets/css/screen.css') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(200) + .end(doEnd(done)); + }); + + it('should retrieve default robots.txt', function (done) { + request.get('/robots.txt') + .expect('Cache-Control', testUtils.cacheRules.hour) + .expect('ETag', /[0-9a-f]{32}/i) + .expect(200) + .end(doEnd(done)); + }); + + it('should retrieve default favicon.ico', function (done) { + request.get('/favicon.ico') + .expect('Cache-Control', testUtils.cacheRules.day) + .expect('ETag', /[0-9a-f]{32}/i) + .expect(200) + .end(doEnd(done)); + }); + + // at the moment there is no image fixture to test + // it('should retrieve image assets', function (done) { + // request.get('/content/images/some.jpg') + // .expect('Cache-Control', testUtils.cacheRules.year) + // .end(doEnd(done)); + // }); + }); + }); + + describe('Static page', function () { + before(addPosts); + after(testUtils.teardown); + it('should redirect without slash', function (done) { - request.get('/welcome-to-ghost/amp') - .expect('Location', '/welcome-to-ghost/amp/') + request.get('/static-page-test') + .expect('Location', '/static-page-test/') .expect('Cache-Control', testUtils.cacheRules.year) .expect(301) .end(doEnd(done)); }); - it('should redirect uppercase', function (done) { - request.get('/Welcome-To-Ghost/AMP/') - .expect('Location', '/welcome-to-ghost/amp/') - .expect('Cache-Control', testUtils.cacheRules.year) - .expect(301) - .end(doEnd(done)); - }); - - it('should respond with html for valid url', function (done) { - request.get('/welcome-to-ghost/amp/') + it('should respond with xml', function (done) { + request.get('/static-page-test/') .expect('Content-Type', /html/) .expect('Cache-Control', testUtils.cacheRules.public) .expect(200) + .end(doEnd(done)); + }); + + describe('edit', function () { + it('should redirect without slash', function (done) { + request.get('/static-page-test/edit') + .expect('Location', '/static-page-test/edit/') + .expect('Cache-Control', testUtils.cacheRules.year) + .expect(301) + .end(doEnd(done)); + }); + + it('should redirect to editor', function (done) { + request.get('/static-page-test/edit/') + .expect('Location', /ghost\/editor\/\w+/) + .expect('Cache-Control', testUtils.cacheRules.public) + .expect(302) + .end(doEnd(done)); + }); + + it('should 404 for non-edit parameter', function (done) { + request.get('/static-page-test/notedit/') + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(404) + .expect(/Page not found/) + .end(doEnd(done)); + }); + }); + + describe('amp', function () { + it('should 404 for amp parameter', function (done) { + request.get('/static-page-test/amp/') + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(404) + .expect(/Page not found/) + .end(doEnd(done)); + }); + }); + }); + + describe('Post preview', function () { + before(addPosts); + after(testUtils.teardown); + + it('should display draft posts accessed via uuid', function (done) { + request.get('/p/d52c42ae-2755-455c-80ec-70b2ec55c903/') + .expect('Content-Type', /html/) + .expect(200) .end(function (err, res) { if (err) { return done(err); @@ -296,28 +454,170 @@ describe('Frontend Routing', function () { should.not.exist(res.headers['set-cookie']); should.exist(res.headers.date); - $('title').text().should.equal('Welcome to Ghost'); + $('title').text().should.equal('Not finished yet'); $('.content .post').length.should.equal(1); $('.poweredby').text().should.equal('Proudly published with Ghost'); - $('body.amp-template').length.should.equal(1); + $('body.post-template').length.should.equal(1); $('article.post').length.should.equal(1); done(); }); }); - it('should not work with date permalinks', function (done) { - // get today's date - var date = moment().format('YYYY/MM/DD'); - - request.get('/' + date + '/welcome-to-ghost/amp/') - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(404) - .expect(/Page not found/) + it('should redirect published posts to their live url', function (done) { + request.get('/p/2ac6b4f6-e1f3-406c-9247-c94a0496d39d/') + .expect(301) + .expect('Location', '/short-and-sweet/') + .expect('Cache-Control', testUtils.cacheRules.public) .end(doEnd(done)); }); - it('should not render AMP, when AMP is disabled', function (done) { + it('404s unknown uuids', function (done) { + request.get('/p/aac6b4f6-e1f3-406c-9247-c94a0496d39f/') + .expect(404) + .end(doEnd(done)); + }); + }); + + describe('Post with Ghost in the url', function () { + before(addPosts); + after(testUtils.teardown); + + // All of Ghost's admin depends on the /ghost/ in the url to work properly + // Badly formed regexs can cause breakage if a post slug starts with the 5 letters ghost + it('should retrieve a blog post with ghost at the start of the url', function (done) { + request.get('/ghostly-kitchen-sink/') + .expect('Cache-Control', testUtils.cacheRules.public) + .expect(200) + .end(doEnd(done)); + }); + }); + + describe('Site Map', function () { + before(testUtils.teardown); + + before(function (done) { + testUtils.initData().then(function () { + return testUtils.fixtures.insertPostsAndTags(); + }).then(function () { + done(); + }).catch(done); + }); + + it('should serve sitemap.xml', function (done) { + request.get('/sitemap.xml') + .expect(200) + .expect('Cache-Control', testUtils.cacheRules.hour) + .expect('Content-Type', 'text/xml; charset=utf-8') + .end(function (err, res) { + res.text.should.match(/sitemapindex/); + doEnd(done)(err, res); + }); + }); + + it('should serve sitemap-posts.xml', function (done) { + request.get('/sitemap-posts.xml') + .expect(200) + .expect('Cache-Control', testUtils.cacheRules.hour) + .expect('Content-Type', 'text/xml; charset=utf-8') + .end(function (err, res) { + res.text.should.match(/urlset/); + doEnd(done)(err, res); + }); + }); + + it('should serve sitemap-pages.xml', function (done) { + request.get('/sitemap-posts.xml') + .expect(200) + .expect('Cache-Control', testUtils.cacheRules.hour) + .expect('Content-Type', 'text/xml; charset=utf-8') + .end(function (err, res) { + res.text.should.match(/urlset/); + doEnd(done)(err, res); + }); + }); + + // TODO: Other pages and verify content + }); + }); + + describe('FORK', function () { + describe('Subdirectory (no slash)', function () { + var forkedGhost, request; + + before(function (done) { + testUtils.fork.ghost({ + url: 'http://localhost/blog' + }, 'testsubdir') + .then(function (child) { + forkedGhost = child; + request = require('supertest'); + request = request('http://localhost:' + child.port); + }).then(done).catch(done); + }); + + after(function (done) { + if (forkedGhost) { + forkedGhost.kill(done); + } else { + done(new Error('No forked ghost process exists, test setup must have failed.')); + } + }); + + it('http://localhost should 404', function (done) { + request.get('/') + .expect(404) + .end(doEnd(done)); + }); + + it('http://localhost/ should 404', function (done) { + request.get('/') + .expect(404) + .end(doEnd(done)); + }); + + it('http://localhost/blog should 301 to http://localhost/blog/', function (done) { + request.get('/blog') + .expect(301) + .expect('Location', '/blog/') + .end(doEnd(done)); + }); + + it('http://localhost/blog/ should 200', function (done) { + request.get('/blog/') + .expect(200) + .end(doEnd(done)); + }); + + it('http://localhost/blog/welcome-to-ghost should 301 to http://localhost/blog/welcome-to-ghost/', function (done) { + request.get('/blog/welcome-to-ghost') + .expect(301) + .expect('Location', '/blog/welcome-to-ghost/') + .expect('Cache-Control', testUtils.cacheRules.year) + .end(doEnd(done)); + }); + + it('http://localhost/blog/welcome-to-ghost/ should 200', function (done) { + request.get('/blog/welcome-to-ghost/') + .expect(200) + .end(doEnd(done)); + }); + + it('/blog/tag/getting-started should 301 to /blog/tag/getting-started/', function (done) { + request.get('/blog/tag/getting-started') + .expect(301) + .expect('Location', '/blog/tag/getting-started/') + .expect('Cache-Control', testUtils.cacheRules.year) + .end(doEnd(done)); + }); + + it('/blog/tag/getting-started/ should 200', function (done) { + request.get('/blog/tag/getting-started/') + .expect(200) + .end(doEnd(done)); + }); + + it('/blog/welcome-to-ghost/amp/ should 200', function (done) { var originalFn = settingsCache.get; sandbox.stub(settingsCache, 'get', function (key) { @@ -325,446 +625,156 @@ describe('Frontend Routing', function () { return originalFn(key); } - return false; + return true; + }); + request.get('/blog/welcome-to-ghost/amp/') + .expect(200) + .end(doEnd(done)); + }); + }); + + describe('Subdirectory (with slash)', function () { + var forkedGhost, request; + + before(function (done) { + testUtils.fork.ghost({ + url: 'http://localhost/blog/' + }, 'testsubdir') + .then(function (child) { + forkedGhost = child; + request = require('supertest'); + request = request('http://localhost:' + child.port); + }).then(done).catch(done); + }); + + after(function (done) { + if (forkedGhost) { + forkedGhost.kill(done); + } else { + done(new Error('No forked ghost process exists, test setup must have failed.')); + } + }); + + it('http://localhost should 404', function (done) { + request.get('/') + .expect(404) + .end(doEnd(done)); + }); + + it('http://localhost/ should 404', function (done) { + request.get('/') + .expect(404) + .end(doEnd(done)); + }); + + it('/blog should 301 to /blog/', function (done) { + request.get('/blog') + .expect(301) + .expect('Location', '/blog/') + .end(doEnd(done)); + }); + + it('/blog/ should 200', function (done) { + request.get('/blog/') + .expect(200) + .end(doEnd(done)); + }); + + it('/blog/welcome-to-ghost should 301 to /blog/welcome-to-ghost/', function (done) { + request.get('/blog/welcome-to-ghost') + .expect(301) + .expect('Location', '/blog/welcome-to-ghost/') + .expect('Cache-Control', testUtils.cacheRules.year) + .end(doEnd(done)); + }); + + it('/blog/welcome-to-ghost/ should 200', function (done) { + request.get('/blog/welcome-to-ghost/') + .expect(200) + .end(doEnd(done)); + }); + + it('/blog/tag/getting-started should 301 to /blog/tag/getting-started/', function (done) { + request.get('/blog/tag/getting-started') + .expect(301) + .expect('Location', '/blog/tag/getting-started/') + .expect('Cache-Control', testUtils.cacheRules.year) + .end(doEnd(done)); + }); + + it('/blog/tag/getting-started/ should 200', function (done) { + request.get('/blog/tag/getting-started/') + .expect(200) + .end(doEnd(done)); + }); + + it('/blog/welcome-to-ghost/amp/ should 200', function (done) { + var originalFn = settingsCache.get; + + sandbox.stub(settingsCache, 'get', function (key) { + if (key !== 'amp') { + return originalFn(key); + } + + return true; }); - request.get('/welcome-to-ghost/amp/') - .expect(404) - .expect(/Page not found/) - .end(doEnd(done)); - }); - }); - - describe('Static assets', function () { - it('should retrieve theme assets', function (done) { - request.get('/assets/css/screen.css') - .expect('Cache-Control', testUtils.cacheRules.year) + request.get('/blog/welcome-to-ghost/amp/') .expect(200) .end(doEnd(done)); }); - it('should retrieve default robots.txt', function (done) { - request.get('/robots.txt') - .expect('Cache-Control', testUtils.cacheRules.hour) - .expect('ETag', /[0-9a-f]{32}/i) - .expect(200) - .end(doEnd(done)); - }); - - it('should retrieve default favicon.ico', function (done) { - request.get('/favicon.ico') - .expect('Cache-Control', testUtils.cacheRules.day) - .expect('ETag', /[0-9a-f]{32}/i) - .expect(200) - .end(doEnd(done)); - }); - - // at the moment there is no image fixture to test - // it('should retrieve image assets', function (done) { - // request.get('/content/images/some.jpg') - // .expect('Cache-Control', testUtils.cacheRules.year) - // .end(doEnd(done)); - // }); - }); - }); - - describe('Static page', function () { - before(addPosts); - after(testUtils.teardown); - - it('should redirect without slash', function (done) { - request.get('/static-page-test') - .expect('Location', '/static-page-test/') - .expect('Cache-Control', testUtils.cacheRules.year) - .expect(301) - .end(doEnd(done)); - }); - - it('should respond with xml', function (done) { - request.get('/static-page-test/') - .expect('Content-Type', /html/) - .expect('Cache-Control', testUtils.cacheRules.public) - .expect(200) - .end(doEnd(done)); - }); - - describe('edit', function () { - it('should redirect without slash', function (done) { - request.get('/static-page-test/edit') - .expect('Location', '/static-page-test/edit/') + it('should uncapitalise correctly with 301 to subdir', function (done) { + request.get('/blog/AAA/') + .expect('Location', '/blog/aaa/') .expect('Cache-Control', testUtils.cacheRules.year) .expect(301) .end(doEnd(done)); }); - - it('should redirect to editor', function (done) { - request.get('/static-page-test/edit/') - .expect('Location', /ghost\/editor\/\w+/) - .expect('Cache-Control', testUtils.cacheRules.public) - .expect(302) - .end(doEnd(done)); - }); - - it('should 404 for non-edit parameter', function (done) { - request.get('/static-page-test/notedit/') - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(404) - .expect(/Page not found/) - .end(doEnd(done)); - }); }); - describe('amp', function () { - it('should 404 for amp parameter', function (done) { - request.get('/static-page-test/amp/') - .expect('Cache-Control', testUtils.cacheRules.private) - .expect(404) - .expect(/Page not found/) - .end(doEnd(done)); - }); - }); - }); + // we'll use X-Forwarded-Proto: https to simulate an 'https://' request behind a proxy + describe('HTTPS', function () { + var forkedGhost, request; - describe('Post preview', function () { - before(addPosts); - after(testUtils.teardown); - - it('should display draft posts accessed via uuid', function (done) { - request.get('/p/d52c42ae-2755-455c-80ec-70b2ec55c903/') - .expect('Content-Type', /html/) - .expect(200) - .end(function (err, res) { - if (err) { - return done(err); + before(function (done) { + testUtils.fork.ghost({ + url: 'http://localhost:2370/', + server: { + port: 2370 } - - var $ = cheerio.load(res.text); - - should.not.exist(res.headers['x-cache-invalidate']); - should.not.exist(res.headers['X-CSRF-Token']); - should.not.exist(res.headers['set-cookie']); - should.exist(res.headers.date); - - $('title').text().should.equal('Not finished yet'); - $('.content .post').length.should.equal(1); - $('.poweredby').text().should.equal('Proudly published with Ghost'); - $('body.post-template').length.should.equal(1); - $('article.post').length.should.equal(1); - - done(); - }); - }); - - it('should redirect published posts to their live url', function (done) { - request.get('/p/2ac6b4f6-e1f3-406c-9247-c94a0496d39d/') - .expect(301) - .expect('Location', '/short-and-sweet/') - .expect('Cache-Control', testUtils.cacheRules.public) - .end(doEnd(done)); - }); - - it('404s unknown uuids', function (done) { - request.get('/p/aac6b4f6-e1f3-406c-9247-c94a0496d39f/') - .expect(404) - .end(doEnd(done)); - }); - }); - - describe('Post with Ghost in the url', function () { - before(addPosts); - after(testUtils.teardown); - - // All of Ghost's admin depends on the /ghost/ in the url to work properly - // Badly formed regexs can cause breakage if a post slug starts with the 5 letters ghost - it('should retrieve a blog post with ghost at the start of the url', function (done) { - request.get('/ghostly-kitchen-sink/') - .expect('Cache-Control', testUtils.cacheRules.public) - .expect(200) - .end(doEnd(done)); - }); - }); - - describe('Subdirectory (no slash)', function () { - var forkedGhost, request; - - before(function (done) { - testUtils.fork.ghost({ - url: 'http://localhost/blog' - }, 'testsubdir') - .then(function (child) { - forkedGhost = child; - request = require('supertest'); - request = request('http://localhost:' + child.port); - }).then(done).catch(done); - }); - - after(function (done) { - if (forkedGhost) { - forkedGhost.kill(done); - } else { - done(new Error('No forked ghost process exists, test setup must have failed.')); - } - }); - - it('http://localhost should 404', function (done) { - request.get('/') - .expect(404) - .end(doEnd(done)); - }); - - it('http://localhost/ should 404', function (done) { - request.get('/') - .expect(404) - .end(doEnd(done)); - }); - - it('http://localhost/blog should 301 to http://localhost/blog/', function (done) { - request.get('/blog') - .expect(301) - .expect('Location', '/blog/') - .end(doEnd(done)); - }); - - it('http://localhost/blog/ should 200', function (done) { - request.get('/blog/') - .expect(200) - .end(doEnd(done)); - }); - - it('http://localhost/blog/welcome-to-ghost should 301 to http://localhost/blog/welcome-to-ghost/', function (done) { - request.get('/blog/welcome-to-ghost') - .expect(301) - .expect('Location', '/blog/welcome-to-ghost/') - .expect('Cache-Control', testUtils.cacheRules.year) - .end(doEnd(done)); - }); - - it('http://localhost/blog/welcome-to-ghost/ should 200', function (done) { - request.get('/blog/welcome-to-ghost/') - .expect(200) - .end(doEnd(done)); - }); - - it('/blog/tag/getting-started should 301 to /blog/tag/getting-started/', function (done) { - request.get('/blog/tag/getting-started') - .expect(301) - .expect('Location', '/blog/tag/getting-started/') - .expect('Cache-Control', testUtils.cacheRules.year) - .end(doEnd(done)); - }); - - it('/blog/tag/getting-started/ should 200', function (done) { - request.get('/blog/tag/getting-started/') - .expect(200) - .end(doEnd(done)); - }); - - it('/blog/welcome-to-ghost/amp/ should 200', function (done) { - var originalFn = settingsCache.get; - - sandbox.stub(settingsCache, 'get', function (key) { - if (key !== 'amp') { - return originalFn(key); - } - - return true; + }, 'testhttps') + .then(function (child) { + forkedGhost = child; + request = require('supertest'); + request = request('http://localhost:2370'); + }).then(done).catch(done); }); - request.get('/blog/welcome-to-ghost/amp/') - .expect(200) - .end(doEnd(done)); - }); - }); - - describe('Subdirectory (with slash)', function () { - var forkedGhost, request; - - before(function (done) { - testUtils.fork.ghost({ - url: 'http://localhost/blog/' - }, 'testsubdir') - .then(function (child) { - forkedGhost = child; - request = require('supertest'); - request = request('http://localhost:' + child.port); - }).then(done).catch(done); - }); - - after(function (done) { - if (forkedGhost) { - forkedGhost.kill(done); - } else { - done(new Error('No forked ghost process exists, test setup must have failed.')); - } - }); - - it('http://localhost should 404', function (done) { - request.get('/') - .expect(404) - .end(doEnd(done)); - }); - - it('http://localhost/ should 404', function (done) { - request.get('/') - .expect(404) - .end(doEnd(done)); - }); - - it('/blog should 301 to /blog/', function (done) { - request.get('/blog') - .expect(301) - .expect('Location', '/blog/') - .end(doEnd(done)); - }); - - it('/blog/ should 200', function (done) { - request.get('/blog/') - .expect(200) - .end(doEnd(done)); - }); - - it('/blog/welcome-to-ghost should 301 to /blog/welcome-to-ghost/', function (done) { - request.get('/blog/welcome-to-ghost') - .expect(301) - .expect('Location', '/blog/welcome-to-ghost/') - .expect('Cache-Control', testUtils.cacheRules.year) - .end(doEnd(done)); - }); - - it('/blog/welcome-to-ghost/ should 200', function (done) { - request.get('/blog/welcome-to-ghost/') - .expect(200) - .end(doEnd(done)); - }); - - it('/blog/tag/getting-started should 301 to /blog/tag/getting-started/', function (done) { - request.get('/blog/tag/getting-started') - .expect(301) - .expect('Location', '/blog/tag/getting-started/') - .expect('Cache-Control', testUtils.cacheRules.year) - .end(doEnd(done)); - }); - - it('/blog/tag/getting-started/ should 200', function (done) { - request.get('/blog/tag/getting-started/') - .expect(200) - .end(doEnd(done)); - }); - - it('/blog/welcome-to-ghost/amp/ should 200', function (done) { - var originalFn = settingsCache.get; - - sandbox.stub(settingsCache, 'get', function (key) { - if (key !== 'amp') { - return originalFn(key); + after(function (done) { + if (forkedGhost) { + forkedGhost.kill(done); + } else { + done(new Error('No forked ghost process exists, test setup must have failed.')); } - - return true; }); - request.get('/blog/welcome-to-ghost/amp/') - .expect(200) - .end(doEnd(done)); + it('should set links to url over non-HTTPS', function (done) { + request.get('/') + .expect(200) + .expect(//) + .expect(/Ghost<\/a\>/) + .end(doEnd(done)); + }); + + it('should set links over HTTPS besides canonical', function (done) { + request.get('/') + .set('X-Forwarded-Proto', 'https') + .expect(200) + .expect(//) + .expect(/Ghost<\/a\>/) + .end(doEnd(done)); + }); }); - - it('should uncapitalise correctly with 301 to subdir', function (done) { - request.get('/blog/AAA/') - .expect('Location', '/blog/aaa/') - .expect('Cache-Control', testUtils.cacheRules.year) - .expect(301) - .end(doEnd(done)); - }); - }); - - // we'll use X-Forwarded-Proto: https to simulate an 'https://' request behind a proxy - describe('HTTPS', function () { - var forkedGhost, request; - - before(function (done) { - testUtils.fork.ghost({ - forceAdminSSL: {redirect: false}, - urlSSL: 'https://localhost/', - server: { - port: 2370 - } - }, 'testhttps') - .then(function (child) { - forkedGhost = child; - request = require('supertest'); - request = request('http://127.0.0.1:2370'); - }).then(done).catch(done); - }); - - after(function (done) { - if (forkedGhost) { - forkedGhost.kill(done); - } else { - done(new Error('No forked ghost process exists, test setup must have failed.')); - } - }); - - it('should set links to url over non-HTTPS', function (done) { - request.get('/') - .expect(200) - .expect(//) - .expect(/Ghost<\/a\>/) - .end(doEnd(done)); - }); - - it('should set links to urlSSL over HTTPS besides canonical', function (done) { - request.get('/') - .set('X-Forwarded-Proto', 'https') - .expect(200) - .expect(//) - .expect(/Ghost<\/a\>/) - .end(doEnd(done)); - }); - }); - - describe('Site Map', function () { - before(testUtils.teardown); - - before(function (done) { - testUtils.initData().then(function () { - return testUtils.fixtures.insertPostsAndTags(); - }).then(function () { - done(); - }).catch(done); - }); - - it('should serve sitemap.xml', function (done) { - request.get('/sitemap.xml') - .expect(200) - .expect('Cache-Control', testUtils.cacheRules.hour) - .expect('Content-Type', 'text/xml; charset=utf-8') - .end(function (err, res) { - res.text.should.match(/sitemapindex/); - doEnd(done)(err, res); - }); - }); - - it('should serve sitemap-posts.xml', function (done) { - request.get('/sitemap-posts.xml') - .expect(200) - .expect('Cache-Control', testUtils.cacheRules.hour) - .expect('Content-Type', 'text/xml; charset=utf-8') - .end(function (err, res) { - res.text.should.match(/urlset/); - doEnd(done)(err, res); - }); - }); - - it('should serve sitemap-pages.xml', function (done) { - request.get('/sitemap-posts.xml') - .expect(200) - .expect('Cache-Control', testUtils.cacheRules.hour) - .expect('Content-Type', 'text/xml; charset=utf-8') - .end(function (err, res) { - res.text.should.match(/urlset/); - doEnd(done)(err, res); - }); - }); - - // TODO: Other pages and verify content }); }); diff --git a/core/test/unit/ghost_url_spec.js b/core/test/unit/ghost_url_spec.js index 098cb1bee9..2cfc7476f0 100644 --- a/core/test/unit/ghost_url_spec.js +++ b/core/test/unit/ghost_url_spec.js @@ -30,7 +30,7 @@ describe('Ghost Ajax Helper', function () { ghostUrl.init({ clientId: '', clientSecret: '', - url: utils.url.apiUrl({cors: true}) + url: utils.url.urlFor('api', {cors: true}, true) }); ghostUrl.url.api().should.equal('//testblog.com/ghost/api/v0.1/'); @@ -40,7 +40,7 @@ describe('Ghost Ajax Helper', function () { ghostUrl.init({ clientId: '', clientSecret: '', - url: utils.url.apiUrl({cors: true}) + url: utils.url.urlFor('api', {cors: true}, true) }); ghostUrl.url.api('a/', '/b', '/c/').should.equal('//testblog.com/ghost/api/v0.1/a/b/c/'); @@ -50,7 +50,7 @@ describe('Ghost Ajax Helper', function () { ghostUrl.init({ clientId: 'ghost-frontend', clientSecret: 'notasecret', - url: utils.url.apiUrl({cors: true}) + url: utils.url.urlFor('api', {cors: true}, true) }); ghostUrl.url.api().should.equal('//testblog.com/ghost/api/v0.1/?client_id=ghost-frontend&client_secret=notasecret'); @@ -60,7 +60,7 @@ describe('Ghost Ajax Helper', function () { ghostUrl.init({ clientId: 'ghost-frontend', clientSecret: 'notasecret', - url: utils.url.apiUrl({cors: true}) + url: utils.url.urlFor('api', {cors: true}, true) }); var rendered = ghostUrl.url.api({a: 'string', b: 5, c: 'en coded'}); @@ -98,7 +98,7 @@ describe('Ghost Ajax Helper', function () { ghostUrl.init({ clientId: 'ghost-frontend', clientSecret: 'notasecret', - url: utils.url.apiUrl({cors: true}) + url: utils.url.urlFor('api', {cors: true}, true) }); var rendered = ghostUrl.url.api('posts/', '/tags/', '/count', {include: 'tags,tests', page: 2}); @@ -114,10 +114,11 @@ describe('Ghost Ajax Helper', function () { configUtils.set({ url: 'https://testblog.com/' }); + ghostUrl.init({ clientId: 'ghost-frontend', clientSecret: 'notasecret', - url: utils.url.apiUrl({cors: true}) + url: utils.url.urlFor('api', true) }); var rendered = ghostUrl.url.api('posts/', '/tags/', '/count', {include: 'tags,tests', page: 2}); @@ -136,7 +137,7 @@ describe('Ghost Ajax Helper', function () { ghostUrl.init({ clientId: 'ghost-frontend', clientSecret: 'notasecret', - url: utils.url.apiUrl({cors: true}) + url: utils.url.urlFor('api', true) }); var rendered = ghostUrl.url.api('posts/', '/tags/', '/count', {include: 'tags,tests', page: 2}); @@ -156,7 +157,7 @@ describe('Ghost Ajax Helper', function () { ghostUrl.init({ clientId: 'ghost-frontend', clientSecret: 'notasecret', - url: utils.url.apiUrl({cors: true}) + url: utils.url.urlFor('api', {cors: true}, true) }); var rendered = ghostUrl.url.api('posts', {limit: 3}), diff --git a/core/test/unit/middleware/api/cors_spec.js b/core/test/unit/middleware/api/cors_spec.js index 2fd9edd959..d830180584 100644 --- a/core/test/unit/middleware/api/cors_spec.js +++ b/core/test/unit/middleware/api/cors_spec.js @@ -159,11 +159,14 @@ describe('cors', function () { done(); }); - it('should be enabled if the origin matches config.urlSSL', function (done) { - var origin = 'https://secure.blog'; + it('should be enabled if the origin matches config.url', function (done) { + var origin = 'http://admin:2222'; + configUtils.set({ - url: 'http://my.blog', - urlSSL: origin + url: 'https://blog', + admin: { + url: origin + } }); req.get = sinon.stub().withArgs('origin').returns(origin); diff --git a/core/test/unit/middleware/check-ssl_spec.js b/core/test/unit/middleware/check-ssl_spec.js deleted file mode 100644 index c7fef8aa12..0000000000 --- a/core/test/unit/middleware/check-ssl_spec.js +++ /dev/null @@ -1,226 +0,0 @@ -var sinon = require('sinon'), - should = require('should'), - configUtils = require('../../utils/configUtils'), - checkSSL = require('../../../server/middleware/check-ssl'); - -should.equal(true, true); - -describe('checkSSL', function () { - var res, req, next, sandbox; - - beforeEach(function () { - sandbox = sinon.sandbox.create(); - req = {}; - res = {}; - next = sandbox.spy(); - - configUtils.set({ - url: 'http://default.com:2368/' - }); - }); - - afterEach(function () { - sandbox.restore(); - configUtils.restore(); - }); - - it('should not require SSL (frontend)', function (done) { - req.originalUrl = '/'; - checkSSL(req, res, next); - next.called.should.be.true(); - next.calledWith().should.be.true(); - done(); - }); - - it('should require SSL (frontend)', function (done) { - req.originalUrl = '/'; - req.secure = true; - checkSSL(req, res, next); - next.called.should.be.true(); - next.calledWith().should.be.true(); - done(); - }); - - it('should not require SSL (admin)', function (done) { - req.originalUrl = '/ghost'; - res.isAdmin = true; - checkSSL(req, res, next); - next.called.should.be.true(); - next.calledWith().should.be.true(); - done(); - }); - - it('should not redirect with SSL (admin)', function (done) { - req.originalUrl = '/ghost'; - res.isAdmin = true; - res.secure = true; - - checkSSL(req, res, next); - next.called.should.be.true(); - next.calledWith().should.be.true(); - done(); - }); - - it('should not redirect with force admin SSL (admin)', function (done) { - req.originalUrl = '/ghost'; - res.isAdmin = true; - req.secure = true; - configUtils.set({ - url: 'http://default.com:2368/', - forceAdminSSL: true - }); - checkSSL(req, res, next); - next.called.should.be.true(); - next.calledWith().should.be.true(); - done(); - }); - - it('should redirect with force admin SSL (admin)', function (done) { - req.originalUrl = '/ghost/'; - res.isAdmin = true; - res.redirect = {}; - req.secure = false; - configUtils.set({ - url: 'http://default.com:2368/', - urlSSL: '', - forceAdminSSL: true - }); - sandbox.stub(res, 'redirect', function (statusCode, url) { - statusCode.should.eql(301); - url.should.not.be.empty(); - url.should.eql('https://default.com:2368/ghost/'); - return; - }); - checkSSL(req, res, next); - next.called.should.be.false(); - done(); - }); - - it('should redirect to subdirectory with force admin SSL (admin)', function (done) { - req.originalUrl = '/blog/ghost/'; - res.isAdmin = true; - res.redirect = {}; - req.secure = false; - configUtils.set({ - url: 'http://default.com:2368/blog/', - urlSSL: '', - forceAdminSSL: true - }); - sandbox.stub(res, 'redirect', function (statusCode, url) { - statusCode.should.eql(301); - url.should.not.be.empty(); - url.should.eql('https://default.com:2368/blog/ghost/'); - return; - }); - checkSSL(req, res, next); - next.called.should.be.false(); - done(); - }); - - it('should redirect and keep query with force admin SSL (admin)', function (done) { - req.originalUrl = '/ghost/'; - req.query = { - test: 'true' - }; - res.isAdmin = true; - res.redirect = {}; - req.secure = false; - configUtils.set({ - url: 'http://default.com:2368/', - urlSSL: '', - forceAdminSSL: true - }); - sandbox.stub(res, 'redirect', function (statusCode, url) { - statusCode.should.eql(301); - url.should.not.be.empty(); - url.should.eql('https://default.com:2368/ghost/?test=true'); - return; - }); - checkSSL(req, res, next); - next.called.should.be.false(); - done(); - }); - - it('should redirect with with config.url being SSL (frontend)', function (done) { - req.originalUrl = '/'; - req.secure = false; - res.redirect = {}; - configUtils.set({ - url: 'https://default.com:2368', - urlSSL: '', - forceAdminSSL: true - }); - sandbox.stub(res, 'redirect', function (statusCode, url) { - statusCode.should.eql(301); - url.should.not.be.empty(); - url.should.eql('https://default.com:2368/'); - return; - }); - checkSSL(req, res, next); - next.called.should.be.false(); - done(); - }); - - it('should redirect to urlSSL (admin)', function (done) { - req.originalUrl = '/ghost/'; - res.isAdmin = true; - res.redirect = {}; - req.secure = false; - configUtils.set({ - url: 'http://default.com:2368/', - urlSSL: 'https://ssl-domain.com:2368/', - forceAdminSSL: true - }); - sandbox.stub(res, 'redirect', function (statusCode, url) { - statusCode.should.eql(301); - url.should.not.be.empty(); - url.should.eql('https://ssl-domain.com:2368/ghost/'); - return; - }); - checkSSL(req, res, next); - next.called.should.be.false(); - done(); - }); - - it('should not redirect if redirect:false (admin)', function (done) { - req.originalUrl = '/ghost/'; - res.isAdmin = true; - res.sendStatus = {}; - req.secure = false; - configUtils.set({ - url: 'http://default.com:2368/', - forceAdminSSL: { - redirect: false - } - }); - sandbox.stub(res, 'sendStatus', function (statusCode) { - statusCode.should.eql(403); - return; - }); - checkSSL(req, res, next); - next.called.should.be.false(); - done(); - }); - - it('should redirect to correct path with force admin SSL (admin as subapp)', function (done) { - req.url = '/'; - req.originalUrl = '/ghost/'; - res.isAdmin = true; - res.redirect = {}; - req.secure = false; - configUtils.set({ - url: 'http://default.com:2368/', - urlSSL: '', - forceAdminSSL: true - }); - sandbox.stub(res, 'redirect', function (statusCode, url) { - statusCode.should.eql(301); - url.should.not.be.empty(); - url.should.eql('https://default.com:2368/ghost/'); - return; - }); - checkSSL(req, res, next); - next.called.should.be.false(); - done(); - }); -}); diff --git a/core/test/unit/middleware/theme-handler_spec.js b/core/test/unit/middleware/theme-handler_spec.js index 91b602f507..b9a40175b8 100644 --- a/core/test/unit/middleware/theme-handler_spec.js +++ b/core/test/unit/middleware/theme-handler_spec.js @@ -67,23 +67,6 @@ describe('Theme Handler', function () { next.called.should.be.true(); }); - it('handles secure context', function () { - var themeOptSpy = sandbox.stub(hbs, 'updateTemplateOptions'); - req.secure = true; - res.locals = {}; - configUtils.set({urlSSL: 'https://secure.blog'}); - - themeHandler.configHbsForContext(req, res, next); - - themeOptSpy.calledOnce.should.be.true(); - themeOptSpy.firstCall.args[0].should.be.an.Object().and.have.property('data'); - themeOptSpy.firstCall.args[0].data.should.be.an.Object().and.have.property('blog'); - themeOptSpy.firstCall.args[0].data.blog.should.be.an.Object().and.have.property('url'); - themeOptSpy.firstCall.args[0].data.blog.url.should.eql('https://secure.blog'); - res.locals.secure.should.equal(true); - next.called.should.be.true(); - }); - it('sets view path', function () { req.secure = true; res.locals = {}; diff --git a/core/test/unit/middleware/url-redirects_spec.js b/core/test/unit/middleware/url-redirects_spec.js new file mode 100644 index 0000000000..e5b08cdf6e --- /dev/null +++ b/core/test/unit/middleware/url-redirects_spec.js @@ -0,0 +1,196 @@ +var sinon = require('sinon'), + should = require('should'), + configUtils = require('../../utils/configUtils'), + urlRedirects = require('../../../server/middleware/url-redirects'), + sandbox = sinon.sandbox.create(); + +should.equal(true, true); + +describe('checkSSL', function () { + var res, req, next, host; + + beforeEach(function () { + req = { + get: function get() { + return host; + } + }; + res = { + redirect: sandbox.spy() + }; + + next = sandbox.spy(); + }); + + afterEach(function () { + sandbox.restore(); + configUtils.restore(); + host = null; + }); + + it('blog is http, requester uses http', function (done) { + configUtils.set({ + url: 'http://default.com:2368/' + }); + + host = 'default.com:2368'; + + req.originalUrl = '/'; + urlRedirects(req, res, next); + next.called.should.be.true(); + res.redirect.called.should.be.false(); + next.calledWith().should.be.true(); + done(); + }); + + it('blog is https, requester uses https', function (done) { + configUtils.set({ + url: 'https://default.com:2368/' + }); + + host = 'default.com:2368'; + + req.originalUrl = '/'; + req.secure = true; + urlRedirects(req, res, next); + next.called.should.be.true(); + res.redirect.called.should.be.false(); + next.calledWith().should.be.true(); + done(); + }); + + it('blog is https, requester uses http [redirect]', function (done) { + configUtils.set({ + url: 'https://default.com:2368/' + }); + + host = 'default.com:2368'; + + req.originalUrl = '/'; + urlRedirects(req, res, next); + next.called.should.be.false(); + res.redirect.called.should.be.true(); + done(); + }); + + it('blog is http, requester uses https', function (done) { + configUtils.set({ + url: 'http://default.com:2368/' + }); + + host = 'default.com:2368'; + + req.originalUrl = '/'; + req.secure = true; + urlRedirects(req, res, next); + next.called.should.be.true(); + res.redirect.called.should.be.false(); + done(); + }); + + it('blog is http, requester uses https', function (done) { + configUtils.set({ + url: 'http://default.com:2368/' + }); + + host = 'default.com:2368/'; + + req.originalUrl = '/'; + req.secure = true; + urlRedirects(req, res, next); + next.called.should.be.true(); + res.redirect.called.should.be.false(); + done(); + }); + + it('admin is blog url and http, requester is http', function (done) { + configUtils.set({ + url: 'http://default.com:2368' + }); + + host = 'default.com:2368'; + res.isAdmin = true; + + req.originalUrl = '/ghost'; + urlRedirects(req, res, next); + next.called.should.be.true(); + res.redirect.called.should.be.false(); + done(); + }); + + it('admin is custom url and https, requester is http [redirect]', function (done) { + configUtils.set({ + url: 'http://default.com:2368', + admin: { + url: 'https://default.com:2368' + } + }); + + host = 'default.com:2368'; + res.isAdmin = true; + + req.originalUrl = '/ghost'; + urlRedirects(req, res, next); + next.called.should.be.false(); + res.redirect.calledWith(301, 'https://default.com:2368/ghost').should.be.true(); + done(); + }); + + it('admin is custom url and https, requester is http [redirect]', function (done) { + configUtils.set({ + url: 'http://default.com:2368', + admin: { + url: 'https://admin.default.com:2368' + } + }); + + host = 'default.com:2368'; + res.isAdmin = true; + + req.originalUrl = '/ghost'; + urlRedirects(req, res, next); + next.called.should.be.false(); + res.redirect.calledWith(301, 'https://admin.default.com:2368/ghost').should.be.true(); + done(); + }); + + it('subdirectory [redirect]', function (done) { + configUtils.set({ + url: 'http://default.com:2368/blog', + admin: { + url: 'https://admin.default.com:2368' + } + }); + + host = 'default.com:2368'; + res.isAdmin = true; + + req.originalUrl = '/blog/ghost'; + urlRedirects(req, res, next); + next.called.should.be.false(); + res.redirect.calledWith(301, 'https://admin.default.com:2368/blog/ghost').should.be.true(); + done(); + }); + + it('keeps query [redirect]', function (done) { + configUtils.set({ + url: 'http://default.com:2368', + admin: { + url: 'https://admin.default.com:2368' + } + }); + + host = 'default.com:2368'; + res.isAdmin = true; + + req.originalUrl = '/ghost'; + req.query = { + test: true + }; + + urlRedirects(req, res, next); + next.called.should.be.false(); + res.redirect.calledWith(301, 'https://admin.default.com:2368/ghost?test=true').should.be.true(); + done(); + }); +}); diff --git a/core/test/unit/slack_spec.js b/core/test/unit/slack_spec.js index 53eab2d7ed..47c55ad20d 100644 --- a/core/test/unit/slack_spec.js +++ b/core/test/unit/slack_spec.js @@ -195,7 +195,7 @@ describe('Slack', function () { isPostStub.returns(false); settingsObj.settings[0] = slackObjWithUrl; - configUtils.set('forceAdminSSL', true); + configUtils.set('url', 'https://myblog.com'); // assertions makeRequestAssertions = function (requestOptions, requestData) { diff --git a/core/test/unit/utils/url_spec.js b/core/test/unit/utils/url_spec.js index 688430dd0f..8a2b8f5264 100644 --- a/core/test/unit/utils/url_spec.js +++ b/core/test/unit/utils/url_spec.js @@ -104,22 +104,22 @@ describe('Url', function () { it('should return home url when asked for', function () { var testContext = 'home'; - configUtils.set({url: 'http://my-ghost-blog.com', urlSSL: 'https://my-ghost-blog.com'}); + configUtils.set({url: 'http://my-ghost-blog.com'}); utils.url.urlFor(testContext).should.equal('/'); utils.url.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/'); utils.url.urlFor(testContext, {secure: true}, true).should.equal('https://my-ghost-blog.com/'); - configUtils.set({url: 'http://my-ghost-blog.com/', urlSSL: 'https://my-ghost-blog.com/'}); + configUtils.set({url: 'http://my-ghost-blog.com/'}); utils.url.urlFor(testContext).should.equal('/'); utils.url.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/'); utils.url.urlFor(testContext, {secure: true}, true).should.equal('https://my-ghost-blog.com/'); - configUtils.set({url: 'http://my-ghost-blog.com/blog', urlSSL: 'https://my-ghost-blog.com/blog'}); + configUtils.set({url: 'http://my-ghost-blog.com/blog'}); utils.url.urlFor(testContext).should.equal('/blog/'); utils.url.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/'); utils.url.urlFor(testContext, {secure: true}, true).should.equal('https://my-ghost-blog.com/blog/'); - configUtils.set({url: 'http://my-ghost-blog.com/blog/', urlSSL: 'https://my-ghost-blog.com/blog/'}); + configUtils.set({url: 'http://my-ghost-blog.com/blog/'}); utils.url.urlFor(testContext).should.equal('/blog/'); utils.url.urlFor(testContext, true).should.equal('http://my-ghost-blog.com/blog/'); utils.url.urlFor(testContext, {secure: true}, true).should.equal('https://my-ghost-blog.com/blog/'); @@ -245,7 +245,7 @@ describe('Url', function () { var testContext = 'nav', testData; - configUtils.set({url: 'http://my-ghost-blog.com', urlSSL: 'https://my-ghost-blog.com'}); + configUtils.set({url: 'http://my-ghost-blog.com'}); testData = {nav: {url: 'http://my-ghost-blog.com/short-and-sweet/'}}; utils.url.urlFor(testContext, testData).should.equal('http://my-ghost-blog.com/short-and-sweet/'); @@ -286,13 +286,10 @@ describe('Url', function () { utils.url.urlFor(testContext, testData).should.equal('mailto:marshmallow@my-ghost-blog.com'); }); - it('should return other known paths when requested', function () { + it('sitemap: should return other known paths when requested', function () { configUtils.set({url: 'http://my-ghost-blog.com'}); utils.url.urlFor('sitemap_xsl').should.equal('/sitemap.xsl'); utils.url.urlFor('sitemap_xsl', true).should.equal('http://my-ghost-blog.com/sitemap.xsl'); - - utils.url.urlFor('api').should.equal('/ghost/api/v0.1'); - utils.url.urlFor('api', true).should.equal('http://my-ghost-blog.com/ghost/api/v0.1'); }); it('admin: relative', function () { @@ -303,7 +300,7 @@ describe('Url', function () { utils.url.urlFor('admin').should.equal('/ghost/'); }); - it('admin: forceAdminSSL is false', function () { + it('admin: url is http', function () { configUtils.set({ url: 'http://my-ghost-blog.com' }); @@ -311,22 +308,15 @@ describe('Url', function () { utils.url.urlFor('admin', true).should.equal('http://my-ghost-blog.com/ghost/'); }); - it('admin: forceAdminSSL is true', function () { + it('admin: custom admin url is set', function () { configUtils.set({ url: 'http://my-ghost-blog.com', - forceAdminSSL: true + admin: { + url: 'https://admin.my-ghost-blog.com' + } }); - utils.url.urlFor('admin', true).should.equal('https://my-ghost-blog.com/ghost/'); - }); - - it('admin: forceAdminSSL is true', function () { - configUtils.set({ - url: 'http://my-ghost-blog.com', - forceAdminSSL: true - }); - - utils.url.urlFor('admin', true).should.equal('https://my-ghost-blog.com/ghost/'); + utils.url.urlFor('admin', true).should.equal('https://admin.my-ghost-blog.com/ghost/'); }); it('admin: blog is on subdir', function () { @@ -337,6 +327,14 @@ describe('Url', function () { utils.url.urlFor('admin', true).should.equal('http://my-ghost-blog.com/blog/ghost/'); }); + it('admin: blog is on subdir', function () { + configUtils.set({ + url: 'http://my-ghost-blog.com/blog/' + }); + + utils.url.urlFor('admin', true).should.equal('http://my-ghost-blog.com/blog/ghost/'); + }); + it('admin: blog is on subdir', function () { configUtils.set({ url: 'http://my-ghost-blog.com/blog' @@ -344,6 +342,93 @@ describe('Url', function () { utils.url.urlFor('admin').should.equal('/blog/ghost/'); }); + + it('admin: blog is on subdir', function () { + configUtils.set({ + url: 'http://my-ghost-blog.com/blog', + admin: { + url: 'http://something.com' + } + }); + + utils.url.urlFor('admin', true).should.equal('http://something.com/blog/ghost/'); + }); + + it('admin: blog is on subdir', function () { + configUtils.set({ + url: 'http://my-ghost-blog.com/blog', + admin: { + url: 'http://something.com/blog' + } + }); + + utils.url.urlFor('admin', true).should.equal('http://something.com/blog/ghost/'); + }); + + it('admin: blog is on subdir', function () { + configUtils.set({ + url: 'http://my-ghost-blog.com/blog', + admin: { + url: 'http://something.com/blog/' + } + }); + + utils.url.urlFor('admin', true).should.equal('http://something.com/blog/ghost/'); + }); + + it('admin: blog is on subdir', function () { + configUtils.set({ + url: 'http://my-ghost-blog.com/blog/', + admin: { + url: 'http://something.com/blog' + } + }); + + utils.url.urlFor('admin', true).should.equal('http://something.com/blog/ghost/'); + }); + + it('api: should return admin url is set', function () { + configUtils.set({ + url: 'http://my-ghost-blog.com', + admin: { + url: 'https://something.de' + } + }); + + utils.url.urlFor('api', true).should.eql('https://something.de/ghost/api/v0.1/'); + }); + + it('api: url has subdir', function () { + configUtils.set({ + url: 'http://my-ghost-blog.com/blog' + }); + + utils.url.urlFor('api', true).should.eql('http://my-ghost-blog.com/blog/ghost/api/v0.1/'); + }); + + it('api: should return http if config.url is http', function () { + configUtils.set({ + url: 'http://my-ghost-blog.com' + }); + + utils.url.urlFor('api', true).should.eql('http://my-ghost-blog.com/ghost/api/v0.1/'); + }); + + it('api: should return https if config.url is https', function () { + configUtils.set({ + url: 'https://my-ghost-blog.com' + }); + + utils.url.urlFor('api', true).should.eql('https://my-ghost-blog.com/ghost/api/v0.1/'); + }); + + it('api: cors should return no protocol', function () { + configUtils.set({ + url: 'http://my-ghost-blog.com' + }); + + utils.url.urlFor('api', {cors: true}, true).should.eql('//my-ghost-blog.com/ghost/api/v0.1/'); + }); }); describe('urlPathForPost', function () { @@ -432,77 +517,4 @@ describe('Url', function () { utils.url.urlPathForPost(testData).should.equal(postLink); }); }); - - describe('apiUrl', function () { - it('should return https config.url if forceAdminSSL set', function () { - configUtils.set({ - url: 'http://my-ghost-blog.com', - forceAdminSSL: true - }); - - utils.url.apiUrl().should.eql('https://my-ghost-blog.com/ghost/api/v0.1/'); - }); - - it('should return https config.urlSSL if forceAdminSSL set and urlSSL is misconfigured', function () { - configUtils.set({ - url: 'http://my-ghost-blog.com', - urlSSL: 'http://other-ghost-blog.com', - forceAdminSSL: true - }); - - utils.url.apiUrl().should.eql('https://other-ghost-blog.com/ghost/api/v0.1/'); - }); - - it('should return https config.urlSSL if forceAdminSSL set', function () { - configUtils.set({ - url: 'http://my-ghost-blog.com', - urlSSL: 'https://other-ghost-blog.com', - forceAdminSSL: true - }); - - utils.url.apiUrl().should.eql('https://other-ghost-blog.com/ghost/api/v0.1/'); - }); - - it('should return https config.urlSSL if set and misconfigured & forceAdminSSL is NOT set', function () { - configUtils.set({ - url: 'http://my-ghost-blog.com', - urlSSL: 'http://other-ghost-blog.com' - }); - - utils.url.apiUrl().should.eql('https://other-ghost-blog.com/ghost/api/v0.1/'); - }); - - it('should return https config.urlSSL if set & forceAdminSSL is NOT set', function () { - configUtils.set({ - url: 'http://my-ghost-blog.com', - urlSSL: 'https://other-ghost-blog.com' - }); - - utils.url.apiUrl().should.eql('https://other-ghost-blog.com/ghost/api/v0.1/'); - }); - - it('should return https config.url if config.url is https & forceAdminSSL is NOT set', function () { - configUtils.set({ - url: 'https://my-ghost-blog.com' - }); - - utils.url.apiUrl().should.eql('https://my-ghost-blog.com/ghost/api/v0.1/'); - }); - - it('CORS: should return no protocol config.url if config.url is NOT https & forceAdminSSL/urlSSL is NOT set', function () { - configUtils.set({ - url: 'http://my-ghost-blog.com' - }); - - utils.url.apiUrl({cors: true}).should.eql('//my-ghost-blog.com/ghost/api/v0.1/'); - }); - - it('should return protocol config.url if config.url is NOT https & forceAdminSSL/urlSSL is NOT set', function () { - configUtils.set({ - url: 'http://my-ghost-blog.com' - }); - - utils.url.apiUrl().should.eql('http://my-ghost-blog.com/ghost/api/v0.1/'); - }); - }); }); diff --git a/core/test/utils/configUtils.js b/core/test/utils/configUtils.js index 7a2114f29e..e271707753 100644 --- a/core/test/utils/configUtils.js +++ b/core/test/utils/configUtils.js @@ -27,6 +27,12 @@ configUtils.set = function () { * nconf keeps this as a reference and then it can happen that the defaultConfig get's overridden by new values */ configUtils.restore = function () { + /** + * we have to reset the whole config object + * config keys, which get set via a test and do not exist in the config files, won't get reseted + */ + config.reset(); + _.each(configUtils.defaultConfig, function (value, key) { config.set(key, _.cloneDeep(value)); });