From 9321289c1d684aec5f2d2e2304af41ea39d55983 Mon Sep 17 00:00:00 2001 From: Sebastian Gierlinger Date: Wed, 16 Apr 2014 12:09:03 +0200 Subject: [PATCH] Move post API to primary document format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #2580 - added new format to post API methods - added post object parsing and wrapping to admin - removed unused ‚user‘ object from API response - updated tests --- core/client/models/post.js | 15 +- core/server/api/index.js | 4 +- core/server/api/posts.js | 41 +++--- core/server/controllers/frontend.js | 8 +- core/server/models/base.js | 4 +- core/server/models/post.js | 14 +- core/test/functional/routes/api/posts_test.js | 124 ++++++++-------- core/test/integration/api/api_posts_spec.js | 2 +- .../integration/model/model_posts_spec.js | 4 - core/test/unit/frontend_spec.js | 137 +++++++++--------- core/test/utils/api.js | 2 +- 11 files changed, 191 insertions(+), 164 deletions(-) diff --git a/core/client/models/post.js b/core/client/models/post.js index 157eb8e59d..26f5c792fa 100644 --- a/core/client/models/post.js +++ b/core/client/models/post.js @@ -1,4 +1,4 @@ -/*global Ghost, _, Backbone */ +/*global Ghost, _, Backbone, JSON */ (function () { 'use strict'; @@ -11,6 +11,10 @@ blacklist: ['published', 'draft'], parse: function (resp) { + + if (resp.posts) { + resp = resp.posts[0]; + } if (resp.status) { resp.published = resp.status === 'published'; resp.draft = resp.status === 'draft'; @@ -39,6 +43,15 @@ return tag.id === tagToRemove.id || tag.name === tagToRemove.name; }); this.set('tags', tags); + }, + sync: function (method, model, options) { + //wrap post in {posts: [{...}]} + if (method === 'create' || method === 'update') { + options.data = JSON.stringify({posts: [this.attributes]}); + options.contentType = 'application/json'; + } + + return Backbone.Model.prototype.sync.apply(this, arguments); } }); diff --git a/core/server/api/index.js b/core/server/api/index.js index a97eda2a42..3e75bc87c4 100644 --- a/core/server/api/index.js +++ b/core/server/api/index.js @@ -29,8 +29,8 @@ function cacheInvalidationHeader(req, result) { cacheInvalidate = '/*'; } else if (endpoint === 'posts') { cacheInvalidate = '/, /page/*, /rss/, /rss/*, /tag/*'; - if (id && jsonResult.slug) { - return config.urlForPost(settings, jsonResult).then(function (postUrl) { + if (id && jsonResult.posts[0].slug) { + return config.urlForPost(settings, jsonResult.posts[0]).then(function (postUrl) { return cacheInvalidate + ', ' + postUrl; }); } diff --git a/core/server/api/posts.js b/core/server/api/posts.js index 17ecf55bb0..3c2aeda5e8 100644 --- a/core/server/api/posts.js +++ b/core/server/api/posts.js @@ -5,10 +5,17 @@ var when = require('when'), filteredUserAttributes = require('./users').filteredAttributes, posts; +function checkPostData(postData) { + if (_.isEmpty(postData) || _.isEmpty(postData.posts) || _.isEmpty(postData.posts[0])) { + return when.reject({code: 400, message: 'No root key (\'posts\') provided.'}); + } + return when.resolve(postData); +} + // ## Posts posts = { - // #### Browse + // #### Browse // **takes:** filter / pagination parameters browse: function browse(options) { options = options || {}; @@ -21,14 +28,12 @@ posts = { for (i = 0; i < omitted.posts.length; i = i + 1) { omitted.posts[i].author = _.omit(omitted.posts[i].author, filteredUserAttributes); - omitted.posts[i].user = _.omit(omitted.posts[i].user, filteredUserAttributes); } return omitted; }); }, // #### Read - // **takes:** an identifier (id or slug?) read: function read(args) { // **returns:** a promise for a single post in a json object @@ -39,8 +44,7 @@ posts = { if (result) { omitted = result.toJSON(); omitted.author = _.omit(omitted.author, filteredUserAttributes); - omitted.user = _.omit(omitted.user, filteredUserAttributes); - return omitted; + return { posts: [ omitted ]}; } return when.reject({code: 404, message: 'Post not found'}); @@ -57,7 +61,6 @@ posts = { }, // #### Edit - // **takes:** a json object with all the properties which should be updated edit: function edit(postData) { // **returns:** a promise for the resulting post in a json object @@ -66,21 +69,15 @@ posts = { } var self = this; return canThis(self.user).edit.post(postData.id).then(function () { - return dataProvider.Post.edit(postData).then(function (result) { + return checkPostData(postData).then(function (checkedPostData) { + return dataProvider.Post.edit(checkedPostData.posts[0]); + }).then(function (result) { if (result) { var omitted = result.toJSON(); omitted.author = _.omit(omitted.author, filteredUserAttributes); - omitted.user = _.omit(omitted.user, filteredUserAttributes); - return omitted; + return { posts: [ omitted ]}; } return when.reject({code: 404, message: 'Post not found'}); - }).otherwise(function (error) { - return dataProvider.Post.findOne({id: postData.id, status: 'all'}).then(function (result) { - if (!result) { - return when.reject({code: 404, message: 'Post not found'}); - } - return when.reject({message: error.message}); - }); }); }, function () { return when.reject({code: 403, message: 'You do not have permission to edit this post.'}); @@ -88,7 +85,6 @@ posts = { }, // #### Add - // **takes:** a json object representing a post, add: function add(postData) { // **returns:** a promise for the resulting post in a json object @@ -97,14 +93,19 @@ posts = { } return canThis(this.user).create.post().then(function () { - return dataProvider.Post.add(postData); + return checkPostData(postData).then(function (checkedPostData) { + return dataProvider.Post.add(checkedPostData.posts[0]); + }).then(function (result) { + var omitted = result.toJSON(); + omitted.author = _.omit(omitted.author, filteredUserAttributes); + return { posts: [ omitted ]}; + }); }, function () { return when.reject({code: 403, message: 'You do not have permission to add posts.'}); }); }, // #### Destroy - // **takes:** an identifier (id or slug?) destroy: function destroy(args) { // **returns:** a promise for a json response with the id of the deleted post @@ -113,7 +114,7 @@ posts = { } return canThis(this.user).remove.post(args.id).then(function () { - return when(posts.read({id : args.id, status: 'all'})).then(function (result) { + return posts.read({id : args.id, status: 'all'}).then(function (result) { return dataProvider.Post.destroy(args.id).then(function () { var deletedObj = result; return deletedObj; diff --git a/core/server/controllers/frontend.js b/core/server/controllers/frontend.js index 7c220e0325..bc93eb8a6c 100644 --- a/core/server/controllers/frontend.js +++ b/core/server/controllers/frontend.js @@ -172,7 +172,10 @@ frontendControllers = { // Query database to find post return api.posts.read(postLookup); - }).then(function (post) { + }).then(function (result) { + var post = result.posts[0], + slugDate = [], + slugFormat = []; if (!post) { return next(); @@ -208,9 +211,6 @@ frontendControllers = { // we will check it against the post published date // to verify it's correct. if (params.year || params.month || params.day) { - var slugDate = [], - slugFormat = []; - if (params.year) { slugDate.push(params.year); slugFormat.push('YYYY'); diff --git a/core/server/models/base.js b/core/server/models/base.js index ca2b5996a0..f7e84b2583 100644 --- a/core/server/models/base.js +++ b/core/server/models/base.js @@ -149,7 +149,9 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ edit: function (editedObj, options) { options = options || {}; return this.forge({id: editedObj.id}).fetch(options).then(function (foundObj) { - return foundObj.save(editedObj, options); + if (foundObj) { + return foundObj.save(editedObj, options); + } }); }, diff --git a/core/server/models/post.js b/core/server/models/post.js index e5a505713c..d647945b9d 100644 --- a/core/server/models/post.js +++ b/core/server/models/post.js @@ -192,10 +192,6 @@ Post = ghostBookshelf.Model.extend({ }, // Relations - user: function () { - return this.belongsTo(User, 'created_by'); - }, - author: function () { return this.belongsTo(User, 'author_id'); }, @@ -210,7 +206,7 @@ Post = ghostBookshelf.Model.extend({ // Extends base model findAll to eager-fetch author and user relationships. findAll: function (options) { options = options || {}; - options.withRelated = [ 'author', 'user', 'tags' ]; + options.withRelated = [ 'author', 'tags' ]; return ghostBookshelf.Model.findAll.call(this, options); }, @@ -227,7 +223,7 @@ Post = ghostBookshelf.Model.extend({ delete args.status; } - options.withRelated = [ 'author', 'user', 'tags' ]; + options.withRelated = [ 'author', 'tags' ]; return ghostBookshelf.Model.findOne.call(this, args, options); }, @@ -293,7 +289,7 @@ Post = ghostBookshelf.Model.extend({ } // Fetch related models - opts.withRelated = [ 'author', 'user', 'tags' ]; + opts.withRelated = [ 'author', 'tags' ]; // If a query param for a tag is attached // we need to fetch the tag model to find its id @@ -439,7 +435,9 @@ Post = ghostBookshelf.Model.extend({ var self = this; return ghostBookshelf.Model.edit.call(this, editedPost, options).then(function (post) { - return self.findOne({status: 'all', id: post.id}, options); + if (post) { + return self.findOne({status: 'all', id: post.id}, options); + } }); }, destroy: function (_identifier, options) { diff --git a/core/test/functional/routes/api/posts_test.js b/core/test/functional/routes/api/posts_test.js index c5388acb76..6227b049aa 100644 --- a/core/test/functional/routes/api/posts_test.js +++ b/core/test/functional/routes/api/posts_test.js @@ -198,8 +198,9 @@ describe('Post API', function () { res.should.be.json; var jsonResponse = res.body; jsonResponse.should.exist; - testUtils.API.checkResponse(jsonResponse, 'post'); - jsonResponse.page.should.eql(0); + jsonResponse.posts.should.exist; + testUtils.API.checkResponse(jsonResponse.posts[0], 'post'); + jsonResponse.posts[0].page.should.eql(0); done(); }); }); @@ -216,8 +217,9 @@ describe('Post API', function () { res.should.be.json; var jsonResponse = res.body; jsonResponse.should.exist; - testUtils.API.checkResponse(jsonResponse, 'post'); - jsonResponse.page.should.eql(1); + jsonResponse.posts.should.exist; + testUtils.API.checkResponse(jsonResponse.posts[0], 'post'); + jsonResponse.posts[0].page.should.eql(1); done(); }); }); @@ -281,7 +283,7 @@ describe('Post API', function () { var newTitle = 'My Post', changedTitle = 'My Post changed', publishedState = 'published', - newPost = {status: 'draft', title: newTitle, markdown: 'my post'}; + newPost = {posts: [{status: 'draft', title: newTitle, markdown: 'my post'}]}; request.post(testUtils.API.getApiQuery('posts/')) .set('X-CSRF-Token', csrfToken) @@ -294,12 +296,12 @@ describe('Post API', function () { res.should.be.json; var draftPost = res.body; - draftPost.should.exist; - draftPost.title.should.eql(newTitle); - draftPost.status = publishedState; - testUtils.API.checkResponse(draftPost, 'post'); + draftPost.posts.should.exist; + draftPost.posts[0].title.should.eql(newTitle); + draftPost.posts[0].status = publishedState; + testUtils.API.checkResponse(draftPost.posts[0], 'post'); - request.put(testUtils.API.getApiQuery('posts/' + draftPost.id + '/')) + request.put(testUtils.API.getApiQuery('posts/' + draftPost.posts[0].id + '/')) .set('X-CSRF-Token', csrfToken) .send(draftPost) .expect(200) @@ -309,15 +311,15 @@ describe('Post API', function () { } var publishedPost = res.body; - - res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + publishedPost.slug + '/'); + res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + publishedPost.posts[0].slug + '/'); res.should.be.json; publishedPost.should.exist; - publishedPost.title.should.eql(newTitle); - publishedPost.status.should.eql(publishedState); - testUtils.API.checkResponse(publishedPost, 'post'); + publishedPost.posts.should.exist; + publishedPost.posts[0].title.should.eql(newTitle); + publishedPost.posts[0].status.should.eql(publishedState); + testUtils.API.checkResponse(publishedPost.posts[0], 'post'); - request.put(testUtils.API.getApiQuery('posts/' + publishedPost.id + '/')) + request.put(testUtils.API.getApiQuery('posts/' + publishedPost.posts[0].id + '/')) .set('X-CSRF-Token', csrfToken) .send(publishedPost) .expect(200) @@ -327,11 +329,12 @@ describe('Post API', function () { } var updatedPost = res.body; - res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + updatedPost.slug + '/'); + res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + updatedPost.posts[0].slug + '/'); res.should.be.json; updatedPost.should.exist; - updatedPost.title.should.eql(newTitle); - testUtils.API.checkResponse(updatedPost, 'post'); + updatedPost.posts.should.exist; + updatedPost.posts[0].title.should.eql(newTitle); + testUtils.API.checkResponse(updatedPost.posts[0], 'post'); done(); }); }); @@ -352,8 +355,8 @@ describe('Post API', function () { var jsonResponse = res.body, changedValue = 'My new Title'; - jsonResponse.should.exist; - jsonResponse.title = changedValue; + jsonResponse.posts[0].should.exist; + jsonResponse.posts[0].title = changedValue; request.put(testUtils.API.getApiQuery('posts/1/')) .set('X-CSRF-Token', csrfToken) @@ -365,12 +368,12 @@ describe('Post API', function () { } var putBody = res.body; - res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + putBody.slug + '/'); + res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + putBody.posts[0].slug + '/'); res.should.be.json; putBody.should.exist; - putBody.title.should.eql(changedValue); + putBody.posts[0].title.should.eql(changedValue); - testUtils.API.checkResponse(putBody, 'post'); + testUtils.API.checkResponse(putBody.posts[0], 'post'); done(); }); }); @@ -386,8 +389,8 @@ describe('Post API', function () { var jsonResponse = res.body, changedValue = true; jsonResponse.should.exist; - jsonResponse.page.should.eql(0); - jsonResponse.page = changedValue; + jsonResponse.posts[0].page.should.eql(0); + jsonResponse.posts[0].page = changedValue; request.put(testUtils.API.getApiQuery('posts/1/')) .set('X-CSRF-Token', csrfToken) @@ -399,12 +402,12 @@ describe('Post API', function () { } var putBody = res.body; - res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + putBody.slug + '/'); + res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + putBody.posts[0].slug + '/'); res.should.be.json; putBody.should.exist; - putBody.page.should.eql(changedValue); + putBody.posts[0].page.should.eql(changedValue); - testUtils.API.checkResponse(putBody, 'post'); + testUtils.API.checkResponse(putBody.posts[0], 'post'); done(); }); }); @@ -421,8 +424,8 @@ describe('Post API', function () { var jsonResponse = res.body, changedValue = false; jsonResponse.should.exist; - jsonResponse.page.should.eql(1); - jsonResponse.page = changedValue; + jsonResponse.posts[0].page.should.eql(1); + jsonResponse.posts[0].page = changedValue; request.put(testUtils.API.getApiQuery('posts/1/')) .set('X-CSRF-Token', csrfToken) @@ -434,12 +437,12 @@ describe('Post API', function () { } var putBody = res.body; - res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + putBody.slug + '/'); + res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + putBody.posts[0].slug + '/'); res.should.be.json; putBody.should.exist; - putBody.page.should.eql(changedValue); + putBody.posts[0].page.should.eql(changedValue); - testUtils.API.checkResponse(putBody, 'post'); + testUtils.API.checkResponse(putBody.posts[0], 'post'); done(); }); }); @@ -490,15 +493,16 @@ describe('Post API', function () { } var putBody = res.body; - res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + putBody.slug + '/'); + res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + putBody.posts[0].slug + '/'); res.should.be.json; putBody.should.exist; - putBody.title.should.eql(changedValue); - if (_.isEmpty(putBody.published_at)) { + putBody.posts.should.exist; + putBody.posts[0].title.should.eql(changedValue); + if (_.isEmpty(putBody.posts[0].published_at)) { should.fail('null', 'valid date', 'publish_at should not be empty'); done(); } - testUtils.API.checkResponse(putBody, 'post'); + testUtils.API.checkResponse(putBody.posts[0], 'post'); done(); }); }); @@ -517,9 +521,9 @@ describe('Post API', function () { var jsonResponse = res.body, changedValue = 'My new Title'; - jsonResponse.title.exist; - jsonResponse.testvalue = changedValue; - jsonResponse.id = 99; + jsonResponse.posts[0].title.exist; + jsonResponse.posts[0].testvalue = changedValue; + jsonResponse.posts[0].id = 99; request.put(testUtils.API.getApiQuery('posts/99/')) .set('X-CSRF-Token', csrfToken) .send(jsonResponse) @@ -551,9 +555,10 @@ describe('Post API', function () { res.should.be.json; var jsonResponse = res.body; jsonResponse.should.exist; - res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + jsonResponse.slug + '/'); - testUtils.API.checkResponse(jsonResponse, 'post'); - jsonResponse.id.should.eql(deletePostId); + jsonResponse.posts.should.exist; + res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + jsonResponse.posts[0].slug + '/'); + testUtils.API.checkResponse(jsonResponse.posts[0], 'post'); + jsonResponse.posts[0].id.should.eql(deletePostId); done(); }); }); @@ -579,13 +584,15 @@ describe('Post API', function () { it('can delete a new draft', function (done) { var newTitle = 'My Post', publishedState = 'draft', - newPost = {status: publishedState, title: newTitle, markdown: 'my post'}; + newPost = {posts: [{status: publishedState, title: newTitle, markdown: 'my post'}]}; request.post(testUtils.API.getApiQuery('posts/')) .set('X-CSRF-Token', csrfToken) .send(newPost) .expect(200) .end(function (err ,res) { + console.log("end"); + console.log(err); if (err) { return done(err); } @@ -594,11 +601,11 @@ describe('Post API', function () { res.should.be.json; draftPost.should.exist; - draftPost.title.should.eql(newTitle); - draftPost.status = publishedState; - testUtils.API.checkResponse(draftPost, 'post'); + draftPost.posts[0].title.should.eql(newTitle); + draftPost.posts[0].status = publishedState; + testUtils.API.checkResponse(draftPost.posts[0], 'post'); - request.del(testUtils.API.getApiQuery('posts/' + draftPost.id + '/')) + request.del(testUtils.API.getApiQuery('posts/' + draftPost.posts[0].id + '/')) .set('X-CSRF-Token', csrfToken) .expect(200) .end(function (err, res) { @@ -609,7 +616,8 @@ describe('Post API', function () { res.should.be.json; var jsonResponse = res.body jsonResponse.should.exist; - testUtils.API.checkResponse(jsonResponse, 'post'); + jsonResponse.posts.should.exist; + testUtils.API.checkResponse(jsonResponse.posts[0], 'post'); done(); }); }); @@ -677,9 +685,10 @@ describe('Post API', function () { var jsonResponse = res.body; jsonResponse.should.exist; - testUtils.API.checkResponse(jsonResponse, 'post'); - jsonResponse.slug.should.not.match(/^\/[0-9]{4}\/[0-9]{2}\/[0-9]{2}/); - jsonResponse.page.should.eql(0); + jsonResponse.posts.should.exist; + testUtils.API.checkResponse(jsonResponse.posts[0], 'post'); + jsonResponse.posts[0].slug.should.not.match(/^\/[0-9]{4}\/[0-9]{2}\/[0-9]{2}/); + jsonResponse.posts[0].page.should.eql(0); done(); }); }); @@ -694,7 +703,8 @@ describe('Post API', function () { var jsonResponse = res.body, changedValue = 'My new Title'; jsonResponse.should.exist; - jsonResponse.title = changedValue; + jsonResponse.posts.should.exist; + jsonResponse.posts[0].title = changedValue; request.put(testUtils.API.getApiQuery('posts/2/')) .set('X-CSRF-Token', csrfToken) @@ -709,14 +719,14 @@ describe('Post API', function () { dd = ("0" + today.getDate()).slice(-2), mm = ("0" + (today.getMonth() + 1)).slice(-2), yyyy = today.getFullYear(), - postLink = '/' + yyyy + '/' + mm + '/' + dd + '/' + putBody.slug + '/'; + postLink = '/' + yyyy + '/' + mm + '/' + dd + '/' + putBody.posts[0].slug + '/'; res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, ' + postLink); res.should.be.json; putBody.should.exist; - putBody.title.should.eql(changedValue); + putBody.posts[0].title.should.eql(changedValue); - testUtils.API.checkResponse(putBody, 'post'); + testUtils.API.checkResponse(putBody.posts[0], 'post'); done(); }); }); diff --git a/core/test/integration/api/api_posts_spec.js b/core/test/integration/api/api_posts_spec.js index 107316dbdd..f956144e19 100644 --- a/core/test/integration/api/api_posts_spec.js +++ b/core/test/integration/api/api_posts_spec.js @@ -52,7 +52,7 @@ describe('Post API', function () { return PostAPI.read({slug: firstPost.slug}); }).then(function (found) { should.exist(found); - testUtils.API.checkResponse(found, 'post'); + testUtils.API.checkResponse(found.posts[0], 'post'); done(); }).then(null, done); }); diff --git a/core/test/integration/model/model_posts_spec.js b/core/test/integration/model/model_posts_spec.js index 15b5983977..d1cc351cc2 100644 --- a/core/test/integration/model/model_posts_spec.js +++ b/core/test/integration/model/model_posts_spec.js @@ -75,9 +75,7 @@ describe('Post Model', function () { firstPost = results.models[0].toJSON(); firstPost.author.should.be.an.Object; - firstPost.user.should.be.an.Object; firstPost.author.name.should.equal(DataGenerator.Content.users[0].name); - firstPost.user.name.should.equal(DataGenerator.Content.users[0].name); done(); }, done); @@ -91,9 +89,7 @@ describe('Post Model', function () { firstPost = result.toJSON(); firstPost.author.should.be.an.Object; - firstPost.user.should.be.an.Object; firstPost.author.name.should.equal(testUtils.DataGenerator.Content.users[0].name); - firstPost.user.name.should.equal(testUtils.DataGenerator.Content.users[0].name); done(); }, done); diff --git a/core/test/unit/frontend_spec.js b/core/test/unit/frontend_spec.js index 7ad37bdde7..6ec1928f4f 100644 --- a/core/test/unit/frontend_spec.js +++ b/core/test/unit/frontend_spec.js @@ -360,29 +360,35 @@ describe('Frontend Controller', function () { describe('single', function () { var mockPosts = [{ - 'status': 'published', - 'id': 1, - 'title': 'Test static page', - 'slug': 'test-static-page', - 'markdown': 'Test static page content', - 'page': 1, - 'published_at': new Date('2013/12/30').getTime() + 'posts': [{ + 'status': 'published', + 'id': 1, + 'title': 'Test static page', + 'slug': 'test-static-page', + 'markdown': 'Test static page content', + 'page': 1, + 'published_at': new Date('2013/12/30').getTime() + }] }, { - 'status': 'published', - 'id': 2, - 'title': 'Test normal post', - 'slug': 'test-normal-post', - 'markdown': 'The test normal post content', - 'page': 0, - 'published_at': new Date('2014/1/2').getTime() + 'posts': [{ + 'status': 'published', + 'id': 2, + 'title': 'Test normal post', + 'slug': 'test-normal-post', + 'markdown': 'The test normal post content', + 'page': 0, + 'published_at': new Date('2014/1/2').getTime() + }] }, { - 'status': 'published', - 'id': 3, - 'title': 'About', - 'slug': 'about', - 'markdown': 'This is the about page content', - 'page': 1, - 'published_at': new Date('2014/1/30').getTime() + 'posts': [{ + 'status': 'published', + 'id': 3, + 'title': 'About', + 'slug': 'about', + 'markdown': 'This is the about page content', + 'page': 1, + 'published_at': new Date('2014/1/30').getTime() + }] }], // Helper function to prevent unit tests // from failing via timeout when they @@ -395,7 +401,9 @@ describe('Frontend Controller', function () { beforeEach(function () { sandbox.stub(api.posts, 'read', function (args) { - return when(_.find(mockPosts, args)); + return when(_.find(mockPosts, function(mock) { + return mock.posts[0].slug === args.slug; + })); }); apiSettingsStub = sandbox.stub(api.settings, 'read'); @@ -433,16 +441,15 @@ describe('Frontend Controller', function () { it('it will render custom page template if it exists', function (done) { var req = { - path: '/' + mockPosts[2].slug + path: '/' + mockPosts[2].posts[0].slug }, res = { render: function (view, context) { - assert.equal(view, 'page-' + mockPosts[2].slug); - assert.equal(context.post, mockPosts[2]); + assert.equal(view, 'page-' + mockPosts[2].posts[0].slug); + assert.equal(context.post, mockPosts[2].posts[0]); done(); } }; - frontend.single(req, res, failTest(done)); }); }); @@ -456,12 +463,12 @@ describe('Frontend Controller', function () { it('will render static page via /:slug', function (done) { var req = { - path: '/' + mockPosts[0].slug + path: '/' + mockPosts[0].posts[0].slug }, res = { render: function (view, context) { assert.equal(view, 'page'); - assert.equal(context.post, mockPosts[0]); + assert.equal(context.post, mockPosts[0].posts[0]); done(); } }; @@ -471,7 +478,7 @@ describe('Frontend Controller', function () { it('will NOT render static page via /YYY/MM/DD/:slug', function (done) { var req = { - path: '/' + ['2012/12/30', mockPosts[0].slug].join('/') + path: '/' + ['2012/12/30', mockPosts[0].posts[0].slug].join('/') }, res = { render: sinon.spy() @@ -485,13 +492,13 @@ describe('Frontend Controller', function () { it('will redirect static page to admin edit page via /:slug/edit', function (done) { var req = { - path: '/' + [mockPosts[0].slug, 'edit'].join('/') + path: '/' + [mockPosts[0].posts[0].slug, 'edit'].join('/') }, res = { render: sinon.spy(), redirect: function(arg) { res.render.called.should.be.false; - arg.should.eql(adminEditPagePath + mockPosts[0].id + '/'); + arg.should.eql(adminEditPagePath + mockPosts[0].posts[0].id + '/'); done(); } }; @@ -501,7 +508,7 @@ describe('Frontend Controller', function () { it('will NOT redirect static page to admin edit page via /YYYY/MM/DD/:slug/edit', function (done) { var req = { - path: '/' + ['2012/12/30', mockPosts[0].slug, 'edit'].join('/') + path: '/' + ['2012/12/30', mockPosts[0].posts[0].slug, 'edit'].join('/') }, res = { render: sinon.spy(), @@ -525,12 +532,12 @@ describe('Frontend Controller', function () { it('will render static page via /:slug', function (done) { var req = { - path: '/' + mockPosts[0].slug + path: '/' + mockPosts[0].posts[0].slug }, res = { render: function (view, context) { assert.equal(view, 'page'); - assert.equal(context.post, mockPosts[0]); + assert.equal(context.post, mockPosts[0].posts[0]); done(); } }; @@ -540,7 +547,7 @@ describe('Frontend Controller', function () { it('will NOT render static page via /YYYY/MM/DD/:slug', function (done) { var req = { - path: '/' + ['2012/12/30', mockPosts[0].slug].join('/') + path: '/' + ['2012/12/30', mockPosts[0].posts[0].slug].join('/') }, res = { render: sinon.spy() @@ -554,13 +561,13 @@ describe('Frontend Controller', function () { it('will redirect static page to admin edit page via /:slug/edit', function (done) { var req = { - path: '/' + [mockPosts[0].slug, 'edit'].join('/') + path: '/' + [mockPosts[0].posts[0].slug, 'edit'].join('/') }, res = { render: sinon.spy(), redirect: function (arg) { res.render.called.should.be.false; - arg.should.eql(adminEditPagePath + mockPosts[0].id + '/'); + arg.should.eql(adminEditPagePath + mockPosts[0].posts[0].id + '/'); done(); } }; @@ -570,7 +577,7 @@ describe('Frontend Controller', function () { it('will NOT redirect static page to admin edit page via /YYYY/MM/DD/:slug/edit', function (done) { var req = { - path: '/' + ['2012/12/30', mockPosts[0].slug, 'edit'].join('/') + path: '/' + ['2012/12/30', mockPosts[0].posts[0].slug, 'edit'].join('/') }, res = { render: sinon.spy(), @@ -596,13 +603,13 @@ describe('Frontend Controller', function () { it('will render post via /:slug', function (done) { var req = { - path: '/' + mockPosts[1].slug + path: '/' + mockPosts[1].posts[0].slug }, res = { render: function (view, context) { assert.equal(view, 'post'); assert(context.post, 'Context object has post attribute'); - assert.equal(context.post, mockPosts[1]); + assert.equal(context.post, mockPosts[1].posts[0]); done(); } }; @@ -612,7 +619,7 @@ describe('Frontend Controller', function () { it('will NOT render post via /YYYY/MM/DD/:slug', function (done) { var req = { - path: '/' + ['2012/12/30', mockPosts[1].slug].join('/') + path: '/' + ['2012/12/30', mockPosts[1].posts[0].slug].join('/') }, res = { render: sinon.spy() @@ -627,13 +634,13 @@ describe('Frontend Controller', function () { // Handle Edit append it('will redirect post to admin edit page via /:slug/edit', function (done) { var req = { - path: '/' + [mockPosts[1].slug, 'edit'].join('/') + path: '/' + [mockPosts[1].posts[0].slug, 'edit'].join('/') }, res = { render: sinon.spy(), redirect: function(arg) { res.render.called.should.be.false; - arg.should.eql(adminEditPagePath + mockPosts[1].id + '/'); + arg.should.eql(adminEditPagePath + mockPosts[1].posts[0].id + '/'); done(); } }; @@ -643,7 +650,7 @@ describe('Frontend Controller', function () { it('will NOT redirect post to admin edit page via /YYYY/MM/DD/:slug/edit', function (done) { var req = { - path: '/' + ['2012/12/30', mockPosts[1].slug, 'edit'].join('/') + path: '/' + ['2012/12/30', mockPosts[1].posts[0].slug, 'edit'].join('/') }, res = { render: sinon.spy(), @@ -666,15 +673,15 @@ describe('Frontend Controller', function () { }); it('will render post via /YYYY/MM/DD/:slug', function (done) { - var date = moment(mockPosts[1].published_at).format('YYYY/MM/DD'), + var date = moment(mockPosts[1].posts[0].published_at).format('YYYY/MM/DD'), req = { - path: '/' + [date, mockPosts[1].slug].join('/') + path: '/' + [date, mockPosts[1].posts[0].slug].join('/') }, res = { render: function (view, context) { assert.equal(view, 'post'); assert(context.post, 'Context object has post attribute'); - assert.equal(context.post, mockPosts[1]); + assert.equal(context.post, mockPosts[1].posts[0]); done(); } }; @@ -685,7 +692,7 @@ describe('Frontend Controller', function () { it('will NOT render post via /YYYY/MM/DD/:slug with non-matching date in url', function (done) { var date = moment(mockPosts[1].published_at).subtract('days', 1).format('YYYY/MM/DD'), req = { - path: '/' + [date, mockPosts[1].slug].join('/') + path: '/' + [date, mockPosts[1].posts[0].slug].join('/') }, res = { render: sinon.spy() @@ -699,7 +706,7 @@ describe('Frontend Controller', function () { it('will NOT render post via /:slug', function (done) { var req = { - path: '/' + mockPosts[1].slug + path: '/' + mockPosts[1].posts[0].slug }, res = { render: sinon.spy() @@ -713,15 +720,15 @@ describe('Frontend Controller', function () { // Handle Edit append it('will redirect post to admin edit page via /YYYY/MM/DD/:slug/edit', function (done) { - var dateFormat = moment(mockPosts[1].published_at).format('YYYY/MM/DD'), + var dateFormat = moment(mockPosts[1].posts[0].published_at).format('YYYY/MM/DD'), req = { - path: '/' + [dateFormat, mockPosts[1].slug, 'edit'].join('/') + path: '/' + [dateFormat, mockPosts[1].posts[0].slug, 'edit'].join('/') }, res = { render: sinon.spy(), redirect: function (arg) { res.render.called.should.be.false; - arg.should.eql(adminEditPagePath + mockPosts[1].id + '/'); + arg.should.eql(adminEditPagePath + mockPosts[1].posts[0].id + '/'); done(); } }; @@ -731,7 +738,7 @@ describe('Frontend Controller', function () { it('will NOT redirect post to admin edit page via /:slug/edit', function (done) { var req = { - path: '/' + [mockPosts[1].slug, 'edit'].join('/') + path: '/' + [mockPosts[1].posts[0].slug, 'edit'].join('/') }, res = { render: sinon.spy(), @@ -754,15 +761,15 @@ describe('Frontend Controller', function () { }); it('will render post via /:year/:slug', function (done) { - var date = moment(mockPosts[1].published_at).format('YYYY'), + var date = moment(mockPosts[1].posts[0].published_at).format('YYYY'), req = { - path: '/' + [date, mockPosts[1].slug].join('/') + path: '/' + [date, mockPosts[1].posts[0].slug].join('/') }, res = { render: function (view, context) { assert.equal(view, 'post'); assert(context.post, 'Context object has post attribute'); - assert.equal(context.post, mockPosts[1]); + assert.equal(context.post, mockPosts[1].posts[0]); done(); } }; @@ -771,9 +778,9 @@ describe('Frontend Controller', function () { }); it('will NOT render post via /YYYY/MM/DD/:slug', function (done) { - var date = moment(mockPosts[1].published_at).format('YYYY/MM/DD'), + var date = moment(mockPosts[1].posts[0].published_at).format('YYYY/MM/DD'), req = { - path: '/' + [date, mockPosts[1].slug].join('/') + path: '/' + [date, mockPosts[1].posts[0].slug].join('/') }, res = { render: sinon.spy() @@ -786,9 +793,9 @@ describe('Frontend Controller', function () { }); it('will NOT render post via /:year/slug when year does not match post year', function (done) { - var date = moment(mockPosts[1].published_at).subtract('years', 1).format('YYYY'), + var date = moment(mockPosts[1].posts[0].published_at).subtract('years', 1).format('YYYY'), req = { - path: '/' + [date, mockPosts[1].slug].join('/') + path: '/' + [date, mockPosts[1].posts[0].slug].join('/') }, res = { render: sinon.spy() @@ -802,7 +809,7 @@ describe('Frontend Controller', function () { it('will NOT render post via /:slug', function (done) { var req = { - path: '/' + mockPosts[1].slug + path: '/' + mockPosts[1].posts[0].slug }, res = { render: sinon.spy() @@ -816,15 +823,15 @@ describe('Frontend Controller', function () { // Handle Edit append it('will redirect post to admin edit page via /:year/:slug/edit', function (done) { - var date = moment(mockPosts[1].published_at).format('YYYY'), + var date = moment(mockPosts[1].posts[0].published_at).format('YYYY'), req = { - path: '/' + [date, mockPosts[1].slug, 'edit'].join('/') + path: '/' + [date, mockPosts[1].posts[0].slug, 'edit'].join('/') }, res = { render: sinon.spy(), redirect: function (arg) { res.render.called.should.be.false; - arg.should.eql(adminEditPagePath + mockPosts[1].id + '/'); + arg.should.eql(adminEditPagePath + mockPosts[1].posts[0].id + '/'); done(); } }; @@ -834,7 +841,7 @@ describe('Frontend Controller', function () { it('will NOT redirect post to admin edit page /:slug/edit', function (done) { var req = { - path: '/' + [mockPosts[1].slug, 'edit'].join('/') + path: '/' + [mockPosts[1].posts[0].slug, 'edit'].join('/') }, res = { render: sinon.spy(), diff --git a/core/test/utils/api.js b/core/test/utils/api.js index 577961c265..1e7018c1e6 100644 --- a/core/test/utils/api.js +++ b/core/test/utils/api.js @@ -8,7 +8,7 @@ var _ = require('lodash'), posts: ['posts', 'page', 'limit', 'pages', 'total'], post: ['id', 'uuid', 'title', 'slug', 'markdown', 'html', 'meta_title', 'meta_description', 'featured', 'image', 'status', 'language', 'author_id', 'created_at', 'created_by', 'updated_at', - 'updated_by', 'published_at', 'published_by', 'page', 'author', 'user', 'tags'], + 'updated_by', 'published_at', 'published_by', 'page', 'author', 'tags'], // TODO: remove databaseVersion, dbHash settings: ['databaseVersion', 'dbHash', 'title', 'description', 'email', 'logo', 'cover', 'defaultLang', "permalinks", 'postsPerPage', 'forceI18n', 'activeTheme', 'activeApps', 'installedApps',