text: var should = require('should'),
      sinon = require('sinon'),
      rewire = require('rewire'),
      _ = require('lodash'),
      Promise = require('bluebird'),
      testUtils = require('../utils'),
      channelConfig = require('../../server/controllers/frontend/channel-config'),
      api = require('../../server/api'),
      settingsCache = require('../../server/settings/cache'),
      rss = rewire('../../server/data/xml/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 () {
      var req, res, posts;
  
      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'};
          });
      });
  
      afterEach(function () {
          sandbox.restore();
          rss = rewire('../../server/data/xml/rss');
          configUtils.restore();
      });
  
      describe('Check XML', function () {
          beforeEach(function () {
              req = {
                  params: {},
                  originalUrl: '/rss/'
              };
  
              res = {
                  locals: {
                      safeVersion: '0.6'
                  },
                  set: sinon.stub()
              };
  
              configUtils.set({url: 'http://my-ghost-blog.com'});
          });
  
          it('should get the RSS tags correct', function (done) {
              rss.__set__('getData', function () {
                  return Promise.resolve({
                      title: 'Test Title',
                      description: 'Testing Desc',
                      permalinks: '/:slug/',
                      results: {posts: [], meta: {pagination: {pages: 1}}}
                  });
              });
  
              res.send = function send(xmlData) {
                  should.exist(xmlData);
                  res.set.calledWith('Content-Type', 'text/xml; charset=UTF-8').should.be.true();
  
                  // xml & rss tags
                  xmlData.should.match(/^<\?xml version="1.0" encoding="UTF-8"\?>/);
                  xmlData.should.match(/<rss/);
                  xmlData.should.match(/xmlns:dc="http:\/\/purl.org\/dc\/elements\/1.1\/"/);
                  xmlData.should.match(/xmlns:content="http:\/\/purl.org\/rss\/1.0\/modules\/content\/"/);
                  xmlData.should.match(/xmlns:atom="http:\/\/www.w3.org\/2005\/Atom"/);
                  xmlData.should.match(/version="2.0"/);
                  xmlData.should.match(/xmlns:media="http:\/\/search.yahoo.com\/mrss\/"/);
  
                  // channel tags
                  xmlData.should.match(/<channel><title><!\[CDATA\[Test Title\]\]><\/title>/);
                  xmlData.should.match(/<description><!\[CDATA\[Testing Desc\]\]><\/description>/);
                  xmlData.should.match(/<link>http:\/\/my-ghost-blog.com\/<\/link>/);
                  xmlData.should.match(/<image><url>http:\/\/my-ghost-blog.com\/favicon.png<\/url><title>Test Title<\/title><link>http:\/\/my-ghost-blog.com\/<\/link><\/image>/);
                  xmlData.should.match(/<generator>Ghost 0.6<\/generator>/);
                  xmlData.should.match(/<lastBuildDate>.*?<\/lastBuildDate>/);
                  xmlData.should.match(/<atom:link href="http:\/\/my-ghost-blog.com\/rss\/" rel="self"/);
                  xmlData.should.match(/type="application\/rss\+xml"\/><ttl>60<\/ttl>/);
                  xmlData.should.match(/<\/channel><\/rss>$/);
  
                  done();
              };
  
              res.locals.channel = channelConfig.get('index');
              res.locals.channel.isRSS = true;
              rss(req, res, failTest(done));
          });
  
          it('should get the item tags correct', function (done) {
              rss.__set__('getData', function () {
                  return Promise.resolve({
                      title: 'Test Title',
                      description: 'Testing Desc',
                      permalinks: '/:slug/',
                      results: {posts: posts, meta: {pagination: {pages: 1}}}
                  });
              });
  
              res.send = function send(xmlData) {
                  should.exist(xmlData);
  
                  // item tags
                  xmlData.should.match(/<item><title><!\[CDATA\[HTML Ipsum\]\]><\/title>/);
                  xmlData.should.match(/<description><!\[CDATA\[<h1>HTML Ipsum Presents<\/h1>/);
                  xmlData.should.match(/<link>http:\/\/my-ghost-blog.com\/html-ipsum\/<\/link>/);
                  xmlData.should.match(/<image><url>http:\/\/my-ghost-blog.com\/favicon.png<\/url><title>Test Title<\/title><link>http:\/\/my-ghost-blog.com\/<\/link><\/image>/);
                  xmlData.should.match(/<guid isPermaLink="false">/);
                  xmlData.should.match(/<\/guid><dc:creator><!\[CDATA\[Joe Bloggs\]\]><\/dc:creator>/);
                  xmlData.should.match(/<pubDate>Thu, 01 Jan 2015/);
                  xmlData.should.match(/<content:encoded><!\[CDATA\[<h1>HTML Ipsum Presents<\/h1><p><strong>Pellentes/);
                  xmlData.should.match(/<\/code><\/pre>\]\]><\/content:encoded><\/item>/);
                  xmlData.should.not.match(/<author>/);
  
                  // basic structure check
                  var postEnd = '<\/code><\/pre>\]\]><\/content:encoded>',
                      firstIndex = xmlData.indexOf(postEnd);
  
                  // The first title should be before the first content
                  xmlData.indexOf('HTML Ipsum').should.be.below(firstIndex);
                  // The second title should be after the first content
                  xmlData.indexOf('Ghostly Kitchen Sink').should.be.above(firstIndex);
  
                  // done
                  done();
              };
  
              res.locals.channel = channelConfig.get('index');
              res.locals.channel.isRSS = true;
              rss(req, res, failTest(done));
          });
  
          it('should only return visible tags', function (done) {
              var postWithTags = posts[2];
              postWithTags.tags = [
                  {name: 'public', visibility: 'public'},
                  {name: 'internal', visibility: 'internal'},
                  {name: 'visibility'}
              ];
  
              rss.__set__('getData', function () {
                  return Promise.resolve({
                      title: 'Test Title',
                      description: 'Testing Desc',
                      permalinks: '/:slug/',
                      results: {posts: [postWithTags], meta: {pagination: {pages: 1}}}
                  });
              });
  
              res.send = function send(xmlData) {
                  should.exist(xmlData);
                  // item tags
                  xmlData.should.match(/<title><!\[CDATA\[Short and Sweet\]\]>/);
                  xmlData.should.match(/<description><!\[CDATA\[test stuff/);
                  xmlData.should.match(/<content:encoded><!\[CDATA\[<div class="kg-card-markdown"><h2 id="testing">testing<\/h2>\n/);
                  xmlData.should.match(/<img src="http:\/\/placekitten.com\/500\/200"/);
                  xmlData.should.match(/<media:content url="http:\/\/placekitten.com\/500\/200" medium="image"\/>/);
                  xmlData.should.match(/<category><!\[CDATA\[public\]\]/);
                  xmlData.should.match(/<category><!\[CDATA\[visibility\]\]/);
                  xmlData.should.not.match(/<category><!\[CDATA\[internal\]\]/);
                  done();
              };
  
              res.locals.channel = channelConfig.get('index');
              res.locals.channel.isRSS = true;
              rss(req, res, failTest(done));
          });
  
          it('should use meta_description and image where available', function (done) {
              rss.__set__('getData', function () {
                  return Promise.resolve({
                      title: 'Test Title',
                      description: 'Testing Desc',
                      permalinks: '/:slug/',
                      results: {posts: [posts[2]], meta: {pagination: {pages: 1}}}
                  });
              });
  
              res.send = function send(xmlData) {
                  should.exist(xmlData);
  
                  // special/optional tags
                  xmlData.should.match(/<title><!\[CDATA\[Short and Sweet\]\]>/);
                  xmlData.should.match(/<description><!\[CDATA\[test stuff/);
                  xmlData.should.match(/<content:encoded><!\[CDATA\[<div class="kg-card-markdown"><h2 id="testing">testing<\/h2>\n/);
                  xmlData.should.match(/<img src="http:\/\/placekitten.com\/500\/200"/);
                  xmlData.should.match(/<media:content url="http:\/\/placekitten.com\/500\/200" medium="image"\/>/);
  
                  done();
              };
  
              res.locals.channel = channelConfig.get('index');
              res.locals.channel.isRSS = true;
              rss(req, res, failTest(done));
          });
  
          it('should process urls correctly', function (done) {
              rss.__set__('getData', function () {
                  return Promise.resolve({
                      title: 'Test Title',
                      description: 'Testing Desc',
                      permalinks: '/:slug/',
                      results: {posts: [posts[3]], meta: {pagination: {pages: 1}}}
                  });
              });
  
              res.send = function send(xmlData) {
                  should.exist(xmlData);
  
                  // anchor URL - <a href="#nowhere" title="Anchor URL">
                  xmlData.should.match(/<a href="http:\/\/my-ghost-blog.com\/not-so-short-bit-complex\/#nowhere" title="Anchor URL">/);
  
                  // relative URL - <a href="/about#nowhere" title="Relative URL">
                  xmlData.should.match(/<a href="http:\/\/my-ghost-blog.com\/about#nowhere" title="Relative URL">/);
  
                  // protocol relative URL - <a href="//somewhere.com/link#nowhere" title="Protocol Relative URL">
                  xmlData.should.match(/<a href="\/\/somewhere.com\/link#nowhere" title="Protocol Relative URL">/);
  
                  // absolute URL - <a href="http://somewhere.com/link#nowhere" title="Absolute URL">
                  xmlData.should.match(/<a href="http:\/\/somewhere.com\/link#nowhere" title="Absolute URL">/);
  
                  // done
                  done();
              };
  
              res.locals.channel = channelConfig.get('index');
              res.locals.channel.isRSS = true;
              rss(req, res, failTest(done));
          });
  
          it('should process urls correctly with subdirectory', function (done) {
              configUtils.set({url: 'http://my-ghost-blog.com/blog/'});
              rss.__set__('getData', function () {
                  return Promise.resolve({
                      title: 'Test Title',
                      description: 'Testing Desc',
                      permalinks: '/:slug/',
                      results: {posts: [posts[3]], meta: {pagination: {pages: 1}}}
                  });
              });
  
              res.send = function send(xmlData) {
                  should.exist(xmlData);
  
                  // anchor URL - <a href="#nowhere" title="Anchor URL">
                  xmlData.should.match(/<a href="http:\/\/my-ghost-blog.com\/blog\/not-so-short-bit-complex\/#nowhere" title="Anchor URL">/);
  
                  // relative URL - <a href="/about#nowhere" title="Relative URL">
                  xmlData.should.match(/<a href="http:\/\/my-ghost-blog.com\/blog\/about#nowhere" title="Relative URL">/);
  
                  // absolute URL - <a href="http://somewhere.com/link#nowhere" title="Absolute URL">
                  xmlData.should.match(/<a href="http:\/\/somewhere.com\/link#nowhere" title="Absolute URL">/);
  
                  // done
                  done();
              };
  
              res.locals.channel = channelConfig.get('index');
              res.locals.channel.isRSS = true;
              rss(req, res, failTest(done));
          });
      });
  
      describe('dataBuilder', function () {
          var apiBrowseStub, apiTagStub, apiUserStub;
  
          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'
              });
          });
  
          it('should process the data correctly for the index feed', function (done) {
              res.send = function send(xmlData) {
                  apiBrowseStub.calledOnce.should.be.true();
                  apiBrowseStub.calledWith({page: 1, include: 'author,tags'}).should.be.true();
                  xmlData.should.match(/<channel><title><!\[CDATA\[Test\]\]><\/title>/);
                  done();
              };
  
              res.locals.channel = channelConfig.get('index');
              res.locals.channel.isRSS = true;
              rss(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 = channelConfig.get('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();
              };
  
              rss(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 = channelConfig.get('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();
              };
  
              rss(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 = channelConfig.get('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();
              };
  
              rss(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 = channelConfig.get('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();
              };
  
              rss(req, res, failTest(done));
          });
      });
  
      describe('caching', function () {
          beforeEach(function () {
              req = {
                  params: {},
                  originalUrl: '/rss/'
              };
  
              res = {
                  locals: {
                      safeVersion: '0.6'
                  },
                  set: sinon.stub()
              };
  
              configUtils.set({url: 'http://my-ghost-blog.com'});
          });
  
          it('should not rebuild xml for same data and url', function (done) {
              var xmlData;
              rss.__set__('getData', function () {
                  return Promise.resolve({
                      title: 'Test Title',
                      description: 'Testing Desc',
                      permalinks: '/:slug/',
                      results: {posts: [], meta: {pagination: {pages: 1}}}
                  });
              });
              res.locals.channel = channelConfig.get('index');
              res.locals.channel.isRSS = true;
  
              function secondCall() {
                  res.send = function sendFirst(data) {
                      // The data should be identical, no changing lastBuildDate
                      data.should.equal(xmlData);
  
                      // Now call done!
                      done();
                  };
  
                  rss(req, res, failTest(done));
              }
  
              function firstCall() {
                  res.send = function sendFirst(data) {
                      xmlData = data;
  
                      // Call RSS again to check that we didn't rebuild
                      secondCall();
                  };
  
                  rss(req, res, failTest(done));
              }
  
              firstCall();
          });
      });
  
      describe('pagination', function () {
          beforeEach(function () {
              res = {
                  locals: {version: ''},
                  redirect: sandbox.spy(),
                  render: sandbox.spy()
              };
  
              rss.__set__('getData', function () {
                  return Promise.resolve({
                      title: 'Test',
                      description: 'Testing',
                      permalinks: '/:slug/',
                      results: {posts: [], meta: {pagination: {pages: 3}}}
                  });
              });
          });
  
          it('Should 404 if page number too big', function (done) {
              configUtils.set({url: 'http://localhost:82832/'});
  
              req = {params: {page: 4}, route: {path: '/rss/:page/'}};
              req.originalUrl = req.route.path.replace(':page', req.params.page);
              res.locals.channel = channelConfig.get('index');
              res.locals.channel.isRSS = true;
  
              rss(req, res, function (err) {
                  should.exist(err);
                  err.statusCode.should.eql(404);
                  res.redirect.called.should.be.false();
                  res.render.called.should.be.false();
                  done();
              }).catch(done);
          });
  
          it('Redirects to last page if page number too big with subdirectory', function (done) {
              configUtils.set({url: 'http://localhost:82832/blog'});
  
              req = {params: {page: 4}, route: {path: '/rss/:page/'}};
              req.originalUrl = req.route.path.replace(':page', req.params.page);
              res.locals.channel = channelConfig.get('index');
              res.locals.channel.isRSS = true;
  
              rss(req, res, function (err) {
                  should.exist(err);
                  err.statusCode.should.eql(404);
                  res.redirect.called.should.be.false();
                  res.render.called.should.be.false();
                  done();
              }).catch(done);
          });
      });
  });
  