From d051050e1cb66c82730fead290863b3c878e5687 Mon Sep 17 00:00:00 2001 From: kirrg001 Date: Fri, 4 Jan 2019 14:14:07 +0100 Subject: [PATCH] Refactored routing config for multiple api versions refs #10124 - one clean v0.1 and v2 config file for routing --- core/server/apps/amp/lib/router.js | 2 +- .../services/routing/CollectionRouter.js | 11 +- core/server/services/routing/PreviewRouter.js | 9 +- .../services/routing/StaticPagesRouter.js | 24 +- .../server/services/routing/TaxonomyRouter.js | 12 +- core/server/services/routing/bootstrap.js | 14 +- .../resource-config.js => config/v0.1.js} | 22 +- core/server/services/routing/config/v2.js | 54 + .../services/routing/controllers/preview.js | 4 +- .../services/routing/controllers/static.js | 4 +- .../services/routing/helpers/entry-lookup.js | 5 +- .../services/routing/helpers/fetch-data.js | 6 +- core/server/services/settings/validate.js | 33 +- core/test/integration/web/site_spec.js | 4454 ++++++++++++----- core/test/unit/apps/amp/router_spec.js | 11 +- .../services/routing/CollectionRouter_spec.js | 38 +- .../services/routing/TaxonomyRouter_spec.js | 4 +- .../routing/controllers/preview_spec.js | 4 +- .../routing/controllers/static_spec.js | 1 + .../routing/helpers/entry-lookup_spec.js | 12 +- .../routing/helpers/fetch-data_spec.js | 2 +- .../unit/services/settings/loader_spec.js | 6 + .../unit/services/settings/validate_spec.js | 1986 +++++--- 23 files changed, 4683 insertions(+), 2035 deletions(-) rename core/server/services/routing/{assets/resource-config.js => config/v0.1.js} (70%) create mode 100644 core/server/services/routing/config/v2.js diff --git a/core/server/apps/amp/lib/router.js b/core/server/apps/amp/lib/router.js index 6424d536fc5a..b7eff69eee3a 100644 --- a/core/server/apps/amp/lib/router.js +++ b/core/server/apps/amp/lib/router.js @@ -64,7 +64,7 @@ function getPostData(req, res, next) { } // @NOTE: amp is not supported for static pages - helpers.entryLookup(urlWithoutSubdirectoryWithoutAmp, {permalinks, query: {resource: 'posts'}}, res.locals) + helpers.entryLookup(urlWithoutSubdirectoryWithoutAmp, {permalinks, query: {controller: 'posts', resource: 'posts'}}, res.locals) .then((result) => { if (result && result.entry) { req.body.post = result.entry; diff --git a/core/server/services/routing/CollectionRouter.js b/core/server/services/routing/CollectionRouter.js index 0cd4b5f3f85e..aa5d8116a4ea 100644 --- a/core/server/services/routing/CollectionRouter.js +++ b/core/server/services/routing/CollectionRouter.js @@ -8,9 +8,11 @@ const middlewares = require('./middlewares'); const RSSRouter = require('./RSSRouter'); class CollectionRouter extends ParentRouter { - constructor(mainRoute, object) { + constructor(mainRoute, object, RESOURCE_CONFIG) { super('CollectionRouter'); + this.RESOURCE_CONFIG = RESOURCE_CONFIG.QUERY.post; + this.routerName = mainRoute === '/' ? 'index' : mainRoute.replace(/\//g, ''); // NOTE: index/parent route e.g. /, /podcast/, /magic/ ;) @@ -94,10 +96,7 @@ class CollectionRouter extends ParentRouter { order: this.order, permalinks: this.permalinks.getValue({withUrlOptions: true}), resourceType: this.getResourceType(), - query: { - alias: 'posts', - resource: 'posts' - }, + query: this.RESOURCE_CONFIG, context: this.context, frontPageTemplate: 'home', templates: this.templates, @@ -142,7 +141,7 @@ class CollectionRouter extends ParentRouter { } getResourceType() { - return 'posts'; + return this.RESOURCE_CONFIG.resourceAlias || this.RESOURCE_CONFIG.resource; } getRoute(options) { diff --git a/core/server/services/routing/PreviewRouter.js b/core/server/services/routing/PreviewRouter.js index 2214009f9e22..d1bbbf5c4058 100644 --- a/core/server/services/routing/PreviewRouter.js +++ b/core/server/services/routing/PreviewRouter.js @@ -3,9 +3,11 @@ const urlService = require('../url'); const controllers = require('./controllers'); class PreviewRouter extends ParentRouter { - constructor() { + constructor(RESOURCE_CONFIG) { super('PreviewRouter'); + this.RESOURCE_CONFIG = RESOURCE_CONFIG.QUERY.preview; + this.route = {value: '/p/'}; this._registerRoutes(); @@ -20,10 +22,7 @@ class PreviewRouter extends ParentRouter { _prepareContext(req, res, next) { res.routerOptions = { type: 'entry', - query: { - alias: 'preview', - resource: 'posts' - } + query: this.RESOURCE_CONFIG }; next(); diff --git a/core/server/services/routing/StaticPagesRouter.js b/core/server/services/routing/StaticPagesRouter.js index 08ef58ac7d49..594699286c00 100644 --- a/core/server/services/routing/StaticPagesRouter.js +++ b/core/server/services/routing/StaticPagesRouter.js @@ -1,17 +1,28 @@ const debug = require('ghost-ignition').debug('services:routing:static-pages-router'); +const urlService = require('../url'); const ParentRouter = require('./ParentRouter'); const controllers = require('./controllers'); const common = require('../../lib/common'); class StaticPagesRouter extends ParentRouter { - constructor() { + constructor(RESOURCE_CONFIG) { super('StaticPagesRouter'); + this.RESOURCE_CONFIG = RESOURCE_CONFIG.QUERY.page; + this.permalinks = { value: '/:slug/' }; - this.permalinks.getValue = () => { + this.permalinks.getValue = (options = {}) => { + options = options || {}; + + // @NOTE: url options are only required when registering urls in express. + // e.g. the UrlService will access the routes and doesn't want to know about possible url options + if (options.withUrlOptions) { + return urlService.utils.urlJoin(this.permalinks.value, '/:options(edit)?/'); + } + return this.permalinks.value; }; @@ -26,7 +37,7 @@ class StaticPagesRouter extends ParentRouter { this.router().param('slug', this._respectDominantRouter.bind(this)); // REGISTER: permalink for static pages - this.mountRoute(this.permalinks.getValue(), controllers.entry); + this.mountRoute(this.permalinks.getValue({withUrlOptions: true}), controllers.entry); common.events.emit('router.created', this); } @@ -35,12 +46,9 @@ class StaticPagesRouter extends ParentRouter { res.routerOptions = { type: 'entry', filter: this.filter, - permalinks: this.permalinks.getValue(), + permalinks: this.permalinks.getValue({withUrlOptions: true}), resourceType: this.getResourceType(), - query: { - alias: 'pages', - resource: 'posts' - }, + query: this.RESOURCE_CONFIG, context: ['page'] }; diff --git a/core/server/services/routing/TaxonomyRouter.js b/core/server/services/routing/TaxonomyRouter.js index 725feb9e3edc..969a19433b05 100644 --- a/core/server/services/routing/TaxonomyRouter.js +++ b/core/server/services/routing/TaxonomyRouter.js @@ -5,13 +5,13 @@ const RSSRouter = require('./RSSRouter'); const urlService = require('../url'); const controllers = require('./controllers'); const middlewares = require('./middlewares'); -const RESOURCE_CONFIG = require('./assets/resource-config'); class TaxonomyRouter extends ParentRouter { - constructor(key, permalinks) { + constructor(key, permalinks, RESOURCE_CONFIG) { super('Taxonomy'); this.taxonomyKey = key; + this.RESOURCE_CONFIG = RESOURCE_CONFIG; this.permalinks = { value: permalinks @@ -53,8 +53,8 @@ class TaxonomyRouter extends ParentRouter { type: 'channel', name: this.taxonomyKey, permalinks: this.permalinks.getValue(), - data: {[this.taxonomyKey]: RESOURCE_CONFIG.QUERY[this.taxonomyKey]}, - filter: RESOURCE_CONFIG.TAXONOMIES[this.taxonomyKey].filter, + data: {[this.taxonomyKey]: this.RESOURCE_CONFIG.QUERY[this.taxonomyKey]}, + filter: this.RESOURCE_CONFIG.TAXONOMIES[this.taxonomyKey].filter, resourceType: this.getResourceType(), context: [this.taxonomyKey], slugTemplate: true, @@ -65,11 +65,11 @@ class TaxonomyRouter extends ParentRouter { } _redirectEditOption(req, res) { - urlService.utils.redirectToAdmin(302, res, RESOURCE_CONFIG.TAXONOMIES[this.taxonomyKey].editRedirect.replace(':slug', req.params.slug)); + urlService.utils.redirectToAdmin(302, res, this.RESOURCE_CONFIG.TAXONOMIES[this.taxonomyKey].editRedirect.replace(':slug', req.params.slug)); } getResourceType() { - return RESOURCE_CONFIG.QUERY[this.taxonomyKey].alias; + return this.RESOURCE_CONFIG.TAXONOMIES[this.taxonomyKey].resource; } getRoute() { diff --git a/core/server/services/routing/bootstrap.js b/core/server/services/routing/bootstrap.js index 876d32744c63..de2435b10120 100644 --- a/core/server/services/routing/bootstrap.js +++ b/core/server/services/routing/bootstrap.js @@ -2,6 +2,7 @@ const debug = require('ghost-ignition').debug('services:routing:bootstrap'); const _ = require('lodash'); const common = require('../../lib/common'); const settingsService = require('../settings'); +const themeService = require('../themes'); const StaticRoutesRouter = require('./StaticRoutesRouter'); const StaticPagesRouter = require('./StaticPagesRouter'); const CollectionRouter = require('./CollectionRouter'); @@ -37,7 +38,10 @@ module.exports.init = (options = {start: false}) => { * - is the PreviewRouter an app? */ module.exports.start = () => { - const previewRouter = new PreviewRouter(); + const apiVersion = themeService.getActive().engine('ghost-api'); + const RESOURCE_CONFIG = require(`../../services/routing/config/${apiVersion}`); + + const previewRouter = new PreviewRouter(RESOURCE_CONFIG); siteRouter.mountRouter(previewRouter.router()); registry.setRouter('previewRouter', previewRouter); @@ -45,26 +49,26 @@ module.exports.start = () => { const dynamicRoutes = settingsService.get('routes'); _.each(dynamicRoutes.routes, (value, key) => { - const staticRoutesRouter = new StaticRoutesRouter(key, value); + const staticRoutesRouter = new StaticRoutesRouter(key, value, RESOURCE_CONFIG); siteRouter.mountRouter(staticRoutesRouter.router()); registry.setRouter(staticRoutesRouter.identifier, staticRoutesRouter); }); _.each(dynamicRoutes.taxonomies, (value, key) => { - const taxonomyRouter = new TaxonomyRouter(key, value); + const taxonomyRouter = new TaxonomyRouter(key, value, RESOURCE_CONFIG); siteRouter.mountRouter(taxonomyRouter.router()); registry.setRouter(taxonomyRouter.identifier, taxonomyRouter); }); _.each(dynamicRoutes.collections, (value, key) => { - const collectionRouter = new CollectionRouter(key, value); + const collectionRouter = new CollectionRouter(key, value, RESOURCE_CONFIG); siteRouter.mountRouter(collectionRouter.router()); registry.setRouter(collectionRouter.identifier, collectionRouter); }); - const staticPagesRouter = new StaticPagesRouter(); + const staticPagesRouter = new StaticPagesRouter(RESOURCE_CONFIG); siteRouter.mountRouter(staticPagesRouter.router()); registry.setRouter('staticPagesRouter', staticPagesRouter); diff --git a/core/server/services/routing/assets/resource-config.js b/core/server/services/routing/config/v0.1.js similarity index 70% rename from core/server/services/routing/assets/resource-config.js rename to core/server/services/routing/config/v0.1.js index 96946caa1f5b..3884f8ab51aa 100644 --- a/core/server/services/routing/assets/resource-config.js +++ b/core/server/services/routing/config/v0.1.js @@ -1,7 +1,7 @@ /* eslint-disable */ module.exports.QUERY = { tag: { - alias: 'tags', + controller: 'tags', type: 'read', resource: 'tags', options: { @@ -10,7 +10,8 @@ module.exports.QUERY = { } }, author: { - alias: 'authors', + resourceAlias: 'authors', + controller: 'users', type: 'read', resource: 'users', options: { @@ -19,7 +20,8 @@ module.exports.QUERY = { } }, user: { - alias: 'authors', + resourceAlias: 'authors', + controller: 'users', type: 'read', resource: 'users', options: { @@ -28,7 +30,7 @@ module.exports.QUERY = { } }, post: { - alias: 'posts', + controller: 'posts', type: 'read', resource: 'posts', options: { @@ -38,7 +40,7 @@ module.exports.QUERY = { } }, page: { - alias: 'pages', + controller: 'posts', type: 'read', resource: 'posts', options: { @@ -46,17 +48,23 @@ module.exports.QUERY = { status: 'published', page: 1 } + }, + preview: { + controller: 'posts', + resource: 'posts' } }; module.exports.TAXONOMIES = { tag: { filter: 'tags:\'%s\'+tags.visibility:public', - editRedirect: '#/settings/tags/:slug/' + editRedirect: '#/settings/tags/:slug/', + resource: 'tags' }, author: { filter: 'authors:\'%s\'', - editRedirect: '#/team/:slug/' + editRedirect: '#/team/:slug/', + resource: 'authors' } }; /* eslint-enable */ diff --git a/core/server/services/routing/config/v2.js b/core/server/services/routing/config/v2.js new file mode 100644 index 000000000000..e32708b7ca91 --- /dev/null +++ b/core/server/services/routing/config/v2.js @@ -0,0 +1,54 @@ +/* eslint-disable */ +module.exports.QUERY = { + tag: { + controller: 'tagsPublic', + type: 'read', + resource: 'tags', + options: { + slug: '%s', + visibility: 'public' + } + }, + author: { + controller: 'authors', + type: 'read', + resource: 'authors', + options: { + slug: '%s' + } + }, + post: { + controller: 'posts', + type: 'read', + resource: 'posts', + options: { + slug: '%s' + } + }, + page: { + controller: 'pages', + type: 'read', + resource: 'pages', + options: { + slug: '%s' + } + }, + preview: { + controller: 'preview', + resource: 'preview' + } +}; + +module.exports.TAXONOMIES = { + tag: { + filter: 'tags:\'%s\'+tags.visibility:public', + editRedirect: '#/settings/tags/:slug/', + resource: 'tags' + }, + author: { + filter: 'authors:\'%s\'', + editRedirect: '#/team/:slug/', + resource: 'authors' + } +}; +/* eslint-enable */ diff --git a/core/server/services/routing/controllers/preview.js b/core/server/services/routing/controllers/preview.js index 87fbd9277d5e..b1f0a8f80dcb 100644 --- a/core/server/services/routing/controllers/preview.js +++ b/core/server/services/routing/controllers/preview.js @@ -14,10 +14,10 @@ module.exports = function previewController(req, res, next) { include: 'author,authors,tags' }; - (api[res.routerOptions.query.alias] || api[res.routerOptions.query.resource]) + api[res.routerOptions.query.controller] .read(params) .then(function then(result) { - const post = (result[res.routerOptions.query.alias] || result[res.routerOptions.query.resource])[0]; + const post = result[res.routerOptions.query.resource][0]; if (!post) { return next(); diff --git a/core/server/services/routing/controllers/static.js b/core/server/services/routing/controllers/static.js index 76404417dc49..c3fb08d9f7ff 100644 --- a/core/server/services/routing/controllers/static.js +++ b/core/server/services/routing/controllers/static.js @@ -18,7 +18,7 @@ function processQuery(query, locals) { } // Return a promise for the api query - return (api[query.alias] || api[query.resource])[query.type](query.options); + return api[query.controller][query.type](query.options); } module.exports = function staticController(req, res, next) { @@ -41,7 +41,7 @@ module.exports = function staticController(req, res, next) { if (config.type === 'browse') { response.data[name] = result[name]; } else { - response.data[name] = result[name][config.alias] || result[name][config.resource]; + response.data[name] = result[name][config.resource]; } }); } diff --git a/core/server/services/routing/helpers/entry-lookup.js b/core/server/services/routing/helpers/entry-lookup.js index 0b2c38290f33..4667e41d0c6a 100644 --- a/core/server/services/routing/helpers/entry-lookup.js +++ b/core/server/services/routing/helpers/entry-lookup.js @@ -10,7 +10,6 @@ function entryLookup(postUrl, routerOptions, locals) { const api = require('../../../api')[locals.apiVersion]; const targetPath = url.parse(postUrl).path; const permalinks = routerOptions.permalinks; - let isEditURL = false; // CASE: e.g. /:slug/ -> { slug: 'value' } @@ -36,10 +35,10 @@ function entryLookup(postUrl, routerOptions, locals) { * Query database to find entry. * @deprecated: `author`, will be removed in Ghost 3.0 */ - return (api[routerOptions.query.alias] || api[routerOptions.query.resource]) + return api[routerOptions.query.controller] .read(_.extend(_.pick(params, 'slug', 'id'), {include: 'author,authors,tags'})) .then(function then(result) { - const entry = (result[routerOptions.query.alias] || result[routerOptions.query.resource])[0]; + const entry = result[routerOptions.query.resource][0]; if (!entry) { return Promise.resolve(); diff --git a/core/server/services/routing/helpers/fetch-data.js b/core/server/services/routing/helpers/fetch-data.js index 67eb8bdd773d..3a30dcf69263 100644 --- a/core/server/services/routing/helpers/fetch-data.js +++ b/core/server/services/routing/helpers/fetch-data.js @@ -9,7 +9,7 @@ const Promise = require('bluebird'); const queryDefaults = { type: 'browse', resource: 'posts', - alias: 'posts', + controller: 'posts', options: {} }; @@ -51,7 +51,7 @@ function processQuery(query, slugParam, locals) { }); // Return a promise for the api query - return (api[query.alias] || api[query.resource])[query.type](query.options); + return api[query.controller][query.type](query.options); } /** @@ -103,7 +103,7 @@ function fetchData(pathOptions, routerOptions, locals) { if (config.type === 'browse') { response.data[name] = results[name]; } else { - response.data[name] = results[name][config.alias] || results[name][config.resource]; + response.data[name] = results[name][config.resource]; } }); } diff --git a/core/server/services/settings/validate.js b/core/server/services/settings/validate.js index 0eb8f9825683..9c40dfda065b 100644 --- a/core/server/services/settings/validate.js +++ b/core/server/services/settings/validate.js @@ -1,7 +1,9 @@ const _ = require('lodash'); +const debug = require('ghost-ignition').debug('services:settings:validate'); const common = require('../../lib/common'); -const RESOURCE_CONFIG = require('../../services/routing/assets/resource-config'); +const themeService = require('../../services/themes'); const _private = {}; +let RESOURCE_CONFIG; _private.validateTemplate = function validateTemplate(object) { // CASE: /about/: about @@ -57,11 +59,14 @@ _private.validateData = function validateData(object) { } longForm.query[options.resourceKey || resourceKey] = {}; - longForm.query[options.resourceKey || resourceKey] = _.cloneDeep(RESOURCE_CONFIG.QUERY[resourceKey]); + longForm.query[options.resourceKey || resourceKey] = _.cloneDeep(_.omit(RESOURCE_CONFIG.QUERY[resourceKey], 'resourceAlias')); // redirect is enabled by default when using the short form longForm.router = { - [RESOURCE_CONFIG.QUERY[resourceKey].alias]: [{slug: slug, redirect: true}] + [RESOURCE_CONFIG.QUERY[resourceKey].resourceAlias || RESOURCE_CONFIG.QUERY[resourceKey].resource]: [{ + slug: slug, + redirect: true + }] }; longForm.query[options.resourceKey || resourceKey].options.slug = slug; @@ -75,7 +80,7 @@ _private.validateData = function validateData(object) { const requiredQueryFields = ['type', 'resource']; const allowedQueryValues = { type: ['read', 'browse'], - resource: _.union(_.map(RESOURCE_CONFIG.QUERY, 'resource'), _.map(RESOURCE_CONFIG.QUERY, 'alias')) + resource: _.map(RESOURCE_CONFIG.QUERY, 'resource') }; const allowedQueryOptions = ['limit', 'order', 'filter', 'include', 'slug', 'visibility', 'status', 'page']; const allowedRouterOptions = ['redirect', 'slug']; @@ -146,30 +151,28 @@ _private.validateData = function validateData(object) { data.query[key][option] = object.data[key][option]; }); - const DEFAULT_RESOURCE = _.find(RESOURCE_CONFIG.QUERY, {alias: data.query[key].resource}) || _.find(RESOURCE_CONFIG.QUERY, {resource: data.query[key].resource}); + const DEFAULT_RESOURCE = _.find(RESOURCE_CONFIG.QUERY, {resource: data.query[key].resource}); - // CASE: you define resource:pages and the alias is "pages". We need to load the internal alias/resource structure, otherwise we break api versions. - data.query[key].alias = DEFAULT_RESOURCE.alias; data.query[key].resource = DEFAULT_RESOURCE.resource; - data.query[key] = _.defaults(data.query[key], _.omit(DEFAULT_RESOURCE, 'options')); + data.query[key] = _.defaults(data.query[key], _.omit(DEFAULT_RESOURCE, ['options', 'resourceAlias'])); data.query[key].options = _.pick(object.data[key], allowedQueryOptions); if (data.query[key].type === 'read') { data.query[key].options = _.defaults(data.query[key].options, DEFAULT_RESOURCE.options); } - if (!data.router.hasOwnProperty(DEFAULT_RESOURCE.alias)) { - data.router[DEFAULT_RESOURCE.alias] = []; + if (!data.router.hasOwnProperty(DEFAULT_RESOURCE.resourceAlias || DEFAULT_RESOURCE.resource)) { + data.router[DEFAULT_RESOURCE.resourceAlias || DEFAULT_RESOURCE.resource] = []; } // CASE: we do not allowed redirects for type browse if (data.query[key].type === 'read') { let entry = _.pick(object.data[key], allowedRouterOptions); entry = _.defaults(entry, defaultRouterOptions); - data.router[DEFAULT_RESOURCE.alias].push(entry); + data.router[DEFAULT_RESOURCE.resourceAlias || DEFAULT_RESOURCE.resource].push(entry); } else { - data.router[DEFAULT_RESOURCE.alias].push(defaultRouterOptions); + data.router[DEFAULT_RESOURCE.resourceAlias || DEFAULT_RESOURCE.resource].push(defaultRouterOptions); } }); @@ -389,6 +392,12 @@ module.exports = function validate(object) { object.taxonomies = {}; } + const apiVersion = themeService.getActive().engine('ghost-api'); + + debug('api version', apiVersion); + + RESOURCE_CONFIG = require(`../../services/routing/config/${apiVersion}`); + object.routes = _private.validateRoutes(object.routes); object.collections = _private.validateCollections(object.collections); object.taxonomies = _private.validateTaxonomies(object.taxonomies); diff --git a/core/test/integration/web/site_spec.js b/core/test/integration/web/site_spec.js index 1e590ed67366..24a1a814fec3 100644 --- a/core/test/integration/web/site_spec.js +++ b/core/test/integration/web/site_spec.js @@ -4,9 +4,8 @@ const should = require('should'), cheerio = require('cheerio'), testUtils = require('../../utils'), configUtils = require('../../utils/configUtils'), - api = require('../../../server/api'), settingsService = require('../../../server/services/settings'), - themeConfig = require('../../../server/services/themes/config'), + themeService = require('../../../server/services/themes'), siteApp = require('../../../server/web/parent-app'), sandbox = sinon.sandbox.create(); @@ -16,364 +15,185 @@ describe('Integration - Web - Site', function () { before(testUtils.teardown); before(testUtils.setup('users:roles', 'posts')); - describe('default routes.yaml', function () { - before(function () { - sandbox.stub(themeConfig, 'create').returns({ - posts_per_page: 2 - }); - - testUtils.integrationTesting.urlService.resetGenerators(); - testUtils.integrationTesting.defaultMocks(sandbox); - testUtils.integrationTesting.overrideGhostConfig(configUtils); - - return testUtils.integrationTesting.initGhost() - .then(function () { - app = siteApp({start: true}); - return testUtils.integrationTesting.urlService.waitTillFinished(); - }); - }); - - beforeEach(function () { - configUtils.set('url', 'http://example.com'); - - sandbox.spy(api.posts, 'browse'); - }); - - afterEach(function () { - api.posts.browse.restore(); - }); - - after(function () { - configUtils.restore(); - sandbox.restore(); - }); - - describe('behaviour: default cases', function () { - it('serve post', function () { - const req = { - secure: true, - method: 'GET', - url: '/html-ipsum/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('post'); - }); - }); - - it('post not found', function () { - const req = { - secure: true, - method: 'GET', - url: '/not-found/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(404); - response.template.should.eql('error-404'); - }); - }); - - it('serve static page', function () { - const req = { - secure: true, - method: 'GET', - url: '/static-page-test/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('page'); - }); - }); - - it('serve author', function () { - const req = { - secure: true, - method: 'GET', - url: '/author/joe-bloggs/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - const $ = cheerio.load(response.body); - - response.statusCode.should.eql(200); - response.template.should.eql('author'); - - $('.author-bio').length.should.equal(1); - }); - }); - - it('serve tag', function () { - const req = { - secure: true, - method: 'GET', - url: '/tag/bacon/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('tag'); - - api.posts.browse.args[0][0].filter.should.eql('tags:\'bacon\'+tags.visibility:public'); - api.posts.browse.args[0][0].page.should.eql(1); - api.posts.browse.args[0][0].limit.should.eql(2); - }); - }); - - it('serve tag rss', function () { - const req = { - secure: true, - method: 'GET', - url: '/tag/bacon/rss/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - }); - }); - - it('serve collection', function () { - const req = { - secure: true, - method: 'GET', - url: '/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - const $ = cheerio.load(response.body); - - response.statusCode.should.eql(200); - response.template.should.eql('index'); - - $('.post-card').length.should.equal(2); - - should.exist(response.res.locals.context); - should.exist(response.res.locals.version); - should.exist(response.res.locals.safeVersion); - should.exist(response.res.locals.safeVersion); - should.exist(response.res.locals.relativeUrl); - should.exist(response.res.locals.secure); - should.exist(response.res.routerOptions); - }); - }); + describe('v0.1', function () { + const api = require('../../../server/api')["v0.1"]; - it('serve collection: page 2', function () { - const req = { - secure: true, - method: 'GET', - url: '/page/2/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - const $ = cheerio.load(response.body); + describe('default routes.yaml', function () { + before(function () { + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + testUtils.integrationTesting.overrideGhostConfig(configUtils); - response.statusCode.should.eql(200); - response.template.should.eql('index'); + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v0.1'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); - $('.post-card').length.should.equal(2); + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); }); }); - it('serve public asset', function () { - const req = { - secure: false, - method: 'GET', - url: '/public/ghost-sdk.js', - host: 'example.com' - }; + beforeEach(function () { + configUtils.set('url', 'http://example.com'); - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - }); + sandbox.spy(api.posts, 'browse'); }); - it('serve theme asset', function () { - //configUtils.set('url', 'https://example.com'); - - const req = { - secure: true, - method: 'GET', - url: '/assets/css/screen.css', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - }); + afterEach(function () { + api.posts.browse.restore(); }); - }); - describe('behaviour: prettify', function () { - it('url without slash', function () { - const req = { - secure: false, - method: 'GET', - url: '/prettify-me', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(301); - response.headers.location.should.eql('/prettify-me/'); - }); + after(function () { + configUtils.restore(); + sandbox.restore(); }); - }); - describe('behaviour: url redirects', function () { - describe('url options', function () { - it('should not redirect /edit/', function () { + describe('behaviour: default cases', function () { + it('serve post', function () { const req = { - secure: false, - host: 'example.com', + secure: true, method: 'GET', - url: '/edit/' + url: '/html-ipsum/', + host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - response.statusCode.should.eql(404); + response.statusCode.should.eql(200); + response.template.should.eql('post'); }); }); - it('should redirect static page /edit/', function () { + it('post not found', function () { const req = { - secure: false, - host: 'example.com', + secure: true, method: 'GET', - url: '/static-page-test/edit/' + url: '/not-found/', + host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - response.statusCode.should.eql(302); + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); }); }); - it('should redirect post /edit/', function () { + it('serve static page', function () { const req = { - secure: false, - host: 'example.com', + secure: true, method: 'GET', - url: '/html-ipsum/edit/' + url: '/static-page-test/', + host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - response.statusCode.should.eql(302); + response.statusCode.should.eql(200); + response.template.should.eql('page'); }); }); - }); - describe('pagination', function () { - it('redirect /page/1/ to /', function () { + it('serve author', function () { const req = { - secure: false, - host: 'example.com', + secure: true, method: 'GET', - url: '/page/1/' + url: '/author/joe-bloggs/', + host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - response.statusCode.should.eql(301); - response.headers.location.should.eql('/'); + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('author'); + + $('.author-bio').length.should.equal(1); }); }); - }); - describe('rss', function () { - it('redirect /feed/ to /rss/', function () { + it('serve tag', function () { const req = { - secure: false, - host: 'example.com', + secure: true, method: 'GET', - url: '/feed/' + url: '/tag/bacon/', + host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - response.statusCode.should.eql(301); - response.headers.location.should.eql('/rss/'); + response.statusCode.should.eql(200); + response.template.should.eql('tag'); + + api.posts.browse.args[0][0].filter.should.eql('tags:\'bacon\'+tags.visibility:public'); + api.posts.browse.args[0][0].page.should.eql(1); + api.posts.browse.args[0][0].limit.should.eql(2); }); }); - it('redirect /rss/1/ to /rss/', function () { + it('serve tag rss', function () { const req = { - secure: false, - host: 'example.com', + secure: true, method: 'GET', - url: '/rss/1/' + url: '/tag/bacon/rss/', + host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - response.statusCode.should.eql(301); - response.headers.location.should.eql('/rss/'); + response.statusCode.should.eql(200); }); }); - }); - - describe('protocol', function () { - it('blog is https, request is http', function () { - configUtils.set('url', 'https://example.com'); + it('serve collection', function () { const req = { - secure: false, - host: 'example.com', + secure: true, method: 'GET', - url: '/html-ipsum' + url: '/', + host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - response.statusCode.should.eql(301); - response.headers.location.should.eql('https://example.com/html-ipsum/'); + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('index'); + + $('.post-card').length.should.equal(2); + + should.exist(response.res.locals.context); + should.exist(response.res.locals.version); + should.exist(response.res.locals.safeVersion); + should.exist(response.res.locals.safeVersion); + should.exist(response.res.locals.relativeUrl); + should.exist(response.res.locals.secure); + should.exist(response.res.routerOptions); }); }); - it('blog is https, request is http, trailing slash exists already', function () { - configUtils.set('url', 'https://example.com'); - + it('serve collection: page 2', function () { const req = { - secure: false, + secure: true, method: 'GET', - url: '/html-ipsum/', + url: '/page/2/', host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - response.statusCode.should.eql(301); - response.headers.location.should.eql('https://example.com/html-ipsum/'); + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('index'); + + $('.post-card').length.should.equal(2); }); }); - }); - - describe('assets', function () { - it('blog is https, request is http', function () { - configUtils.set('url', 'https://example.com'); + it('serve public asset', function () { const req = { secure: false, method: 'GET', @@ -383,986 +203,3204 @@ describe('Integration - Web - Site', function () { return testUtils.mocks.express.invoke(app, req) .then(function (response) { - response.statusCode.should.eql(301); - response.headers.location.should.eql('https://example.com/public/ghost-sdk.js'); + response.statusCode.should.eql(200); }); }); - it('blog is https, request is http', function () { - configUtils.set('url', 'https://example.com'); + it('serve theme asset', function () { + //configUtils.set('url', 'https://example.com'); const req = { - secure: false, + secure: true, method: 'GET', - url: '/favicon.png', + url: '/assets/css/screen.css', host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - response.statusCode.should.eql(301); - response.headers.location.should.eql('https://example.com/favicon.png'); + response.statusCode.should.eql(200); }); }); + }); - it('blog is https, request is http', function () { - configUtils.set('url', 'https://example.com'); - + describe('behaviour: prettify', function () { + it('url without slash', function () { const req = { secure: false, method: 'GET', - url: '/assets/css/main.css', + url: '/prettify-me', host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { response.statusCode.should.eql(301); - response.headers.location.should.eql('https://example.com/assets/css/main.css'); + response.headers.location.should.eql('/prettify-me/'); }); }); }); - }); - }); - describe('extended routes.yaml: collections', function () { - describe('2 collections', function () { - before(function () { - sandbox.stub(settingsService, 'get').returns({ - routes: { - '/': 'home' - }, + describe('behaviour: url redirects', function () { + describe('url options', function () { + it('should not redirect /edit/', function () { + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/edit/' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + }); + }); - collections: { - '/podcast/': { - permalink: '/podcast/:slug/', - filter: 'featured:true' - }, + it('should redirect static page /edit/', function () { + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/static-page-test/edit/' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(302); + }); + }); - '/something/': { - permalink: '/something/:slug/' - } - }, + it('should redirect post /edit/', function () { + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/html-ipsum/edit/' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(302); + }); + }); + }); - taxonomies: { - tag: '/categories/:slug/', - author: '/authors/:slug/' - } + describe('pagination', function () { + it('redirect /page/1/ to /', function () { + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/page/1/' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('/'); + }); + }); }); - testUtils.integrationTesting.urlService.resetGenerators(); - testUtils.integrationTesting.defaultMocks(sandbox); + describe('rss', function () { + it('redirect /feed/ to /rss/', function () { + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/feed/' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('/rss/'); + }); + }); - return testUtils.integrationTesting.initGhost() - .then(function () { - app = siteApp({start: true}); - return testUtils.integrationTesting.urlService.waitTillFinished(); + it('redirect /rss/1/ to /rss/', function () { + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/rss/1/' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('/rss/'); + }); }); - }); + }); - beforeEach(function () { - testUtils.integrationTesting.overrideGhostConfig(configUtils); - }); + describe('protocol', function () { + it('blog is https, request is http', function () { + configUtils.set('url', 'https://example.com'); + + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/html-ipsum' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('https://example.com/html-ipsum/'); + }); + }); - afterEach(function () { - configUtils.restore(); - }); + it('blog is https, request is http, trailing slash exists already', function () { + configUtils.set('url', 'https://example.com'); + + const req = { + secure: false, + method: 'GET', + url: '/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('https://example.com/html-ipsum/'); + }); + }); + }); - after(function () { - sandbox.restore(); - }); + describe('assets', function () { + it('blog is https, request is http', function () { + configUtils.set('url', 'https://example.com'); + + const req = { + secure: false, + method: 'GET', + url: '/public/ghost-sdk.js', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('https://example.com/public/ghost-sdk.js'); + }); + }); - it('serve static route', function () { - const req = { - secure: true, - method: 'GET', - url: '/', - host: 'example.com' - }; + it('blog is https, request is http', function () { + configUtils.set('url', 'https://example.com'); + + const req = { + secure: false, + method: 'GET', + url: '/favicon.png', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('https://example.com/favicon.png'); + }); + }); - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('default'); + it('blog is https, request is http', function () { + configUtils.set('url', 'https://example.com'); + + const req = { + secure: false, + method: 'GET', + url: '/assets/css/main.css', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('https://example.com/assets/css/main.css'); + }); }); + }); }); + }); - it('serve rss', function () { - const req = { - secure: true, - method: 'GET', - url: '/podcast/rss/', - host: 'example.com' - }; + describe('extended routes.yaml: collections', function () { + describe('2 collections', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: { + '/': 'home' + }, - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - }); - }); + collections: { + '/podcast/': { + permalink: '/podcast/:slug/', + filter: 'featured:true' + }, - it('serve post', function () { - const req = { - secure: true, - method: 'GET', - url: '/something/html-ipsum/', - host: 'example.com' - }; + '/something/': { + permalink: '/something/:slug/', + filter: 'featured:false' + } + }, - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('post'); + taxonomies: { + tag: '/categories/:slug/', + author: '/authors/:slug/' + } }); - }); - - it('serve collection: podcast', function () { - const req = { - secure: true, - method: 'GET', - url: '/podcast/', - host: 'example.com' - }; - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - const $ = cheerio.load(response.body); + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); - response.statusCode.should.eql(200); - response.template.should.eql('index'); + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v0.1'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); - $('.post-card').length.should.equal(2); - }); - }); + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); - it('serve collection: something', function () { - const req = { - secure: true, - method: 'GET', - url: '/something/', - host: 'example.com' - }; + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - const $ = cheerio.load(response.body); + afterEach(function () { + configUtils.restore(); + }); - response.statusCode.should.eql(200); - response.template.should.eql('index'); + after(function () { + sandbox.restore(); + }); - $('.post-card').length.should.equal(2); - }); - }); - }); + it('serve static route', function () { + const req = { + secure: true, + method: 'GET', + url: '/', + host: 'example.com' + }; - describe('no collections', function () { - before(function () { - sandbox.stub(settingsService, 'get').returns({ - routes: { - '/test/': 'test' - }, - collections: {}, - taxonomies: {} + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('default'); + }); }); - testUtils.integrationTesting.urlService.resetGenerators(); - testUtils.integrationTesting.defaultMocks(sandbox); + it('serve rss', function () { + const req = { + secure: true, + method: 'GET', + url: '/podcast/rss/', + host: 'example.com' + }; - return testUtils.integrationTesting.initGhost() - .then(function () { - app = siteApp({start: true}); - return testUtils.integrationTesting.urlService.waitTillFinished(); - }); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); - beforeEach(function () { - testUtils.integrationTesting.overrideGhostConfig(configUtils); - }); + it('serve post', function () { + const req = { + secure: true, + method: 'GET', + url: '/something/html-ipsum/', + host: 'example.com' + }; - afterEach(function () { - configUtils.restore(); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('post'); + }); + }); - after(function () { - sandbox.restore(); - }); + it('serve collection: podcast', function () { + const req = { + secure: true, + method: 'GET', + url: '/podcast/', + host: 'example.com' + }; - it('serve route', function () { - const req = { - secure: true, - method: 'GET', - url: '/test/', - host: 'example.com' - }; + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('default'); - }); - }); - }); + response.statusCode.should.eql(200); + response.template.should.eql('index'); - describe('static permalink route', function () { - before(function () { - sandbox.stub(settingsService, 'get').returns({ - routes: {}, + $('.post-card').length.should.equal(2); + }); + }); - collections: { - '/podcast/': { - permalink: '/featured/', - filter: 'featured:true' - }, + it('serve collection: something', function () { + const req = { + secure: true, + method: 'GET', + url: '/something/', + host: 'example.com' + }; - '/': { - permalink: '/:slug/' - } - }, + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); - taxonomies: {} - }); + response.statusCode.should.eql(200); + response.template.should.eql('index'); - testUtils.integrationTesting.urlService.resetGenerators(); - testUtils.integrationTesting.defaultMocks(sandbox); + $('.post-card').length.should.equal(2); + }); + }); + }); - return testUtils.integrationTesting.initGhost() - .then(function () { - app = siteApp({start: true}); - return testUtils.integrationTesting.urlService.waitTillFinished(); + describe('no collections', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: { + '/test/': 'test' + }, + collections: {}, + taxonomies: {} }); - }); - beforeEach(function () { - testUtils.integrationTesting.overrideGhostConfig(configUtils); - }); + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); - afterEach(function () { - configUtils.restore(); - }); + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v0.1'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); - after(function () { - sandbox.restore(); - }); + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); - it('serve post', function () { - const req = { - secure: true, - method: 'GET', - url: '/featured/', - host: 'example.com' - }; + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - // We can't find a post with the slug "featured" - response.statusCode.should.eql(404); - response.template.should.eql('error-404'); - }); - }); + afterEach(function () { + configUtils.restore(); + }); - it('serve post', function () { - const req = { - secure: true, - method: 'GET', - url: '/html-ipsum/', - host: 'example.com' - }; + after(function () { + sandbox.restore(); + }); - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('post'); - }); + it('serve route', function () { + const req = { + secure: true, + method: 'GET', + url: '/test/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('default'); + }); + }); }); - it('serve author', function () { - const req = { - secure: true, - method: 'GET', - url: '/author/joe-bloggs/', - host: 'example.com' - }; + describe('static permalink route', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(404); - response.template.should.eql('error-404'); - }); - }); + collections: { + '/podcast/': { + permalink: '/featured/', + filter: 'featured:true' + }, - it('serve tag', function () { - const req = { - secure: true, - method: 'GET', - url: '/tag/bacon/', - host: 'example.com' - }; + '/': { + permalink: '/:slug/' + } + }, - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(404); - response.template.should.eql('error-404'); + taxonomies: {} }); - }); - }); - describe('primary author permalink', function () { - before(function () { - sandbox.stub(settingsService, 'get').returns({ - routes: {}, + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); - collections: { - '/something/': { - permalink: '/:primary_author/:slug/' - } - }, + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v0.1'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); - taxonomies: {} + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); }); - testUtils.integrationTesting.urlService.resetGenerators(); - testUtils.integrationTesting.defaultMocks(sandbox); + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); - return testUtils.integrationTesting.initGhost() - .then(function () { - app = siteApp({start: true}); - return testUtils.integrationTesting.urlService.waitTillFinished(); - }); - }); + afterEach(function () { + configUtils.restore(); + }); - beforeEach(function () { - testUtils.integrationTesting.overrideGhostConfig(configUtils); - }); + after(function () { + sandbox.restore(); + }); - afterEach(function () { - configUtils.restore(); - }); + it('serve post', function () { + const req = { + secure: true, + method: 'GET', + url: '/featured/', + host: 'example.com' + }; - after(function () { - sandbox.restore(); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + // We can't find a post with the slug "featured" + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); + }); + }); - it('serve post', function () { - const req = { - secure: true, - method: 'GET', - url: '/joe-bloggs/html-ipsum/', - host: 'example.com' - }; + it('serve post', function () { + const req = { + secure: true, + method: 'GET', + url: '/html-ipsum/', + host: 'example.com' + }; - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('post'); - }); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('post'); + }); + }); + + it('serve author', function () { + const req = { + secure: true, + method: 'GET', + url: '/author/joe-bloggs/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); + }); + }); + + it('serve tag', function () { + const req = { + secure: true, + method: 'GET', + url: '/tag/bacon/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); + }); + }); + }); + + describe('primary author permalink', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, + + collections: { + '/something/': { + permalink: '/:primary_author/:slug/' + } + }, + + taxonomies: {} + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v0.1'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve post', function () { + const req = { + secure: true, + method: 'GET', + url: '/joe-bloggs/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('post'); + }); + }); + + it('post without author', function () { + const req = { + secure: true, + method: 'GET', + url: '/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); + }); + }); + + it('page', function () { + const req = { + secure: true, + method: 'GET', + url: '/static-page-test/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('page'); + }); + }); + }); + + describe('primary tag permalink', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, + + collections: { + '/something/': { + permalink: '/something/:primary_tag/:slug/' + } + }, + + taxonomies: {} + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v0.1'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve post', function () { + const req = { + secure: true, + method: 'GET', + url: '/something/kitchen-sink/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('post'); + }); + }); + + it('post without tag', function () { + const req = { + secure: true, + method: 'GET', + url: '/something/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); + }); + }); + + it('post without tag', function () { + const req = { + secure: true, + method: 'GET', + url: '/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); + }); + }); + + it('page', function () { + const req = { + secure: true, + method: 'GET', + url: '/static-page-test/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('page'); + }); + }); + }); + + describe('collection with data key', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, + + collections: { + '/food/': { + permalink: '/food/:slug/', + filter: 'tag:bacon', + data: { + query: { + tag: { + controller: 'tags', + resource: 'tags', + type: 'read', + options: { + slug: 'bacon' + } + } + }, + router: { + tags: [{redirect: true, slug: 'bacon'}] + } + } + }, + '/sport/': { + permalink: '/sport/:slug/', + filter: 'tag:pollo', + data: { + query: { + apollo: { + controller: 'tags', + resource: 'tags', + type: 'read', + options: { + slug: 'pollo' + } + } + }, + router: { + tags: [{redirect: false, slug: 'bacon'}] + } + } + } + }, + + taxonomies: { + tag: '/categories/:slug/', + author: '/authors/:slug/' + } + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v0.1'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve /food/', function () { + const req = { + secure: true, + method: 'GET', + url: '/food/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('index'); + }); + }); + + it('serve bacon tag', function () { + const req = { + secure: true, + method: 'GET', + url: '/categories/bacon/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + }); + }); + + it('serve /sport/', function () { + const req = { + secure: true, + method: 'GET', + url: '/sport/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('index'); + }); + }); + + it('serve pollo tag', function () { + const req = { + secure: true, + method: 'GET', + url: '/categories/pollo/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); + }); + }); + + describe('extended routes.yaml: templates', function () { + describe('default template, no template', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, + + collections: { + '/': { + permalink: '/:slug/', + templates: ['default'] + }, + '/magic/': { + permalink: '/magic/:slug/' + } + } + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v0.1'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve collection', function () { + const req = { + secure: true, + method: 'GET', + url: '/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('default'); + }); + }); + + it('serve second collectiom', function () { + const req = { + secure: true, + method: 'GET', + url: '/magic/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('index'); + }); + }); + }); + + describe('two templates', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, + + collections: { + '/': { + permalink: '/:slug/', + templates: ['something', 'default'] + } + } + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v0.1'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve collection', function () { + const req = { + secure: true, + method: 'GET', + url: '/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('default'); + }); + }); + }); + + describe('home.hbs priority', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, + + collections: { + '/': { + permalink: '/:slug/', + templates: ['something', 'default'] + }, + '/magic/': { + permalink: '/magic/:slug/', + templates: ['something', 'default'] + } + } + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox, {theme: 'test-theme'}); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v0.1'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve collection', function () { + const req = { + secure: true, + method: 'GET', + url: '/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('home'); + }); + }); + + it('serve second page collection: should use index.hbs', function () { + const req = { + secure: true, + method: 'GET', + url: '/magic/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('something'); + }); + }); + }); + }); + + describe('extended routes.yaml: routes', function () { + describe('channels', function () { + before(testUtils.teardown); + before(testUtils.setup('users:roles', 'posts')); + + before(function () { + testUtils.integrationTesting.defaultMocks(sandbox, {theme: 'test-theme-channels'}); + + sandbox.stub(settingsService, 'get').returns({ + routes: { + '/channel1/': { + controller: 'channel', + filter: 'tag:kitchen-sink', + data: { + query: { + tag: { + controller: 'tags', + resource: 'tags', + type: 'read', + options: { + slug: 'kitchen-sink' + } + } + }, + router: { + tags: [{redirect: true, slug: 'kitchen-sink'}] + } + } + }, + + '/channel2/': { + controller: 'channel', + filter: 'tag:bacon', + data: { + query: { + tag: { + controller: 'tags', + resource: 'tags', + type: 'read', + options: { + slug: 'bacon' + } + } + }, + router: { + tags: [{redirect: true, slug: 'bacon'}] + } + }, + templates: ['default'] + }, + + '/channel3/': { + controller: 'channel', + filter: 'author:joe-bloggs', + data: { + query: { + joe: { + controller: 'users', + resource: 'users', + type: 'read', + options: { + slug: 'joe-bloggs', + redirect: false + } + } + }, + router: { + authors: [{redirect: false, slug: 'joe-bloggs'}] + } + } + }, + + '/channel4/': { + controller: 'channel', + filter: 'author:joe-bloggs' + }, + + '/channel5/': { + controller: 'channel', + data: { + query: { + tag: { + controller: 'users', + resource: 'users', + type: 'read', + options: { + slug: 'joe-bloggs', + redirect: false + } + } + }, + router: { + authors: [{redirect: false, slug: 'joe-bloggs'}] + } + } + }, + + '/channel6/': { + controller: 'channel', + data: { + query: { + post: { + controller: 'posts', + resource: 'posts', + type: 'read', + options: { + slug: 'html-ipsum', + redirect: true + } + } + }, + router: { + posts: [{redirect: true, slug: 'html-ipsum'}] + } + } + }, + + '/channel7/': { + controller: 'channel', + data: { + query: { + post: { + controller: 'posts', + resource: 'posts', + type: 'read', + options: { + slug: 'static-page-test', + redirect: true + } + } + }, + router: { + posts: [{redirect: true, slug: 'static-page-test'}] + } + } + } + }, + + collections: { + '/': { + permalink: '/:slug/' + } + }, + + taxonomies: { + tag: '/tag/:slug/', + author: '/author/:slug/' + } + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v0.1'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(10); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve channel 1', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel1/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('index'); + + $('.post-card').length.should.equal(2); + }); + }); + + it('serve channel 1: rss', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel1/rss/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.headers['content-type'].should.eql('text/xml; charset=UTF-8'); + }); + }); + + it('serve channel 2', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel2/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('default'); + + // default tempalte does not list posts + $('.post-card').length.should.equal(0); + }); + }); + + it('serve channel 3', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel3/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('channel3'); + }); + }); + + it('serve channel 4', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel4/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('index'); + + $('.post-card').length.should.equal(4); + }); + }); + + it('serve channel 5', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel5/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('index'); + + $('.post-card').length.should.equal(4); + }); + }); + + it('serve channel 6', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel6/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('index'); + + $('.post-card').length.should.equal(4); + }); + }); + + it('serve channel 7', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel7/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('index'); + + $('.post-card').length.should.equal(4); + }); + }); + + it('serve kitching-sink: redirect', function () { + const req = { + secure: true, + method: 'GET', + url: '/tag/kitchen-sink/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('/channel1/'); + }); + }); + + it('serve html-ipsum: redirect', function () { + const req = { + secure: true, + method: 'GET', + url: '/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('/channel6/'); + }); + }); + + it('serve html-ipsum: redirect', function () { + const req = { + secure: true, + method: 'GET', + url: '/static-page-test/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('/channel7/'); + }); + }); + + it('serve chorizo: no redirect', function () { + const req = { + secure: true, + method: 'GET', + url: '/tag/chorizo/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); + + it('serve joe-bloggs', function () { + const req = { + secure: true, + method: 'GET', + url: '/author/joe-bloggs/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); + }); + }); + + describe('extended routes.yaml (5): rss override', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: { + '/about/': 'about', + '/podcast/rss/': { + templates: ['podcast/rss'], + content_type: 'xml' + }, + '/cooking/': { + controller: 'channel', + rss: false + }, + '/flat/': { + controller: 'channel' + } + }, + + collections: { + '/podcast/': { + permalink: '/:slug/', + filter: 'featured:true', + templates: ['home'], + rss: false + }, + '/music/': { + permalink: '/:slug/', + rss: false + }, + '/': { + permalink: '/:slug/' + } + }, + + taxonomies: {} + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox, {theme: 'test-theme'}); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v0.1'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve /rss/', function () { + const req = { + secure: true, + method: 'GET', + url: '/rss/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); + + it('serve /music/rss/', function () { + const req = { + secure: true, + method: 'GET', + url: '/music/rss/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + }); + }); + + it('serve /cooking/rss/', function () { + const req = { + secure: true, + method: 'GET', + url: '/cooking/rss/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + }); + }); + + it('serve /flat/rss/', function () { + const req = { + secure: true, + method: 'GET', + url: '/flat/rss/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); + + it('serve /podcast/rss/', function () { + const req = { + secure: true, + method: 'GET', + url: '/podcast/rss/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('podcast/rss'); + response.headers['content-type'].should.eql('text/xml; charset=utf-8'); + response.body.match(//g).length.should.eql(2); + }); + }); + + it('serve /podcast/', function () { + const req = { + secure: true, + method: 'GET', + url: '/podcast/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + response.statusCode.should.eql(200); + $('head link')[2].attribs.href.should.eql('https://127.0.0.1:2369/rss/'); + }); + }); + }); + }); + + describe('v2', function () { + let postSpy; + + describe('default routes.yaml', function () { + before(function () { + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + testUtils.integrationTesting.overrideGhostConfig(configUtils); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v2'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + const postsAPI = require('../../../server/api/v2/posts'); + configUtils.set('url', 'http://example.com'); + postSpy = sandbox.spy(postsAPI.browse, 'query'); + }); + + afterEach(function () { + postSpy.restore(); + }); + + after(function () { + configUtils.restore(); + sandbox.restore(); + }); + + describe('behaviour: default cases', function () { + it('serve post', function () { + const req = { + secure: true, + method: 'GET', + url: '/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('post'); + }); + }); + + it('post not found', function () { + const req = { + secure: true, + method: 'GET', + url: '/not-found/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); + }); + }); + + it('serve static page', function () { + const req = { + secure: true, + method: 'GET', + url: '/static-page-test/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('page'); + }); + }); + + it('serve author', function () { + const req = { + secure: true, + method: 'GET', + url: '/author/joe-bloggs/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('author'); + + $('.author-bio').length.should.equal(1); + }); + }); + + it('serve tag', function () { + const req = { + secure: true, + method: 'GET', + url: '/tag/bacon/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('tag'); + + postSpy.args[0][0].options.filter.should.eql('tags:\'bacon\'+page:false'); + postSpy.args[0][0].options.page.should.eql(1); + postSpy.args[0][0].options.limit.should.eql(2); + }); + }); + + it('serve tag rss', function () { + const req = { + secure: true, + method: 'GET', + url: '/tag/bacon/rss/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); + + it('serve collection', function () { + const req = { + secure: true, + method: 'GET', + url: '/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('index'); + + $('.post-card').length.should.equal(2); + + should.exist(response.res.locals.context); + should.exist(response.res.locals.version); + should.exist(response.res.locals.safeVersion); + should.exist(response.res.locals.safeVersion); + should.exist(response.res.locals.relativeUrl); + should.exist(response.res.locals.secure); + should.exist(response.res.routerOptions); + }); + }); + + it('serve collection: page 2', function () { + const req = { + secure: true, + method: 'GET', + url: '/page/2/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('index'); + + $('.post-card').length.should.equal(2); + }); + }); + + it('serve public asset', function () { + const req = { + secure: false, + method: 'GET', + url: '/public/ghost-sdk.js', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); + + it('serve theme asset', function () { + //configUtils.set('url', 'https://example.com'); + + const req = { + secure: true, + method: 'GET', + url: '/assets/css/screen.css', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); + }); + + describe('behaviour: prettify', function () { + it('url without slash', function () { + const req = { + secure: false, + method: 'GET', + url: '/prettify-me', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('/prettify-me/'); + }); + }); + }); + + describe('behaviour: url redirects', function () { + describe('url options', function () { + it('should not redirect /edit/', function () { + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/edit/' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + }); + }); + + it('should redirect static page /edit/', function () { + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/static-page-test/edit/' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(302); + }); + }); + + it('should redirect post /edit/', function () { + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/html-ipsum/edit/' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(302); + }); + }); + }); + + describe('pagination', function () { + it('redirect /page/1/ to /', function () { + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/page/1/' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('/'); + }); + }); + }); + + describe('rss', function () { + it('redirect /feed/ to /rss/', function () { + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/feed/' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('/rss/'); + }); + }); + + it('redirect /rss/1/ to /rss/', function () { + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/rss/1/' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('/rss/'); + }); + }); + }); + + describe('protocol', function () { + it('blog is https, request is http', function () { + configUtils.set('url', 'https://example.com'); + + const req = { + secure: false, + host: 'example.com', + method: 'GET', + url: '/html-ipsum' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('https://example.com/html-ipsum/'); + }); + }); + + it('blog is https, request is http, trailing slash exists already', function () { + configUtils.set('url', 'https://example.com'); + + const req = { + secure: false, + method: 'GET', + url: '/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('https://example.com/html-ipsum/'); + }); + }); + }); + + describe('assets', function () { + it('blog is https, request is http', function () { + configUtils.set('url', 'https://example.com'); + + const req = { + secure: false, + method: 'GET', + url: '/public/ghost-sdk.js', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('https://example.com/public/ghost-sdk.js'); + }); + }); + + it('blog is https, request is http', function () { + configUtils.set('url', 'https://example.com'); + + const req = { + secure: false, + method: 'GET', + url: '/favicon.png', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('https://example.com/favicon.png'); + }); + }); + + it('blog is https, request is http', function () { + configUtils.set('url', 'https://example.com'); + + const req = { + secure: false, + method: 'GET', + url: '/assets/css/main.css', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('https://example.com/assets/css/main.css'); + }); + }); + }); + }); + }); + + describe('extended routes.yaml: collections', function () { + describe('2 collections', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: { + '/': 'home' + }, + + collections: { + '/podcast/': { + permalink: '/podcast/:slug/', + filter: 'featured:true' + }, + + '/something/': { + permalink: '/something/:slug/', + filter: 'featured:false' + } + }, + + taxonomies: { + tag: '/categories/:slug/', + author: '/authors/:slug/' + } + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v2'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve static route', function () { + const req = { + secure: true, + method: 'GET', + url: '/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('default'); + }); + }); + + it('serve rss', function () { + const req = { + secure: true, + method: 'GET', + url: '/podcast/rss/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); + + it('serve post', function () { + const req = { + secure: true, + method: 'GET', + url: '/something/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('post'); + }); + }); + + it('serve collection: podcast', function () { + const req = { + secure: true, + method: 'GET', + url: '/podcast/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('index'); + + $('.post-card').length.should.equal(2); + }); + }); + + it('serve collection: something', function () { + const req = { + secure: true, + method: 'GET', + url: '/something/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + + response.statusCode.should.eql(200); + response.template.should.eql('index'); + + $('.post-card').length.should.equal(2); + }); + }); + }); + + describe('no collections', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: { + '/test/': 'test' + }, + collections: {}, + taxonomies: {} + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v2'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve route', function () { + const req = { + secure: true, + method: 'GET', + url: '/test/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('default'); + }); + }); + }); + + describe('static permalink route', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, + + collections: { + '/podcast/': { + permalink: '/featured/', + filter: 'featured:true' + }, + + '/': { + permalink: '/:slug/' + } + }, + + taxonomies: {} + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v2'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve post', function () { + const req = { + secure: true, + method: 'GET', + url: '/featured/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + // We can't find a post with the slug "featured" + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); + }); + }); + + it('serve post', function () { + const req = { + secure: true, + method: 'GET', + url: '/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('post'); + }); + }); + + it('serve author', function () { + const req = { + secure: true, + method: 'GET', + url: '/author/joe-bloggs/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); + }); + }); + + it('serve tag', function () { + const req = { + secure: true, + method: 'GET', + url: '/tag/bacon/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); + }); + }); + }); + + describe('primary author permalink', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, + + collections: { + '/something/': { + permalink: '/:primary_author/:slug/' + } + }, + + taxonomies: {} + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v2'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve post', function () { + const req = { + secure: true, + method: 'GET', + url: '/joe-bloggs/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('post'); + }); + }); + + it('post without author', function () { + const req = { + secure: true, + method: 'GET', + url: '/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); + }); + }); + + it('page', function () { + const req = { + secure: true, + method: 'GET', + url: '/static-page-test/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('page'); + }); + }); + }); + + describe('primary tag permalink', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, + + collections: { + '/something/': { + permalink: '/something/:primary_tag/:slug/' + } + }, + + taxonomies: {} + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v2'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); - it('post without author', function () { - const req = { - secure: true, - method: 'GET', - url: '/html-ipsum/', - host: 'example.com' - }; + it('serve post', function () { + const req = { + secure: true, + method: 'GET', + url: '/something/kitchen-sink/html-ipsum/', + host: 'example.com' + }; - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(404); - response.template.should.eql('error-404'); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('post'); + }); + }); + + it('post without tag', function () { + const req = { + secure: true, + method: 'GET', + url: '/something/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); + }); + }); + + it('post without tag', function () { + const req = { + secure: true, + method: 'GET', + url: '/html-ipsum/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + response.template.should.eql('error-404'); + }); + }); + + it('page', function () { + const req = { + secure: true, + method: 'GET', + url: '/static-page-test/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('page'); + }); + }); }); - it('page', function () { - const req = { - secure: true, - method: 'GET', - url: '/static-page-test/', - host: 'example.com' - }; + describe('collection with data key', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, + + collections: { + '/food/': { + permalink: '/food/:slug/', + filter: 'tag:bacon+tag:-chorizo', + data: { + query: { + tag: { + controller: 'tagsPublic', + resource: 'tags', + type: 'read', + options: { + slug: 'bacon' + } + } + }, + router: { + tags: [{redirect: true, slug: 'bacon'}] + } + } + }, + '/sport/': { + permalink: '/sport/:slug/', + filter: 'tag:chorizo+tag:-bacon', + data: { + query: { + apollo: { + controller: 'tagsPublic', + resource: 'tags', + type: 'read', + options: { + slug: 'chorizo' + } + } + }, + router: { + tags: [{redirect: false, slug: 'chorizo'}] + } + } + } + }, - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('page'); + taxonomies: { + tag: '/categories/:slug/', + author: '/authors/:slug/' + } }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v2'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve /food/', function () { + const req = { + secure: true, + method: 'GET', + url: '/food/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('index'); + }); + }); + + it('serve bacon tag', function () { + const req = { + secure: true, + method: 'GET', + url: '/categories/bacon/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + }); + }); + + it('serve /sport/', function () { + const req = { + secure: true, + method: 'GET', + url: '/sport/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('index'); + }); + }); + + it('serve chorizo tag', function () { + const req = { + secure: true, + method: 'GET', + url: '/categories/chorizo/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); }); }); - describe('primary tag permalink', function () { - before(function () { - sandbox.stub(settingsService, 'get').returns({ - routes: {}, + describe('extended routes.yaml: templates', function () { + describe('default template, no template', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, - collections: { - '/something/': { - permalink: '/something/:primary_tag/:slug/' + collections: { + '/': { + permalink: '/:slug/', + templates: ['default'] + }, + '/magic/': { + permalink: '/magic/:slug/' + } } - }, + }); - taxonomies: {} + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v2'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); }); - testUtils.integrationTesting.urlService.resetGenerators(); - testUtils.integrationTesting.defaultMocks(sandbox); + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); - return testUtils.integrationTesting.initGhost() - .then(function () { - app = siteApp({start: true}); - return testUtils.integrationTesting.urlService.waitTillFinished(); - }); - }); + afterEach(function () { + configUtils.restore(); + }); - beforeEach(function () { - testUtils.integrationTesting.overrideGhostConfig(configUtils); - }); + after(function () { + sandbox.restore(); + }); - afterEach(function () { - configUtils.restore(); - }); + it('serve collection', function () { + const req = { + secure: true, + method: 'GET', + url: '/', + host: 'example.com' + }; - after(function () { - sandbox.restore(); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('default'); + }); + }); - it('serve post', function () { - const req = { - secure: true, - method: 'GET', - url: '/something/kitchen-sink/html-ipsum/', - host: 'example.com' - }; + it('serve second collectiom', function () { + const req = { + secure: true, + method: 'GET', + url: '/magic/', + host: 'example.com' + }; - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('post'); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('index'); + }); + }); }); - it('post without tag', function () { - const req = { - secure: true, - method: 'GET', - url: '/something/html-ipsum/', - host: 'example.com' - }; + describe('two templates', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(404); - response.template.should.eql('error-404'); + collections: { + '/': { + permalink: '/:slug/', + templates: ['something', 'default'] + } + } }); - }); - it('post without tag', function () { - const req = { - secure: true, - method: 'GET', - url: '/html-ipsum/', - host: 'example.com' - }; + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox); - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(404); - response.template.should.eql('error-404'); - }); + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v2'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve collection', function () { + const req = { + secure: true, + method: 'GET', + url: '/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('default'); + }); + }); }); - it('page', function () { - const req = { - secure: true, - method: 'GET', - url: '/static-page-test/', - host: 'example.com' - }; + describe('home.hbs priority', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: {}, - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('page'); + collections: { + '/': { + permalink: '/:slug/', + templates: ['something', 'default'] + }, + '/magic/': { + permalink: '/magic/:slug/', + templates: ['something', 'default'] + } + } }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox, {theme: 'test-theme'}); + + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v2'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve collection', function () { + const req = { + secure: true, + method: 'GET', + url: '/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('home'); + }); + }); + + it('serve second page collection: should use index.hbs', function () { + const req = { + secure: true, + method: 'GET', + url: '/magic/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('something'); + }); + }); }); }); - describe('collection with data key', function () { - before(function () { - sandbox.stub(settingsService, 'get').returns({ - routes: {}, + describe('extended routes.yaml: routes', function () { + describe('channels', function () { + before(testUtils.teardown); + before(testUtils.setup('users:roles', 'posts')); + + before(function () { + testUtils.integrationTesting.defaultMocks(sandbox, {theme: 'test-theme-channels'}); + + sandbox.stub(settingsService, 'get').returns({ + routes: { + '/channel1/': { + controller: 'channel', + filter: 'tag:kitchen-sink', + data: { + query: { + tag: { + controller: 'tagsPublic', + resource: 'tags', + type: 'read', + options: { + slug: 'kitchen-sink' + } + } + }, + router: { + tags: [{redirect: true, slug: 'kitchen-sink'}] + } + } + }, - collections: { - '/food/': { - permalink: '/food/:slug/', - filter: 'tag:bacon', - data: { - query: { - tag: { - alias: 'tags', - resource: 'tags', - type: 'read', - options: { - slug: 'bacon' + '/channel2/': { + controller: 'channel', + filter: 'tag:bacon', + data: { + query: { + tag: { + controller: 'tagsPublic', + resource: 'tags', + type: 'read', + options: { + slug: 'bacon' + } } + }, + router: { + tags: [{redirect: true, slug: 'bacon'}] } }, - router: { - tags: [{redirect: true, slug: 'bacon'}] + templates: ['default'] + }, + + '/channel3/': { + controller: 'channel', + filter: 'author:joe-bloggs', + data: { + query: { + joe: { + controller: 'authors', + resource: 'authors', + type: 'read', + options: { + slug: 'joe-bloggs', + redirect: false + } + } + }, + router: { + authors: [{redirect: false, slug: 'joe-bloggs'}] + } } - } - }, - '/sport/': { - permalink: '/sport/:slug/', - filter: 'tag:pollo', - data: { - query: { - apollo: { - alias: 'tags', - resource: 'tags', - type: 'read', - options: { - slug: 'pollo' + }, + + '/channel4/': { + controller: 'channel', + filter: 'author:joe-bloggs' + }, + + '/channel5/': { + controller: 'channel', + data: { + query: { + tag: { + controller: 'authors', + resource: 'authors', + type: 'read', + options: { + slug: 'joe-bloggs', + redirect: false + } } + }, + router: { + authors: [{redirect: false, slug: 'joe-bloggs'}] } - }, - router: { - tags: [{redirect: false, slug: 'bacon'}] } - } - } - }, + }, - taxonomies: { - tag: '/categories/:slug/', - author: '/authors/:slug/' - } - }); + '/channel6/': { + controller: 'channel', + data: { + query: { + post: { + controller: 'posts', + resource: 'posts', + type: 'read', + options: { + slug: 'html-ipsum', + redirect: true + } + } + }, + router: { + posts: [{redirect: true, slug: 'html-ipsum'}] + } + } + } + }, - testUtils.integrationTesting.urlService.resetGenerators(); - testUtils.integrationTesting.defaultMocks(sandbox); + collections: { + '/': { + permalink: '/:slug/' + } + }, - return testUtils.integrationTesting.initGhost() - .then(function () { - app = siteApp({start: true}); - return testUtils.integrationTesting.urlService.waitTillFinished(); + taxonomies: { + tag: '/tag/:slug/', + author: '/author/:slug/' + } }); - }); - beforeEach(function () { - testUtils.integrationTesting.overrideGhostConfig(configUtils); - }); - - afterEach(function () { - configUtils.restore(); - }); + testUtils.integrationTesting.urlService.resetGenerators(); - after(function () { - sandbox.restore(); - }); + return testUtils.integrationTesting.initGhost() + .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v2'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(10); - it('serve /food/', function () { - const req = { - secure: true, - method: 'GET', - url: '/food/', - host: 'example.com' - }; + app = siteApp({start: true}); + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('index'); - }); - }); + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); - it('serve bacon tag', function () { - const req = { - secure: true, - method: 'GET', - url: '/categories/bacon/', - host: 'example.com' - }; + afterEach(function () { + configUtils.restore(); + }); - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(301); - }); - }); + after(function () { + sandbox.restore(); + }); - it('serve /sport/', function () { - const req = { - secure: true, - method: 'GET', - url: '/sport/', - host: 'example.com' - }; + it('serve channel 1', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel1/', + host: 'example.com' + }; - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('index'); - }); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); - it('serve pollo tag', function () { - const req = { - secure: true, - method: 'GET', - url: '/categories/pollo/', - host: 'example.com' - }; + response.statusCode.should.eql(200); + response.template.should.eql('index'); - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - }); - }); - }); - }); + $('.post-card').length.should.equal(2); + }); + }); - describe('extended routes.yaml: templates', function () { - describe('default template, no template', function () { - before(function () { - sandbox.stub(settingsService, 'get').returns({ - routes: {}, + it('serve channel 1: rss', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel1/rss/', + host: 'example.com' + }; - collections: { - '/': { - permalink: '/:slug/', - templates: ['default'] - }, - '/magic/': { - permalink: '/magic/:slug/' - } - } + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.headers['content-type'].should.eql('text/xml; charset=UTF-8'); + }); }); - testUtils.integrationTesting.urlService.resetGenerators(); - testUtils.integrationTesting.defaultMocks(sandbox); + it('serve channel 2', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel2/', + host: 'example.com' + }; - return testUtils.integrationTesting.initGhost() - .then(function () { - app = siteApp({start: true}); - return testUtils.integrationTesting.urlService.waitTillFinished(); - }); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); - beforeEach(function () { - testUtils.integrationTesting.overrideGhostConfig(configUtils); - }); + response.statusCode.should.eql(200); + response.template.should.eql('default'); - afterEach(function () { - configUtils.restore(); - }); + // default tempalte does not list posts + $('.post-card').length.should.equal(0); + }); + }); - after(function () { - sandbox.restore(); - }); + it('serve channel 3', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel3/', + host: 'example.com' + }; - it('serve collection', function () { - const req = { - secure: true, - method: 'GET', - url: '/', - host: 'example.com' - }; + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('default'); - }); - }); + response.statusCode.should.eql(200); + response.template.should.eql('channel3'); + }); + }); - it('serve second collectiom', function () { - const req = { - secure: true, - method: 'GET', - url: '/magic/', - host: 'example.com' - }; + it('serve channel 4', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel4/', + host: 'example.com' + }; - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('index'); - }); - }); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); - describe('two templates', function () { - before(function () { - sandbox.stub(settingsService, 'get').returns({ - routes: {}, + response.statusCode.should.eql(200); + response.template.should.eql('index'); - collections: { - '/': { - permalink: '/:slug/', - templates: ['something', 'default'] - } - } + $('.post-card').length.should.equal(4); + }); }); - testUtils.integrationTesting.urlService.resetGenerators(); - testUtils.integrationTesting.defaultMocks(sandbox); - - return testUtils.integrationTesting.initGhost() - .then(function () { - app = siteApp({start: true}); - return testUtils.integrationTesting.urlService.waitTillFinished(); - }); - }); + it('serve channel 5', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel5/', + host: 'example.com' + }; - beforeEach(function () { - testUtils.integrationTesting.overrideGhostConfig(configUtils); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); - afterEach(function () { - configUtils.restore(); - }); + response.statusCode.should.eql(200); + response.template.should.eql('index'); - after(function () { - sandbox.restore(); - }); + $('.post-card').length.should.equal(4); + }); + }); - it('serve collection', function () { - const req = { - secure: true, - method: 'GET', - url: '/', - host: 'example.com' - }; + it('serve channel 6', function () { + const req = { + secure: true, + method: 'GET', + url: '/channel6/', + host: 'example.com' + }; - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('default'); - }); - }); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); - describe('home.hbs priority', function () { - before(function () { - sandbox.stub(settingsService, 'get').returns({ - routes: {}, + response.statusCode.should.eql(200); + response.template.should.eql('index'); - collections: { - '/': { - permalink: '/:slug/', - templates: ['something', 'default'] - }, - '/magic/': { - permalink: '/magic/:slug/', - templates: ['something', 'default'] - } - } + $('.post-card').length.should.equal(4); + }); }); - testUtils.integrationTesting.urlService.resetGenerators(); - testUtils.integrationTesting.defaultMocks(sandbox, {theme: 'test-theme'}); - - return testUtils.integrationTesting.initGhost() - .then(function () { - app = siteApp({start: true}); - return testUtils.integrationTesting.urlService.waitTillFinished(); - }); - }); + it('serve kitching-sink: redirect', function () { + const req = { + secure: true, + method: 'GET', + url: '/tag/kitchen-sink/', + host: 'example.com' + }; - beforeEach(function () { - testUtils.integrationTesting.overrideGhostConfig(configUtils); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('/channel1/'); + }); + }); - afterEach(function () { - configUtils.restore(); - }); + it('serve html-ipsum: redirect', function () { + const req = { + secure: true, + method: 'GET', + url: '/html-ipsum/', + host: 'example.com' + }; - after(function () { - sandbox.restore(); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(301); + response.headers.location.should.eql('/channel6/'); + }); + }); - it('serve collection', function () { - const req = { - secure: true, - method: 'GET', - url: '/', - host: 'example.com' - }; + it('serve chorizo: no redirect', function () { + const req = { + secure: true, + method: 'GET', + url: '/tag/chorizo/', + host: 'example.com' + }; - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('home'); - }); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); - it('serve second page collection: should use index.hbs', function () { - const req = { - secure: true, - method: 'GET', - url: '/magic/', - host: 'example.com' - }; + it('serve joe-bloggs', function () { + const req = { + secure: true, + method: 'GET', + url: '/author/joe-bloggs/', + host: 'example.com' + }; - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('something'); - }); + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); }); }); - }); - - describe('extended routes.yaml: routes', function () { - describe('channels', function () { - before(testUtils.teardown); - before(testUtils.setup('users:roles', 'posts')); + describe('extended routes.yaml (5): rss override', function () { before(function () { - testUtils.integrationTesting.defaultMocks(sandbox, {theme: 'test-theme-channels'}); - sandbox.stub(settingsService, 'get').returns({ routes: { - '/channel1/': { - controller: 'channel', - filter: 'tag:kitchen-sink', - data: { - query: { - tag: { - alias: 'tags', - resource: 'tags', - type: 'read', - options: { - slug: 'kitchen-sink' - } - } - }, - router: { - tags: [{redirect: true, slug: 'kitchen-sink'}] - } - } - }, - - '/channel2/': { - controller: 'channel', - filter: 'tag:bacon', - data: { - query: { - tag: { - alias: 'tags', - resource: 'tags', - type: 'read', - options: { - slug: 'bacon' - } - } - }, - router: { - tags: [{redirect: true, slug: 'bacon'}] - } - }, - templates: ['default'] - }, - - '/channel3/': { - controller: 'channel', - filter: 'author:joe-bloggs', - data: { - query: { - tag: { - alias: 'authors', - resource: 'users', - type: 'read', - options: { - slug: 'joe-bloggs', - redirect: false - } - } - }, - router: { - users: [{redirect: false, slug: 'joe-bloggs'}] - } - } - }, - - '/channel4/': { - controller: 'channel', - filter: 'author:joe-bloggs' + '/about/': 'about', + '/podcast/rss/': { + templates: ['podcast/rss'], + content_type: 'xml' }, - - '/channel5/': { - controller: 'channel', - data: { - query: { - tag: { - alias: 'authors', - resource: 'users', - type: 'read', - options: { - slug: 'joe-bloggs', - redirect: false - } - } - }, - router: { - users: [{redirect: false, slug: 'joe-bloggs'}] - } - } - }, - - '/channel6/': { + '/cooking/': { controller: 'channel', - data: { - query: { - post: { - resource: 'posts', - type: 'read', - options: { - slug: 'html-ipsum', - redirect: true - } - } - }, - router: { - posts: [{redirect: true, slug: 'html-ipsum'}] - } - } + rss: false }, - - '/channel7/': { - controller: 'channel', - data: { - query: { - post: { - resource: 'posts', - type: 'read', - options: { - slug: 'static-page-test', - redirect: true - } - } - }, - router: { - posts: [{redirect: true, slug: 'static-page-test'}] - } - } + '/flat/': { + controller: 'channel' } }, collections: { + '/podcast/': { + permalink: '/:slug/', + filter: 'featured:true', + templates: ['home'], + rss: false + }, + '/music/': { + permalink: '/:slug/', + rss: false + }, '/': { permalink: '/:slug/' } }, - taxonomies: { - tag: '/tag/:slug/', - author: '/author/:slug/' - } + taxonomies: {} }); testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox, {theme: 'test-theme'}); return testUtils.integrationTesting.initGhost() .then(function () { + sandbox.stub(themeService.getActive(), 'engine').withArgs('ghost-api').returns('v2'); + sandbox.stub(themeService.getActive(), 'config').withArgs('posts_per_page').returns(2); + app = siteApp({start: true}); return testUtils.integrationTesting.urlService.waitTillFinished(); }); @@ -1380,374 +3418,94 @@ describe('Integration - Web - Site', function () { sandbox.restore(); }); - it('serve channel 1', function () { - const req = { - secure: true, - method: 'GET', - url: '/channel1/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - const $ = cheerio.load(response.body); - - response.statusCode.should.eql(200); - response.template.should.eql('index'); - - $('.post-card').length.should.equal(2); - }); - }); - - it('serve channel 1: rss', function () { - const req = { - secure: true, - method: 'GET', - url: '/channel1/rss/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.headers['content-type'].should.eql('text/xml; charset=UTF-8'); - }); - }); - - it('serve channel 2', function () { + it('serve /rss/', function () { const req = { secure: true, method: 'GET', - url: '/channel2/', + url: '/rss/', host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - const $ = cheerio.load(response.body); - response.statusCode.should.eql(200); - response.template.should.eql('default'); - - // default tempalte does not list posts - $('.post-card').length.should.equal(0); }); }); - it('serve channel 3', function () { + it('serve /music/rss/', function () { const req = { secure: true, method: 'GET', - url: '/channel3/', + url: '/music/rss/', host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - const $ = cheerio.load(response.body); - - response.statusCode.should.eql(200); - response.template.should.eql('channel3'); + response.statusCode.should.eql(404); }); }); - it('serve channel 4', function () { + it('serve /cooking/rss/', function () { const req = { secure: true, method: 'GET', - url: '/channel4/', + url: '/cooking/rss/', host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - const $ = cheerio.load(response.body); - - response.statusCode.should.eql(200); - response.template.should.eql('index'); - - $('.post-card').length.should.equal(4); + response.statusCode.should.eql(404); }); }); - it('serve channel 5', function () { + it('serve /flat/rss/', function () { const req = { secure: true, method: 'GET', - url: '/channel5/', + url: '/flat/rss/', host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - const $ = cheerio.load(response.body); - response.statusCode.should.eql(200); - response.template.should.eql('index'); - - $('.post-card').length.should.equal(4); }); }); - it('serve channel 6', function () { + it('serve /podcast/rss/', function () { const req = { secure: true, method: 'GET', - url: '/channel6/', + url: '/podcast/rss/', host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { - const $ = cheerio.load(response.body); - response.statusCode.should.eql(200); - response.template.should.eql('index'); - - $('.post-card').length.should.equal(4); + response.template.should.eql('podcast/rss'); + response.headers['content-type'].should.eql('text/xml; charset=utf-8'); + response.body.match(//g).length.should.eql(2); }); }); - it('serve channel 7', function () { + it('serve /podcast/', function () { const req = { secure: true, method: 'GET', - url: '/channel7/', + url: '/podcast/', host: 'example.com' }; return testUtils.mocks.express.invoke(app, req) .then(function (response) { const $ = cheerio.load(response.body); - - response.statusCode.should.eql(200); - response.template.should.eql('index'); - - $('.post-card').length.should.equal(4); - }); - }); - - it('serve kitching-sink: redirect', function () { - const req = { - secure: true, - method: 'GET', - url: '/tag/kitchen-sink/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(301); - response.headers.location.should.eql('/channel1/'); - }); - }); - - it('serve html-ipsum: redirect', function () { - const req = { - secure: true, - method: 'GET', - url: '/html-ipsum/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(301); - response.headers.location.should.eql('/channel6/'); - }); - }); - - it('serve html-ipsum: redirect', function () { - const req = { - secure: true, - method: 'GET', - url: '/static-page-test/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(301); - response.headers.location.should.eql('/channel7/'); - }); - }); - - it('serve chorizo: no redirect', function () { - const req = { - secure: true, - method: 'GET', - url: '/tag/chorizo/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - }); - }); - - it('serve joe-bloggs', function () { - const req = { - secure: true, - method: 'GET', - url: '/author/joe-bloggs/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { response.statusCode.should.eql(200); + $('head link')[2].attribs.href.should.eql('https://127.0.0.1:2369/rss/'); }); }); }); }); - - describe('extended routes.yaml (5): rss override', function () { - before(function () { - sandbox.stub(settingsService, 'get').returns({ - routes: { - '/about/': 'about', - '/podcast/rss/': { - templates: ['podcast/rss'], - content_type: 'xml' - }, - '/cooking/': { - controller: 'channel', - rss: false - }, - '/flat/': { - controller: 'channel' - } - }, - - collections: { - '/podcast/': { - permalink: '/:slug/', - filter: 'featured:true', - templates: ['home'], - rss: false - }, - '/music/': { - permalink: '/:slug/', - rss: false - }, - '/': { - permalink: '/:slug/' - } - }, - - taxonomies: {} - }); - - testUtils.integrationTesting.urlService.resetGenerators(); - testUtils.integrationTesting.defaultMocks(sandbox, {theme: 'test-theme'}); - - return testUtils.integrationTesting.initGhost() - .then(function () { - app = siteApp({start: true}); - return testUtils.integrationTesting.urlService.waitTillFinished(); - }); - }); - - beforeEach(function () { - testUtils.integrationTesting.overrideGhostConfig(configUtils); - }); - - afterEach(function () { - configUtils.restore(); - }); - - after(function () { - sandbox.restore(); - }); - - it('serve /rss/', function () { - const req = { - secure: true, - method: 'GET', - url: '/rss/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - }); - }); - - it('serve /music/rss/', function () { - const req = { - secure: true, - method: 'GET', - url: '/music/rss/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(404); - }); - }); - - it('serve /cooking/rss/', function () { - const req = { - secure: true, - method: 'GET', - url: '/cooking/rss/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(404); - }); - }); - - it('serve /flat/rss/', function () { - const req = { - secure: true, - method: 'GET', - url: '/flat/rss/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - }); - }); - - it('serve /podcast/rss/', function () { - const req = { - secure: true, - method: 'GET', - url: '/podcast/rss/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - response.statusCode.should.eql(200); - response.template.should.eql('podcast/rss'); - response.headers['content-type'].should.eql('text/xml; charset=utf-8'); - response.body.match(//g).length.should.eql(2); - }); - }); - - it('serve /podcast/', function () { - const req = { - secure: true, - method: 'GET', - url: '/podcast/', - host: 'example.com' - }; - - return testUtils.mocks.express.invoke(app, req) - .then(function (response) { - const $ = cheerio.load(response.body); - response.statusCode.should.eql(200); - $('head link')[2].attribs.href.should.eql('https://127.0.0.1:2369/rss/'); - }); - }); - }); }); diff --git a/core/test/unit/apps/amp/router_spec.js b/core/test/unit/apps/amp/router_spec.js index bd7bdef8390d..99104c2e92b4 100644 --- a/core/test/unit/apps/amp/router_spec.js +++ b/core/test/unit/apps/amp/router_spec.js @@ -123,9 +123,10 @@ describe('Unit - apps/amp/lib/router', function () { urlService.getPermalinkByUrl.withArgs('/welcome/').returns('/:slug/'); - helpers.entryLookup.withArgs('/welcome/', {permalinks: '/:slug/', query: {resource: 'posts'}}).resolves({ - entry: post - }); + helpers.entryLookup.withArgs('/welcome/', {permalinks: '/:slug/', query: {controller: 'posts', resource: 'posts'}}) + .resolves({ + entry: post + }); ampController.getPostData(req, res, function () { req.body.post.should.be.eql(post); @@ -139,7 +140,7 @@ describe('Unit - apps/amp/lib/router', function () { urlService.getPermalinkByUrl.withArgs('/welcome/').returns('/:slug/'); - helpers.entryLookup.withArgs('/welcome/', {permalinks: '/:slug/', query: {resource: 'posts'}}).resolves({ + helpers.entryLookup.withArgs('/welcome/', {permalinks: '/:slug/', query: {controller: 'posts', resource: 'posts'}}).resolves({ entry: post }); @@ -154,7 +155,7 @@ describe('Unit - apps/amp/lib/router', function () { urlService.getPermalinkByUrl.withArgs('/welcome/').returns('/:slug/'); - helpers.entryLookup.withArgs('/welcome/', {permalinks: '/:slug/', query: {resource: 'posts'}}) + helpers.entryLookup.withArgs('/welcome/', {permalinks: '/:slug/', query: {controller: 'posts', resource: 'posts'}}) .rejects(new common.errors.NotFoundError()); ampController.getPostData(req, res, function (err) { diff --git a/core/test/unit/services/routing/CollectionRouter_spec.js b/core/test/unit/services/routing/CollectionRouter_spec.js index ff060c7acb17..42874a00d1b7 100644 --- a/core/test/unit/services/routing/CollectionRouter_spec.js +++ b/core/test/unit/services/routing/CollectionRouter_spec.js @@ -5,7 +5,8 @@ const should = require('should'), common = require('../../../../server/lib/common'), controllers = require('../../../../server/services/routing/controllers'), CollectionRouter = require('../../../../server/services/routing/CollectionRouter'), - sandbox = sinon.sandbox.create(); + sandbox = sinon.sandbox.create(), + RESOURCE_CONFIG = {QUERY: {post: {controller: 'posts', resource: 'posts'}}}; describe('UNIT - services/routing/CollectionRouter', function () { let req, res, next; @@ -32,7 +33,7 @@ describe('UNIT - services/routing/CollectionRouter', function () { describe('instantiate', function () { it('default', function () { - const collectionRouter = new CollectionRouter('/', {permalink: '/:slug/'}); + const collectionRouter = new CollectionRouter('/', {permalink: '/:slug/'}, RESOURCE_CONFIG); should.exist(collectionRouter.router); @@ -67,9 +68,9 @@ describe('UNIT - services/routing/CollectionRouter', function () { }); it('router name', function () { - const collectionRouter1 = new CollectionRouter('/', {permalink: '/:slug/'}); - const collectionRouter2 = new CollectionRouter('/podcast/', {permalink: '/:slug/'}); - const collectionRouter3 = new CollectionRouter('/hello/world/', {permalink: '/:slug/'}); + const collectionRouter1 = new CollectionRouter('/', {permalink: '/:slug/'}, RESOURCE_CONFIG); + const collectionRouter2 = new CollectionRouter('/podcast/', {permalink: '/:slug/'}, RESOURCE_CONFIG); + const collectionRouter3 = new CollectionRouter('/hello/world/', {permalink: '/:slug/'}, RESOURCE_CONFIG); collectionRouter1.routerName.should.eql('index'); collectionRouter2.routerName.should.eql('podcast'); @@ -81,7 +82,7 @@ describe('UNIT - services/routing/CollectionRouter', function () { }); it('collection lives under /blog/', function () { - const collectionRouter = new CollectionRouter('/blog/', {permalink: '/blog/:year/:slug/'}); + const collectionRouter = new CollectionRouter('/blog/', {permalink: '/blog/:year/:slug/'}, RESOURCE_CONFIG); should.exist(collectionRouter.router); @@ -115,13 +116,13 @@ describe('UNIT - services/routing/CollectionRouter', function () { }); it('with custom filter', function () { - const collectionRouter = new CollectionRouter('/', {permalink: '/:slug/', filter: 'featured:true'}); + const collectionRouter = new CollectionRouter('/', {permalink: '/:slug/', filter: 'featured:true'}, RESOURCE_CONFIG); collectionRouter.getFilter().should.eql('featured:true'); }); it('with templates', function () { - const collectionRouter = new CollectionRouter('/magic/', {permalink: '/:slug/', templates: ['home', 'index']}); + const collectionRouter = new CollectionRouter('/magic/', {permalink: '/:slug/', templates: ['home', 'index']}, RESOURCE_CONFIG); // they are getting reversed because we unshift the templates in the helper collectionRouter.templates.should.eql(['index', 'home']); @@ -130,7 +131,7 @@ describe('UNIT - services/routing/CollectionRouter', function () { describe('fn: _prepareEntriesContext', function () { it('index collection', function () { - const collectionRouter = new CollectionRouter('/', {permalink: '/:slug/'}); + const collectionRouter = new CollectionRouter('/', {permalink: '/:slug/'}, RESOURCE_CONFIG); collectionRouter._prepareEntriesContext(req, res, next); @@ -139,7 +140,7 @@ describe('UNIT - services/routing/CollectionRouter', function () { type: 'collection', filter: undefined, permalinks: '/:slug/:options(edit)?/', - query: {alias: 'posts', resource: 'posts'}, + query: {controller: 'posts', resource: 'posts'}, frontPageTemplate: 'home', templates: [], identifier: collectionRouter.identifier, @@ -153,7 +154,12 @@ describe('UNIT - services/routing/CollectionRouter', function () { }); it('with templates, with order + limit, no index collection', function () { - const collectionRouter = new CollectionRouter('/magic/', {permalink: '/:slug/', order: 'published asc', limit: 19, templates: ['home', 'index']}); + const collectionRouter = new CollectionRouter('/magic/', { + permalink: '/:slug/', + order: 'published asc', + limit: 19, + templates: ['home', 'index'] + }, RESOURCE_CONFIG); collectionRouter._prepareEntriesContext(req, res, next); @@ -162,7 +168,7 @@ describe('UNIT - services/routing/CollectionRouter', function () { type: 'collection', filter: undefined, permalinks: '/:slug/:options(edit)?/', - query: {alias: 'posts', resource: 'posts'}, + query: {controller: 'posts', resource: 'posts'}, frontPageTemplate: 'home', templates: ['index', 'home'], identifier: collectionRouter.identifier, @@ -179,7 +185,7 @@ describe('UNIT - services/routing/CollectionRouter', function () { describe('timezone changes', function () { describe('no dated permalink', function () { it('default', function () { - const collectionRouter = new CollectionRouter('/magic/', {permalink: '/:slug/'}); + const collectionRouter = new CollectionRouter('/magic/', {permalink: '/:slug/'}, RESOURCE_CONFIG); sandbox.stub(collectionRouter, 'emit'); @@ -192,7 +198,7 @@ describe('UNIT - services/routing/CollectionRouter', function () { }); it('tz has not changed', function () { - const collectionRouter = new CollectionRouter('/magic/', {permalink: '/:slug/'}); + const collectionRouter = new CollectionRouter('/magic/', {permalink: '/:slug/'}, RESOURCE_CONFIG); sandbox.stub(collectionRouter, 'emit'); @@ -207,7 +213,7 @@ describe('UNIT - services/routing/CollectionRouter', function () { describe('with dated permalink', function () { it('default', function () { - const collectionRouter = new CollectionRouter('/magic/', {permalink: '/:year/:slug/'}); + const collectionRouter = new CollectionRouter('/magic/', {permalink: '/:year/:slug/'}, RESOURCE_CONFIG); sandbox.stub(collectionRouter, 'emit'); @@ -220,7 +226,7 @@ describe('UNIT - services/routing/CollectionRouter', function () { }); it('tz has not changed', function () { - const collectionRouter = new CollectionRouter('/magic/', {permalink: '/:year/:slug/'}); + const collectionRouter = new CollectionRouter('/magic/', {permalink: '/:year/:slug/'}, RESOURCE_CONFIG); sandbox.stub(collectionRouter, 'emit'); diff --git a/core/test/unit/services/routing/TaxonomyRouter_spec.js b/core/test/unit/services/routing/TaxonomyRouter_spec.js index 1b19db04a8e7..bbcb05d729dd 100644 --- a/core/test/unit/services/routing/TaxonomyRouter_spec.js +++ b/core/test/unit/services/routing/TaxonomyRouter_spec.js @@ -5,7 +5,7 @@ const should = require('should'), common = require('../../../../server/lib/common'), controllers = require('../../../../server/services/routing/controllers'), TaxonomyRouter = require('../../../../server/services/routing/TaxonomyRouter'), - RESOURCE_CONFIG = require('../../../../server/services/routing/assets/resource-config'), + RESOURCE_CONFIG = require('../../../../server/services/routing/config/v2'), sandbox = sinon.sandbox.create(); describe('UNIT - services/routing/TaxonomyRouter', function () { @@ -63,7 +63,7 @@ describe('UNIT - services/routing/TaxonomyRouter', function () { }); it('fn: _prepareContext', function () { - const taxonomyRouter = new TaxonomyRouter('tag', '/tag/:slug/'); + const taxonomyRouter = new TaxonomyRouter('tag', '/tag/:slug/', RESOURCE_CONFIG); taxonomyRouter._prepareContext(req, res, next); next.calledOnce.should.eql(true); diff --git a/core/test/unit/services/routing/controllers/preview_spec.js b/core/test/unit/services/routing/controllers/preview_spec.js index db44483b17dd..089907dacbe6 100644 --- a/core/test/unit/services/routing/controllers/preview_spec.js +++ b/core/test/unit/services/routing/controllers/preview_spec.js @@ -46,7 +46,7 @@ describe('Unit - services/routing/controllers/preview', function () { res = { routerOptions: { - query: {alias: 'preview', resource: 'posts'} + query: {controller: 'posts', resource: 'posts'} }, locals: { apiVersion: 'v0.1' @@ -165,7 +165,7 @@ describe('Unit - services/routing/controllers/preview', function () { res = { routerOptions: { - query: {alias: 'preview', resource: 'posts'} + query: {controller: 'preview', resource: 'preview'} }, locals: { apiVersion: 'v2' diff --git a/core/test/unit/services/routing/controllers/static_spec.js b/core/test/unit/services/routing/controllers/static_spec.js index ea4c03a3092b..f6bc986e66c0 100644 --- a/core/test/unit/services/routing/controllers/static_spec.js +++ b/core/test/unit/services/routing/controllers/static_spec.js @@ -90,6 +90,7 @@ describe('Unit - services/routing/controllers/static', function () { it('extra data to fetch', function (done) { res.routerOptions.data = { tag: { + controller: 'tags', resource: 'tags', type: 'read', options: { diff --git a/core/test/unit/services/routing/helpers/entry-lookup_spec.js b/core/test/unit/services/routing/helpers/entry-lookup_spec.js index 65baa9a088ae..4b8ab437f74f 100644 --- a/core/test/unit/services/routing/helpers/entry-lookup_spec.js +++ b/core/test/unit/services/routing/helpers/entry-lookup_spec.js @@ -23,7 +23,7 @@ describe('Unit - services/routing/helpers/entry-lookup', function () { describe('static pages', function () { const routerOptions = { permalinks: '/:slug/', - query: {alias: 'pages', resource: 'posts'} + query: {controller: 'posts', resource: 'posts'} }; let pages; @@ -54,7 +54,7 @@ describe('Unit - services/routing/helpers/entry-lookup', function () { describe('Permalinks: /:slug/', function () { const routerOptions = { permalinks: '/:slug/', - query: {alias: 'pages', resource: 'posts'} + query: {controller: 'posts', resource: 'posts'} }; beforeEach(function () { @@ -122,7 +122,7 @@ describe('Unit - services/routing/helpers/entry-lookup', function () { describe('Permalinks: /:year/:month/:day/:slug/', function () { const routerOptions = { permalinks: '/:year/:month/:day/:slug/', - query: {alias: 'pages', resource: 'posts'} + query: {controller: 'posts', resource: 'posts'} }; beforeEach(function () { @@ -194,7 +194,7 @@ describe('Unit - services/routing/helpers/entry-lookup', function () { describe('with url options', function () { const routerOptions = { permalinks: '/:slug/:options(edit)?', - query: {alias: 'pages', resource: 'posts'} + query: {controller: 'posts', resource: 'posts'} }; beforeEach(function () { @@ -274,7 +274,7 @@ describe('Unit - services/routing/helpers/entry-lookup', function () { describe('static pages', function () { const routerOptions = { permalinks: '/:slug/', - query: {alias: 'pages', resource: 'posts'} + query: {controller: 'pages', resource: 'pages'} }; let pages; @@ -325,7 +325,7 @@ describe('Unit - services/routing/helpers/entry-lookup', function () { describe('posts', function () { const routerOptions = { permalinks: '/:slug/', - query: {alias: 'posts', resource: 'posts'} + query: {controller: 'posts', resource: 'posts'} }; let posts; diff --git a/core/test/unit/services/routing/helpers/fetch-data_spec.js b/core/test/unit/services/routing/helpers/fetch-data_spec.js index 74f9c97cf5b0..ec720c05e23e 100644 --- a/core/test/unit/services/routing/helpers/fetch-data_spec.js +++ b/core/test/unit/services/routing/helpers/fetch-data_spec.js @@ -154,7 +154,7 @@ describe('Unit - services/routing/helpers/fetch-data', function () { filter: 'tags:%s', data: { tag: { - alias: 'tags', + controller: 'tags', type: 'read', resource: 'tags', options: {slug: '%s'} diff --git a/core/test/unit/services/settings/loader_spec.js b/core/test/unit/services/settings/loader_spec.js index 6a584ad67d17..c9bdf9c0d06d 100644 --- a/core/test/unit/services/settings/loader_spec.js +++ b/core/test/unit/services/settings/loader_spec.js @@ -29,10 +29,13 @@ describe('UNIT > Settings Service:', function () { }, taxonomies: {tag: '/tag/{slug}/', author: '/author/{slug}/'} }; + let yamlParserStub; + let validateStub; beforeEach(function () { yamlParserStub = sinon.stub(); + validateStub = sinon.stub(); }); it('can find yaml settings file and returns a settings object', function () { @@ -40,7 +43,10 @@ describe('UNIT > Settings Service:', function () { const expectedSettingsFile = path.join(__dirname, '../../../utils/fixtures/settings/goodroutes.yaml'); yamlParserStub.returns(yamlStubFile); + validateStub.returns({routes: {}, collections: {}, taxonomies: {}}); + loadSettings.__set__('yamlParser', yamlParserStub); + loadSettings.__set__('validate', validateStub); const setting = loadSettings('goodroutes'); should.exist(setting); diff --git a/core/test/unit/services/settings/validate_spec.js b/core/test/unit/services/settings/validate_spec.js index 80dfd5d0175c..e709bdbcb1c3 100644 --- a/core/test/unit/services/settings/validate_spec.js +++ b/core/test/unit/services/settings/validate_spec.js @@ -1,782 +1,1578 @@ -const should = require('should'), - common = require('../../../../server/lib/common'), - validate = require('../../../../server/services/settings/validate'); +const should = require('should'); +const sinon = require('sinon'); +const common = require('../../../../server/lib/common'); +const themesService = require('../../../../server/services/themes'); +const validate = require('../../../../server/services/settings/validate'); +const sandbox = sinon.sandbox.create(); should.equal(true, true); describe('UNIT: services/settings/validate', function () { - it('no type definitions / empty yaml file', function () { - const object = validate({}); + let apiVersion; - object.should.eql({collections: {}, routes: {}, taxonomies: {}}); + before(function () { + sandbox.stub(themesService, 'getActive').returns({ + engine: () => { + return apiVersion; + } + }); + }); + + after(function () { + sandbox.restore(); }); - it('throws error when using :\w+ notiation in collection', function () { - try { + describe('v0.1', function () { + before(function () { + apiVersion = 'v0.1'; + }); + + it('no type definitions / empty yaml file', function () { + const object = validate({}); + + object.should.eql({collections: {}, routes: {}, taxonomies: {}}); + }); + + it('throws error when using :\w+ notiation in collection', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/magic/{slug}/' + }, + '/': { + permalink: '/:slug/' + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('throws error when using :\w+ notiation in taxonomies', function () { + try { + validate({ + taxonomies: { + tag: '/categories/:slug/' + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('throws error when using an undefined taxonomy', function () { + try { + validate({ + taxonomies: { + sweet_baked_good: '/patisserie/{slug}' + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('throws error when permalink is missing (collection)', function () { + try { + validate({ + collections: { + permalink: '/{slug}/' + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('throws error without leading or trailing slashes', function () { + try { + validate({ + routes: { + about: 'about' + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('throws error without leading or trailing slashes', function () { + try { + validate({ + routes: { + '/about': 'about' + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('throws error without leading or trailing slashes', function () { + try { + validate({ + routes: { + 'about/': 'about' + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('throws error without leading or trailing slashes', function () { + try { + validate({ + collections: { + 'magic/': { + permalink: '/{slug}/' + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('throws error without leading or trailing slashes', function () { + try { + validate({ + collections: { + magic: { + permalink: '/{slug}/' + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('throws error without leading or trailing slashes', function () { + try { + validate({ + collections: { + '/magic': { + permalink: '/{slug}/' + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('throws error without leading or trailing slashes', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/{slug}' + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('throws error without leading or trailing slashes', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '{slug}' + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('no validation error for routes', function () { validate({ + routes: { + '/': 'home' + } + }); + }); + + it('no validation error for / collection', function () { + validate({ + collections: { + '/': { + permalink: '/{primary_tag}/{slug}/' + } + } + }); + }); + + it('transforms {.*} notation into :\w+', function () { + const object = validate({ collections: { '/magic/': { - permalink: '/magic/{slug}/' + permalink: '/magic/{year}/{slug}/' }, '/': { - permalink: '/:slug/' + permalink: '/{slug}/' } + }, + taxonomies: { + tag: '/tags/{slug}/', + author: '/authors/{slug}/', } }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } - - throw new Error('should fail'); - }); - it('throws error when using :\w+ notiation in taxonomies', function () { - try { - validate({ + object.should.eql({ + routes: {}, taxonomies: { - tag: '/categories/:slug/' + tag: '/tags/:slug/', + author: '/authors/:slug/' + }, + collections: { + '/magic/': { + permalink: '/magic/:year/:slug/', + templates: [] + }, + '/': { + permalink: '/:slug/', + templates: [] + } } }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } + }); - throw new Error('should fail'); - }); + describe('template definitions', function () { + it('single value', function () { + const object = validate({ + routes: { + '/about/': 'about', + '/me/': { + template: 'me' + } + }, + collections: { + '/': { + permalink: '/{slug}/', + template: 'test' + } + } + }); - it('throws error when using an undefined taxonomy', function () { - try { - validate({ - taxonomies: { - sweet_baked_good: '/patisserie/{slug}' + object.should.eql({ + taxonomies: {}, + routes: { + '/about/': { + templates: ['about'] + }, + '/me/': { + templates: ['me'] + } + }, + collections: { + '/': { + permalink: '/:slug/', + templates: ['test'] + } + } + }); + }); + + it('array', function () { + const object = validate({ + routes: { + '/about/': 'about', + '/me/': { + template: ['me'] + } + }, + collections: { + '/': { + permalink: '/{slug}/', + template: ['test'] + } + } + }); + + object.should.eql({ + taxonomies: {}, + routes: { + '/about/': { + templates: ['about'] + }, + '/me/': { + templates: ['me'] + } + }, + collections: { + '/': { + permalink: '/:slug/', + templates: ['test'] + } + } + }); + }); + }); + + describe('data definitions', function () { + it('shortform', function () { + const object = validate({ + routes: { + '/food/': { + data: 'tag.food' + }, + '/music/': { + data: 'tag.music' + }, + '/ghost/': { + data: 'user.ghost' + }, + '/sleep/': { + data: { + bed: 'tag.bed', + dream: 'tag.dream' + } + }, + '/lala/': { + data: 'author.carsten' + } + }, + collections: { + '/more/': { + permalink: '/{slug}/', + data: { + home: 'page.home' + } + }, + '/podcast/': { + permalink: '/podcast/{slug}/', + data: { + something: 'tag.something' + } + }, + '/': { + permalink: '/{slug}/', + data: 'tag.sport' + } + } + }); + + object.should.eql({ + taxonomies: {}, + routes: { + '/food/': { + data: { + query: { + tag: { + controller: 'tags', + resource: 'tags', + type: 'read', + options: { + slug: 'food', + visibility: 'public' + } + } + }, + router: { + tags: [{redirect: true, slug: 'food'}] + } + }, + templates: [] + }, + '/ghost/': { + data: { + query: { + user: { + controller: 'users', + resource: 'users', + type: 'read', + options: { + slug: 'ghost', + visibility: 'public' + } + } + }, + router: { + authors: [{redirect: true, slug: 'ghost'}] + } + }, + templates: [] + }, + '/music/': { + data: { + query: { + tag: { + controller: 'tags', + resource: 'tags', + type: 'read', + options: { + slug: 'music', + visibility: 'public' + } + } + }, + router: { + tags: [{redirect: true, slug: 'music'}] + } + }, + templates: [] + }, + '/sleep/': { + data: { + query: { + bed: { + controller: 'tags', + resource: 'tags', + type: 'read', + options: { + slug: 'bed', + visibility: 'public' + } + }, + dream: { + controller: 'tags', + resource: 'tags', + type: 'read', + options: { + slug: 'dream', + visibility: 'public' + } + } + }, + router: { + tags: [{redirect: true, slug: 'bed'}, {redirect: true, slug: 'dream'}] + } + }, + templates: [] + }, + '/lala/': { + data: { + query: { + author: { + controller: 'users', + resource: 'users', + type: 'read', + options: { + slug: 'carsten', + visibility: 'public' + } + } + }, + router: { + authors: [{redirect: true, slug: 'carsten'}] + } + }, + templates: [] + } + }, + collections: { + '/more/': { + permalink: '/:slug/', + data: { + query: { + home: { + controller: 'posts', + resource: 'posts', + type: 'read', + options: { + page: 1, + slug: 'home', + status: 'published' + } + } + }, + router: { + posts: [{redirect: true, slug: 'home'}] + } + }, + templates: [] + }, + '/podcast/': { + permalink: '/podcast/:slug/', + data: { + query: { + something: { + controller: 'tags', + resource: 'tags', + type: 'read', + options: { + slug: 'something', + visibility: 'public' + } + } + }, + router: { + tags: [{redirect: true, slug: 'something'}] + } + }, + templates: [] + }, + '/': { + permalink: '/:slug/', + data: { + query: { + tag: { + controller: 'tags', + resource: 'tags', + type: 'read', + options: { + slug: 'sport', + visibility: 'public' + } + } + }, + router: { + tags: [{redirect: true, slug: 'sport'}] + } + }, + templates: [] + } + } + }); + }); + + it('longform', function () { + const object = validate({ + routes: { + '/food/': { + data: { + food: { + resource: 'posts', + type: 'browse' + } + } + }, + '/wellness/': { + data: { + posts: { + resource: 'posts', + type: 'read', + redirect: false + } + } + }, + '/partyparty/': { + data: { + people: { + resource: 'users', + type: 'read', + slug: 'djgutelaune', + redirect: true + } + } + } + }, + collections: { + '/yoga/': { + permalink: '/{slug}/', + data: { + gym: { + resource: 'posts', + type: 'read', + slug: 'ups', + status: 'draft' + } + } + }, + } + }); + + object.should.eql({ + taxonomies: {}, + routes: { + '/food/': { + data: { + query: { + food: { + controller: 'posts', + resource: 'posts', + type: 'browse', + options: {} + } + }, + router: { + posts: [{redirect: true}] + } + }, + templates: [] + }, + '/wellness/': { + data: { + query: { + posts: { + controller: 'posts', + resource: 'posts', + type: 'read', + options: { + status: 'published', + slug: '%s', + page: 0 + } + } + }, + router: { + posts: [{redirect: false}] + } + }, + templates: [] + }, + '/partyparty/': { + data: { + query: { + people: { + controller: 'users', + resource: 'users', + type: 'read', + options: { + slug: 'djgutelaune', + visibility: 'public' + } + } + }, + router: { + authors: [{redirect: true, slug: 'djgutelaune'}] + } + }, + templates: [] + } + }, + collections: { + '/yoga/': { + permalink: '/:slug/', + data: { + query: { + gym: { + controller: 'posts', + resource: 'posts', + type: 'read', + options: { + page: 0, + slug: 'ups', + status: 'draft' + } + } + }, + router: { + posts: [{redirect: true, slug: 'ups'}] + } + }, + templates: [] + } + } + }); + }); + + it('errors: data shortform incorrect', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/{slug}/', + data: 'tag:test' + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('errors: data longform resource is missing', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/{slug}/', + data: { + type: 'edit' + } + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('errors: data longform type is missing', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/{slug}/', + data: { + resource: 'subscribers' + } + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } - throw new Error('should fail'); - }); + throw new Error('should fail'); + }); - it('throws error when permalink is missing (collection)', function () { - try { - validate({ - collections: { - permalink: '/{slug}/' + it('errors: data longform name is author', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/{slug}/', + data: { + author: { + resource: 'users' + } + } + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } - throw new Error('should fail'); - }); + throw new Error('should fail'); + }); - it('throws error without leading or trailing slashes', function () { - try { - validate({ - routes: { - about: 'about' + it('errors: data longform does not use a custom name at all', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/{slug}/', + data: { + resource: 'users' + } + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } - throw new Error('should fail'); + throw new Error('should fail'); + }); + }); }); - it('throws error without leading or trailing slashes', function () { - try { - validate({ - routes: { - '/about': 'about' - } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } + describe('v2', function () { + before(function () { + apiVersion = 'v2'; + }); - throw new Error('should fail'); - }); + it('no type definitions / empty yaml file', function () { + const object = validate({}); - it('throws error without leading or trailing slashes', function () { - try { - validate({ - routes: { - 'about/': 'about' - } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } + object.should.eql({collections: {}, routes: {}, taxonomies: {}}); + }); - throw new Error('should fail'); - }); + it('throws error when using :\w+ notiation in collection', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/magic/{slug}/' + }, + '/': { + permalink: '/:slug/' + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } - it('throws error without leading or trailing slashes', function () { - try { - validate({ - collections: { - 'magic/': { - permalink: '/{slug}/' + throw new Error('should fail'); + }); + + it('throws error when using :\w+ notiation in taxonomies', function () { + try { + validate({ + taxonomies: { + tag: '/categories/:slug/' } - } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } - throw new Error('should fail'); - }); + throw new Error('should fail'); + }); - it('throws error without leading or trailing slashes', function () { - try { - validate({ - collections: { - magic: { - permalink: '/{slug}/' + it('throws error when using an undefined taxonomy', function () { + try { + validate({ + taxonomies: { + sweet_baked_good: '/patisserie/{slug}' } - } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } - throw new Error('should fail'); - }); + throw new Error('should fail'); + }); - it('throws error without leading or trailing slashes', function () { - try { - validate({ - collections: { - '/magic': { + it('throws error when permalink is missing (collection)', function () { + try { + validate({ + collections: { permalink: '/{slug}/' } - } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } - throw new Error('should fail'); - }); + throw new Error('should fail'); + }); - it('throws error without leading or trailing slashes', function () { - try { - validate({ - collections: { - '/magic/': { - permalink: '/{slug}' + it('throws error without leading or trailing slashes', function () { + try { + validate({ + routes: { + about: 'about' } - } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } - throw new Error('should fail'); - }); + throw new Error('should fail'); + }); - it('throws error without leading or trailing slashes', function () { - try { - validate({ - collections: { - '/magic/': { - permalink: '{slug}' + it('throws error without leading or trailing slashes', function () { + try { + validate({ + routes: { + '/about': 'about' } - } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } - throw new Error('should fail'); - }); + throw new Error('should fail'); + }); - it('no validation error for routes', function () { - validate({ - routes: { - '/': 'home' + it('throws error without leading or trailing slashes', function () { + try { + validate({ + routes: { + 'about/': 'about' + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; } + + throw new Error('should fail'); }); - }); - it('no validation error for / collection', function () { - validate({ - collections: { - '/': { - permalink: '/{primary_tag}/{slug}/' - } + it('throws error without leading or trailing slashes', function () { + try { + validate({ + collections: { + 'magic/': { + permalink: '/{slug}/' + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; } + + throw new Error('should fail'); }); - }); - it('transforms {.*} notation into :\w+', function () { - const object = validate({ - collections: { - '/magic/': { - permalink: '/magic/{year}/{slug}/' - }, - '/': { - permalink: '/{slug}/' - } - }, - taxonomies: { - tag: '/tags/{slug}/', - author: '/authors/{slug}/', + it('throws error without leading or trailing slashes', function () { + try { + validate({ + collections: { + magic: { + permalink: '/{slug}/' + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; } + + throw new Error('should fail'); }); - object.should.eql({ - routes: {}, - taxonomies: { - tag: '/tags/:slug/', - author: '/authors/:slug/' - }, - collections: { - '/magic/': { - permalink: '/magic/:year/:slug/', - templates: [] - }, - '/': { - permalink: '/:slug/', - templates: [] - } + it('throws error without leading or trailing slashes', function () { + try { + validate({ + collections: { + '/magic': { + permalink: '/{slug}/' + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; } + + throw new Error('should fail'); }); - }); - describe('template definitions', function () { - it('single value', function () { - const object = validate({ - routes: { - '/about/': 'about', - '/me/': { - template: 'me' + it('throws error without leading or trailing slashes', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/{slug}' + } } - }, - collections: { - '/': { - permalink: '/{slug}/', - template: 'test' + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('throws error without leading or trailing slashes', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '{slug}' + } } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } + + throw new Error('should fail'); + }); + + it('no validation error for routes', function () { + validate({ + routes: { + '/': 'home' } }); + }); - object.should.eql({ - taxonomies: {}, - routes: { - '/about/': { - templates: ['about'] - }, - '/me/': { - templates: ['me'] - } - }, + it('no validation error for / collection', function () { + validate({ collections: { '/': { - permalink: '/:slug/', - templates: ['test'] + permalink: '/{primary_tag}/{slug}/' } } }); }); - it('array', function () { + it('transforms {.*} notation into :\w+', function () { const object = validate({ - routes: { - '/about/': 'about', - '/me/': { - template: ['me'] - } - }, collections: { + '/magic/': { + permalink: '/magic/{year}/{slug}/' + }, '/': { - permalink: '/{slug}/', - template: ['test'] + permalink: '/{slug}/' } + }, + taxonomies: { + tag: '/tags/{slug}/', + author: '/authors/{slug}/', } }); object.should.eql({ - taxonomies: {}, - routes: { - '/about/': { - templates: ['about'] - }, - '/me/': { - templates: ['me'] - } + routes: {}, + taxonomies: { + tag: '/tags/:slug/', + author: '/authors/:slug/' }, collections: { + '/magic/': { + permalink: '/magic/:year/:slug/', + templates: [] + }, '/': { permalink: '/:slug/', - templates: ['test'] + templates: [] } } }); }); - }); - describe('data definitions', function () { - it('shortform', function () { - const object = validate({ - routes: { - '/food/': { - data: 'tag.food' - }, - '/music/': { - data: 'tag.music' + describe('template definitions', function () { + it('single value', function () { + const object = validate({ + routes: { + '/about/': 'about', + '/me/': { + template: 'me' + } }, - '/ghost/': { - data: 'user.ghost' + collections: { + '/': { + permalink: '/{slug}/', + template: 'test' + } + } + }); + + object.should.eql({ + taxonomies: {}, + routes: { + '/about/': { + templates: ['about'] + }, + '/me/': { + templates: ['me'] + } }, - '/sleep/': { - data: { - bed: 'tag.bed', - dream: 'tag.dream' + collections: { + '/': { + permalink: '/:slug/', + templates: ['test'] + } + } + }); + }); + + it('array', function () { + const object = validate({ + routes: { + '/about/': 'about', + '/me/': { + template: ['me'] } }, - '/lala/': { - data: 'author.carsten' + collections: { + '/': { + permalink: '/{slug}/', + template: ['test'] + } } - }, - collections: { - '/more/': { - permalink: '/{slug}/', - data: { - home: 'page.home' + }); + + object.should.eql({ + taxonomies: {}, + routes: { + '/about/': { + templates: ['about'] + }, + '/me/': { + templates: ['me'] } }, - '/podcast/': { - permalink: '/podcast/{slug}/', - data: { - something: 'tag.something' + collections: { + '/': { + permalink: '/:slug/', + templates: ['test'] } - }, - '/': { - permalink: '/{slug}/', - data: 'tag.sport' } - } + }); }); + }); - object.should.eql({ - taxonomies: {}, - routes: { - '/food/': { - data: { - query: { - tag: { - alias: 'tags', - resource: 'tags', - type: 'read', - options: { - slug: 'food', - visibility: 'public' - } - } - }, - router: { - tags: [{redirect: true, slug: 'food'}] + describe('data definitions', function () { + it('shortform', function () { + const object = validate({ + routes: { + '/food/': { + data: 'tag.food' + }, + '/music/': { + data: 'tag.music' + }, + '/ghost/': { + data: 'author.ghost' + }, + '/sleep/': { + data: { + bed: 'tag.bed', + dream: 'tag.dream' } }, - templates: [] + '/lala/': { + data: 'author.carsten' + } }, - '/ghost/': { - data: { - query: { - user: { - alias: 'authors', - resource: 'users', - type: 'read', - options: { - slug: 'ghost', - visibility: 'public' + collections: { + '/more/': { + permalink: '/{slug}/', + data: { + home: 'page.home' + } + }, + '/podcast/': { + permalink: '/podcast/{slug}/', + data: { + something: 'tag.something' + } + }, + '/': { + permalink: '/{slug}/', + data: 'tag.sport' + } + } + }); + + object.should.eql({ + taxonomies: {}, + routes: { + '/food/': { + data: { + query: { + tag: { + controller: 'tagsPublic', + resource: 'tags', + type: 'read', + options: { + slug: 'food', + visibility: 'public' + } } + }, + router: { + tags: [{redirect: true, slug: 'food'}] } }, - router: { - authors: [{redirect: true, slug: 'ghost'}] - } + templates: [] }, - templates: [] - }, - '/music/': { - data: { - query: { - tag: { - alias: 'tags', - resource: 'tags', - type: 'read', - options: { - slug: 'music', - visibility: 'public' + '/ghost/': { + data: { + query: { + author: { + controller: 'authors', + resource: 'authors', + type: 'read', + options: { + slug: 'ghost' + } } + }, + router: { + authors: [{redirect: true, slug: 'ghost'}] } }, - router: { - tags: [{redirect: true, slug: 'music'}] - } + templates: [] }, - templates: [] - }, - '/sleep/': { - data: { - query: { - bed: { - alias: 'tags', - resource: 'tags', - type: 'read', - options: { - slug: 'bed', - visibility: 'public' + '/music/': { + data: { + query: { + tag: { + controller: 'tagsPublic', + resource: 'tags', + type: 'read', + options: { + slug: 'music', + visibility: 'public' + } } }, - dream: { - alias: 'tags', - resource: 'tags', - type: 'read', - options: { - slug: 'dream', - visibility: 'public' - } + router: { + tags: [{redirect: true, slug: 'music'}] } }, - router: { - tags: [{redirect: true, slug: 'bed'}, {redirect: true, slug: 'dream'}] - } + templates: [] }, - templates: [] - }, - '/lala/': { - data: { - query: { - author: { - alias: 'authors', - resource: 'users', - type: 'read', - options: { - slug: 'carsten', - visibility: 'public' + '/sleep/': { + data: { + query: { + bed: { + controller: 'tagsPublic', + resource: 'tags', + type: 'read', + options: { + slug: 'bed', + visibility: 'public' + } + }, + dream: { + controller: 'tagsPublic', + resource: 'tags', + type: 'read', + options: { + slug: 'dream', + visibility: 'public' + } } + }, + router: { + tags: [{redirect: true, slug: 'bed'}, {redirect: true, slug: 'dream'}] } }, - router: { - authors: [{redirect: true, slug: 'carsten'}] - } + templates: [] }, - templates: [] - } - }, - collections: { - '/more/': { - permalink: '/:slug/', - data: { - query: { - home: { - alias: 'pages', - resource: 'posts', - type: 'read', - options: { - page: 1, - slug: 'home', - status: 'published' + '/lala/': { + data: { + query: { + author: { + controller: 'authors', + resource: 'authors', + type: 'read', + options: { + slug: 'carsten' + } } + }, + router: { + authors: [{redirect: true, slug: 'carsten'}] } }, - router: { - pages: [{redirect: true, slug: 'home'}] - } - }, - templates: [] + templates: [] + } }, - '/podcast/': { - permalink: '/podcast/:slug/', - data: { - query: { - something: { - alias: 'tags', - resource: 'tags', - type: 'read', - options: { - slug: 'something', - visibility: 'public' + collections: { + '/more/': { + permalink: '/:slug/', + data: { + query: { + home: { + controller: 'pages', + resource: 'pages', + type: 'read', + options: { + slug: 'home' + } } + }, + router: { + pages: [{redirect: true, slug: 'home'}] } }, - router: { - tags: [{redirect: true, slug: 'something'}] - } + templates: [] }, - templates: [] - }, - '/': { - permalink: '/:slug/', - data: { - query: { - tag: { - alias: 'tags', - resource: 'tags', - type: 'read', - options: { - slug: 'sport', - visibility: 'public' + '/podcast/': { + permalink: '/podcast/:slug/', + data: { + query: { + something: { + controller: 'tagsPublic', + resource: 'tags', + type: 'read', + options: { + slug: 'something', + visibility: 'public' + } } + }, + router: { + tags: [{redirect: true, slug: 'something'}] } }, - router: { - tags: [{redirect: true, slug: 'sport'}] - } + templates: [] }, - templates: [] - } - } - }); - }); - - it('longform', function () { - const object = validate({ - routes: { - '/food/': { - data: { - food: { - resource: 'posts', - type: 'browse' - } - } - }, - '/wellness/': { - data: { - posts: { - resource: 'posts', - type: 'read', - redirect: false - } - } - }, - '/partyparty/': { - data: { - people: { - resource: 'users', - type: 'read', - slug: 'djgutelaune', - redirect: true - } + '/': { + permalink: '/:slug/', + data: { + query: { + tag: { + controller: 'tagsPublic', + resource: 'tags', + type: 'read', + options: { + slug: 'sport', + visibility: 'public' + } + } + }, + router: { + tags: [{redirect: true, slug: 'sport'}] + } + }, + templates: [] } } - }, - collections: { - '/yoga/': { - permalink: '/{slug}/', - data: { - gym: { - resource: 'posts', - type: 'read', - slug: 'ups', - status: 'draft' - } - } - }, - } + }); }); - object.should.eql({ - taxonomies: {}, - routes: { - '/food/': { - data: { - query: { + it('longform', function () { + const object = validate({ + routes: { + '/food/': { + data: { food: { - alias: 'posts', resource: 'posts', - type: 'browse', - options: {} + type: 'browse' } - }, - router: { - posts: [{redirect: true}] } }, - templates: [] - }, - '/wellness/': { - data: { - query: { + '/wellness/': { + data: { posts: { - alias: 'posts', resource: 'posts', type: 'read', - options: { - status: 'published', - slug: '%s', - page: 0 - } + redirect: false } - }, - router: { - posts: [{redirect: false}] } }, - templates: [] - }, - '/partyparty/': { - data: { - query: { + '/partyparty/': { + data: { people: { - alias: 'authors', - resource: 'users', + resource: 'authors', type: 'read', - options: { - slug: 'djgutelaune', - visibility: 'public' - } + slug: 'djgutelaune', + redirect: true } - }, - router: { - authors: [{redirect: true, slug: 'djgutelaune'}] } - }, - templates: [] - } - }, - collections: { - '/yoga/': { - permalink: '/:slug/', - data: { - query: { + } + }, + collections: { + '/yoga/': { + permalink: '/{slug}/', + data: { gym: { - alias: 'posts', resource: 'posts', type: 'read', - options: { - page: 0, - slug: 'ups', - status: 'draft' - } + slug: 'ups', + status: 'draft' } - }, - router: { - posts: [{redirect: true, slug: 'ups'}] } }, - templates: [] } - } - }); - }); + }); - it('errors: data shortform incorrect', function () { - try { - validate({ + object.should.eql({ + taxonomies: {}, + routes: { + '/food/': { + data: { + query: { + food: { + controller: 'posts', + resource: 'posts', + type: 'browse', + options: {} + } + }, + router: { + posts: [{redirect: true}] + } + }, + templates: [] + }, + '/wellness/': { + data: { + query: { + posts: { + controller: 'posts', + resource: 'posts', + type: 'read', + options: { + slug: '%s' + } + } + }, + router: { + posts: [{redirect: false}] + } + }, + templates: [] + }, + '/partyparty/': { + data: { + query: { + people: { + controller: 'authors', + resource: 'authors', + type: 'read', + options: { + slug: 'djgutelaune' + } + } + }, + router: { + authors: [{redirect: true, slug: 'djgutelaune'}] + } + }, + templates: [] + } + }, collections: { - '/magic/': { - permalink: '/{slug}/', - data: 'tag:test' + '/yoga/': { + permalink: '/:slug/', + data: { + query: { + gym: { + controller: 'posts', + resource: 'posts', + type: 'read', + options: { + slug: 'ups', + status: 'draft' + } + } + }, + router: { + posts: [{redirect: true, slug: 'ups'}] + } + }, + templates: [] } } }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } + }); - throw new Error('should fail'); - }); + it('errors: data shortform incorrect', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/{slug}/', + data: 'tag:test' + } + } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } - it('errors: data longform resource is missing', function () { - try { - validate({ - collections: { - '/magic/': { - permalink: '/{slug}/', - data: { - type: 'edit' + throw new Error('should fail'); + }); + + it('errors: data longform resource is missing', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/{slug}/', + data: { + type: 'edit' + } } } - } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } - throw new Error('should fail'); - }); + throw new Error('should fail'); + }); - it('errors: data longform type is missing', function () { - try { - validate({ - collections: { - '/magic/': { - permalink: '/{slug}/', - data: { - resource: 'subscribers' + it('errors: data longform type is missing', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/{slug}/', + data: { + resource: 'subscribers' + } } } - } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } - throw new Error('should fail'); - }); + throw new Error('should fail'); + }); - it('errors: data longform name is author', function () { - try { - validate({ - collections: { - '/magic/': { - permalink: '/{slug}/', - data: { - author: { - resource: 'users' + it('errors: data longform name is author', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/{slug}/', + data: { + author: { + resource: 'users' + } } } } - } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } - throw new Error('should fail'); - }); + throw new Error('should fail'); + }); - it('errors: data longform does not use a custom name at all', function () { - try { - validate({ - collections: { - '/magic/': { - permalink: '/{slug}/', - data: { - resource: 'users' + it('errors: data longform does not use a custom name at all', function () { + try { + validate({ + collections: { + '/magic/': { + permalink: '/{slug}/', + data: { + resource: 'users' + } } } - } - }); - } catch (err) { - (err instanceof common.errors.ValidationError).should.be.true(); - return; - } + }); + } catch (err) { + (err instanceof common.errors.ValidationError).should.be.true(); + return; + } - throw new Error('should fail'); + throw new Error('should fail'); + }); }); }); });