🐛 Fixed __GHOST_URL__ appearing in generated excerpts

refs https://github.com/TryGhost/Team/issues/467
refs a6f5eb71be

- When a generated excerpt is calculated for posts/page resources it uses raw model! to get the data. Model contains untranformed __GHOST_URL__ markup which has to be additionally processed before extracint an excerpt or use the transformed `plaintext` from available attributes (chose the latter to decrease complexity)
- Removed model dependency as `attrs` at this point of serialization should always contain the `plaintext` field. It's ugly and has an unsolved bug report here - https://github.com/TryGhost/Ghost/issues/10396. The reliance should be solved at some point, but definitely not a part of this issue
This commit is contained in:
Naz 2021-03-17 19:12:40 +13:00
parent 958775b068
commit 6b07d4b2a0
9 changed files with 54 additions and 53 deletions

View File

@ -6,7 +6,7 @@ module.exports.forPost = (frame, model, attrs) => {
if (!Object.prototype.hasOwnProperty.call(frame.options, 'columns') ||
(frame.options.columns.includes('excerpt') && frame.options.formats && frame.options.formats.includes('plaintext'))) {
if (_.isEmpty(attrs.custom_excerpt)) {
const plaintext = model.get('plaintext');
const plaintext = attrs.plaintext;
if (plaintext) {
attrs.excerpt = plaintext.substring(0, 500);

View File

@ -4,7 +4,7 @@ module.exports.forPost = (frame, model, attrs) => {
if (!Object.prototype.hasOwnProperty.call(frame.options, 'columns') ||
(frame.options.columns.includes('excerpt') && frame.options.formats && frame.options.formats.includes('plaintext'))) {
if (_.isEmpty(attrs.custom_excerpt)) {
const plaintext = model.get('plaintext');
const plaintext = attrs.plaintext;
if (plaintext) {
attrs.excerpt = plaintext.substring(0, 500);

View File

@ -6,7 +6,7 @@ module.exports.forPost = (frame, model, attrs) => {
if (!Object.prototype.hasOwnProperty.call(frame.options, 'columns') ||
(frame.options.columns.includes('excerpt') && frame.options.formats && frame.options.formats.includes('plaintext'))) {
if (_.isEmpty(attrs.custom_excerpt)) {
const plaintext = model.get('plaintext');
const plaintext = attrs.plaintext;
if (plaintext) {
attrs.excerpt = plaintext.substring(0, 500);

View File

@ -242,14 +242,19 @@ describe('api/canary/content/posts', function () {
});
it('can read post with fields', function () {
const complexPostId = testUtils.DataGenerator.Content.posts.find(p => p.slug === 'not-so-short-bit-complex').id;
return request
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[0].id}/?key=${validKey}&fields=title,slug`))
.get(localUtils.API.getApiQuery(`posts/${complexPostId}/?key=${validKey}&fields=title,slug,excerpt&formats=plaintext`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
localUtils.API.checkResponse(res.body.posts[0], 'post', null, null, ['id', 'title', 'slug']);
localUtils.API.checkResponse(res.body.posts[0], 'post', null, null, ['id', 'title', 'slug', 'excerpt', 'plaintext']);
// excerpt should transform links to absolute URLs
res.body.posts[0].excerpt.should.match(/\* Aliquam \[http:\/\/127.0.0.1:2369\/about#nowhere\]/);
});
});

View File

@ -183,14 +183,19 @@ describe('api/v2/content/posts', function () {
});
it('can read post with fields', function () {
const complexPostId = testUtils.DataGenerator.Content.posts.find(p => p.slug === 'not-so-short-bit-complex').id;
return request
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[0].id}/?key=${validKey}&fields=title,slug`))
.get(localUtils.API.getApiQuery(`posts/${complexPostId}/?key=${validKey}&fields=title,slug,excerpt&formats=plaintext`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
localUtils.API.checkResponse(res.body.posts[0], 'post', null, null, ['id', 'title', 'slug']);
localUtils.API.checkResponse(res.body.posts[0], 'post', null, null, ['id', 'title', 'slug', 'excerpt', 'plaintext']);
// excerpt should transform links to absolute URLs
res.body.posts[0].excerpt.should.match(/\* Aliquam \[http:\/\/127.0.0.1:2369\/about#nowhere\]/);
});
});

View File

@ -242,14 +242,19 @@ describe('api/v3/content/posts', function () {
});
it('can read post with fields', function () {
const complexPostId = testUtils.DataGenerator.Content.posts.find(p => p.slug === 'not-so-short-bit-complex').id;
return request
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[0].id}/?key=${validKey}&fields=title,slug`))
.get(localUtils.API.getApiQuery(`posts/${complexPostId}/?key=${validKey}&fields=title,slug,excerpt&formats=plaintext`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
localUtils.API.checkResponse(res.body.posts[0], 'post', null, null, ['id', 'title', 'slug']);
localUtils.API.checkResponse(res.body.posts[0], 'post', null, null, ['id', 'title', 'slug', 'excerpt', 'plaintext']);
// excerpt should transform links to absolute URLs
res.body.posts[0].excerpt.should.match(/\* Aliquam \[http:\/\/127.0.0.1:2369\/about#nowhere\]/);
});
});

View File

@ -1,5 +1,4 @@
const should = require('should');
const sinon = require('sinon');
const extraAttrsUtil = require('../../../../../../../../core/server/api/canary/utils/serializers/output/utils/extra-attrs');
describe('Unit: canary/utils/serializers/output/utils/extra-attrs', function () {
@ -9,38 +8,34 @@ describe('Unit: canary/utils/serializers/output/utils/extra-attrs', function ()
let model;
beforeEach(function () {
model = sinon.stub();
model.get = sinon.stub();
model.get.withArgs('plaintext').returns(new Array(5000).join('A'));
});
describe('for post', function () {
it('respects custom excerpt', function () {
const attrs = {custom_excerpt: 'custom excerpt'};
const attrs = {
custom_excerpt: 'custom excerpt',
plaintext: new Array(5000).join('A')
};
extraAttrsUtil.forPost(frame, model, attrs);
model.get.called.should.be.false();
attrs.excerpt.should.eql(attrs.custom_excerpt);
});
it('no custom excerpt', function () {
const attrs = {};
const attrs = {
plaintext: new Array(5000).join('A')
};
extraAttrsUtil.forPost(frame, model, attrs);
model.get.called.should.be.true();
attrs.excerpt.should.eql(new Array(501).join('A'));
});
it('has excerpt when plaintext is null', function () {
model.get.withArgs('plaintext').returns(null);
const attrs = {};
const attrs = {
plaintext: null
};
extraAttrsUtil.forPost(frame, model, attrs);
model.get.called.should.be.true();
attrs.should.have.property('excerpt');
(attrs.excerpt === null).should.be.true();

View File

@ -9,38 +9,34 @@ describe('Unit: v2/utils/serializers/output/utils/extra-attrs', function () {
let model;
beforeEach(function () {
model = sinon.stub();
model.get = sinon.stub();
model.get.withArgs('plaintext').returns(new Array(5000).join('A'));
});
describe('for post', function () {
it('respects custom excerpt', function () {
const attrs = {custom_excerpt: 'custom excerpt'};
const attrs = {
custom_excerpt: 'custom excerpt',
plaintext: new Array(5000).join('A')
};
extraAttrsUtil.forPost(frame, model, attrs);
model.get.called.should.be.false();
attrs.excerpt.should.eql(attrs.custom_excerpt);
});
it('no custom excerpt', function () {
const attrs = {};
const attrs = {
plaintext: new Array(5000).join('A')
};
extraAttrsUtil.forPost(frame, model, attrs);
model.get.called.should.be.true();
attrs.excerpt.should.eql(new Array(501).join('A'));
});
it('has excerpt when plaintext is null', function () {
model.get.withArgs('plaintext').returns(null);
const attrs = {};
const attrs = {
plaintext: null
};
extraAttrsUtil.forPost(frame, model, attrs);
model.get.called.should.be.true();
attrs.should.have.property('excerpt');
(attrs.excerpt === null).should.be.true();

View File

@ -1,5 +1,4 @@
const should = require('should');
const sinon = require('sinon');
const extraAttrsUtil = require('../../../../../../../../core/server/api/v3/utils/serializers/output/utils/extra-attrs');
describe('Unit: v3/utils/serializers/output/utils/extra-attrs', function () {
@ -9,38 +8,34 @@ describe('Unit: v3/utils/serializers/output/utils/extra-attrs', function () {
let model;
beforeEach(function () {
model = sinon.stub();
model.get = sinon.stub();
model.get.withArgs('plaintext').returns(new Array(5000).join('A'));
});
describe('for post', function () {
it('respects custom excerpt', function () {
const attrs = {custom_excerpt: 'custom excerpt'};
const attrs = {
custom_excerpt: 'custom excerpt',
plaintext: new Array(5000).join('A')
};
extraAttrsUtil.forPost(frame, model, attrs);
model.get.called.should.be.false();
attrs.excerpt.should.eql(attrs.custom_excerpt);
});
it('no custom excerpt', function () {
const attrs = {};
const attrs = {
plaintext: new Array(5000).join('A')
};
extraAttrsUtil.forPost(frame, model, attrs);
model.get.called.should.be.true();
attrs.excerpt.should.eql(new Array(501).join('A'));
});
it('has excerpt when plaintext is null', function () {
model.get.withArgs('plaintext').returns(null);
const attrs = {};
const attrs = {
plaintext: null
};
extraAttrsUtil.forPost(frame, model, attrs);
model.get.called.should.be.true();
attrs.should.have.property('excerpt');
(attrs.excerpt === null).should.be.true();