mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-03 16:38:22 +03:00
parent
27714075b5
commit
a153400164
@ -25,5 +25,9 @@ module.exports = {
|
||||
|
||||
get webhooks() {
|
||||
return shared.pipeline(require('./webhooks'), localUtils);
|
||||
},
|
||||
|
||||
get posts() {
|
||||
return shared.pipeline(require('./posts'), localUtils);
|
||||
}
|
||||
};
|
||||
|
184
core/server/api/v2/posts.js
Normal file
184
core/server/api/v2/posts.js
Normal file
@ -0,0 +1,184 @@
|
||||
const models = require('../../models');
|
||||
const common = require('../../lib/common');
|
||||
const urlService = require('../../services/url');
|
||||
const allowedIncludes = ['created_by', 'updated_by', 'published_by', 'author', 'tags', 'authors', 'authors.roles'];
|
||||
const unsafeAttrs = ['author_id', 'status', 'authors'];
|
||||
|
||||
module.exports = {
|
||||
docName: 'posts',
|
||||
browse: {
|
||||
options: [
|
||||
'include',
|
||||
'filter',
|
||||
'fields',
|
||||
'formats',
|
||||
'status',
|
||||
'limit',
|
||||
'order',
|
||||
'debug'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: allowedIncludes
|
||||
},
|
||||
formats: {
|
||||
values: models.Post.allowedFormats
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: {
|
||||
unsafeAttrs: unsafeAttrs
|
||||
},
|
||||
query(frame) {
|
||||
return models.Post.findPage(frame.options);
|
||||
}
|
||||
},
|
||||
|
||||
read: {
|
||||
options: [
|
||||
'include',
|
||||
'filter',
|
||||
'fields',
|
||||
'status',
|
||||
'formats',
|
||||
'debug'
|
||||
],
|
||||
data: [
|
||||
'id',
|
||||
'slug',
|
||||
'status',
|
||||
'uuid'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: allowedIncludes
|
||||
},
|
||||
formats: {
|
||||
values: models.Post.allowedFormats
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: {
|
||||
unsafeAttrs: unsafeAttrs
|
||||
},
|
||||
query(frame) {
|
||||
return models.Post.findOne(frame.data, frame.options)
|
||||
.then((model) => {
|
||||
if (!model) {
|
||||
throw new common.errors.NotFoundError({
|
||||
message: common.i18n.t('errors.api.posts.postNotFound')
|
||||
});
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
add: {
|
||||
statusCode: 201,
|
||||
headers: {},
|
||||
options: [
|
||||
'include'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: allowedIncludes
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: {
|
||||
unsafeAttrs: unsafeAttrs
|
||||
},
|
||||
query(frame) {
|
||||
return models.Post.add(frame.data.posts[0], frame.options)
|
||||
.then((model) => {
|
||||
if (model.get('status') !== 'published') {
|
||||
this.headers.cacheInvalidate = false;
|
||||
} else {
|
||||
this.headers.cacheInvalidate = true;
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
edit: {
|
||||
headers: {},
|
||||
options: [
|
||||
'include',
|
||||
'id'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: allowedIncludes
|
||||
},
|
||||
id: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: {
|
||||
unsafeAttrs: unsafeAttrs
|
||||
},
|
||||
query(frame) {
|
||||
return models.Post.edit(frame.data.posts[0], frame.options)
|
||||
.then((model) => {
|
||||
if (model.get('status') === 'published' ||
|
||||
model.get('status') === 'draft' && model.updated('status') === 'published') {
|
||||
this.headers.cacheInvalidate = true;
|
||||
} else if (model.get('status') === 'draft' && model.updated('status') !== 'published') {
|
||||
this.headers.cacheInvalidate = {
|
||||
value: urlService.utils.urlFor({
|
||||
relativeUrl: urlService.utils.urlJoin('/p', model.get('uuid'), '/')
|
||||
})
|
||||
};
|
||||
} else {
|
||||
this.headers.cacheInvalidate = false;
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
destroy: {
|
||||
statusCode: 204,
|
||||
headers: {
|
||||
cacheInvalidate: true
|
||||
},
|
||||
options: [
|
||||
'include',
|
||||
'id'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: allowedIncludes
|
||||
},
|
||||
id: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: {
|
||||
unsafeAttrs: unsafeAttrs
|
||||
},
|
||||
query(frame) {
|
||||
frame.options.require = true;
|
||||
|
||||
return models.Post.destroy(frame.options)
|
||||
.return(null)
|
||||
.catch(models.Post.NotFoundError, () => {
|
||||
throw new common.errors.NotFoundError({
|
||||
message: common.i18n.t('errors.api.posts.postNotFound')
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
@ -1,5 +1,9 @@
|
||||
module.exports = {
|
||||
get pages() {
|
||||
return require('./pages');
|
||||
},
|
||||
|
||||
get posts() {
|
||||
return require('./posts');
|
||||
}
|
||||
};
|
||||
|
62
core/server/api/v2/utils/serializers/input/posts.js
Normal file
62
core/server/api/v2/utils/serializers/input/posts.js
Normal file
@ -0,0 +1,62 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
add(apiConfig, frame) {
|
||||
/**
|
||||
* Convert author property to author_id to match the name in the database.
|
||||
*
|
||||
* @deprecated: `author`, might be removed in Ghost 3.0
|
||||
*/
|
||||
if (frame.data.posts[0].hasOwnProperty('author')) {
|
||||
frame.data.posts[0].author_id = frame.data.posts[0].author;
|
||||
delete frame.data.posts[0].author;
|
||||
}
|
||||
|
||||
/**
|
||||
* CASE: we don't support updating nested-nested relations e.g. `post.authors[*].roles` yet.
|
||||
*
|
||||
* Bookshelf-relations supports this feature, BUT bookshelf's `hasChanged` fn will currently
|
||||
* clash with this, because `hasChanged` won't be able to tell if relations have changed or not.
|
||||
* It would always return `changed.roles = [....]`. It would always throw a model event that relations
|
||||
* were updated, which is not true.
|
||||
*
|
||||
* Bookshelf-relations can tell us if a relation has changed, it knows that.
|
||||
* But the connection between our model layer, Bookshelf's `hasChanged` fn and Bookshelf-relations
|
||||
* is not present. As long as we don't support this case, we have to ignore this.
|
||||
*/
|
||||
if (frame.data.posts[0].authors && frame.data.posts[0].authors.length) {
|
||||
_.each(frame.data.posts[0].authors, (author, index) => {
|
||||
if (author.hasOwnProperty('roles')) {
|
||||
delete frame.data.posts[0].authors[index].roles;
|
||||
}
|
||||
|
||||
if (author.hasOwnProperty('permissions')) {
|
||||
delete frame.data.posts[0].authors[index].permissions;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Model notation is: `tag.parent_id`.
|
||||
* The API notation is `tag.parent`.
|
||||
*/
|
||||
if (frame.data.posts[0].hasOwnProperty('tags')) {
|
||||
if (_.isArray(frame.data.posts[0].tags) && frame.data.posts[0].tags.length) {
|
||||
_.each(frame.data.posts[0].tags, (tag, index) => {
|
||||
if (tag.hasOwnProperty('parent')) {
|
||||
frame.data.posts[0].tags[index].parent_id = tag.parent;
|
||||
delete frame.data.posts[0].tags[index].parent;
|
||||
}
|
||||
|
||||
if (tag.hasOwnProperty('posts')) {
|
||||
delete frame.data.posts[0].tags[index].posts;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
edit(apiConfig, frame) {
|
||||
this.add(apiConfig, frame);
|
||||
}
|
||||
};
|
@ -13,5 +13,9 @@ module.exports = {
|
||||
|
||||
get webhooks() {
|
||||
return require('./webhooks');
|
||||
},
|
||||
|
||||
get posts() {
|
||||
return require('./posts');
|
||||
}
|
||||
};
|
||||
|
100
core/server/api/v2/utils/serializers/output/posts.js
Normal file
100
core/server/api/v2/utils/serializers/output/posts.js
Normal file
@ -0,0 +1,100 @@
|
||||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:posts');
|
||||
const urlService = require('../../../../../services/url');
|
||||
|
||||
// @TODO: refactor if we add users+tags controllers
|
||||
const urlsForUser = (user) => {
|
||||
user.url = urlService.getUrlByResourceId(user.id, {absolute: true});
|
||||
|
||||
if (user.profile_image) {
|
||||
user.profile_image = urlService.utils.urlFor('image', {image: user.profile_image}, true);
|
||||
}
|
||||
|
||||
if (user.cover_image) {
|
||||
user.cover_image = urlService.utils.urlFor('image', {image: user.cover_image}, true);
|
||||
}
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
const urlsForTag = (tag) => {
|
||||
tag.url = urlService.getUrlByResourceId(tag.id, {absolute: true});
|
||||
|
||||
if (tag.feature_image) {
|
||||
tag.feature_image = urlService.utils.urlFor('image', {image: tag.feature_image}, true);
|
||||
}
|
||||
|
||||
return tag;
|
||||
};
|
||||
|
||||
// @TODO: Update the url decoration in https://github.com/TryGhost/Ghost/pull/9969.
|
||||
const absoluteUrls = (attrs, options) => {
|
||||
attrs.url = urlService.getUrlByResourceId(attrs.id, {absolute: true});
|
||||
|
||||
if (attrs.feature_image) {
|
||||
attrs.feature_image = urlService.utils.urlFor('image', {image: attrs.feature_image}, true);
|
||||
}
|
||||
|
||||
if (attrs.og_image) {
|
||||
attrs.og_image = urlService.utils.urlFor('image', {image: attrs.og_image}, true);
|
||||
}
|
||||
|
||||
if (attrs.twitter_image) {
|
||||
attrs.twitter_image = urlService.utils.urlFor('image', {image: attrs.twitter_image}, true);
|
||||
}
|
||||
|
||||
if (attrs.html) {
|
||||
attrs.html = urlService.utils.makeAbsoluteUrls(attrs.html, urlService.utils.urlFor('home', true), attrs.url).html();
|
||||
}
|
||||
|
||||
if (options.columns && !options.columns.includes('url')) {
|
||||
delete attrs.url;
|
||||
}
|
||||
|
||||
if (options && options.withRelated) {
|
||||
options.withRelated.forEach((relation) => {
|
||||
// @NOTE: this block also decorates primary_tag/primary_author objects as they
|
||||
// are being passed by reference in tags/authors. Might be refactored into more explicit call
|
||||
// in the future, but is good enough for current use-case
|
||||
if (relation === 'tags' && attrs.tags) {
|
||||
attrs.tags = attrs.tags.map(tag => urlsForTag(tag));
|
||||
}
|
||||
|
||||
if (relation === 'author' && attrs.author) {
|
||||
attrs.author = urlsForUser(attrs.author);
|
||||
}
|
||||
|
||||
if (relation === 'authors' && attrs.authors) {
|
||||
attrs.authors = attrs.authors.map(author => urlsForUser(author));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return attrs;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
all(models, apiConfig, frame) {
|
||||
debug('all');
|
||||
|
||||
// CASE: e.g. destroy returns null
|
||||
if (!models) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (models.meta) {
|
||||
frame.response = {
|
||||
posts: models.data.map(model => absoluteUrls(model.toJSON(frame.options), frame.options)),
|
||||
meta: models.meta
|
||||
};
|
||||
|
||||
debug(frame.response);
|
||||
return;
|
||||
}
|
||||
|
||||
frame.response = {
|
||||
posts: [absoluteUrls(models.toJSON(frame.options), frame.options)]
|
||||
};
|
||||
|
||||
debug(frame.response);
|
||||
}
|
||||
};
|
@ -1 +1,5 @@
|
||||
module.exports = {};
|
||||
module.exports = {
|
||||
get posts() {
|
||||
return require('./posts');
|
||||
}
|
||||
};
|
||||
|
49
core/server/api/v2/utils/validators/input/posts.js
Normal file
49
core/server/api/v2/utils/validators/input/posts.js
Normal file
@ -0,0 +1,49 @@
|
||||
const _ = require('lodash');
|
||||
const Promise = require('bluebird');
|
||||
const common = require('../../../../../lib/common');
|
||||
|
||||
module.exports = {
|
||||
add(apiConfig, frame) {
|
||||
/**
|
||||
* Ensure correct incoming `post.authors` structure.
|
||||
*
|
||||
* NOTE:
|
||||
* The `post.authors[*].id` attribute is required till we release Ghost 3.0.
|
||||
* Ghost 1.x keeps the deprecated support for `post.author_id`, which is the primary author id and needs to be
|
||||
* updated if the order of the `post.authors` array changes.
|
||||
* If we allow adding authors via the post endpoint e.g. `authors=[{name: 'newuser']` (no id property), it's hard
|
||||
* to update the primary author id (`post.author_id`), because the new author `id` is generated when attaching
|
||||
* the author to the post. And the attach operation happens in bookshelf-relations, which happens after
|
||||
* the event handling in the post model.
|
||||
*
|
||||
* It's solvable, but not worth right now solving, because the admin UI does not support this feature.
|
||||
*
|
||||
* TLDR; You can only attach existing authors to a post.
|
||||
*
|
||||
* @TODO: remove `id` restriction in Ghost 3.0
|
||||
*/
|
||||
if (frame.data.posts[0].hasOwnProperty('authors')) {
|
||||
if (!_.isArray(frame.data.posts[0].authors) ||
|
||||
(frame.data.posts[0].authors.length && _.filter(frame.data.posts[0].authors, 'id').length !== frame.data.posts[0].authors.length)) {
|
||||
return Promise.reject(new common.errors.BadRequestError({
|
||||
message: common.i18n.t('errors.api.utils.invalidStructure', {key: 'posts[*].authors'})
|
||||
}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
edit(apiConfig, frame) {
|
||||
const result = this.add(apiConfig, frame);
|
||||
|
||||
if (result instanceof Promise) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (frame.options.id && frame.data[apiConfig.docName][0].id
|
||||
&& frame.options.id !== frame.data[apiConfig.docName][0].id) {
|
||||
return Promise.reject(new common.errors.BadRequestError({
|
||||
message: common.i18n.t('errors.api.utils.invalidIdProvided')
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
@ -26,13 +26,12 @@ module.exports = function apiRoutes() {
|
||||
router.get('/configuration/:key', mw.authAdminAPI, api.http(api.configuration.read));
|
||||
|
||||
// ## Posts
|
||||
router.get('/posts', mw.authAdminAPI, api.http(api.posts.browse));
|
||||
|
||||
router.post('/posts', mw.authAdminAPI, api.http(api.posts.add));
|
||||
router.get('/posts/:id', mw.authAdminAPI, api.http(api.posts.read));
|
||||
router.get('/posts/slug/:slug', mw.authAdminAPI, api.http(api.posts.read));
|
||||
router.put('/posts/:id', mw.authAdminAPI, api.http(api.posts.edit));
|
||||
router.del('/posts/:id', mw.authAdminAPI, api.http(api.posts.destroy));
|
||||
router.get('/posts', mw.authAdminAPI, apiv2.http(apiv2.posts.browse));
|
||||
router.post('/posts', mw.authAdminAPI, apiv2.http(apiv2.posts.add));
|
||||
router.get('/posts/:id', mw.authAdminAPI, apiv2.http(apiv2.posts.read));
|
||||
router.get('/posts/slug/:slug', mw.authAdminAPI, apiv2.http(apiv2.posts.read));
|
||||
router.put('/posts/:id', mw.authAdminAPI, apiv2.http(apiv2.posts.edit));
|
||||
router.del('/posts/:id', mw.authAdminAPI, apiv2.http(apiv2.posts.destroy));
|
||||
|
||||
// ## Schedules
|
||||
router.put('/schedules/posts/:id', [
|
||||
|
541
core/test/functional/api/v2/admin/posts_spec.js
Normal file
541
core/test/functional/api/v2/admin/posts_spec.js
Normal file
@ -0,0 +1,541 @@
|
||||
const should = require('should');
|
||||
const supertest = require('supertest');
|
||||
const _ = require('lodash');
|
||||
const ObjectId = require('bson-objectid');
|
||||
const moment = require('moment-timezone');
|
||||
const testUtils = require('../../../../utils');
|
||||
const localUtils = require('./utils');
|
||||
const config = require('../../../../../../core/server/config');
|
||||
const ghost = testUtils.startGhost;
|
||||
let request;
|
||||
|
||||
describe('Posts API V2', function () {
|
||||
let ghostServer;
|
||||
|
||||
describe('As Owner', function () {
|
||||
let ownerCookie;
|
||||
|
||||
before(function () {
|
||||
return ghost()
|
||||
.then(function (_ghostServer) {
|
||||
ghostServer = _ghostServer;
|
||||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return localUtils.doAuth(request, 'users:extra', 'posts');
|
||||
})
|
||||
.then(function (cookie) {
|
||||
ownerCookie = cookie;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Browse', function () {
|
||||
it('retrieves all published posts only by default', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.posts);
|
||||
testUtils.API.checkResponse(jsonResponse, 'posts');
|
||||
jsonResponse.posts.should.have.length(11);
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
|
||||
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
|
||||
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
|
||||
|
||||
// Ensure default order
|
||||
jsonResponse.posts[0].slug.should.eql('welcome');
|
||||
jsonResponse.posts[10].slug.should.eql('html-ipsum');
|
||||
|
||||
// Absolute urls by default
|
||||
jsonResponse.posts[0].url.should.eql(`${config.get('url')}/welcome/`);
|
||||
jsonResponse.posts[9].feature_image.should.eql(`${config.get('url')}/content/images/2018/hey.jpg`);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can retrieve multiple post formats', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?formats=plaintext,mobiledoc&limit=3&order=title%20ASC'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.posts);
|
||||
testUtils.API.checkResponse(jsonResponse, 'posts');
|
||||
jsonResponse.posts.should.have.length(3);
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0], 'post', ['mobiledoc', 'plaintext'], ['html']);
|
||||
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
|
||||
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
|
||||
|
||||
// ensure order works
|
||||
jsonResponse.posts[0].slug.should.eql('apps-integrations');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fields & formats combined', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?formats=mobiledoc,html&fields=id,title'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.posts);
|
||||
testUtils.API.checkResponse(jsonResponse, 'posts');
|
||||
jsonResponse.posts.should.have.length(11);
|
||||
|
||||
testUtils.API.checkResponse(
|
||||
jsonResponse.posts[0],
|
||||
'post',
|
||||
null,
|
||||
null,
|
||||
['mobiledoc', 'id', 'title', 'html']
|
||||
);
|
||||
|
||||
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('with includes', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?include=tags,authors'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.posts);
|
||||
testUtils.API.checkResponse(jsonResponse, 'posts');
|
||||
jsonResponse.posts.should.have.length(11);
|
||||
testUtils.API.checkResponse(
|
||||
jsonResponse.posts[0],
|
||||
'post',
|
||||
['tags', 'authors']
|
||||
);
|
||||
|
||||
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
|
||||
jsonResponse.posts[0].tags.length.should.eql(1);
|
||||
jsonResponse.posts[0].authors.length.should.eql(1);
|
||||
jsonResponse.posts[0].tags[0].url.should.eql(`${config.get('url')}/tag/getting-started/`);
|
||||
jsonResponse.posts[0].authors[0].url.should.eql(`${config.get('url')}/author/ghost/`);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fields combined with formats and include', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?formats=mobiledoc,html&fields=id,title&include=authors'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.posts);
|
||||
testUtils.API.checkResponse(jsonResponse, 'posts');
|
||||
jsonResponse.posts.should.have.length(11);
|
||||
testUtils.API.checkResponse(
|
||||
jsonResponse.posts[0],
|
||||
'post',
|
||||
null,
|
||||
null,
|
||||
['mobiledoc', 'id', 'title', 'html', 'authors']
|
||||
);
|
||||
|
||||
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can use a filter', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?filter=page:[false,true]&status=all'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse.posts);
|
||||
testUtils.API.checkResponse(jsonResponse, 'posts');
|
||||
jsonResponse.posts.should.have.length(15);
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
|
||||
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('read', function () {
|
||||
it('by id', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.posts);
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
|
||||
jsonResponse.posts[0].id.should.equal(testUtils.DataGenerator.Content.posts[0].id);
|
||||
jsonResponse.posts[0].page.should.not.be.ok();
|
||||
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
|
||||
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
|
||||
jsonResponse.posts[0].author.should.be.a.String();
|
||||
testUtils.API.isISO8601(jsonResponse.posts[0].created_at).should.be.true();
|
||||
jsonResponse.posts[0].created_by.should.be.a.String();
|
||||
// Tags aren't included by default
|
||||
should.not.exist(jsonResponse.posts[0].tags);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('by id, with formats', function (done) {
|
||||
request
|
||||
.get(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/?formats=plaintext,mobiledoc'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse.posts);
|
||||
jsonResponse.posts.should.have.length(1);
|
||||
jsonResponse.posts[0].id.should.equal(testUtils.DataGenerator.Content.posts[0].id);
|
||||
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0], 'post', ['mobiledoc', 'plaintext'], ['html']);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can retrieve a post by slug', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/slug/welcome/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.posts);
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
|
||||
jsonResponse.posts[0].slug.should.equal('welcome');
|
||||
jsonResponse.posts[0].page.should.not.be.ok();
|
||||
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
|
||||
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
|
||||
jsonResponse.posts[0].author.should.be.a.String();
|
||||
jsonResponse.posts[0].created_by.should.be.a.String();
|
||||
// Tags aren't included by default
|
||||
should.not.exist(jsonResponse.posts[0].tags);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('with includes', function (done) {
|
||||
request
|
||||
.get(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/?include=authors,tags,created_by'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.posts);
|
||||
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0], 'post', ['tags', 'authors']);
|
||||
|
||||
jsonResponse.posts[0].author.should.be.a.String();
|
||||
jsonResponse.posts[0].page.should.not.be.ok();
|
||||
|
||||
jsonResponse.posts[0].authors[0].should.be.an.Object();
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0].authors[0], 'user', ['url']);
|
||||
|
||||
jsonResponse.posts[0].tags[0].should.be.an.Object();
|
||||
testUtils.API.checkResponse(jsonResponse.posts[0].tags[0], 'tag', ['url']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can\'t retrieve non existent post', function (done) {
|
||||
request.get(localUtils.API.getApiQuery(`posts/${ObjectId.generate()}/`))
|
||||
.set('Origin', config.get('url'))
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(404)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.errors);
|
||||
testUtils.API.checkResponseValue(jsonResponse.errors[0], ['message', 'errorType']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('add', function () {
|
||||
it('default', function () {
|
||||
const post = {
|
||||
title: 'My post',
|
||||
status: 'draft',
|
||||
published_at: '2016-05-30T07:00:00.000Z',
|
||||
mobiledoc: testUtils.DataGenerator.markdownToMobiledoc('my post'),
|
||||
created_at: moment().subtract(2, 'days').toDate(),
|
||||
updated_at: moment().subtract(2, 'days').toDate(),
|
||||
created_by: ObjectId.generate(),
|
||||
updated_by: ObjectId.generate()
|
||||
};
|
||||
|
||||
return request.post(localUtils.API.getApiQuery('posts'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({posts: [post]})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(201)
|
||||
.then((res) => {
|
||||
res.body.posts.length.should.eql(1);
|
||||
testUtils.API.checkResponse(res.body.posts[0], 'post');
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
|
||||
res.body.posts[0].title.should.eql(post.title);
|
||||
res.body.posts[0].status.should.eql(post.status);
|
||||
res.body.posts[0].published_at.should.eql('2016-05-30T07:00:00.000Z');
|
||||
res.body.posts[0].published_at = '2016-05-30T09:00:00.000Z';
|
||||
res.body.posts[0].created_at.should.not.eql(post.created_at.toISOString());
|
||||
res.body.posts[0].updated_at.should.not.eql(post.updated_at.toISOString());
|
||||
res.body.posts[0].updated_by.should.not.eql(post.updated_by);
|
||||
res.body.posts[0].created_by.should.not.eql(post.created_by);
|
||||
});
|
||||
});
|
||||
|
||||
it('published post', function () {
|
||||
const post = {
|
||||
posts: [{
|
||||
status: 'published'
|
||||
}]
|
||||
};
|
||||
|
||||
return request.post(localUtils.API.getApiQuery('posts'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send(post)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(201)
|
||||
.then((res) => {
|
||||
res.body.posts.length.should.eql(1);
|
||||
testUtils.API.checkResponse(res.body.posts[0], 'post');
|
||||
res.body.posts[0].status.should.eql('published');
|
||||
res.headers['x-cache-invalidate'].should.eql('/*');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit', function () {
|
||||
it('default', function () {
|
||||
const post = {
|
||||
title: 'My new Title',
|
||||
author: testUtils.DataGenerator.Content.extraUsers[0].id,
|
||||
custom_template: 'custom-about'
|
||||
};
|
||||
|
||||
return request
|
||||
.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({posts: [post]})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
res.headers['x-cache-invalidate'].should.eql('/*');
|
||||
testUtils.API.checkResponse(res.body.posts[0], 'post');
|
||||
|
||||
res.body.posts[0].title.should.eql(post.title);
|
||||
res.body.posts[0].author.should.eql(post.author);
|
||||
res.body.posts[0].status.should.eql('published');
|
||||
res.body.posts[0].custom_template.should.eql('custom-about');
|
||||
});
|
||||
});
|
||||
|
||||
it('update dates', function () {
|
||||
const post = {
|
||||
created_by: ObjectId.generate(),
|
||||
updated_by: ObjectId.generate(),
|
||||
created_at: moment().add(2, 'days').format(),
|
||||
updated_at: moment().add(2, 'days').format()
|
||||
};
|
||||
|
||||
return request
|
||||
.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({posts: [post]})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
res.headers['x-cache-invalidate'].should.eql('/*');
|
||||
testUtils.API.checkResponse(res.body.posts[0], 'post');
|
||||
|
||||
// We expect that the changed properties aren't changed, they are still the same than before.
|
||||
res.body.posts[0].created_by.should.not.eql(post.created_by);
|
||||
res.body.posts[0].updated_by.should.not.eql(post.updated_by);
|
||||
res.body.posts[0].created_at.should.not.eql(post.created_at);
|
||||
|
||||
// `updated_at` is automatically set, but it's not the date we send to override.
|
||||
res.body.posts[0].updated_at.should.not.eql(post.updated_at);
|
||||
});
|
||||
});
|
||||
|
||||
it('update draft', function () {
|
||||
const post = {
|
||||
title: 'update draft'
|
||||
};
|
||||
|
||||
return request.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[3].id))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({posts: [post]})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
res.headers['x-cache-invalidate'].should.eql('/p/' + res.body.posts[0].uuid + '/');
|
||||
});
|
||||
});
|
||||
|
||||
it('unpublish', function () {
|
||||
const post = {
|
||||
status: 'draft'
|
||||
};
|
||||
|
||||
return request
|
||||
.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[1].id + '/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({posts: [post]})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
res.headers['x-cache-invalidate'].should.eql('/*');
|
||||
res.body.posts[0].status.should.eql('draft');
|
||||
});
|
||||
});
|
||||
|
||||
it('published_at = null', function () {
|
||||
return request
|
||||
.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({
|
||||
posts: [{published_at: null}]
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
res.headers['x-cache-invalidate'].should.eql('/*');
|
||||
should.exist(res.body.posts);
|
||||
should.exist(res.body.posts[0].published_at);
|
||||
testUtils.API.checkResponse(res.body.posts[0], 'post');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('destroy', function () {
|
||||
it('default', function () {
|
||||
return request
|
||||
.del(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(204)
|
||||
.then((res) => {
|
||||
res.body.should.be.empty();
|
||||
res.headers['x-cache-invalidate'].should.eql('/*');
|
||||
});
|
||||
});
|
||||
|
||||
it('non existent post', function () {
|
||||
return request
|
||||
.del(localUtils.API.getApiQuery('posts/' + ObjectId.generate() + '/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(404)
|
||||
.then((res) => {
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
should.exist(res.body);
|
||||
should.exist(res.body.errors);
|
||||
testUtils.API.checkResponseValue(res.body.errors[0], ['message', 'errorType']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -168,6 +168,23 @@ describe('Unit: api/shared/validators/input/all', function () {
|
||||
should.exist(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('invalid fields', function () {
|
||||
const frame = {
|
||||
options: {
|
||||
context: {},
|
||||
id: 'invalid'
|
||||
}
|
||||
};
|
||||
|
||||
const apiConfig = {};
|
||||
|
||||
return shared.validators.input.all.all(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
should.exist(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('browse', function () {
|
||||
|
83
core/test/unit/api/v2/utils/serializers/output/posts_spec.js
Normal file
83
core/test/unit/api/v2/utils/serializers/output/posts_spec.js
Normal file
@ -0,0 +1,83 @@
|
||||
const should = require('should');
|
||||
const sinon = require('sinon');
|
||||
const testUtils = require('../../../../../../utils');
|
||||
const urlService = require('../../../../../../../server/services/url');
|
||||
const serializers = require('../../../../../../../server/api/v2/utils/serializers');
|
||||
const sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('Unit: v2/utils/serializers/output/posts', function () {
|
||||
let postModel;
|
||||
|
||||
beforeEach(function () {
|
||||
postModel = (data) => {
|
||||
return {
|
||||
toJSON: sandbox.stub().returns(data)
|
||||
};
|
||||
};
|
||||
|
||||
sandbox.stub(urlService, 'getUrlByResourceId').returns('getUrlByResourceId');
|
||||
sandbox.stub(urlService.utils, 'urlFor').returns('urlFor');
|
||||
sandbox.stub(urlService.utils, 'makeAbsoluteUrls').returns({html: sandbox.stub()});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('Ensure absolute urls are returned by default', function () {
|
||||
it('meta & models & relations', function () {
|
||||
const apiConfig = {};
|
||||
const frame = {
|
||||
options: {
|
||||
withRelated: ['tags', 'authors']
|
||||
}
|
||||
};
|
||||
|
||||
const ctrlResponse = {
|
||||
data: [
|
||||
postModel(testUtils.DataGenerator.forKnex.createPost({
|
||||
id: 'id1',
|
||||
feature_image: 'value',
|
||||
tags: [{
|
||||
id: 'id3',
|
||||
feature_image: 'value'
|
||||
}],
|
||||
authors: [{
|
||||
id: 'id4',
|
||||
name: 'Ghosty'
|
||||
}]
|
||||
})),
|
||||
postModel(testUtils.DataGenerator.forKnex.createPost({
|
||||
id: 'id2',
|
||||
html: '<img href=/content/test.jpf'
|
||||
|
||||
}))
|
||||
],
|
||||
meta: {}
|
||||
};
|
||||
|
||||
serializers.output.posts.all(ctrlResponse, apiConfig, frame);
|
||||
|
||||
frame.response.posts[0].hasOwnProperty('url').should.be.true();
|
||||
frame.response.posts[0].tags[0].hasOwnProperty('url').should.be.true();
|
||||
frame.response.posts[0].authors[0].hasOwnProperty('url').should.be.true();
|
||||
frame.response.posts[1].hasOwnProperty('url').should.be.true();
|
||||
|
||||
urlService.utils.urlFor.callCount.should.eql(4);
|
||||
urlService.utils.urlFor.getCall(0).args.should.eql(['image', {image: 'value'}, true]);
|
||||
urlService.utils.urlFor.getCall(1).args.should.eql(['home', true]);
|
||||
urlService.utils.urlFor.getCall(2).args.should.eql(['image', {image: 'value'}, true]);
|
||||
urlService.utils.urlFor.getCall(3).args.should.eql(['home', true]);
|
||||
|
||||
urlService.utils.makeAbsoluteUrls.callCount.should.eql(2);
|
||||
urlService.utils.makeAbsoluteUrls.getCall(0).args.should.eql(['## markdown', 'urlFor', 'getUrlByResourceId']);
|
||||
urlService.utils.makeAbsoluteUrls.getCall(1).args.should.eql(['<img href=/content/test.jpf', 'urlFor', 'getUrlByResourceId']);
|
||||
|
||||
urlService.getUrlByResourceId.callCount.should.eql(4);
|
||||
urlService.getUrlByResourceId.getCall(0).args.should.eql(['id1', {absolute: true}]);
|
||||
urlService.getUrlByResourceId.getCall(1).args.should.eql(['id3', {absolute: true}]);
|
||||
urlService.getUrlByResourceId.getCall(2).args.should.eql(['id4', {absolute: true}]);
|
||||
urlService.getUrlByResourceId.getCall(3).args.should.eql(['id2', {absolute: true}]);
|
||||
});
|
||||
});
|
||||
});
|
105
core/test/unit/api/v2/utils/validators/input/posts_spec.js
Normal file
105
core/test/unit/api/v2/utils/validators/input/posts_spec.js
Normal file
@ -0,0 +1,105 @@
|
||||
const should = require('should');
|
||||
const Promise = require('bluebird');
|
||||
const common = require('../../../../../../../server/lib/common');
|
||||
const validators = require('../../../../../../../server/api/v2/utils/validators');
|
||||
|
||||
describe('Unit: v2/utils/validators/input/posts', function () {
|
||||
describe('add', function () {
|
||||
it('authors structure', function () {
|
||||
const apiConfig = {
|
||||
docName: 'posts'
|
||||
};
|
||||
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
posts: [
|
||||
{
|
||||
authors: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.posts.add(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.BadRequestError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('authors structure', function () {
|
||||
const apiConfig = {
|
||||
docName: 'posts'
|
||||
};
|
||||
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
posts: [
|
||||
{
|
||||
authors: [{
|
||||
name: 'hey'
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.posts.add(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.BadRequestError).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('authors structure', function () {
|
||||
const apiConfig = {
|
||||
docName: 'posts'
|
||||
};
|
||||
|
||||
const frame = {
|
||||
options: {},
|
||||
data: {
|
||||
posts: [
|
||||
{
|
||||
authors: [{
|
||||
id: 'correct',
|
||||
name: 'ja'
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.posts.add(apiConfig, frame);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit', function () {
|
||||
it('id mismatch', function () {
|
||||
const apiConfig = {
|
||||
docName: 'posts'
|
||||
};
|
||||
|
||||
const frame = {
|
||||
options: {
|
||||
id: 'zwei'
|
||||
},
|
||||
data: {
|
||||
posts: [
|
||||
{
|
||||
id: 'eins'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return validators.input.posts.edit(apiConfig, frame)
|
||||
.then(Promise.reject)
|
||||
.catch((err) => {
|
||||
(err instanceof common.errors.BadRequestError).should.be.true();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user