diff --git a/core/server/controllers/channel.js b/core/server/controllers/channel.js
index 56a5d3caca3b..f324a458a892 100644
--- a/core/server/controllers/channel.js
+++ b/core/server/controllers/channel.js
@@ -9,7 +9,7 @@ var _ = require('lodash'),
renderChannel = require('./frontend/render-channel');
// This here is a controller.
-// The "route" is handled in controllers/channels/router.js
+// The "route" is handled in services/channels/router.js
// There's both a top-level channelS router, and an individual channel one
module.exports = function channelController(req, res, next) {
// Parse the parameters we need from the URL
diff --git a/core/server/controllers/frontend/context.js b/core/server/controllers/frontend/context.js
index 35964a53f9cc..6b212754e610 100644
--- a/core/server/controllers/frontend/context.js
+++ b/core/server/controllers/frontend/context.js
@@ -18,7 +18,6 @@ var config = require('../../config'),
privatePattern = new RegExp('^\\/' + config.get('routeKeywords').private + '\\/'),
subscribePattern = new RegExp('^\\/' + config.get('routeKeywords').subscribe + '\\/'),
ampPattern = new RegExp('\\/' + config.get('routeKeywords').amp + '\\/$'),
- rssPattern = new RegExp('^\\/rss\\/'),
homePattern = new RegExp('^\\/$');
function setResponseContext(req, res, data) {
@@ -42,11 +41,6 @@ function setResponseContext(req, res, data) {
res.locals.context.push('home');
}
- // This is not currently used, as setRequestContext is not called for RSS feeds
- if (rssPattern.test(res.locals.relativeUrl)) {
- res.locals.context.push('rss');
- }
-
// Add context 'amp' to either post or page, if we have an `*/amp` route
if (ampPattern.test(res.locals.relativeUrl) && data.post) {
res.locals.context.push('amp');
diff --git a/core/server/controllers/rss.js b/core/server/controllers/rss.js
index 01cd4129e39b..6d99b46b064b 100644
--- a/core/server/controllers/rss.js
+++ b/core/server/controllers/rss.js
@@ -1,6 +1,5 @@
var _ = require('lodash'),
url = require('url'),
- utils = require('../utils'),
errors = require('../errors'),
i18n = require('../i18n'),
safeString = require('../utils/index').safeString,
@@ -10,7 +9,7 @@ var _ = require('lodash'),
fetchData = require('./frontend/fetch-data'),
handleError = require('./frontend/error'),
- rssCache = require('../services/rss'),
+ rssService = require('../services/rss'),
generate;
// @TODO: is this the right logic? Where should this live?!
@@ -33,27 +32,25 @@ function getData(channelOpts) {
channelOpts.data = channelOpts.data || {};
return fetchData(channelOpts).then(function formatResult(result) {
- var response = {};
+ var response = _.pick(result, ['posts', 'meta']);
response.title = getTitle(result.data);
response.description = settingsCache.get('description');
- response.results = {
- posts: result.posts,
- meta: result.meta
- };
return response;
});
}
// This here is a controller.
-// The "route" is handled in controllers/channels/router.js
+// The "route" is handled in services/channels/router.js
// We can only generate RSS for channels, so that sorta makes sense, but the location is rubbish
// @TODO finish refactoring this - it's now a controller
generate = function generate(req, res, next) {
// Parse the parameters we need from the URL
var pageParam = req.params.page !== undefined ? req.params.page : 1,
- slugParam = req.params.slug ? safeString(req.params.slug) : undefined;
+ slugParam = req.params.slug ? safeString(req.params.slug) : undefined,
+ // Base URL needs to be the URL for the feed without pagination:
+ baseUrl = getBaseUrlForRSSReq(req.originalUrl, pageParam);
// @TODO: fix this, we shouldn't change the channel object!
// Set page on postOptions for the query made later
@@ -61,31 +58,13 @@ generate = function generate(req, res, next) {
res.locals.channel.slugParam = slugParam;
return getData(res.locals.channel).then(function handleResult(data) {
- // Base URL needs to be the URL for the feed without pagination:
- var baseUrl = getBaseUrlForRSSReq(req.originalUrl, pageParam),
- maxPage = data.results.meta.pagination.pages;
-
- // If page is greater than number of pages we have, redirect to last page
- if (pageParam > maxPage) {
+ // If page is greater than number of pages we have, go straight to 404
+ if (pageParam > data.meta.pagination.pages) {
return next(new errors.NotFoundError({message: i18n.t('errors.errors.pageNotFound')}));
}
- // Renderer begin
- // Format data
- data.version = res.locals.safeVersion;
- data.siteUrl = utils.url.urlFor('home', {secure: req.secure}, true);
- data.feedUrl = utils.url.urlFor({relativeUrl: baseUrl, secure: req.secure}, true);
- data.secure = req.secure;
-
- // No context, no template
- // @TODO: should we have context? The context file expects it!
-
- // Render call - to a different renderer
- // @TODO this is effectively a renderer
- return rssCache.getXML(baseUrl, data).then(function then(feedXml) {
- res.set('Content-Type', 'text/xml; charset=UTF-8');
- res.send(feedXml);
- });
+ // Render call - to a special RSS renderer
+ return rssService.render(res, baseUrl, data);
}).catch(handleError(next));
};
diff --git a/core/server/services/rss/cache.js b/core/server/services/rss/cache.js
index fe215fae7a82..5e1a7bf772be 100644
--- a/core/server/services/rss/cache.js
+++ b/core/server/services/rss/cache.js
@@ -2,15 +2,15 @@ var crypto = require('crypto'),
generateFeed = require('./generate-feed'),
feedCache = {};
-module.exports.getXML = function getFeedXml(path, data) {
+module.exports.getXML = function getFeedXml(baseUrl, data) {
var dataHash = crypto.createHash('md5').update(JSON.stringify(data)).digest('hex');
- if (!feedCache[path] || feedCache[path].hash !== dataHash) {
+ if (!feedCache[baseUrl] || feedCache[baseUrl].hash !== dataHash) {
// We need to regenerate
- feedCache[path] = {
+ feedCache[baseUrl] = {
hash: dataHash,
- xml: generateFeed(data)
+ xml: generateFeed(baseUrl, data)
};
}
- return feedCache[path].xml;
+ return feedCache[baseUrl].xml;
};
diff --git a/core/server/services/rss/generate-feed.js b/core/server/services/rss/generate-feed.js
index bda3ba71a840..89f89042ce68 100644
--- a/core/server/services/rss/generate-feed.js
+++ b/core/server/services/rss/generate-feed.js
@@ -5,6 +5,7 @@ var downsize = require('downsize'),
processUrls = require('../../utils/make-absolute-urls'),
generateFeed,
+ generateItem,
generateTags;
generateTags = function generateTags(data) {
@@ -20,60 +21,77 @@ generateTags = function generateTags(data) {
return [];
};
-generateFeed = function generateFeed(data) {
- var feed = new RSS({
- title: data.title,
- description: data.description,
- generator: 'Ghost ' + data.version,
- feed_url: data.feedUrl,
- site_url: data.siteUrl,
- image_url: utils.url.urlFor({relativeUrl: 'favicon.png'}, true),
- ttl: '60',
- custom_namespaces: {
- content: 'http://purl.org/rss/1.0/modules/content/',
- media: 'http://search.yahoo.com/mrss/'
- }
- });
+generateItem = function generateItem(post, siteUrl, secure) {
+ var itemUrl = utils.url.urlFor('post', {post: post, secure: secure}, true),
+ htmlContent = processUrls(post.html, siteUrl, itemUrl),
+ item = {
+ title: post.title,
+ // @TODO: DRY this up with data/meta/index & other excerpt code
+ description: post.custom_excerpt || post.meta_description || downsize(htmlContent.html(), {words: 50}),
+ guid: post.id,
+ url: itemUrl,
+ date: post.published_at,
+ categories: generateTags(post),
+ author: post.author ? post.author.name : null,
+ custom_elements: []
+ },
+ imageUrl;
- data.results.posts.forEach(function forEach(post) {
- var itemUrl = utils.url.urlFor('post', {post: post, secure: data.secure}, true),
- htmlContent = processUrls(post.html, data.siteUrl, itemUrl),
- item = {
- title: post.title,
- description: post.custom_excerpt || post.meta_description || downsize(htmlContent.html(), {words: 50}),
- guid: post.id,
- url: itemUrl,
- date: post.published_at,
- categories: generateTags(post),
- author: post.author ? post.author.name : null,
- custom_elements: []
- },
- imageUrl;
+ if (post.feature_image) {
+ imageUrl = utils.url.urlFor('image', {image: post.feature_image, secure: secure}, true);
- if (post.feature_image) {
- imageUrl = utils.url.urlFor('image', {image: post.feature_image, secure: data.secure}, true);
-
- // Add a media content tag
- item.custom_elements.push({
- 'media:content': {
- _attr: {
- url: imageUrl,
- medium: 'image'
- }
+ // Add a media content tag
+ item.custom_elements.push({
+ 'media:content': {
+ _attr: {
+ url: imageUrl,
+ medium: 'image'
}
- });
+ }
+ });
- // Also add the image to the content, because not all readers support media:content
- htmlContent('p').first().before('
');
- htmlContent('img').attr('alt', post.title);
+ // Also add the image to the content, because not all readers support media:content
+ htmlContent('p').first().before('
');
+ htmlContent('img').attr('alt', post.title);
+ }
+
+ item.custom_elements.push({
+ 'content:encoded': {
+ _cdata: htmlContent.html()
}
+ });
- item.custom_elements.push({
- 'content:encoded': {
- _cdata: htmlContent.html()
+ return item;
+};
+
+/**
+ * Generate Feed
+ *
+ * Data is an object which contains the res.locals + results from fetching a channel, but without related data.
+ *
+ * @param {string} baseUrl
+ * @param {{title, description, safeVersion, secure, posts}} data
+ */
+generateFeed = function generateFeed(baseUrl, data) {
+ var siteUrl = utils.url.urlFor('home', {secure: data.secure}, true),
+ feedUrl = utils.url.urlFor({relativeUrl: baseUrl, secure: data.secure}, true),
+ feed = new RSS({
+ title: data.title,
+ description: data.description,
+ generator: 'Ghost ' + data.safeVersion,
+ feed_url: feedUrl,
+ site_url: siteUrl,
+ image_url: utils.url.urlFor({relativeUrl: 'favicon.png'}, true),
+ ttl: '60',
+ custom_namespaces: {
+ content: 'http://purl.org/rss/1.0/modules/content/',
+ media: 'http://search.yahoo.com/mrss/'
}
});
+ data.posts.forEach(function forEach(post) {
+ var item = generateItem(post, siteUrl, data.secure);
+
filters.doFilter('rss.item', item, post).then(function then(item) {
feed.item(item);
});
diff --git a/core/server/services/rss/index.js b/core/server/services/rss/index.js
index b7efec6b26c9..4fd2a734a6c7 100644
--- a/core/server/services/rss/index.js
+++ b/core/server/services/rss/index.js
@@ -1 +1 @@
-module.exports = require('./cache');
+module.exports = require('./renderer');
diff --git a/core/server/services/rss/renderer.js b/core/server/services/rss/renderer.js
new file mode 100644
index 000000000000..4be8bd4efd37
--- /dev/null
+++ b/core/server/services/rss/renderer.js
@@ -0,0 +1,15 @@
+var _ = require('lodash'),
+ rssCache = require('./cache');
+
+module.exports.render = function render(res, baseUrl, data) {
+ // Format data - this is the same as what Express does
+ var rssData = _.merge({}, res.locals, data);
+
+ // Fetch RSS from the cache
+ return rssCache
+ .getXML(baseUrl, rssData)
+ .then(function then(feedXml) {
+ res.set('Content-Type', 'text/xml; charset=UTF-8');
+ res.send(feedXml);
+ });
+};
diff --git a/core/test/unit/controllers/frontend/context_spec.js b/core/test/unit/controllers/frontend/context_spec.js
index b7852037926c..585880e7f0a1 100644
--- a/core/test/unit/controllers/frontend/context_spec.js
+++ b/core/test/unit/controllers/frontend/context_spec.js
@@ -410,48 +410,6 @@ describe('Contexts', function () {
});
});
- describe('RSS', function () {
- // NOTE: this works, but is never used in reality, as setResponseContext isn't called
- // for RSS feeds at the moment.
- it('should correctly identify /rss/ as rss', function () {
- // Setup test
- setupContext('/rss/');
-
- // Execute test
- setResponseContext(req, res, data);
-
- // Check context
- should.exist(res.locals.context);
- res.locals.context.should.be.an.Array().with.lengthOf(1);
- res.locals.context[0].should.eql('rss');
- });
-
- it('will not identify /rss/2/ as rss & paged without page param', function () {
- // Setup test by setting relativeUrl
- setupContext('/rss/2/');
-
- // Execute test
- setResponseContext(req, res, data);
-
- // Check context
- should.exist(res.locals.context);
- res.locals.context.should.be.an.Array().with.lengthOf(1);
- res.locals.context[0].should.eql('rss');
- });
-
- it('should correctly identify /rss/2/ as rss & paged with page param', function () {
- // Setup test by setting relativeUrl
- setupContext('/rss/2/', 2);
-
- // Execute test
- setResponseContext(req, res, data);
-
- should.exist(res.locals.context);
- res.locals.context.should.be.an.Array().with.lengthOf(2);
- res.locals.context[0].should.eql('paged');
- res.locals.context[1].should.eql('rss');
- });
- });
describe('AMP', function () {
it('should correctly identify an AMP post', function () {
// Setup test
diff --git a/core/test/unit/controllers/rss_spec.js b/core/test/unit/controllers/rss_spec.js
index 2dd229e79376..af469b8583ca 100644
--- a/core/test/unit/controllers/rss_spec.js
+++ b/core/test/unit/controllers/rss_spec.js
@@ -3,13 +3,10 @@ var should = require('should'),
rewire = require('rewire'),
_ = require('lodash'),
Promise = require('bluebird'),
- testUtils = require('../../utils'),
channelUtils = require('../../utils/channelUtils'),
- api = require('../../../server/api'),
settingsCache = require('../../../server/settings/cache'),
rssController = rewire('../../../server/controllers/rss'),
- rssCache = require('../../../server/services/rss'),
- configUtils = require('../../utils/configUtils'),
+ rssService = require('../../../server/services/rss'),
sandbox = sinon.sandbox.create();
@@ -24,20 +21,7 @@ function failTest(done) {
describe('RSS', function () {
describe('RSS: Controller only', function () {
- var req, res, posts, getDataStub, resetGetData, rssCacheStub;
-
- before(function () {
- posts = _.cloneDeep(testUtils.DataGenerator.forKnex.posts);
- posts = _.filter(posts, function filter(post) {
- return post.status === 'published' && post.page === false;
- });
-
- _.each(posts, function (post, i) {
- post.id = i;
- post.url = '/' + post.slug + '/';
- post.author = {name: 'Joe Bloggs'};
- });
- });
+ var req, res, next, getDataStub, fakeData, resetGetData, rssServiceStub;
beforeEach(function () {
// Minimum setup of req and res
@@ -49,313 +33,259 @@ describe('RSS', function () {
res = {
locals: {
safeVersion: '0.6',
- channel: channelUtils.getTestChannel('index')
- },
- set: sinon.stub(),
- send: sinon.spy()
+ channel: {postOptions: {}}
+ }
};
- // @TODO Get rid of this! - shouldn't be set on the channel
- res.locals.channel.isRSS = true;
+ next = sandbox.stub();
// Overwrite getData
- getDataStub = sandbox.stub();
+ fakeData = {meta: {pagination: {pages: 3}}};
+ getDataStub = sandbox.stub().returns(new Promise.resolve(fakeData));
resetGetData = rssController.__set__('getData', getDataStub);
- rssCacheStub = sandbox.stub(rssCache, 'getXML').returns(new Promise.resolve('dummyxml'));
+ rssServiceStub = sandbox.stub(rssService, 'render').returns(new Promise.resolve());
});
afterEach(function () {
sandbox.restore();
- configUtils.restore();
resetGetData();
});
it('should fetch data and attempt to send XML', function (done) {
- getDataStub.returns(new Promise.resolve({
- results: {meta: {pagination: {pages: 3}}}
- }));
-
- res.send = function (result) {
- result.should.eql('dummyxml');
- res.set.calledOnce.should.be.true();
- res.set.calledWith('Content-Type', 'text/xml; charset=UTF-8').should.be.true();
- getDataStub.calledOnce.should.be.true();
- rssCacheStub.calledOnce.should.be.true();
- rssCacheStub.calledWith('/rss/').should.be.true();
- done();
- };
-
- rssController(req, res, failTest(done));
+ rssController(req, res, next)
+ .then(function () {
+ next.called.should.be.false();
+
+ getDataStub.calledOnce.should.be.true();
+ getDataStub.calledWith(res.locals.channel).should.be.true();
+
+ rssServiceStub.calledOnce.should.be.true();
+ rssServiceStub.firstCall.args[0].should.eql(res);
+ rssServiceStub.firstCall.args[1].should.eql('/rss/');
+ rssServiceStub.firstCall.args[2].should.match(fakeData);
+ done();
+ })
+ .catch(done);
});
it('can handle paginated urls', function (done) {
- getDataStub.returns(new Promise.resolve({
- results: {meta: {pagination: {pages: 3}}}
- }));
-
req.originalUrl = '/rss/2/';
req.params.page = 2;
- res.send = function (result) {
- result.should.eql('dummyxml');
- res.set.calledOnce.should.be.true();
- res.set.calledWith('Content-Type', 'text/xml; charset=UTF-8').should.be.true();
- getDataStub.calledOnce.should.be.true();
- rssCacheStub.calledOnce.should.be.true();
- rssCacheStub.calledWith('/rss/').should.be.true();
- done();
- };
+ rssController(req, res, next)
+ .then(function () {
+ next.called.should.be.false();
- rssController(req, res, failTest(done));
+ getDataStub.calledOnce.should.be.true();
+ getDataStub.calledWith(res.locals.channel).should.be.true();
+
+ rssServiceStub.calledOnce.should.be.true();
+ rssServiceStub.firstCall.args[0].should.eql(res);
+ rssServiceStub.firstCall.args[1].should.eql('/rss/');
+ rssServiceStub.firstCall.args[2].should.match(fakeData);
+ done();
+ })
+ .catch(done);
});
it('can handle paginated urls with subdirectories', function (done) {
- getDataStub.returns(new Promise.resolve({
- results: {meta: {pagination: {pages: 3}}}
- }));
-
req.originalUrl = '/blog/rss/2/';
req.params.page = 2;
- res.send = function (result) {
- result.should.eql('dummyxml');
- res.set.calledOnce.should.be.true();
- res.set.calledWith('Content-Type', 'text/xml; charset=UTF-8').should.be.true();
- getDataStub.calledOnce.should.be.true();
- rssCacheStub.calledOnce.should.be.true();
- rssCacheStub.calledWith('/blog/rss/').should.be.true();
- done();
- };
+ rssController(req, res, next)
+ .then(function () {
+ next.called.should.be.false();
+
+ getDataStub.calledOnce.should.be.true();
+ getDataStub.calledWith(res.locals.channel).should.be.true();
- rssController(req, res, failTest(done));
+ rssServiceStub.calledOnce.should.be.true();
+ rssServiceStub.firstCall.args[0].should.eql(res);
+ rssServiceStub.firstCall.args[1].should.eql('/blog/rss/');
+ rssServiceStub.firstCall.args[2].should.match(fakeData);
+ done();
+ })
+ .catch(done);
});
it('can handle paginated urls for channels', function (done) {
- getDataStub.returns(new Promise.resolve({
- results: {meta: {pagination: {pages: 3}}}
- }));
-
req.originalUrl = '/tags/test/rss/2/';
req.params.page = 2;
req.params.slug = 'test';
- res.send = function (result) {
- result.should.eql('dummyxml');
- res.set.calledOnce.should.be.true();
- res.set.calledWith('Content-Type', 'text/xml; charset=UTF-8').should.be.true();
- getDataStub.calledOnce.should.be.true();
- rssCacheStub.calledOnce.should.be.true();
- rssCacheStub.calledWith('/tags/test/rss/').should.be.true();
- done();
- };
+ rssController(req, res, next)
+ .then(function () {
+ next.called.should.be.false();
+
+ getDataStub.calledOnce.should.be.true();
+ getDataStub.calledWith(res.locals.channel).should.be.true();
- rssController(req, res, failTest(done));
+ rssServiceStub.calledOnce.should.be.true();
+ rssServiceStub.firstCall.args[0].should.eql(res);
+ rssServiceStub.firstCall.args[1].should.eql('/tags/test/rss/');
+ rssServiceStub.firstCall.args[2].should.match(fakeData);
+ done();
+ })
+ .catch(done);
});
it('should call next with 404 if page number too big', function (done) {
- getDataStub.returns(new Promise.resolve({
- results: {meta: {pagination: {pages: 3}}}
- }));
-
req.originalUrl = '/rss/4/';
req.params.page = 4;
- rssController(req, res, function (err) {
- should.exist(err);
- err.statusCode.should.eql(404);
- res.send.called.should.be.false();
- done();
- });
+ rssController(req, res, next)
+ .then(function () {
+ next.called.should.be.true();
+ next.firstCall.args[0].statusCode.should.eql(404);
+
+ getDataStub.calledOnce.should.be.true();
+ getDataStub.calledWith(res.locals.channel).should.be.true();
+
+ rssServiceStub.called.should.be.false();
+ done();
+ })
+ .catch(done);
});
});
// These tests check the RSS feed from controller to result
// @TODO: test only the data generation, once we've refactored to make that easier
- describe('RSS: data generation', function () {
- var apiBrowseStub, apiTagStub, apiUserStub, req, res;
+ describe('RSS: getData / getTitle', function () {
+ var fetchDataStub, resetFetchData, getData;
beforeEach(function () {
- apiBrowseStub = sandbox.stub(api.posts, 'browse', function () {
- return Promise.resolve({posts: [], meta: {pagination: {pages: 3}}});
- });
-
- apiTagStub = sandbox.stub(api.tags, 'read', function () {
- return Promise.resolve({tags: [{name: 'Magic'}]});
- });
-
- apiUserStub = sandbox.stub(api.users, 'read', function () {
- return Promise.resolve({users: [{name: 'Joe Blogs'}]});
- });
-
- req = {
- params: {},
- originalUrl: '/rss/'
- };
-
- res = {
- locals: {
- safeVersion: '0.6'
- },
- set: sinon.stub()
- };
+ fetchDataStub = sandbox.stub();
+ resetFetchData = rssController.__set__('fetchData', fetchDataStub);
+ getData = rssController.__get__('getData');
sandbox.stub(settingsCache, 'get', function (key) {
var obj = {
title: 'Test',
- description: 'Some Text',
- permalinks: '/:slug/'
+ description: 'Some Text'
};
return obj[key];
});
-
- configUtils.set({
- url: 'http://my-ghost-blog.com'
- });
});
afterEach(function () {
sandbox.restore();
- configUtils.restore();
+ resetFetchData();
});
it('should process the data correctly for the index feed', function (done) {
- // setup
- req.originalUrl = '/rss/';
- res.locals.channel = channelUtils.getTestChannel('index');
- res.locals.channel.isRSS = true;
-
- // test
- res.send = function send(xmlData) {
- apiBrowseStub.calledOnce.should.be.true();
- apiBrowseStub.calledWith({
- page: 1,
- include: 'author,tags'
- }).should.be.true();
- apiTagStub.called.should.be.false();
- apiUserStub.called.should.be.false();
- xmlData.should.match(/<\/title>/);
- xmlData.should.match(//);
- done();
- };
-
- rssController(req, res, failTest(done));
- });
-
- it('should process the data correctly for the paginated index feed', function (done) {
- // setup
- req.originalUrl = '/rss/2/';
- req.params.page = '2';
- res.locals.channel = channelUtils.getTestChannel('index');
- res.locals.channel.isRSS = true;
-
- // test
- res.send = function send(xmlData) {
- apiBrowseStub.calledOnce.should.be.true();
- apiBrowseStub.calledWith({
- page: '2',
- include: 'author,tags'
- }).should.be.true();
-
- apiTagStub.called.should.be.false();
- apiUserStub.called.should.be.false();
- xmlData.should.match(/<\/title>/);
- xmlData.should.match(//);
- done();
- };
-
- rssController(req, res, failTest(done));
+ fetchDataStub.returns(new Promise.resolve({posts: [{test: 'hey'}], meta: {foo: 'you'}}));
+
+ var channel = channelUtils.getTestChannel('index');
+
+ getData(channel)
+ .then(function (result) {
+ fetchDataStub.calledOnce.should.be.true();
+ fetchDataStub.calledWith(channel).should.be.true();
+
+ result.should.eql({
+ title: 'Test',
+ description: 'Some Text',
+ posts: [{test: 'hey'}],
+ meta: {foo: 'you'}
+ });
+ done();
+ })
+ .catch(done);
});
it('should process the data correctly for a tag feed', function (done) {
- // setup
- req.originalUrl = '/tag/magic/rss/';
- req.params.slug = 'magic';
- res.locals.channel = channelUtils.getTestChannel('tag');
- res.locals.channel.isRSS = true;
-
- // test
- res.send = function send(xmlData) {
- apiBrowseStub.calledOnce.should.be.true();
- apiBrowseStub.calledWith({
- page: 1,
- filter: 'tags:\'magic\'+tags.visibility:public',
- include: 'author,tags'
- }).should.be.true();
- apiTagStub.calledOnce.should.be.true();
- xmlData.should.match(/<\/title>/);
- xmlData.should.match(//);
- done();
- };
-
- rssController(req, res, failTest(done));
+ fetchDataStub.returns(new Promise.resolve({posts: [{test: 'hey'}], meta: {foo: 'you'}}));
+
+ var channel = channelUtils.getTestChannel('tag');
+
+ getData(channel)
+ .then(function (result) {
+ fetchDataStub.calledOnce.should.be.true();
+ fetchDataStub.calledWith(channel).should.be.true();
+
+ result.should.eql({
+ title: 'Test',
+ description: 'Some Text',
+ posts: [{test: 'hey'}],
+ meta: {foo: 'you'}
+ });
+ done();
+ })
+ .catch(done);
});
- it('should process the data correctly for a paginated tag feed', function (done) {
- // setup
- req.originalUrl = '/tag/magic/rss/2/';
- req.params.slug = 'magic';
- req.params.page = '2';
- res.locals.channel = channelUtils.getTestChannel('tag');
- res.locals.channel.isRSS = true;
-
- // test
- res.send = function send(xmlData) {
- apiBrowseStub.calledOnce.should.be.true();
- apiBrowseStub.calledWith({
- page: '2',
- filter: 'tags:\'magic\'+tags.visibility:public',
- include: 'author,tags'
- }).should.be.true();
-
- apiTagStub.calledOnce.should.be.true();
- xmlData.should.match(/<\/title>/);
- xmlData.should.match(//);
- done();
- };
+ it('should process the data correctly for a tag feed WITH related data', function (done) {
+ fetchDataStub.returns(new Promise.resolve({
+ posts: [{test: 'hey'}],
+ meta: {foo: 'you'},
+ data: {tag: [{name: 'there'}]}
+ }));
- rssController(req, res, failTest(done));
+ var channel = channelUtils.getTestChannel('tag');
+
+ getData(channel)
+ .then(function (result) {
+ fetchDataStub.calledOnce.should.be.true();
+ fetchDataStub.calledWith(channel).should.be.true();
+
+ result.should.eql({
+ title: 'there - Test',
+ description: 'Some Text',
+ posts: [{test: 'hey'}],
+ meta: {foo: 'you'}
+ });
+ done();
+ })
+ .catch(done);
});
it('should process the data correctly for an author feed', function (done) {
- req.originalUrl = '/author/joe/rss/';
- req.params.slug = 'joe';
- res.locals.channel = channelUtils.getTestChannel('author');
- res.locals.channel.isRSS = true;
-
- // test
- res.send = function send(xmlData) {
- apiBrowseStub.calledOnce.should.be.true();
- apiBrowseStub.calledWith({page: 1, filter: 'author:\'joe\'', include: 'author,tags'}).should.be.true();
- apiUserStub.calledOnce.should.be.true();
- xmlData.should.match(/<\/title>/);
- xmlData.should.match(//);
- done();
- };
-
- rssController(req, res, failTest(done));
+ fetchDataStub.returns(new Promise.resolve({posts: [{test: 'hey'}], meta: {foo: 'you'}}));
+
+ var channel = channelUtils.getTestChannel('author');
+
+ getData(channel)
+ .then(function (result) {
+ fetchDataStub.calledOnce.should.be.true();
+ fetchDataStub.calledWith(channel).should.be.true();
+
+ result.should.eql({
+ title: 'Test',
+ description: 'Some Text',
+ posts: [{test: 'hey'}],
+ meta: {foo: 'you'}
+ });
+ done();
+ })
+ .catch(done);
});
- it('should process the data correctly for a paginated author feed', function (done) {
- req.originalUrl = '/author/joe/rss/2/';
- req.params.slug = 'joe';
- req.params.page = '2';
- res.locals.channel = channelUtils.getTestChannel('author');
- res.locals.channel.isRSS = true;
-
- // test
- res.send = function send(xmlData) {
- apiBrowseStub.calledOnce.should.be.true();
- apiBrowseStub.calledWith({page: '2', filter: 'author:\'joe\'', include: 'author,tags'}).should.be.true();
- apiUserStub.calledOnce.should.be.true();
- xmlData.should.match(/<\/title>/);
- xmlData.should.match(//);
- done();
- };
+ it('should process the data correctly for an author feed WITH related data', function (done) {
+ fetchDataStub.returns(new Promise.resolve({
+ posts: [{test: 'hey'}],
+ meta: {foo: 'you'},
+ data: {author: [{name: 'there'}]}
+ }));
- rssController(req, res, failTest(done));
+ var channel = channelUtils.getTestChannel('author');
+
+ getData(channel)
+ .then(function (result) {
+ fetchDataStub.calledOnce.should.be.true();
+ fetchDataStub.calledWith(channel).should.be.true();
+
+ result.should.eql({
+ title: 'there - Test',
+ description: 'Some Text',
+ posts: [{test: 'hey'}],
+ meta: {foo: 'you'}
+ });
+ done();
+ })
+ .catch(done);
});
});
});
diff --git a/core/test/unit/services/rss/cache_spec.js b/core/test/unit/services/rss/cache_spec.js
index f310eeef597c..edf15effc270 100644
--- a/core/test/unit/services/rss/cache_spec.js
+++ b/core/test/unit/services/rss/cache_spec.js
@@ -27,8 +27,8 @@ describe('RSS: Cache', function () {
data = {
title: 'Test Title',
description: 'Testing Desc',
- permalinks: '/:slug/',
- results: {posts: [], meta: {pagination: {pages: 1}}}
+ posts: [],
+ meta: {pagination: {pages: 1}}
};
rssCache.getXML('/rss/', data)
diff --git a/core/test/unit/services/rss/generate-feed_spec.js b/core/test/unit/services/rss/generate-feed_spec.js
index b754c91b7a14..efba5a4a054b 100644
--- a/core/test/unit/services/rss/generate-feed_spec.js
+++ b/core/test/unit/services/rss/generate-feed_spec.js
@@ -7,6 +7,7 @@ var should = require('should'),
describe('RSS: Generate Feed', function () {
var data = {},
+ baseUrl,
// Static set of posts
posts;
@@ -30,18 +31,18 @@ describe('RSS: Generate Feed', function () {
beforeEach(function () {
configUtils.set({url: 'http://my-ghost-blog.com'});
- data.version = '0.6';
- data.siteUrl = 'http://my-ghost-blog.com/';
- data.feedUrl = 'http://my-ghost-blog.com/rss/';
+ baseUrl = '/rss/';
+
+ data.safeVersion = '0.6';
data.title = 'Test Title';
data.description = 'Testing Desc';
- data.permalinks = '/:slug/';
+ data.meta = {pagination: {pages: 1}};
});
it('should get the RSS tags correct', function (done) {
- data.results = {posts: [], meta: {pagination: {pages: 1}}};
+ data.posts = [];
- generateFeed(data).then(function (xmlData) {
+ generateFeed(baseUrl, data).then(function (xmlData) {
should.exist(xmlData);
// xml & rss tags
@@ -69,9 +70,9 @@ describe('RSS: Generate Feed', function () {
});
it('should get the item tags correct', function (done) {
- data.results = {posts: posts, meta: {pagination: {pages: 1}}};
+ data.posts = posts;
- generateFeed(data).then(function (xmlData) {
+ generateFeed(baseUrl, data).then(function (xmlData) {
should.exist(xmlData);
// item tags
@@ -107,9 +108,9 @@ describe('RSS: Generate Feed', function () {
{name: 'visibility'}
];
- data.results = {posts: [postWithTags], meta: {pagination: {pages: 1}}};
+ data.posts = [postWithTags];
- generateFeed(data).then(function (xmlData) {
+ generateFeed(baseUrl, data).then(function (xmlData) {
should.exist(xmlData);
// item tags
xmlData.should.match(//);
@@ -124,10 +125,28 @@ describe('RSS: Generate Feed', function () {
}).catch(done);
});
+ it('should no error if author is somehow not present', function (done) {
+ data.posts = [_.omit(posts[2], 'author')];
+
+ generateFeed(baseUrl, data).then(function (xmlData) {
+ should.exist(xmlData);
+
+ // special/optional tags
+ xmlData.should.match(//);
+ xmlData.should.match(/