mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-09 04:31:17 +03:00
86c6cec433
refs #5091, #9192, #9178 - Get the RSS module into a much better shape - Controller -> /controllers/rss - Remainder -> /services/rss - Moved tests to match & updated requires
362 lines
13 KiB
JavaScript
362 lines
13 KiB
JavaScript
var should = require('should'),
|
|
sinon = require('sinon'),
|
|
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'),
|
|
|
|
sandbox = sinon.sandbox.create();
|
|
|
|
// Helper function to prevent unit tests
|
|
// from failing via timeout when they
|
|
// should just immediately fail
|
|
function failTest(done) {
|
|
return function (err) {
|
|
done(err);
|
|
};
|
|
}
|
|
|
|
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'};
|
|
});
|
|
});
|
|
|
|
beforeEach(function () {
|
|
// Minimum setup of req and res
|
|
req = {
|
|
params: {},
|
|
originalUrl: '/rss/'
|
|
};
|
|
|
|
res = {
|
|
locals: {
|
|
safeVersion: '0.6',
|
|
channel: channelUtils.getTestChannel('index')
|
|
},
|
|
set: sinon.stub(),
|
|
send: sinon.spy()
|
|
};
|
|
|
|
// @TODO Get rid of this! - shouldn't be set on the channel
|
|
res.locals.channel.isRSS = true;
|
|
|
|
// Overwrite getData
|
|
getDataStub = sandbox.stub();
|
|
|
|
resetGetData = rssController.__set__('getData', getDataStub);
|
|
|
|
rssCacheStub = sandbox.stub(rssCache, 'getXML').returns(new Promise.resolve('dummyxml'));
|
|
});
|
|
|
|
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));
|
|
});
|
|
|
|
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, failTest(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, failTest(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, failTest(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();
|
|
});
|
|
});
|
|
});
|
|
|
|
// 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;
|
|
|
|
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()
|
|
};
|
|
|
|
sandbox.stub(settingsCache, 'get', function (key) {
|
|
var obj = {
|
|
title: 'Test',
|
|
description: 'Some Text',
|
|
permalinks: '/:slug/'
|
|
};
|
|
|
|
return obj[key];
|
|
});
|
|
|
|
configUtils.set({
|
|
url: 'http://my-ghost-blog.com'
|
|
});
|
|
});
|
|
|
|
afterEach(function () {
|
|
sandbox.restore();
|
|
configUtils.restore();
|
|
});
|
|
|
|
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(/<channel><title><!\[CDATA\[Test\]\]><\/title>/);
|
|
xmlData.should.match(/<atom:link href="http:\/\/my-ghost-blog.com\/rss\/" rel="self" type="application\/rss\+xml"\/>/);
|
|
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(/<channel><title><!\[CDATA\[Test\]\]><\/title>/);
|
|
xmlData.should.match(/<atom:link href="http:\/\/my-ghost-blog.com\/rss\/" rel="self" type="application\/rss\+xml"\/>/);
|
|
done();
|
|
};
|
|
|
|
rssController(req, res, failTest(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(/<channel><title><!\[CDATA\[Magic - Test\]\]><\/title>/);
|
|
xmlData.should.match(/<atom:link href="http:\/\/my-ghost-blog.com\/tag\/magic\/rss\/" rel="self" type="application\/rss\+xml"\/>/);
|
|
done();
|
|
};
|
|
|
|
rssController(req, res, failTest(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(/<channel><title><!\[CDATA\[Magic - Test\]\]><\/title>/);
|
|
xmlData.should.match(/<atom:link href="http:\/\/my-ghost-blog.com\/tag\/magic\/rss\/" rel="self" type="application\/rss\+xml"\/>/);
|
|
done();
|
|
};
|
|
|
|
rssController(req, res, failTest(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(/<channel><title><!\[CDATA\[Joe Blogs - Test\]\]><\/title>/);
|
|
xmlData.should.match(/<atom:link href="http:\/\/my-ghost-blog.com\/author\/joe\/rss\/" rel="self" type="application\/rss\+xml"\/>/);
|
|
done();
|
|
};
|
|
|
|
rssController(req, res, failTest(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(/<channel><title><!\[CDATA\[Joe Blogs - Test\]\]><\/title>/);
|
|
xmlData.should.match(/<atom:link href="http:\/\/my-ghost-blog.com\/author\/joe\/rss\/" rel="self" type="application\/rss\+xml"\/>/);
|
|
done();
|
|
};
|
|
|
|
rssController(req, res, failTest(done));
|
|
});
|
|
});
|
|
});
|