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: * @NOTE:
* *
* Ensure we redirect to the correct post url including subdirectory. * 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) { if (urlUtils.absoluteToRelative(entry.url, {withoutSubdirectory: true}) !== req.path) {
debug('redirect'); debug('redirect');

View file

@ -18,8 +18,7 @@ module.exports = function previewController(req, res, next) {
const params = { const params = {
uuid: req.params.uuid, uuid: req.params.uuid,
status: 'all', status: 'all',
// @TODO: Remove "author" if we drop v0.1 include: 'authors,tags'
include: 'author,authors,tags'
}; };
return api[res.routerOptions.query.controller] 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. // We override the `include` property for now, because the full data set is required anyway.
if (_.get(query, 'resource') === 'posts') { if (_.get(query, 'resource') === 'posts') {
_.extend(query.options, { _.extend(query.options, {
// @TODO: Remove "author" when we drop v0.1 include: 'authors,tags'
include: 'author,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 // 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'); 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) {
if (data && data.post && data.post.page) {
if (!res.locals.context.includes('page')) {
res.locals.context.push('page');
}
} else if (data && data.post) {
if (!res.locals.context.includes('post')) { if (!res.locals.context.includes('post')) {
res.locals.context.push('post'); res.locals.context.push('post');
} }

View file

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

View file

@ -7,7 +7,7 @@ const Promise = require('bluebird');
const config = require('../../../../server/config'); const config = require('../../../../server/config');
// The default settings for a default post query // 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 = { const queryDefaults = {
type: 'browse', type: 'browse',
resource: 'posts', resource: 'posts',
@ -22,9 +22,8 @@ const defaultQueryOptions = {
options: { options: {
/** /**
* @deprecated: `author`, will be removed in Ghost 3.0 * @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('next', null);
jsonResponse.meta.pagination.should.have.property('prev', 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); should.exist(jsonResponse.tags[0].count.posts);
}); });

View file

@ -1,9 +1,7 @@
const should = require('should'); const should = require('should');
const _ = require('lodash'); const _ = require('lodash');
const supertest = require('supertest'); const supertest = require('supertest');
const moment = require('moment');
const Promise = require('bluebird'); const Promise = require('bluebird');
const ObjectId = require('bson-objectid');
const testUtils = require('../../../utils'); const testUtils = require('../../../utils');
const localUtils = require('./utils'); const localUtils = require('./utils');
const config = require('../../../../server/config'); const config = require('../../../../server/config');
@ -13,12 +11,11 @@ const ghost = testUtils.startGhost;
let request; let request;
describe('User API', function () { describe('User API', function () {
let ghostServer, inactiveUser, admin; let inactiveUser, admin;
before(function () { before(function () {
return ghost() return ghost()
.then(function (_ghostServer) { .then(function () {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url')); request = supertest.agent(config.get('url'));
}) })
.then(function () { .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].created_at).should.be.true();
testUtils.API.isISO8601(jsonResponse.users[3].updated_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[1].url.should.eql(`${config.get('url')}/404/`);
jsonResponse.users[2].url.should.eql(`${config.get('url')}/author/ghost/`); 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(); done();
}); });

View file

@ -16,7 +16,7 @@ describe('Integration: services/url/UrlService', function () {
models.init(); models.init();
sinon.stub(themes, 'getActive').returns({ 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') { if (generator.router.getResourceType() === 'tags') {
generator.getUrls().length.should.eql(5); generator.getUrls().length.should.eql(3);
} }
if (generator.router.getResourceType() === 'authors') { 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.should.eql('/tag/chorizo/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[3].id); url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[3].id);
url.should.eql('/tag/pollo/'); url.should.eql('/404/'); // tags with no posts should not be public
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[4].id);
url.should.eql('/tag/injection/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[0].id); url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[0].id);
url.should.eql('/author/joe-bloggs/'); url.should.eql('/author/joe-bloggs/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[1].id); 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 = 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 = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[3].id);
url.should.eql('/author/slimer-mcectoplasm/'); url.should.eql('/author/slimer-mcectoplasm/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[4].id); 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 () { it('getResource', function () {
@ -356,11 +353,11 @@ describe('Integration: services/url/UrlService', function () {
} }
if (generator.router.getResourceType() === 'tags') { if (generator.router.getResourceType() === 'tags') {
generator.getUrls().length.should.eql(5); generator.getUrls().length.should.eql(3);
} }
if (generator.router.getResourceType() === 'authors') { 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.should.eql('/category/chorizo/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[3].id); url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[3].id);
url.should.eql('/category/pollo/'); url.should.eql('/404/'); // tags with no posts should not be public
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[4].id);
url.should.eql('/category/injection/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[0].id); url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[0].id);
url.should.eql('/persons/joe-bloggs/'); url.should.eql('/persons/joe-bloggs/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[1].id); 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 = 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 = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[3].id);
url.should.eql('/persons/slimer-mcectoplasm/'); url.should.eql('/persons/slimer-mcectoplasm/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[4].id); 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') { if (generator.router.getResourceType() === 'tags') {
generator.getUrls().length.should.eql(5); generator.getUrls().length.should.eql(3);
} }
if (generator.router.getResourceType() === 'users') { if (generator.router.getResourceType() === 'authors') {
generator.getUrls().length.should.eql(5); generator.getUrls().length.should.eql(2);
} }
}); });
@ -581,25 +575,22 @@ describe('Integration: services/url/UrlService', function () {
url.should.eql('/category/chorizo/'); url.should.eql('/category/chorizo/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[3].id); url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[3].id);
url.should.eql('/category/pollo/'); url.should.eql('/404/'); // tags with no posts should not be public
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.tags[4].id);
url.should.eql('/category/injection/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[0].id); url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[0].id);
url.should.eql('/persons/joe-bloggs/'); url.should.eql('/persons/joe-bloggs/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[1].id); 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 = 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 = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[3].id);
url.should.eql('/persons/slimer-mcectoplasm/'); url.should.eql('/persons/slimer-mcectoplasm/');
url = urlService.getUrlByResourceId(testUtils.DataGenerator.forKnex.users[4].id); 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(); return testUtils.initData();
}).then(function () { }).then(function () {
return testUtils.fixtures.overrideOwnerUser(ownerSlug); return testUtils.fixtures.overrideOwnerUser(ownerSlug);
}).then(function (insertedUser) {
return testUtils.fixtures.insertPosts([
testUtils.DataGenerator.forKnex.createPost({
author_id: insertedUser.id
})
]);
}).then(function () { }).then(function () {
return testUtils.fixtures.insertOneUser(lockedUser); 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); return testUtils.fixtures.insertOneUser(suspendedUser);
}).then(function (insertedUser) {
return testUtils.fixtures.insertPosts([
testUtils.DataGenerator.forKnex.createPost({
author_id: insertedUser.id
})
]);
}).then(function () { }).then(function () {
done(); done();
}).catch(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(); 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 () { describe('v2', function () {
let previewStub; let previewStub;
@ -191,7 +78,7 @@ describe('Unit - services/routing/controllers/preview', function () {
previewStub.withArgs({ previewStub.withArgs({
uuid: req.params.uuid, uuid: req.params.uuid,
status: 'all', status: 'all',
include: 'author,authors,tags' include: 'authors,tags'
}).resolves(apiResponse); }).resolves(apiResponse);
sinon.stub(api.v2, 'preview').get(() => { sinon.stub(api.v2, 'preview').get(() => {
@ -261,7 +148,7 @@ describe('Unit - services/routing/controllers/preview', function () {
previewStub.withArgs({ previewStub.withArgs({
uuid: req.params.uuid, uuid: req.params.uuid,
status: 'all', status: 'all',
include: 'author,authors,tags' include: 'authors,tags'
}).resolves(apiResponse); }).resolves(apiResponse);
sinon.stub(api.canary, 'preview').get(() => { sinon.stub(api.canary, 'preview').get(() => {
@ -331,7 +218,7 @@ describe('Unit - services/routing/controllers/preview', function () {
previewStub.withArgs({ previewStub.withArgs({
uuid: req.params.uuid, uuid: req.params.uuid,
status: 'all', status: 'all',
include: 'author,authors,tags' include: 'authors,tags'
}).resolves(apiResponse); }).resolves(apiResponse);
sinon.stub(api.v3, 'preview').get(() => { sinon.stub(api.v3, 'preview').get(() => {

View file

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

View file

@ -286,7 +286,7 @@ describe('Contexts', function () {
it('should correctly identify AMP page', function () { it('should correctly identify AMP page', function () {
res.locals.relativeUrl = '/welcome-to-ghost/amp/'; 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; delete res.routerOptions;
helpers.context(req, res, data); helpers.context(req, res, data);

View file

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

View file

@ -21,787 +21,6 @@ describe('UNIT: services/settings/validate', function () {
sinon.restore(); 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 () { describe('v2', function () {
before(function () { before(function () {
apiVersion = 'v2'; apiVersion = 'v2';

View file

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

View file

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