From bb17e1c0e9a4bf93df059d4c40a9088f5e71c47d Mon Sep 17 00:00:00 2001 From: Sebastian Gierlinger Date: Sun, 3 Nov 2013 18:13:19 +0100 Subject: [PATCH] Add API tests closes #1189 - added tests - added request module - added status codes to API calls - fixed return values of API calls - fixed that drafts caused an error when being deleted - fixed X-Invalidate-Cache headers - moved testUtils.js to utils/index.js --- core/server/api/db.js | 2 +- core/server/api/index.js | 87 +++++--- core/server/models/post.js | 14 +- core/server/models/user.js | 4 +- .../integration/model_permissions_spec.js | 2 +- core/test/integration/model_posts_spec.js | 5 +- core/test/integration/model_roles_spec.js | 2 +- core/test/integration/model_settings_spec.js | 2 +- core/test/integration/model_tags_spec.js | 2 +- core/test/integration/model_users_spec.js | 2 +- core/test/unit/api_posts_spec.js | 207 ++++++++++++++++-- core/test/unit/api_settings_spec.js | 123 +++++++++++ core/test/unit/api_tags_spec.js | 60 +++++ core/test/unit/api_users_spec.js | 106 +++++++++ core/test/unit/client_ghostdown_spec.js | 2 +- core/test/unit/client_showdown_int_spec.js | 2 +- core/test/unit/errorHandling_spec.js | 2 +- core/test/unit/export_spec.js | 2 +- core/test/unit/ghost_spec.js | 2 +- core/test/unit/import_spec.js | 2 +- core/test/unit/mail_spec.js | 2 +- core/test/unit/permissions_spec.js | 2 +- core/test/unit/plugins_spec.js | 2 +- core/test/unit/server_helpers_index_spec.js | 2 +- core/test/unit/shared_gfm_spec.js | 2 +- core/test/unit/utils/api.js | 130 ++--------- .../unit/{testUtils.js => utils/index.js} | 10 +- package.json | 3 +- 28 files changed, 595 insertions(+), 188 deletions(-) create mode 100644 core/test/unit/api_settings_spec.js create mode 100644 core/test/unit/api_tags_spec.js create mode 100644 core/test/unit/api_users_spec.js rename core/test/unit/{testUtils.js => utils/index.js} (87%) diff --git a/core/server/api/db.js b/core/server/api/db.js index 1c2ef1c716..e6dc577253 100644 --- a/core/server/api/db.js +++ b/core/server/api/db.js @@ -156,4 +156,4 @@ db = { } }; -module.exports.db = db; +module.exports = db; diff --git a/core/server/api/index.js b/core/server/api/index.js index 74d56f8ea7..9aab513467 100644 --- a/core/server/api/index.js +++ b/core/server/api/index.js @@ -20,7 +20,7 @@ var Ghost = require('../../ghost'), settingsObject, settingsCollection, settingsFilter, - filteredUserAttributes = ['password', 'created_by', 'updated_by']; + filteredUserAttributes = ['password', 'created_by', 'updated_by', 'last_login']; // ## Posts posts = { @@ -28,6 +28,7 @@ posts = { // **takes:** filter / pagination parameters browse: function browse(options) { + // **returns:** a promise for a page of posts in a json object //return dataProvider.Post.findPage(options); return dataProvider.Post.findPage(options).then(function (result) { @@ -57,8 +58,8 @@ posts = { omitted.user = _.omit(omitted.user, filteredUserAttributes); return omitted; } + return when.reject({errorCode: 404, message: 'Post not found'}); - return null; }); }, @@ -68,13 +69,28 @@ posts = { edit: function edit(postData) { // **returns:** a promise for the resulting post in a json object if (!this.user) { - return when.reject("You do not have permission to edit this post."); + return when.reject({errorCode: 403, message: 'You do not have permission to edit this post.'}); } - - return canThis(this.user).edit.post(postData.id).then(function () { - return dataProvider.Post.edit(postData); + var self = this; + return canThis(self.user).edit.post(postData.id).then(function () { + return dataProvider.Post.edit(postData).then(function (result) { + if (result) { + var omitted = result.toJSON(); + omitted.author = _.omit(omitted.author, filteredUserAttributes); + omitted.user = _.omit(omitted.user, filteredUserAttributes); + return omitted; + } + return when.reject({errorCode: 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({errorCode: 404, message: 'Post not found'}); + } + return when.reject({message: error.message}); + }); + }); }, function () { - return when.reject("You do not have permission to edit this post."); + return when.reject({errorCode: 403, message: 'You do not have permission to edit this post.'}); }); }, @@ -84,13 +100,13 @@ posts = { add: function add(postData) { // **returns:** a promise for the resulting post in a json object if (!this.user) { - return when.reject("You do not have permission to add posts."); + return when.reject({errorCode: 403, message: 'You do not have permission to add posts.'}); } return canThis(this.user).create.post().then(function () { return dataProvider.Post.add(postData); }, function () { - return when.reject("You do not have permission to add posts."); + return when.reject({errorCode: 403, message: 'You do not have permission to add posts.'}); }); }, @@ -100,11 +116,11 @@ posts = { destroy: function destroy(args) { // **returns:** a promise for a json response with the id of the deleted post if (!this.user) { - return when.reject("You do not have permission to remove posts."); + return when.reject({errorCode: 403, message: 'You do not have permission to remove posts.'}); } return canThis(this.user).remove.post(args.id).then(function () { - return when(posts.read({id : args.id})).then(function (result) { + return when(posts.read({id : args.id, status: 'all'})).then(function (result) { return dataProvider.Post.destroy(args.id).then(function () { var deletedObj = {}; deletedObj.id = result.id; @@ -113,7 +129,7 @@ posts = { }); }); }, function () { - return when.reject("You do not have permission to remove posts."); + return when.reject({errorCode: 403, message: 'You do not have permission to remove posts.'}); }); } }; @@ -157,7 +173,7 @@ users = { return omitted; } - return null; + return when.reject({errorCode: 404, message: 'User not found'}); }); }, @@ -167,7 +183,13 @@ users = { edit: function edit(userData) { // **returns:** a promise for the resulting user in a json object userData.id = this.user; - return dataProvider.User.edit(userData); + return dataProvider.User.edit(userData).then(function (result) { + if (result) { + var omitted = _.omit(result.toJSON(), filteredUserAttributes); + return omitted; + } + return when.reject({errorCode: 404, message: 'User not found'}); + }); }, // #### Add @@ -289,6 +311,7 @@ settings = { // **returns:** a promise for a settings json object if (ghost.settings()) { return when(ghost.settings()).then(function (settings) { + //TODO: omit where type==core return settingsObject(settingsFilter(settings, options.type)); }, errors.logAndThrowError); } @@ -305,7 +328,7 @@ settings = { if (ghost.settings()) { return when(ghost.settings()[options.key]).then(function (setting) { if (!setting) { - return when.reject("Unable to find setting: " + options.key); + return when.reject({errorCode: 404, message: 'Unable to find setting: ' + options.key}); } var res = {}; res.key = options.key; @@ -333,11 +356,18 @@ settings = { ghost.updateSettingsCache(settings); return settingsObject(settingsFilter(ghost.settings(), type)); }); - }, errors.logAndThrowError); + }).otherwise(function (error) { + return dataProvider.Settings.read(key.key).then(function (result) { + if (!result) { + return when.reject({errorCode: 404, message: 'Unable to find setting: ' + key}); + } + return when.reject({message: error.message}); + }); + }); } return dataProvider.Settings.read(key).then(function (setting) { if (!setting) { - return when.reject("Unable to find setting: " + key); + return when.reject({errorCode: 404, message: 'Unable to find setting: ' + key}); } if (!_.isString(value)) { value = JSON.stringify(value); @@ -356,20 +386,18 @@ settings = { function invalidateCache(req, res, result) { var parsedUrl = req._parsedUrl.pathname.replace(/\/$/, '').split('/'), method = req.method, - endpoint = parsedUrl[3], - id = parsedUrl[4], - cacheInvalidate; + endpoint = parsedUrl[4], + id = parsedUrl[5], + cacheInvalidate, + jsonResult = result.toJSON ? result.toJSON() : result; + if (method === 'POST' || method === 'PUT' || method === 'DELETE') { if (endpoint === 'settings' || endpoint === 'users') { cacheInvalidate = "/*"; } else if (endpoint === 'posts') { cacheInvalidate = "/, /page/*, /rss/, /rss/*"; - if (id) { - if (result.toJSON) { - cacheInvalidate += ', /' + result.toJSON().slug + '/'; - } else { - cacheInvalidate += ', /' + result.slug + '/'; - } + if (id && jsonResult.slug) { + cacheInvalidate += ', /' + jsonResult.slug + '/'; } } if (cacheInvalidate) { @@ -394,8 +422,9 @@ requestHandler = function (apiMethod) { invalidateCache(req, res, result); res.json(result || {}); }, function (error) { - error = {error: _.isString(error) ? error : (_.isObject(error) ? error.message : 'Unknown API Error')}; - res.json(400, error); + var errorCode = error.errorCode || 500, + errorMsg = {error: _.isString(error) ? error : (_.isObject(error) ? error.message : 'Unknown API Error')}; + res.json(errorCode, errorMsg); }); }; }; @@ -406,5 +435,5 @@ module.exports.users = users; module.exports.tags = tags; module.exports.notifications = notifications; module.exports.settings = settings; -module.exports.db = db.db; +module.exports.db = db; module.exports.requestHandler = requestHandler; diff --git a/core/server/models/post.js b/core/server/models/post.js index 72589ddfde..8f914b90e6 100644 --- a/core/server/models/post.js +++ b/core/server/models/post.js @@ -341,9 +341,19 @@ Post = ghostBookshelf.Model.extend({ }, add: function (newPostData, options) { - return ghostBookshelf.Model.add.call(this, newPostData, options).tap(function (post) { + var self = this; + return ghostBookshelf.Model.add.call(this, newPostData, options).then(function (post) { // associated models can't be created until the post has an ID, so run this after - return post.updateTags(newPostData.tags); + return when(post.updateTags(newPostData.tags)).then(function () { + return self.findOne({status: 'all', id: post.id}); + }); + }); + }, + edit: function (editedPost, options) { + var self = this; + + return ghostBookshelf.Model.edit.call(this, editedPost, options).then(function (editedObj) { + return self.findOne({status: 'all', id: editedObj.id}); }); }, destroy: function (_identifier, options) { diff --git a/core/server/models/user.js b/core/server/models/user.js index 4584fa799b..6dfbff9ac9 100644 --- a/core/server/models/user.js +++ b/core/server/models/user.js @@ -28,8 +28,8 @@ User = ghostBookshelf.Model.extend({ permittedAttributes: [ 'id', 'uuid', 'name', 'slug', 'password', 'email', 'image', 'cover', 'bio', 'website', 'location', - 'accessibility', 'status', 'language', 'meta_title', 'meta_description', 'created_at', 'created_by', - 'updated_at', 'updated_by' + 'accessibility', 'status', 'language', 'meta_title', 'meta_description', 'last_login', 'created_at', + 'created_by', 'updated_at', 'updated_by' ], validate: function () { diff --git a/core/test/integration/model_permissions_spec.js b/core/test/integration/model_permissions_spec.js index 4ea303a46c..c7e602fb34 100644 --- a/core/test/integration/model_permissions_spec.js +++ b/core/test/integration/model_permissions_spec.js @@ -1,5 +1,5 @@ /*globals describe, it, before, beforeEach, afterEach */ -var testUtils = require('../unit/testUtils'), +var testUtils = require('../unit/utils'), should = require('should'), errors = require('../../server/errorHandling'), diff --git a/core/test/integration/model_posts_spec.js b/core/test/integration/model_posts_spec.js index 1d6ccb643b..78758ebf78 100644 --- a/core/test/integration/model_posts_spec.js +++ b/core/test/integration/model_posts_spec.js @@ -1,5 +1,5 @@ /*globals describe, before, beforeEach, afterEach, it */ -var testUtils = require('../unit/testUtils'), +var testUtils = require('../unit/utils'), should = require('should'), _ = require('underscore'), when = require('when'), @@ -310,8 +310,7 @@ describe('Post Model', function () { }).then(function (newPost) { should.exist(newPost); - - newPost.get('published_at').should.equal(previousPublishedAtDate); + newPost.get('published_at').should.equal(previousPublishedAtDate.getTime()); done(); diff --git a/core/test/integration/model_roles_spec.js b/core/test/integration/model_roles_spec.js index d525ebb63d..68e3081bc4 100644 --- a/core/test/integration/model_roles_spec.js +++ b/core/test/integration/model_roles_spec.js @@ -1,5 +1,5 @@ /*globals describe, it, before, beforeEach, afterEach */ -var testUtils = require('../unit/testUtils'), +var testUtils = require('../unit/utils'), should = require('should'), errors = require('../../server/errorHandling'), diff --git a/core/test/integration/model_settings_spec.js b/core/test/integration/model_settings_spec.js index cf1b0d30ab..4bd2a41be9 100644 --- a/core/test/integration/model_settings_spec.js +++ b/core/test/integration/model_settings_spec.js @@ -1,5 +1,5 @@ /*globals describe, before, beforeEach, afterEach, it*/ -var testUtils = require('../unit/testUtils'), +var testUtils = require('../unit/utils'), should = require('should'), _ = require("underscore"), diff --git a/core/test/integration/model_tags_spec.js b/core/test/integration/model_tags_spec.js index 70c4b9d15d..b85ade3d85 100644 --- a/core/test/integration/model_tags_spec.js +++ b/core/test/integration/model_tags_spec.js @@ -1,5 +1,5 @@ /*globals describe, before, beforeEach, afterEach, it */ -var testUtils = require('../unit/testUtils'), +var testUtils = require('../unit/utils'), _ = require("underscore"), when = require('when'), sequence = require('when/sequence'), diff --git a/core/test/integration/model_users_spec.js b/core/test/integration/model_users_spec.js index fb35ceeaf6..fe37dd86de 100644 --- a/core/test/integration/model_users_spec.js +++ b/core/test/integration/model_users_spec.js @@ -1,5 +1,5 @@ /*globals describe, before, beforeEach, afterEach, it*/ -var testUtils = require('../unit/testUtils'), +var testUtils = require('../unit/utils'), should = require('should'), when = require('when'), _ = require('underscore'), diff --git a/core/test/unit/api_posts_spec.js b/core/test/unit/api_posts_spec.js index cbb6d25ff9..1398a66a2e 100644 --- a/core/test/unit/api_posts_spec.js +++ b/core/test/unit/api_posts_spec.js @@ -1,12 +1,21 @@ /*globals describe, before, beforeEach, afterEach, it */ -var testUtils = require('./testUtils'), +var testUtils = require('./utils'), should = require('should'), - _ = require('underscore'); + _ = require('underscore'), + request = require('request'), + expectedPostsProperties = ['posts', 'page', 'limit', 'pages', 'total'], + expectedPostProperties = ['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']; + + + +request = request.defaults({jar:true}) describe('Post API', function () { var user = testUtils.DataGenerator.forModel.users[0], - authCookie; + csrfToken = ''; before(function (done) { testUtils.clearData() @@ -16,17 +25,22 @@ describe('Post API', function () { }); beforeEach(function (done) { + this.timeout(5000); testUtils.initData() .then(function () { - return testUtils.insertDefaultFixtures(); + testUtils.insertDefaultFixtures(); }) .then(function () { - return testUtils.API.login(user.email, user.password); - }) - .then(function (authResponse) { - authCookie = authResponse; - - done(); + // do a get request to get the CSRF token first + request.get(testUtils.API.getSigninURL(), function (error, response, body) { + var pattern_meta = //i; + pattern_meta.should.exist; + csrfToken = body.match(pattern_meta)[1]; + request.post({uri:testUtils.API.getSigninURL(), + headers: {'X-CSRF-Token': csrfToken}}, function (error, response, body) { + done(); + }).form({email: user.email, password: user.password}); + }); }, done); }); @@ -36,13 +50,170 @@ describe('Post API', function () { }, done); }); -// it('can retrieve a post', function (done) { -// testUtils.API.get(testUtils.API.ApiRouteBase + 'posts/?status=all', authCookie).then(function (result) { -// should.exist(result); -// should.exist(result.response); -// result.response.posts.length.should.be.above(1); -// done(); -// }).otherwise(done); -// }); + it('can retrieve all posts', function (done) { + request.get(testUtils.API.getApiURL('posts/'), function (error, response, body) { + response.should.have.status(200); + response.should.be.json; + var jsonResponse = JSON.parse(body); + jsonResponse.posts.should.exist; + testUtils.API.checkResponse (jsonResponse, expectedPostsProperties); + jsonResponse.posts.should.have.length(5); + testUtils.API.checkResponse (jsonResponse.posts[0], expectedPostProperties); + done(); + }); + }); + + it('can retrieve a post', function (done) { + request.get(testUtils.API.getApiURL('posts/1/'), function (error, response, body) { + response.should.have.status(200); + response.should.be.json; + var jsonResponse = JSON.parse(body); + jsonResponse.should.exist; + testUtils.API.checkResponse (jsonResponse, expectedPostProperties); + done(); + }); + }); + + it('can create a new draft, publish post, update post', function (done) { + var newTitle = 'My Post', + changedTitle = 'My Post changed', + publishedState = 'published', + newPost = {status:'draft', title:newTitle, markdown:'my post'}; + + request.post({uri: testUtils.API.getApiURL('posts/'), + headers: {'X-CSRF-Token': csrfToken}, + json: newPost}, function (error, response, draftPost) { + response.should.have.status(200); + response.should.be.json; + draftPost.should.exist; + draftPost.title.should.eql(newTitle); + draftPost.status = publishedState; + testUtils.API.checkResponse (draftPost, expectedPostProperties); + request.put({uri: testUtils.API.getApiURL('posts/' + draftPost.id + '/'), + headers: {'X-CSRF-Token': csrfToken}, + json: draftPost}, function (error, response, publishedPost) { + response.should.have.status(200); + response.should.be.json; + publishedPost.should.exist; + publishedPost.title.should.eql(newTitle); + publishedPost.status.should.eql(publishedState); + testUtils.API.checkResponse (publishedPost, expectedPostProperties); + request.put({uri: testUtils.API.getApiURL('posts/' + publishedPost.id + '/'), + headers: {'X-CSRF-Token': csrfToken}, + json: publishedPost}, function (error, response, updatedPost) { + response.should.have.status(200); + response.should.be.json; + updatedPost.should.exist; + updatedPost.title.should.eql(newTitle); + testUtils.API.checkResponse (updatedPost, expectedPostProperties); + done(); + }); + }); + }); + }); + + it('can delete a post', function (done) { + var deletePostId = 1; + request.del({uri: testUtils.API.getApiURL('posts/' + deletePostId +'/'), + headers: {'X-CSRF-Token': csrfToken}}, function (error, response, body) { + response.should.have.status(200); + response.should.be.json; + var jsonResponse = JSON.parse(body); + jsonResponse.should.exist; + testUtils.API.checkResponse (jsonResponse, ['id', 'slug']); + jsonResponse.id.should.eql(deletePostId); + done(); + }); + }); + + it('can\'t delete a non existent post', function (done) { + request.del({uri: testUtils.API.getApiURL('posts/99/'), + headers: {'X-CSRF-Token': csrfToken}}, function (error, response, body) { + response.should.have.status(404); + response.should.be.json; + var jsonResponse = JSON.parse(body); + jsonResponse.should.exist; + testUtils.API.checkResponse (jsonResponse, ['error']); + done(); + }); + }); + + it('can delete a new draft', function (done) { + var newTitle = 'My Post', + publishedState = 'draft', + newPost = {status: publishedState, title: newTitle, markdown: 'my post'}; + + request.post({uri: testUtils.API.getApiURL('posts/'), + headers: {'X-CSRF-Token': csrfToken}, + json: newPost}, function (error, response, draftPost) { + response.should.have.status(200); + response.should.be.json; + draftPost.should.exist; + draftPost.title.should.eql(newTitle); + draftPost.status = publishedState; + testUtils.API.checkResponse (draftPost, expectedPostProperties); + request.del({uri: testUtils.API.getApiURL('posts/' + draftPost.id + '/'), + headers: {'X-CSRF-Token': csrfToken}}, function (error, response, body) { + response.should.have.status(200); + response.should.be.json; + var jsonResponse = JSON.parse(body); + jsonResponse.should.exist; + testUtils.API.checkResponse (jsonResponse, ['id', 'slug']); + done(); + }); + }); + }); + + + it('can\'t retrieve non existent post', function (done) { + request.get(testUtils.API.getApiURL('posts/99/'), function (error, response, body) { + response.should.have.status(404); + response.should.be.json; + var jsonResponse = JSON.parse(body); + jsonResponse.should.exist; + testUtils.API.checkResponse (jsonResponse, ['error']); + done(); + }); + }); + + it('can edit a post', function (done) { + request.get(testUtils.API.getApiURL('posts/1/'), function (error, response, body) { + var jsonResponse = JSON.parse(body), + changedValue = 'My new Title'; + jsonResponse.should.exist; + //jsonResponse.websiteshould.be.empty; + jsonResponse.title = changedValue; + + request.put({uri: testUtils.API.getApiURL('posts/1/'), + headers: {'X-CSRF-Token': csrfToken}, + json: jsonResponse}, function (error, response, putBody) { + response.should.have.status(200); + response.should.be.json; + putBody.should.exist; + putBody.title.should.eql(changedValue); + + testUtils.API.checkResponse (putBody, expectedPostProperties); + done(); + }); + }); + }); + + it('can\'t edit non existent post', function (done) { + request.get(testUtils.API.getApiURL('posts/1/'), function (error, response, body) { + var jsonResponse = JSON.parse(body), + changedValue = 'My new Title'; + jsonResponse.title.exist; + jsonResponse.testvalue = changedValue; + jsonResponse.id = 99; + request.put({uri: testUtils.API.getApiURL('posts/99/'), + headers: {'X-CSRF-Token': csrfToken}, + json: jsonResponse}, function (error, response, putBody) { + response.should.have.status(404); + response.should.be.json; + testUtils.API.checkResponse (putBody, ['error']); + done(); + }); + }); + }); }); diff --git a/core/test/unit/api_settings_spec.js b/core/test/unit/api_settings_spec.js new file mode 100644 index 0000000000..348ca89388 --- /dev/null +++ b/core/test/unit/api_settings_spec.js @@ -0,0 +1,123 @@ +/*globals describe, before, beforeEach, afterEach, it */ +var testUtils = require('./utils'), + should = require('should'), + _ = require('underscore'), + request = require('request'), + // TODO: remove databaseVersion + expectedProperties = ['databaseVersion', 'title', 'description', 'email', 'logo', 'cover', 'defaultLang', + 'postsPerPage', 'forceI18n', 'activeTheme', 'activePlugins', 'installedPlugins', 'availableThemes']; + +request = request.defaults({jar:true}) + +describe('Settings API', function () { + + var user = testUtils.DataGenerator.forModel.users[0], + csrfToken = ''; + + before(function (done) { + testUtils.clearData() + .then(function () { + done(); + }, done); + }); + + beforeEach(function (done) { + this.timeout(5000); + testUtils.initData() + .then(function () { + testUtils.insertDefaultFixtures(); + }) + .then(function () { + // do a get request to get the CSRF token first + request.get(testUtils.API.getSigninURL(), function (error, response, body) { + var pattern_meta = //i; + pattern_meta.should.exist; + csrfToken = body.match(pattern_meta)[1]; + request.post({uri:testUtils.API.getSigninURL(), + headers: {'X-CSRF-Token': csrfToken}}, function (error, response, body) { + done(); + }).form({email: user.email, password: user.password}); + }); + }, done); + }); + + afterEach(function (done) { + testUtils.clearData().then(function () { + done(); + }, done); + }); + + // TODO: currently includes values of type=core + it('can retrieve all settings', function (done) { + request.get(testUtils.API.getApiURL('settings/'), function (error, response, body) { + response.should.have.status(200); + response.should.be.json; + var jsonResponse = JSON.parse(body); + jsonResponse.should.exist; + + testUtils.API.checkResponse (jsonResponse, expectedProperties); + done(); + }); + }); + it('can retrieve a setting', function (done) { + request.get(testUtils.API.getApiURL('settings/title/'), function (error, response, body) { + response.should.have.status(200); + response.should.be.json; + var jsonResponse = JSON.parse(body); + + jsonResponse.should.exist; + testUtils.API.checkResponse (jsonResponse, ['key','value']); + jsonResponse.key.should.eql('title'); + done(); + }); + }); + + it('can\'t retrieve non existent setting', function (done) { + request.get(testUtils.API.getApiURL('settings/testsetting/'), function (error, response, body) { + response.should.have.status(404); + response.should.be.json; + var jsonResponse = JSON.parse(body); + jsonResponse.should.exist; + testUtils.API.checkResponse (jsonResponse, ['error']); + done(); + }); + }); + + it('can edit settings', function (done) { + request.get(testUtils.API.getApiURL('settings'), function (error, response, body) { + var jsonResponse = JSON.parse(body), + changedValue = 'Ghost changed'; + jsonResponse.should.exist; + jsonResponse.title = changedValue; + + request.put({uri: testUtils.API.getApiURL('settings/'), + headers: {'X-CSRF-Token': csrfToken}, + json: jsonResponse}, function (error, response, putBody) { + response.should.have.status(200); + response.should.be.json; + putBody.should.exist; + putBody.title.should.eql(changedValue); + testUtils.API.checkResponse (putBody, expectedProperties); + done(); + }); + }); + }); + + it('can\'t edit non existent setting', function (done) { + request.get(testUtils.API.getApiURL('settings'), function (error, response, body) { + var jsonResponse = JSON.parse(body), + newValue = 'new value'; + jsonResponse.should.exist; + jsonResponse.testvalue = newValue; + + request.put({uri: testUtils.API.getApiURL('settings/'), + headers: {'X-CSRF-Token': csrfToken}, + json: jsonResponse}, function (error, response, putBody) { + response.should.have.status(404); + response.should.be.json; + testUtils.API.checkResponse (putBody, ['error']); + done(); + }); + }); + }); +}); diff --git a/core/test/unit/api_tags_spec.js b/core/test/unit/api_tags_spec.js new file mode 100644 index 0000000000..cd6a63c592 --- /dev/null +++ b/core/test/unit/api_tags_spec.js @@ -0,0 +1,60 @@ +/*globals describe, before, beforeEach, afterEach, it */ +var testUtils = require('./utils'), + should = require('should'), + _ = require('underscore'), + request = require('request'), + expectedProperties = ['id', 'uuid', 'name', 'slug', 'description', 'parent_id', + 'meta_title', 'meta_description', 'created_at', 'created_by', 'updated_at', 'updated_by']; + +request = request.defaults({jar:true}) + +describe('Tag API', function () { + + var user = testUtils.DataGenerator.forModel.users[0], + csrfToken = ''; + + before(function (done) { + testUtils.clearData() + .then(function () { + done(); + }, done); + }); + + beforeEach(function (done) { + this.timeout(5000); + testUtils.initData() + .then(function () { + testUtils.insertDefaultFixtures(); + }) + .then(function () { + // do a get request to get the CSRF token first + request.get(testUtils.API.getSigninURL(), function (error, response, body) { + var pattern_meta = //i; + pattern_meta.should.exist; + csrfToken = body.match(pattern_meta)[1]; + request.post({uri:testUtils.API.getSigninURL(), + headers: {'X-CSRF-Token': csrfToken}}, function (error, response, body) { + done(); + }).form({email: user.email, password: user.password}); + }); + }, done); + }); + + afterEach(function (done) { + testUtils.clearData().then(function () { + done(); + }, done); + }); + + it('can retrieve all tags', function (done) { + request.get(testUtils.API.getApiURL('tags/'), function (error, response, body) { + response.should.have.status(200); + response.should.be.json; + var jsonResponse = JSON.parse(body); + jsonResponse.should.exist; + jsonResponse.should.have.length(5); + testUtils.API.checkResponse (jsonResponse[0], expectedProperties); + done(); + }); + }); +}); diff --git a/core/test/unit/api_users_spec.js b/core/test/unit/api_users_spec.js new file mode 100644 index 0000000000..ea85b301ce --- /dev/null +++ b/core/test/unit/api_users_spec.js @@ -0,0 +1,106 @@ +/*globals describe, before, beforeEach, afterEach, it */ +var testUtils = require('./utils'), + should = require('should'), + _ = require('underscore'), + request = require('request'), + expectedProperties = ['id', 'uuid', 'name', 'slug', 'email', 'image', 'cover', 'bio', 'website', + 'location', 'accessibility', 'status', 'language', 'meta_title', 'meta_description', + 'created_at', 'updated_at']; + +request = request.defaults({jar:true}) + +describe('User API', function () { + + var user = testUtils.DataGenerator.forModel.users[0], + csrfToken = ''; + + before(function (done) { + testUtils.clearData() + .then(function () { + done(); + }, done); + }); + + beforeEach(function (done) { + this.timeout(5000); + testUtils.initData() + .then(function () { + testUtils.insertDefaultFixtures(); + }) + .then(function () { + // do a get request to get the CSRF token first + request.get(testUtils.API.getSigninURL(), function (error, response, body) { + var pattern_meta = //i; + pattern_meta.should.exist; + csrfToken = body.match(pattern_meta)[1]; + request.post({uri:testUtils.API.getSigninURL(), + headers: {'X-CSRF-Token': csrfToken}}, function (error, response, body) { + done(); + }).form({email: user.email, password: user.password}); + }); + }, done); + }); + + afterEach(function (done) { + testUtils.clearData().then(function () { + done(); + }, done); + }); + + it('can retrieve all users', function (done) { + request.get(testUtils.API.getApiURL('users/'), function (error, response, body) { + response.should.have.status(200); + response.should.be.json; + var jsonResponse = JSON.parse(body); + jsonResponse[0].should.exist; + + testUtils.API.checkResponse (jsonResponse[0], expectedProperties); + done(); + }); + }); + + it('can retrieve a user', function (done) { + request.get(testUtils.API.getApiURL('users/me/'), function (error, response, body) { + response.should.have.status(200); + response.should.be.json; + var jsonResponse = JSON.parse(body); + jsonResponse.should.exist; + + testUtils.API.checkResponse (jsonResponse, expectedProperties); + done(); + }); + }); + + it('can\'t retrieve non existent user', function (done) { + request.get(testUtils.API.getApiURL('users/99/'), function (error, response, body) { + response.should.have.status(404); + response.should.be.json; + var jsonResponse = JSON.parse(body); + jsonResponse.should.exist; + + testUtils.API.checkResponse (jsonResponse, ['error']); + done(); + }); + }); + + it('can edit a user', function (done) { + request.get(testUtils.API.getApiURL('users/me/'), function (error, response, body) { + var jsonResponse = JSON.parse(body), + changedValue = 'joe-bloggs.ghost.org'; + jsonResponse.should.exist; + jsonResponse.website = changedValue; + + request.put({uri: testUtils.API.getApiURL('users/me/'), + headers: {'X-CSRF-Token': csrfToken}, + json: jsonResponse}, function (error, response, putBody) { + response.should.have.status(200); + response.should.be.json; + putBody.should.exist; + putBody.website.should.eql(changedValue); + + testUtils.API.checkResponse (putBody, expectedProperties); + done(); + }); + }); + }); +}); diff --git a/core/test/unit/client_ghostdown_spec.js b/core/test/unit/client_ghostdown_spec.js index 7725cc16a6..e9a2beb560 100644 --- a/core/test/unit/client_ghostdown_spec.js +++ b/core/test/unit/client_ghostdown_spec.js @@ -6,7 +6,7 @@ */ /*globals describe, it */ -var testUtils = require('./testUtils'), +var testUtils = require('./utils'), should = require('should'), // Stuff we are testing diff --git a/core/test/unit/client_showdown_int_spec.js b/core/test/unit/client_showdown_int_spec.js index 88311df47a..9e0c13c37d 100644 --- a/core/test/unit/client_showdown_int_spec.js +++ b/core/test/unit/client_showdown_int_spec.js @@ -5,7 +5,7 @@ */ /*globals describe, it */ -var testUtils = require('./testUtils'), +var testUtils = require('./utils'), should = require('should'), // Stuff we are testing diff --git a/core/test/unit/errorHandling_spec.js b/core/test/unit/errorHandling_spec.js index 9c362e36c2..081f05e1e1 100644 --- a/core/test/unit/errorHandling_spec.js +++ b/core/test/unit/errorHandling_spec.js @@ -1,5 +1,5 @@ /*globals describe, beforeEach, it*/ -var testUtils = require('./testUtils'), +var testUtils = require('./utils'), should = require('should'), when = require('when'), sinon = require('sinon'), diff --git a/core/test/unit/export_spec.js b/core/test/unit/export_spec.js index eb83e5cdbb..fe1087dd19 100644 --- a/core/test/unit/export_spec.js +++ b/core/test/unit/export_spec.js @@ -1,5 +1,5 @@ /*globals describe, before, beforeEach, afterEach, it*/ -var testUtils = require('./testUtils'), +var testUtils = require('./utils'), should = require('should'), sinon = require('sinon'), when = require('when'), diff --git a/core/test/unit/ghost_spec.js b/core/test/unit/ghost_spec.js index 1e13872e4a..fcc7e4ed3d 100644 --- a/core/test/unit/ghost_spec.js +++ b/core/test/unit/ghost_spec.js @@ -1,5 +1,5 @@ /*globals describe, before, beforeEach, it*/ -var testUtils = require('./testUtils'), +var testUtils = require('./utils'), should = require('should'), sinon = require('sinon'), when = require('when'), diff --git a/core/test/unit/import_spec.js b/core/test/unit/import_spec.js index ff8f3e3d2a..b88e8948d2 100644 --- a/core/test/unit/import_spec.js +++ b/core/test/unit/import_spec.js @@ -1,5 +1,5 @@ /*globals describe, beforeEach, it*/ -var testUtils = require('./testUtils'), +var testUtils = require('./utils'), should = require('should'), sinon = require('sinon'), when = require('when'), diff --git a/core/test/unit/mail_spec.js b/core/test/unit/mail_spec.js index 9b2bda66f6..256bba3b8e 100644 --- a/core/test/unit/mail_spec.js +++ b/core/test/unit/mail_spec.js @@ -1,5 +1,5 @@ /*globals describe, beforeEach, afterEach, it*/ -var testUtils = require('./testUtils'), +var testUtils = require('./utils'), should = require('should'), sinon = require('sinon'), when = require('when'), diff --git a/core/test/unit/permissions_spec.js b/core/test/unit/permissions_spec.js index ba556a84b4..0aef8c8925 100644 --- a/core/test/unit/permissions_spec.js +++ b/core/test/unit/permissions_spec.js @@ -1,5 +1,5 @@ /*globals describe, before, beforeEach, afterEach, it*/ -var testUtils = require('./testUtils'), +var testUtils = require('./utils'), should = require('should'), sinon = require('sinon'), when = require('when'), diff --git a/core/test/unit/plugins_spec.js b/core/test/unit/plugins_spec.js index 4948c9321e..72f2c1e2bb 100644 --- a/core/test/unit/plugins_spec.js +++ b/core/test/unit/plugins_spec.js @@ -1,5 +1,5 @@ /*globals describe, beforeEach, afterEach, before, it*/ -var testUtils = require('./testUtils'), +var testUtils = require('./utils'), should = require('should'), sinon = require('sinon'), _ = require("underscore"), diff --git a/core/test/unit/server_helpers_index_spec.js b/core/test/unit/server_helpers_index_spec.js index 4e089ee7e9..527c2b76e2 100644 --- a/core/test/unit/server_helpers_index_spec.js +++ b/core/test/unit/server_helpers_index_spec.js @@ -1,5 +1,5 @@ /*globals describe, beforeEach, it*/ -var testUtils = require('./testUtils'), +var testUtils = require('./utils'), should = require('should'), sinon = require('sinon'), when = require('when'), diff --git a/core/test/unit/shared_gfm_spec.js b/core/test/unit/shared_gfm_spec.js index 211d16e0f0..3afe70cba9 100644 --- a/core/test/unit/shared_gfm_spec.js +++ b/core/test/unit/shared_gfm_spec.js @@ -4,7 +4,7 @@ */ /*globals describe, it */ -var testUtils = require('./testUtils'), +var testUtils = require('./utils'), should = require('should'), // Stuff we are testing diff --git a/core/test/unit/utils/api.js b/core/test/unit/utils/api.js index e3aaf015a3..b91dfb0d53 100644 --- a/core/test/unit/utils/api.js +++ b/core/test/unit/utils/api.js @@ -1,118 +1,26 @@ var _ = require('underscore'), - when = require('when'), - http = require('http'), - HttpMethods, - ApiRouteBase = '/ghost/api/v0.1/'; + url = require('url'), + ApiRouteBase = '/ghost/api/v0.1/', + host = 'localhost', + port = '2369'; + schema = "http://" -HttpMethods = { - GET: 'GET', - POST: 'POST', - PUT: 'PUT', - DELETE: 'DELETE' -}; - -function createRequest(httpMethod, overrides) { - return _.defaults(overrides, { - 'host': 'localhost', - 'port': '2369', - 'method': httpMethod - }); +function getApiURL (route) { + var baseURL = url.resolve(schema + host + ':' + port, ApiRouteBase); + return url.resolve(baseURL, route); +} +function getSigninURL () { + return url.resolve(schema + host + ':' + port, 'ghost/signin/'); } -function post(route, data, authCookie) { - var jsonData = JSON.stringify(data), - options = createRequest(HttpMethods.POST, { - path: route, - headers: { - 'Content-Type': 'application/json', - 'Content-Length': jsonData.length - } - }), - req, - response = '', - deferred = when.defer(); - - if (authCookie) { - options.headers['Cookie'] = authCookie; +// make sure the API only returns expected properties only +function checkResponse (jsonResponse, expectedProperties) { + Object.keys(jsonResponse).length.should.eql(expectedProperties.length); + for(var i=0; i