0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

🔥 Removed V1 code/references in frontend resources/routing layer (#11087)

no issue

- Removed v1 'author' leftover in include statement for preview controller
- Removed v1 'author' leftover in include statement for preview controller
- Removed v1 'author' leftover in include statement in entry lookup routing helper
- Migrated related test to use v2 API controller
- Removed v0.1 routing confif
- Removed v0.1 url config
- Fixed tests that had to do with url's in resources after removing v0.1 resources from URL cache
- Removed v1 'author' leftover in include statement in static routing helper
- Modified the test to use v2 API
- Removed v1 specific condition with 'page' in context helper
- Fixed dynamic routing spec after theme switch to v2. All tested users have to have at least one published post to be shown as an author
- Fixed URL Service spec to use theme engine v2
This commit is contained in:
Naz Gargol 2019-09-10 11:41:42 +02:00 committed by GitHub
parent a9050f68ea
commit 7dc38e2078
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 143 additions and 2964 deletions

View file

@ -1,70 +0,0 @@
/* eslint-disable */
module.exports.QUERY = {
tag: {
controller: 'tags',
type: 'read',
resource: 'tags',
options: {
slug: '%s',
visibility: 'public'
}
},
author: {
resourceAlias: 'authors',
controller: 'users',
type: 'read',
resource: 'users',
options: {
slug: '%s',
visibility: 'public'
}
},
user: {
resourceAlias: 'authors',
controller: 'users',
type: 'read',
resource: 'users',
options: {
slug: '%s',
visibility: 'public'
}
},
post: {
controller: 'posts',
type: 'read',
resource: 'posts',
options: {
slug: '%s',
status: 'published',
page: 0
}
},
page: {
controller: 'posts',
type: 'read',
resource: 'posts',
options: {
slug: '%s',
status: 'published',
page: 1
}
},
preview: {
controller: 'posts',
resource: 'posts'
}
};
module.exports.TAXONOMIES = {
tag: {
filter: 'tags:\'%s\'+tags.visibility:public',
editRedirect: '#/settings/tags/:slug/',
resource: 'tags'
},
author: {
filter: 'authors:\'%s\'',
editRedirect: '#/team/:slug/',
resource: 'authors'
}
};
/* eslint-enable */

View file

@ -67,13 +67,6 @@ module.exports = function entryController(req, res, next) {
* @NOTE:
*
* Ensure we redirect to the correct post url including subdirectory.
*
* @NOTE:
* Keep in mind, that the logic here is used for v0.1 and v2.
* v0.1 returns relative urls, v2 returns absolute urls.
*
* @TODO:
* Simplify if we drop v0.1.
*/
if (urlUtils.absoluteToRelative(entry.url, {withoutSubdirectory: true}) !== req.path) {
debug('redirect');

View file

@ -18,8 +18,7 @@ module.exports = function previewController(req, res, next) {
const params = {
uuid: req.params.uuid,
status: 'all',
// @TODO: Remove "author" if we drop v0.1
include: 'author,authors,tags'
include: 'authors,tags'
};
return api[res.routerOptions.query.controller]

View file

@ -14,8 +14,7 @@ function processQuery(query, locals) {
// We override the `include` property for now, because the full data set is required anyway.
if (_.get(query, 'resource') === 'posts') {
_.extend(query.options, {
// @TODO: Remove "author" when we drop v0.1
include: 'author,authors,tags'
include: 'authors,tags'
});
}

View file

@ -43,7 +43,7 @@ function setResponseContext(req, res, data) {
}
// Add context 'amp' to either post or page, if we have an `*/amp` route
if (ampPattern.test(res.locals.relativeUrl) && data.post) {
if (ampPattern.test(res.locals.relativeUrl) && (data.post || data.page)) {
res.locals.context.push('amp');
}
@ -64,12 +64,7 @@ function setResponseContext(req, res, data) {
}
}
// @TODO: remove first if condition when we drop v0.1
if (data && data.post && data.post.page) {
if (!res.locals.context.includes('page')) {
res.locals.context.push('page');
}
} else if (data && data.post) {
if (data && data.post) {
if (!res.locals.context.includes('post')) {
res.locals.context.push('post');
}

View file

@ -40,11 +40,7 @@ function entryLookup(postUrl, routerOptions, locals) {
}
let options = {
/**
* @deprecated: `author`, will be removed in Ghost 3.0
* @TODO: Remove "author" when we drop v0.1
*/
include: 'author,authors,tags'
include: 'authors,tags'
};
if (config.get('enableDeveloperExperiments')) {

View file

@ -7,7 +7,7 @@ const Promise = require('bluebird');
const config = require('../../../../server/config');
// The default settings for a default post query
// @TODO: get rid of this config and use v0.1 or v2 config
// @TODO: get rid of this config and use v2, v3 config
const queryDefaults = {
type: 'browse',
resource: 'posts',
@ -22,9 +22,8 @@ const defaultQueryOptions = {
options: {
/**
* @deprecated: `author`, will be removed in Ghost 3.0
* @TODO: Remove "author" when we drop v0.1
*/
include: 'author,authors,tags'
include: 'authors,tags'
}
};

View file

@ -1,121 +0,0 @@
/*
* These are the default resources and filters.
* They contain minimum filters for public accessibility of resources.
*/
module.exports = [
{
type: 'posts',
modelOptions: {
modelName: 'Post',
filter: 'visibility:public+status:published+page:false',
exclude: [
'title',
'mobiledoc',
'html',
'plaintext',
'amp',
'codeinjection_head',
'codeinjection_foot',
'meta_title',
'meta_description',
'custom_excerpt',
'og_image',
'og_title',
'og_description',
'twitter_image',
'twitter_title',
'twitter_description',
'custom_template',
'locale'
],
withRelated: ['tags', 'authors'],
withRelatedPrimary: {
primary_tag: 'tags',
primary_author: 'authors'
},
withRelatedFields: {
tags: ['tags.id', 'tags.slug'],
authors: ['users.id', 'users.slug']
}
},
events: {
add: 'post.published',
update: 'post.published.edited',
remove: 'post.unpublished'
}
},
{
type: 'pages',
modelOptions: {
modelName: 'Post',
exclude: [
'title',
'mobiledoc',
'html',
'plaintext',
'amp',
'codeinjection_head',
'codeinjection_foot',
'meta_title',
'meta_description',
'custom_excerpt',
'og_image',
'og_title',
'og_description',
'twitter_image',
'twitter_title',
'twitter_description',
'custom_template',
'locale',
'tags',
'authors',
'primary_tag',
'primary_author'
],
filter: 'visibility:public+status:published+page:true'
},
events: {
add: 'page.published',
update: 'page.published.edited',
remove: 'page.unpublished'
}
},
{
type: 'tags',
keep: ['id', 'slug', 'updated_at', 'created_at'],
modelOptions: {
modelName: 'Tag',
exclude: ['description', 'meta_title', 'meta_description'],
filter: 'visibility:public'
},
events: {
add: 'tag.added',
update: 'tag.edited',
remove: 'tag.deleted'
}
},
{
type: 'authors',
modelOptions: {
modelName: 'User',
exclude: [
'bio',
'website',
'location',
'facebook',
'twitter',
'accessibility',
'meta_title',
'meta_description',
'tour'
],
filter: 'visibility:public'
},
events: {
add: 'user.activated',
update: 'user.activated.edited',
remove: 'user.deleted'
}
}
];

View file

@ -43,7 +43,9 @@ describe('Tag API', function () {
jsonResponse.meta.pagination.should.have.property('next', null);
jsonResponse.meta.pagination.should.have.property('prev', null);
jsonResponse.tags[0].url.should.eql(`${config.get('url')}/tag/pollo/`);
// returns 404 because this tag has no published posts
jsonResponse.tags[0].url.should.eql(`${config.get('url')}/404/`);
jsonResponse.tags[1].url.should.eql(`${config.get('url')}/tag/kitchen-sink/`);
should.exist(jsonResponse.tags[0].count.posts);
});

View file

@ -1,9 +1,7 @@
const should = require('should');
const _ = require('lodash');
const supertest = require('supertest');
const moment = require('moment');
const Promise = require('bluebird');
const ObjectId = require('bson-objectid');
const testUtils = require('../../../utils');
const localUtils = require('./utils');
const config = require('../../../../server/config');
@ -13,12 +11,11 @@ const ghost = testUtils.startGhost;
let request;
describe('User API', function () {
let ghostServer, inactiveUser, admin;
let inactiveUser, admin;
before(function () {
return ghost()
.then(function (_ghostServer) {
ghostServer = _ghostServer;
.then(function () {
request = supertest.agent(config.get('url'));
})
.then(function () {
@ -82,10 +79,11 @@ describe('User API', function () {
testUtils.API.isISO8601(jsonResponse.users[3].created_at).should.be.true();
testUtils.API.isISO8601(jsonResponse.users[3].updated_at).should.be.true();
jsonResponse.users[0].url.should.eql(`${config.get('url')}/author/admin-user/`);
// only "ghost" author has a published post
jsonResponse.users[0].url.should.eql(`${config.get('url')}/404/`);
jsonResponse.users[1].url.should.eql(`${config.get('url')}/404/`);
jsonResponse.users[2].url.should.eql(`${config.get('url')}/author/ghost/`);
jsonResponse.users[3].url.should.eql(`${config.get('url')}/author/joe-bloggs/`);
jsonResponse.users[3].url.should.eql(`${config.get('url')}/404/`);
done();
});

View file

@ -16,7 +16,7 @@ describe('Integration: services/url/UrlService', function () {
models.init();
sinon.stub(themes, 'getActive').returns({
engine: () => 'v0.1'
engine: () => 'v2'
});
});
@ -148,11 +148,11 @@ describe('Integration: services/url/UrlService', function () {
}
if (generator.router.getResourceType() === 'tags') {
generator.getUrls().length.should.eql(5);
generator.getUrls().length.should.eql(3);
}
if (generator.router.getResourceType() === 'authors') {
generator.getUrls().length.should.eql(5);
generator.getUrls().length.should.eql(2);
}
});
@ -175,25 +175,22 @@ describe('Integration: services/url/UrlService', function () {
url.should.eql('/tag/chorizo/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[3].id);
url.should.eql('/tag/pollo/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[4].id);
url.should.eql('/tag/injection/');
url.should.eql('/404/'); // tags with no posts should not be public
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[0].id);
url.should.eql('/author/joe-bloggs/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[1].id);
url.should.eql('/author/smith-wellingsworth/');
url.should.eql('/404/'); // users with no posts should not be public
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[2].id);
url.should.eql('/author/jimothy-bogendath/');
url.should.eql('/404/'); // users with no posts should not be public
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[3].id);
url.should.eql('/author/slimer-mcectoplasm/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[4].id);
url.should.eql('/author/contributor/');
url.should.eql('/404/'); // users with no posts should not be public
});
it('getResource', function () {
@ -356,11 +353,11 @@ describe('Integration: services/url/UrlService', function () {
}
if (generator.router.getResourceType() === 'tags') {
generator.getUrls().length.should.eql(5);
generator.getUrls().length.should.eql(3);
}
if (generator.router.getResourceType() === 'authors') {
generator.getUrls().length.should.eql(5);
generator.getUrls().length.should.eql(2);
}
});
@ -384,25 +381,22 @@ describe('Integration: services/url/UrlService', function () {
url.should.eql('/category/chorizo/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[3].id);
url.should.eql('/category/pollo/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[4].id);
url.should.eql('/category/injection/');
url.should.eql('/404/'); // tags with no posts should not be public
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[0].id);
url.should.eql('/persons/joe-bloggs/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[1].id);
url.should.eql('/persons/smith-wellingsworth/');
url.should.eql('/404/'); // users with no posts should not be public
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[2].id);
url.should.eql('/persons/jimothy-bogendath/');
url.should.eql('/404/'); // users with no posts should not be public
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[3].id);
url.should.eql('/persons/slimer-mcectoplasm/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[4].id);
url.should.eql('/persons/contributor/');
url.should.eql('/404/'); // users with no posts should not be public
});
});
@ -553,11 +547,11 @@ describe('Integration: services/url/UrlService', function () {
}
if (generator.router.getResourceType() === 'tags') {
generator.getUrls().length.should.eql(5);
generator.getUrls().length.should.eql(3);
}
if (generator.router.getResourceType() === 'users') {
generator.getUrls().length.should.eql(5);
if (generator.router.getResourceType() === 'authors') {
generator.getUrls().length.should.eql(2);
}
});
@ -581,25 +575,22 @@ describe('Integration: services/url/UrlService', function () {
url.should.eql('/category/chorizo/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[3].id);
url.should.eql('/category/pollo/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[4].id);
url.should.eql('/category/injection/');
url.should.eql('/404/'); // tags with no posts should not be public
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[0].id);
url.should.eql('/persons/joe-bloggs/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[1].id);
url.should.eql('/persons/smith-wellingsworth/');
url.should.eql('/404/'); // users with no posts should not be public
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[2].id);
url.should.eql('/persons/jimothy-bogendath/');
url.should.eql('/404/'); // users with no posts should not be public
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[3].id);
url.should.eql('/persons/slimer-mcectoplasm/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[4].id);
url.should.eql('/persons/contributor/');
url.should.eql('/404/'); // users with no posts should not be public
});
});
});

View file

@ -394,10 +394,28 @@ describe('Dynamic Routing', function () {
return testUtils.initData();
}).then(function () {
return testUtils.fixtures.overrideOwnerUser(ownerSlug);
}).then(function (insertedUser) {
return testUtils.fixtures.insertPosts([
testUtils.DataGenerator.forKnex.createPost({
author_id: insertedUser.id
})
]);
}).then(function () {
return testUtils.fixtures.insertOneUser(lockedUser);
}).then(function () {
}).then(function (insertedUser) {
return testUtils.fixtures.insertPosts([
testUtils.DataGenerator.forKnex.createPost({
author_id: insertedUser.id
})
]);
}).then(() => {
return testUtils.fixtures.insertOneUser(suspendedUser);
}).then(function (insertedUser) {
return testUtils.fixtures.insertPosts([
testUtils.DataGenerator.forKnex.createPost({
author_id: insertedUser.id
})
]);
}).then(function () {
done();
}).catch(done);

File diff suppressed because it is too large Load diff

View file

@ -27,119 +27,6 @@ describe('Unit - services/routing/controllers/preview', function () {
configUtils.restore();
});
describe('v0.1', function () {
beforeEach(function () {
post = testUtils.DataGenerator.forKnex.createPost({status: 'draft'});
apiResponse = {
posts: [post]
};
req = {
path: '/',
params: {
uuid: 'something'
},
route: {}
};
res = {
routerOptions: {
query: {controller: 'posts', resource: 'posts'}
},
locals: {
apiVersion: 'v0.1'
},
render: sinon.spy(),
redirect: sinon.spy(),
set: sinon.spy()
};
secureStub = sinon.stub();
sinon.stub(urlUtils, 'redirectToAdmin');
sinon.stub(urlUtils, 'redirect301');
sinon.stub(urlService, 'getUrlByResourceId');
sinon.stub(helpers, 'secure').get(function () {
return secureStub;
});
renderStub = sinon.stub();
sinon.stub(helpers, 'renderEntry').get(function () {
return function () {
return renderStub;
};
});
sinon.stub(api.posts, 'read').withArgs({
uuid: req.params.uuid,
status: 'all',
include: 'author,authors,tags'
}).callsFake(function () {
return Promise.resolve(apiResponse);
});
});
it('should render post', function (done) {
controllers.preview(req, res, failTest(done)).then(function () {
secureStub.called.should.be.true();
renderStub.called.should.be.true();
done();
}).catch(done);
});
it('should call next if post is not found', function (done) {
apiResponse = {posts: []};
controllers.preview(req, res, function (err) {
should.not.exist(err);
renderStub.called.should.be.false();
secureStub.called.should.be.false();
done();
});
});
it('should call redirect if post is published', function (done) {
post.status = 'published';
urlService.getUrlByResourceId.withArgs(post.id).returns('/something/');
urlUtils.redirect301.callsFake(function (res, postUrl) {
postUrl.should.eql('/something/');
renderStub.called.should.be.false();
secureStub.called.should.be.false();
done();
});
controllers.preview(req, res, failTest(done));
});
it('should call redirect if /edit/ (options param) is detected', function (done) {
req.params.options = 'edit';
urlUtils.redirectToAdmin.callsFake(function (statusCode, res, editorUrl) {
statusCode.should.eql(302);
editorUrl.should.eql(EDITOR_URL + post.id);
renderStub.called.should.be.false();
secureStub.called.should.be.false();
done();
});
controllers.preview(req, res, failTest(done));
});
it('should call next for unknown options param detected', function (done) {
req.params.options = 'abcde';
controllers.preview(req, res, function (err) {
should.not.exist(err);
renderStub.called.should.be.false();
secureStub.called.should.be.false();
done();
});
});
});
describe('v2', function () {
let previewStub;
@ -191,7 +78,7 @@ describe('Unit - services/routing/controllers/preview', function () {
previewStub.withArgs({
uuid: req.params.uuid,
status: 'all',
include: 'author,authors,tags'
include: 'authors,tags'
}).resolves(apiResponse);
sinon.stub(api.v2, 'preview').get(() => {
@ -261,7 +148,7 @@ describe('Unit - services/routing/controllers/preview', function () {
previewStub.withArgs({
uuid: req.params.uuid,
status: 'all',
include: 'author,authors,tags'
include: 'authors,tags'
}).resolves(apiResponse);
sinon.stub(api.canary, 'preview').get(() => {
@ -331,7 +218,7 @@ describe('Unit - services/routing/controllers/preview', function () {
previewStub.withArgs({
uuid: req.params.uuid,
status: 'all',
include: 'author,authors,tags'
include: 'authors,tags'
}).resolves(apiResponse);
sinon.stub(api.v3, 'preview').get(() => {

View file

@ -14,22 +14,30 @@ function failTest(done) {
}
describe('Unit - services/routing/controllers/static', function () {
let req, res, secureStub, renderStub, handleErrorStub, formatResponseStub, posts, postsPerPage;
let req,
res,
secureStub,
renderStub,
handleErrorStub,
formatResponseStub,
postsPerPage,
tagsReadStub;
beforeEach(function () {
postsPerPage = 5;
posts = [
testUtils.DataGenerator.forKnex.createPost()
];
secureStub = sinon.stub();
renderStub = sinon.stub();
handleErrorStub = sinon.stub();
formatResponseStub = sinon.stub();
formatResponseStub.entries = sinon.stub();
sinon.stub(api.tags, 'read');
tagsReadStub = sinon.stub().resolves();
sinon.stub(api.v2, 'tagsPublic').get(() => {
return {
read: tagsReadStub
};
});
sinon.stub(helpers, 'secure').get(function () {
return secureStub;
@ -66,7 +74,7 @@ describe('Unit - services/routing/controllers/static', function () {
render: sinon.spy(),
redirect: sinon.spy(),
locals: {
apiVersion: 'v0.1'
apiVersion: 'v2'
}
};
});
@ -78,7 +86,7 @@ describe('Unit - services/routing/controllers/static', function () {
it('no extra data to fetch', function (done) {
helpers.renderer.callsFake(function () {
helpers.formatResponse.entries.calledOnce.should.be.true();
api.tags.read.called.should.be.false();
tagsReadStub.called.should.be.false();
helpers.secure.called.should.be.false();
done();
});
@ -89,7 +97,7 @@ describe('Unit - services/routing/controllers/static', function () {
it('extra data to fetch', function (done) {
res.routerOptions.data = {
tag: {
controller: 'tags',
controller: 'tagsPublic',
resource: 'tags',
type: 'read',
options: {
@ -98,10 +106,10 @@ describe('Unit - services/routing/controllers/static', function () {
}
};
api.tags.read.resolves({tags: [{slug: 'bacon'}]});
tagsReadStub = sinon.stub().resolves({tags: [{slug: 'bacon'}]});
helpers.renderer.callsFake(function () {
api.tags.read.called.should.be.true();
tagsReadStub.called.should.be.true();
helpers.formatResponse.entries.calledOnce.should.be.true();
helpers.secure.calledOnce.should.be.true();
done();

View file

@ -286,7 +286,7 @@ describe('Contexts', function () {
it('should correctly identify AMP page', function () {
res.locals.relativeUrl = '/welcome-to-ghost/amp/';
data.post = testUtils.DataGenerator.forKnex.createPost({page: true});
data.page = testUtils.DataGenerator.forKnex.createPost({page: true});
delete res.routerOptions;
helpers.context(req, res, data);

View file

@ -1,11 +1,12 @@
const should = require('should'),
sinon = require('sinon'),
api = require('../../../../../server/api')['v0.1'],
helpers = require('../../../../../frontend/services/routing/helpers'),
testUtils = require('../../../../utils');
const should = require('should');
const sinon = require('sinon');
const api = require('../../../../../server/api').v2;
const helpers = require('../../../../../frontend/services/routing/helpers');
const testUtils = require('../../../../utils');
describe('Unit - services/routing/helpers/fetch-data', function () {
let posts, tags, locals;
let browsePostsStub, readTagsStub;
beforeEach(function () {
posts = [
@ -22,19 +23,28 @@ describe('Unit - services/routing/helpers/fetch-data', function () {
testUtils.DataGenerator.forKnex.createTag()
];
sinon.stub(api.posts, 'browse')
.resolves({
posts: posts,
meta: {
pagination: {
pages: 2
}
browsePostsStub = sinon.stub().resolves({
posts: posts,
meta: {
pagination: {
pages: 2
}
});
}
});
sinon.stub(api, 'postsPublic').get(() => {
return {
browse: browsePostsStub
};
});
sinon.stub(api.tags, 'read').resolves({tags: tags});
readTagsStub = sinon.stub().resolves({tags: tags});
sinon.stub(api, 'tagsPublic').get(() => {
return {
read: readTagsStub
};
});
locals = {apiVersion: 'v0.1'};
locals = {apiVersion: 'v2'};
});
afterEach(function () {
@ -47,10 +57,10 @@ describe('Unit - services/routing/helpers/fetch-data', function () {
result.should.be.an.Object().with.properties('posts', 'meta');
result.should.not.have.property('data');
api.posts.browse.calledOnce.should.be.true();
api.posts.browse.firstCall.args[0].should.be.an.Object();
api.posts.browse.firstCall.args[0].should.have.property('include');
api.posts.browse.firstCall.args[0].should.not.have.property('filter');
browsePostsStub.calledOnce.should.be.true();
browsePostsStub.firstCall.args[0].should.be.an.Object();
browsePostsStub.firstCall.args[0].should.have.property('include');
browsePostsStub.firstCall.args[0].should.not.have.property('filter');
done();
}).catch(done);
@ -64,11 +74,11 @@ describe('Unit - services/routing/helpers/fetch-data', function () {
result.posts.length.should.eql(posts.length);
api.posts.browse.calledOnce.should.be.true();
api.posts.browse.firstCall.args[0].should.be.an.Object();
api.posts.browse.firstCall.args[0].should.have.property('include');
api.posts.browse.firstCall.args[0].should.have.property('limit', 10);
api.posts.browse.firstCall.args[0].should.have.property('page', 2);
browsePostsStub.calledOnce.should.be.true();
browsePostsStub.firstCall.args[0].should.be.an.Object();
browsePostsStub.firstCall.args[0].should.have.property('include');
browsePostsStub.firstCall.args[0].should.have.property('limit', 10);
browsePostsStub.firstCall.args[0].should.have.property('page', 2);
done();
}).catch(done);
@ -102,11 +112,10 @@ describe('Unit - services/routing/helpers/fetch-data', function () {
// @TODO v3 will deprecate this style (featured.posts)
result.data.featured.posts.length.should.eql(posts.length);
api.posts.browse.calledTwice.should.be.true();
api.posts.browse.firstCall.args[0].should.have.property('include', 'author,authors,tags');
api.posts.browse.secondCall.args[0].should.have.property('filter', 'featured:true');
api.posts.browse.secondCall.args[0].should.have.property('limit', 3);
browsePostsStub.calledTwice.should.be.true();
browsePostsStub.firstCall.args[0].should.have.property('include', 'authors,tags');
browsePostsStub.secondCall.args[0].should.have.property('filter', 'featured:true');
browsePostsStub.secondCall.args[0].should.have.property('limit', 3);
done();
}).catch(done);
});
@ -139,11 +148,11 @@ describe('Unit - services/routing/helpers/fetch-data', function () {
// @TODO v3 will deprecate this style (featured.posts)
result.data.featured.posts.length.should.eql(posts.length);
api.posts.browse.calledTwice.should.be.true();
api.posts.browse.firstCall.args[0].should.have.property('include', 'author,authors,tags');
api.posts.browse.firstCall.args[0].should.have.property('page', 2);
api.posts.browse.secondCall.args[0].should.have.property('filter', 'featured:true');
api.posts.browse.secondCall.args[0].should.have.property('limit', 3);
browsePostsStub.calledTwice.should.be.true();
browsePostsStub.firstCall.args[0].should.have.property('include', 'authors,tags');
browsePostsStub.firstCall.args[0].should.have.property('page', 2);
browsePostsStub.secondCall.args[0].should.have.property('filter', 'featured:true');
browsePostsStub.secondCall.args[0].should.have.property('limit', 3);
done();
}).catch(done);
});
@ -157,7 +166,7 @@ describe('Unit - services/routing/helpers/fetch-data', function () {
filter: 'tags:%s',
data: {
tag: {
controller: 'tags',
controller: 'tagsPublic',
type: 'read',
resource: 'tags',
options: {slug: '%s'}
@ -173,11 +182,11 @@ describe('Unit - services/routing/helpers/fetch-data', function () {
result.posts.length.should.eql(posts.length);
result.data.tag.length.should.eql(tags.length);
api.posts.browse.calledOnce.should.be.true();
api.posts.browse.firstCall.args[0].should.have.property('include');
api.posts.browse.firstCall.args[0].should.have.property('filter', 'tags:testing');
api.posts.browse.firstCall.args[0].should.not.have.property('slug');
api.tags.read.firstCall.args[0].should.have.property('slug', 'testing');
browsePostsStub.calledOnce.should.be.true();
browsePostsStub.firstCall.args[0].should.have.property('include');
browsePostsStub.firstCall.args[0].should.have.property('filter', 'tags:testing');
browsePostsStub.firstCall.args[0].should.not.have.property('slug');
readTagsStub.firstCall.args[0].should.have.property('slug', 'testing');
done();
}).catch(done);
});

View file

@ -21,787 +21,6 @@ describe('UNIT: services/settings/validate', function () {
sinon.restore();
});
describe('v0.1', function () {
before(function () {
apiVersion = 'v0.1';
});
it('no type definitions / empty yaml file', function () {
const object = validate({});
object.should.eql({collections: {}, routes: {}, taxonomies: {}});
});
it('throws error when using :\w+ notiation in collection', function () {
try {
validate({
collections: {
'/magic/': {
permalink: '/magic/{slug}/'
},
'/': {
permalink: '/:slug/'
}
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('throws error when using :\w+ notiation in taxonomies', function () {
try {
validate({
taxonomies: {
tag: '/categories/:slug/'
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('throws error when using an undefined taxonomy', function () {
try {
validate({
taxonomies: {
sweet_baked_good: '/patisserie/{slug}'
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('throws error when permalink is missing (collection)', function () {
try {
validate({
collections: {
permalink: '/{slug}/'
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('throws error without leading or trailing slashes', function () {
try {
validate({
routes: {
about: 'about'
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('throws error without trailing slash', function () {
try {
validate({
routes: {
'/about': 'about'
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('throws error without leading slashe', function () {
try {
validate({
routes: {
'about/': 'about'
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('throws error without leading slash with permalink', function () {
try {
validate({
collections: {
'magic/': {
permalink: '/{slug}/'
}
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('throws error without leading or trailing slashes with permalink', function () {
try {
validate({
collections: {
magic: {
permalink: '/{slug}/'
}
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('throws error without trailing slash with permalink', function () {
try {
validate({
collections: {
'/magic': {
permalink: '/{slug}/'
}
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('throws error without trailing slash in permalink', function () {
try {
validate({
collections: {
'/magic/': {
permalink: '/{slug}'
}
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('throws error without leading or trailing slashes in permalink', function () {
try {
validate({
collections: {
'/magic/': {
permalink: '{slug}'
}
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('no validation error for routes', function () {
validate({
routes: {
'/': 'home'
}
});
});
it('no validation error for / collection', function () {
validate({
collections: {
'/': {
permalink: '/{primary_tag}/{slug}/'
}
}
});
});
it('transforms {.*} notation into :\w+', function () {
const object = validate({
collections: {
'/magic/': {
permalink: '/magic/{year}/{slug}/'
},
'/': {
permalink: '/{slug}/'
}
},
taxonomies: {
tag: '/tags/{slug}/',
author: '/authors/{slug}/'
}
});
object.should.eql({
routes: {},
taxonomies: {
tag: '/tags/:slug/',
author: '/authors/:slug/'
},
collections: {
'/magic/': {
permalink: '/magic/:year/:slug/',
templates: []
},
'/': {
permalink: '/:slug/',
templates: []
}
}
});
});
describe('template definitions', function () {
it('single value', function () {
const object = validate({
routes: {
'/about/': 'about',
'/me/': {
template: 'me'
}
},
collections: {
'/': {
permalink: '/{slug}/',
template: 'test'
}
}
});
object.should.eql({
taxonomies: {},
routes: {
'/about/': {
templates: ['about']
},
'/me/': {
templates: ['me']
}
},
collections: {
'/': {
permalink: '/:slug/',
templates: ['test']
}
}
});
});
it('array', function () {
const object = validate({
routes: {
'/about/': 'about',
'/me/': {
template: ['me']
}
},
collections: {
'/': {
permalink: '/{slug}/',
template: ['test']
}
}
});
object.should.eql({
taxonomies: {},
routes: {
'/about/': {
templates: ['about']
},
'/me/': {
templates: ['me']
}
},
collections: {
'/': {
permalink: '/:slug/',
templates: ['test']
}
}
});
});
});
describe('data definitions', function () {
it('shortform', function () {
const object = validate({
routes: {
'/food/': {
data: 'tag.food'
},
'/music/': {
data: 'tag.music'
},
'/ghost/': {
data: 'user.ghost'
},
'/sleep/': {
data: {
bed: 'tag.bed',
dream: 'tag.dream'
}
},
'/lala/': {
data: 'author.carsten'
}
},
collections: {
'/more/': {
permalink: '/{slug}/',
data: {
home: 'page.home'
}
},
'/podcast/': {
permalink: '/podcast/{slug}/',
data: {
something: 'tag.something'
}
},
'/': {
permalink: '/{slug}/',
data: 'tag.sport'
}
}
});
object.should.eql({
taxonomies: {},
routes: {
'/food/': {
data: {
query: {
tag: {
controller: 'tags',
resource: 'tags',
type: 'read',
options: {
slug: 'food',
visibility: 'public'
}
}
},
router: {
tags: [{redirect: true, slug: 'food'}]
}
},
templates: []
},
'/ghost/': {
data: {
query: {
user: {
controller: 'users',
resource: 'users',
type: 'read',
options: {
slug: 'ghost',
visibility: 'public'
}
}
},
router: {
authors: [{redirect: true, slug: 'ghost'}]
}
},
templates: []
},
'/music/': {
data: {
query: {
tag: {
controller: 'tags',
resource: 'tags',
type: 'read',
options: {
slug: 'music',
visibility: 'public'
}
}
},
router: {
tags: [{redirect: true, slug: 'music'}]
}
},
templates: []
},
'/sleep/': {
data: {
query: {
bed: {
controller: 'tags',
resource: 'tags',
type: 'read',
options: {
slug: 'bed',
visibility: 'public'
}
},
dream: {
controller: 'tags',
resource: 'tags',
type: 'read',
options: {
slug: 'dream',
visibility: 'public'
}
}
},
router: {
tags: [{redirect: true, slug: 'bed'}, {redirect: true, slug: 'dream'}]
}
},
templates: []
},
'/lala/': {
data: {
query: {
author: {
controller: 'users',
resource: 'users',
type: 'read',
options: {
slug: 'carsten',
visibility: 'public'
}
}
},
router: {
authors: [{redirect: true, slug: 'carsten'}]
}
},
templates: []
}
},
collections: {
'/more/': {
permalink: '/:slug/',
data: {
query: {
home: {
controller: 'posts',
resource: 'posts',
type: 'read',
options: {
page: 1,
slug: 'home',
status: 'published'
}
}
},
router: {
posts: [{redirect: true, slug: 'home'}]
}
},
templates: []
},
'/podcast/': {
permalink: '/podcast/:slug/',
data: {
query: {
something: {
controller: 'tags',
resource: 'tags',
type: 'read',
options: {
slug: 'something',
visibility: 'public'
}
}
},
router: {
tags: [{redirect: true, slug: 'something'}]
}
},
templates: []
},
'/': {
permalink: '/:slug/',
data: {
query: {
tag: {
controller: 'tags',
resource: 'tags',
type: 'read',
options: {
slug: 'sport',
visibility: 'public'
}
}
},
router: {
tags: [{redirect: true, slug: 'sport'}]
}
},
templates: []
}
}
});
});
it('longform', function () {
const object = validate({
routes: {
'/food/': {
data: {
food: {
resource: 'posts',
type: 'browse'
}
}
},
'/wellness/': {
data: {
posts: {
resource: 'posts',
type: 'read',
redirect: false
}
}
},
'/partyparty/': {
data: {
people: {
resource: 'users',
type: 'read',
slug: 'djgutelaune',
redirect: true
}
}
}
},
collections: {
'/yoga/': {
permalink: '/{slug}/',
data: {
gym: {
resource: 'posts',
type: 'read',
slug: 'ups',
status: 'draft'
}
}
}
}
});
object.should.eql({
taxonomies: {},
routes: {
'/food/': {
data: {
query: {
food: {
controller: 'posts',
resource: 'posts',
type: 'browse',
options: {}
}
},
router: {
posts: [{redirect: true}]
}
},
templates: []
},
'/wellness/': {
data: {
query: {
posts: {
controller: 'posts',
resource: 'posts',
type: 'read',
options: {
status: 'published',
slug: '%s',
page: 0
}
}
},
router: {
posts: [{redirect: false}]
}
},
templates: []
},
'/partyparty/': {
data: {
query: {
people: {
controller: 'users',
resource: 'users',
type: 'read',
options: {
slug: 'djgutelaune',
visibility: 'public'
}
}
},
router: {
authors: [{redirect: true, slug: 'djgutelaune'}]
}
},
templates: []
}
},
collections: {
'/yoga/': {
permalink: '/:slug/',
data: {
query: {
gym: {
controller: 'posts',
resource: 'posts',
type: 'read',
options: {
page: 0,
slug: 'ups',
status: 'draft'
}
}
},
router: {
posts: [{redirect: true, slug: 'ups'}]
}
},
templates: []
}
}
});
});
it('errors: data shortform incorrect', function () {
try {
validate({
collections: {
'/magic/': {
permalink: '/{slug}/',
data: 'tag:test'
}
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('errors: data longform resource is missing', function () {
try {
validate({
collections: {
'/magic/': {
permalink: '/{slug}/',
data: {
type: 'edit'
}
}
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('errors: data longform type is missing', function () {
try {
validate({
collections: {
'/magic/': {
permalink: '/{slug}/',
data: {
resource: 'subscribers'
}
}
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('errors: data longform name is author', function () {
try {
validate({
collections: {
'/magic/': {
permalink: '/{slug}/',
data: {
author: {
resource: 'users'
}
}
}
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
it('errors: data longform does not use a custom name at all', function () {
try {
validate({
collections: {
'/magic/': {
permalink: '/{slug}/',
data: {
resource: 'users'
}
}
}
});
} catch (err) {
(err instanceof common.errors.ValidationError).should.be.true();
return;
}
throw new Error('should fail');
});
});
});
describe('v2', function () {
before(function () {
apiVersion = 'v2';

View file

@ -4,7 +4,8 @@
"demo": "https://demo.ghost.io",
"version": "2.4.2",
"engines": {
"ghost": ">=2.0.0"
"ghost": ">=2.0.0",
"ghost-api": "v2"
},
"license": "MIT",
"screenshots": {

View file

@ -4,7 +4,8 @@
"demo": "https://demo.ghost.io",
"version": "2.4.2",
"engines": {
"ghost": ">=2.0.0"
"ghost": ">=2.0.0",
"ghost-api": "v2"
},
"license": "MIT",
"screenshots": {