mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-12 16:14:25 +03:00
51ac3f6532
refs #2758 - Post, Tag & User API methods are refactored to use pipeline - Each functional code block is a named task function - Each function takes options, manipulates it, and returns options back - Tasks like permissions can reject if they don't pass, causing the pipeline to fail - Tasks like validating and converting options might be abstracted out into utils - the same for each endpoint - Tasks like the data call can be extremely complex if needs be (like for some user endpoints) - Option validation is mostly factored out to utils - Option conversion is factored out to utils - API utils have 100% test coverage - Minor updates to inline docs, more to do here
250 lines
10 KiB
JavaScript
250 lines
10 KiB
JavaScript
/*globals describe, it, beforeEach, afterEach */
|
|
/*jshint expr:true*/
|
|
var should = require('should'),
|
|
sinon = require('sinon'),
|
|
_ = require('lodash'),
|
|
Promise = require('bluebird'),
|
|
|
|
apiUtils = require('../../server/api/utils');
|
|
|
|
describe('API Utils', function () {
|
|
var sandbox;
|
|
|
|
beforeEach(function () {
|
|
sandbox = sinon.sandbox.create();
|
|
});
|
|
|
|
afterEach(function () {
|
|
sandbox.restore();
|
|
});
|
|
|
|
describe('validate', function () {
|
|
it('should create options when passed no args', function (done) {
|
|
apiUtils.validate()().then(function (options) {
|
|
options.should.eql({});
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('should pick data attrs when passed them', function (done) {
|
|
apiUtils.validate('test', ['id'])(
|
|
{id: 'test', status: 'all', uuid: 'other-test'}
|
|
).then(function (options) {
|
|
options.should.have.ownProperty('data');
|
|
options.data.should.have.ownProperty('id');
|
|
options.should.not.have.ownProperty('id');
|
|
options.data.id.should.eql('test');
|
|
|
|
options.data.should.not.have.ownProperty('status');
|
|
options.should.have.ownProperty('status');
|
|
options.status.should.eql('all');
|
|
|
|
options.should.have.ownProperty('uuid');
|
|
options.uuid.should.eql('other-test');
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('should check data if an object is passed', function (done) {
|
|
var object = {test: [{id: 1}]},
|
|
checkObjectStub = sandbox.stub(apiUtils, 'checkObject').returns(Promise.resolve(object));
|
|
|
|
apiUtils.validate('test')(object, {}).then(function (options) {
|
|
checkObjectStub.calledOnce.should.be.true;
|
|
checkObjectStub.calledWith(object, 'test').should.be.true;
|
|
options.should.have.ownProperty('data');
|
|
options.data.should.have.ownProperty('test');
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('should handle options being undefined', function (done) {
|
|
apiUtils.validate()(undefined).then(function (options) {
|
|
options.should.eql({});
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('should handle options being undefined when provided with object', function (done) {
|
|
var object = {test: [{id: 1}]},
|
|
checkObjectStub = sandbox.stub(apiUtils, 'checkObject').returns(Promise.resolve(object));
|
|
|
|
apiUtils.validate('test')(object, undefined).then(function (options) {
|
|
checkObjectStub.calledOnce.should.be.true;
|
|
checkObjectStub.calledWith(object, 'test').should.be.true;
|
|
options.should.have.ownProperty('data');
|
|
options.data.should.have.ownProperty('test');
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
});
|
|
|
|
describe('prepareInclude', function () {
|
|
it('should handle empty items', function () {
|
|
apiUtils.prepareInclude('', []).should.eql([]);
|
|
});
|
|
|
|
it('should be empty if there are no allowed includes', function () {
|
|
apiUtils.prepareInclude('a,b,c', []).should.eql([]);
|
|
});
|
|
|
|
it('should return correct includes', function () {
|
|
apiUtils.prepareInclude('a,b,c', ['a']).should.eql(['a']);
|
|
apiUtils.prepareInclude('a,b,c', ['a', 'c']).should.eql(['a', 'c']);
|
|
apiUtils.prepareInclude('a,b,c', ['a', 'd']).should.eql(['a']);
|
|
apiUtils.prepareInclude('a,b,c', ['d']).should.eql([]);
|
|
});
|
|
});
|
|
|
|
describe('convertOptions', function () {
|
|
it('should not call prepareInclude if there is no include option', function () {
|
|
var prepareIncludeStub = sandbox.stub(apiUtils, 'prepareInclude');
|
|
apiUtils.convertOptions(['a', 'b', 'c'])({}).should.eql({});
|
|
prepareIncludeStub.called.should.be.false;
|
|
});
|
|
|
|
it('should pass options.include to prepareInclude if provided', function () {
|
|
var expectedResult = ['a', 'b'],
|
|
prepareIncludeStub = sandbox.stub(apiUtils, 'prepareInclude').returns(expectedResult),
|
|
allowed = ['a', 'b', 'c'],
|
|
options = {include: 'a,b'},
|
|
actualResult;
|
|
actualResult = apiUtils.convertOptions(allowed)(_.clone(options));
|
|
|
|
prepareIncludeStub.calledOnce.should.be.true;
|
|
prepareIncludeStub.calledWith(options.include, allowed).should.be.true;
|
|
|
|
actualResult.should.have.hasOwnProperty('include');
|
|
actualResult.include.should.be.an.Array;
|
|
actualResult.include.should.eql(expectedResult);
|
|
});
|
|
});
|
|
|
|
describe('checkObject', function () {
|
|
it('throws an error if the object is empty', function (done) {
|
|
apiUtils.checkObject({}, 'test').then(function () {
|
|
done('This should have thrown an error');
|
|
}).catch(function (error) {
|
|
should.exist(error);
|
|
error.errorType.should.eql('BadRequestError');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('throws an error if the object key is empty', function (done) {
|
|
apiUtils.checkObject({test: []}, 'test').then(function () {
|
|
done('This should have thrown an error');
|
|
}).catch(function (error) {
|
|
should.exist(error);
|
|
error.errorType.should.eql('BadRequestError');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('throws an error if the object key is array with empty object', function (done) {
|
|
apiUtils.checkObject({test: [{}]}, 'test').then(function () {
|
|
done('This should have thrown an error');
|
|
}).catch(function (error) {
|
|
should.exist(error);
|
|
error.errorType.should.eql('BadRequestError');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('passed through a simple, correct object', function (done) {
|
|
var object = {test: [{id: 1}]};
|
|
apiUtils.checkObject(_.cloneDeep(object), 'test').then(function (data) {
|
|
should.exist(data);
|
|
data.should.have.ownProperty('test');
|
|
object.should.eql(data);
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('should do author_id to author conversion for posts', function (done) {
|
|
var object = {posts: [{id: 1, author: 4}]};
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts').then(function (data) {
|
|
should.exist(data);
|
|
data.should.have.ownProperty('posts');
|
|
data.should.not.eql(object);
|
|
data.posts.should.be.an.Array;
|
|
data.posts[0].should.have.ownProperty('author_id');
|
|
data.posts[0].should.not.have.ownProperty('author');
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('should not do author_id to author conversion for posts if not needed', function (done) {
|
|
var object = {posts: [{id: 1, author_id: 4}]};
|
|
apiUtils.checkObject(_.cloneDeep(object), 'posts').then(function (data) {
|
|
should.exist(data);
|
|
data.should.have.ownProperty('posts');
|
|
data.should.eql(object);
|
|
data.posts.should.be.an.Array;
|
|
data.posts[0].should.have.ownProperty('author_id');
|
|
data.posts[0].should.not.have.ownProperty('author');
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('should throw error if invalid editId if provided', function (done) {
|
|
var object = {test: [{id: 1}]};
|
|
apiUtils.checkObject(_.cloneDeep(object), 'test', 3).then(function () {
|
|
done('This should have thrown an error');
|
|
}).catch(function (error) {
|
|
should.exist(error);
|
|
error.errorType.should.eql('BadRequestError');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should ignore undefined editId', function (done) {
|
|
var object = {test: [{id: 1}]};
|
|
apiUtils.checkObject(_.cloneDeep(object), 'test', undefined).then(function (data) {
|
|
should.exist(data);
|
|
data.should.eql(object);
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('should ignore editId if object has no id', function (done) {
|
|
var object = {test: [{uuid: 1}]};
|
|
apiUtils.checkObject(_.cloneDeep(object), 'test', 3).then(function (data) {
|
|
should.exist(data);
|
|
data.should.eql(object);
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
});
|
|
|
|
describe('checkFileExists', function () {
|
|
it('should return true if file exists in input', function () {
|
|
apiUtils.checkFileExists({test: {type: 'file', path: 'path'}}, 'test').should.be.true;
|
|
});
|
|
|
|
it('should return false if file does not exist in input', function () {
|
|
apiUtils.checkFileExists({test: {type: 'file', path: 'path'}}, 'notthere').should.be.false;
|
|
});
|
|
|
|
it('should return false if file is incorrectly structured', function () {
|
|
apiUtils.checkFileExists({test: 'notafile'}, 'test').should.be.false;
|
|
});
|
|
});
|
|
|
|
describe('checkFileIsValid', function () {
|
|
it('returns true if file has valid extension and type', function () {
|
|
apiUtils.checkFileIsValid({name: 'test.txt', type: 'text'}, ['text'], ['.txt']).should.be.true;
|
|
apiUtils.checkFileIsValid({name: 'test.jpg', type: 'jpeg'}, ['text', 'jpeg'], ['.txt', '.jpg']).should.be.true;
|
|
});
|
|
|
|
it('returns false if file has invalid extension', function () {
|
|
apiUtils.checkFileIsValid({name: 'test.txt', type: 'text'}, ['text'], ['.tar']).should.be.false;
|
|
apiUtils.checkFileIsValid({name: 'test', type: 'text'}, ['text'], ['.txt']).should.be.false;
|
|
});
|
|
|
|
it('returns false if file has invalid type', function () {
|
|
apiUtils.checkFileIsValid({name: 'test.txt', type: 'text'}, ['archive'], ['.txt']).should.be.false;
|
|
});
|
|
});
|
|
});
|