🐛 Fixed "Create Post" action error in Zapier when assigning new tags (#13972)

closes https://github.com/TryGhost/Zapier/issues/56

- fixes tag creation when creating posts with `tags: [{slug: 'new'}]` which should be supported
  - assigning tags with only `{slug: 'new'}` was triggering our validation for the required `name` property then bubbling up to the `bookshelf-relations` library resulting in a 500 error
  - the fix applied here is to set the `name` field to the same as the `slug` field if a name is not provided
This commit is contained in:
Kevin Ansfield 2022-01-14 13:37:20 +00:00 committed by GitHub
parent f58b5984cb
commit 96ec60e393
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 293 additions and 15 deletions

View File

@ -100,6 +100,14 @@ Tag = ghostBookshelf.Model.extend({
ghostBookshelf.Model.prototype.onSaving.apply(this, arguments);
// Support tag creation with `posts: [{..., tags: [{slug: 'new'}]}]`
// In that situation we have a slug but no name so validation will fail
// unless we set one automatically. Re-using slug for name matches our
// opposite name->slug behaviour.
if (!newTag.get('name') && newTag.get('slug')) {
this.set('name', newTag.get('slug'));
}
// name: #later slug: hash-later
if (/^#/.test(newTag.get('name'))) {
this.set('visibility', 'internal');

View File

@ -372,11 +372,8 @@ describe('Importer', function () {
exportData.data.posts[1] = testUtils.DataGenerator.forKnex.createPost({slug: 'post2'});
exportData.data.posts[1].title = new Array(600).join('a');
exportData.data.tags[0] = testUtils.DataGenerator.forKnex.createTag({slug: 'tag1'});
exportData.data.tags[0].name = null;
exportData.data.tags[1] = testUtils.DataGenerator.forKnex.createTag({slug: 'tag2'});
exportData.data.tags[1].meta_title = new Array(305).join('a');
exportData.data.tags[0] = testUtils.DataGenerator.forKnex.createTag({slug: 'tag2'});
exportData.data.tags[0].meta_title = new Array(305).join('a');
exportData.data.users[0] = testUtils.DataGenerator.forKnex.createUser();
exportData.data.users[0].bio = new Array(300).join('a');
@ -393,7 +390,7 @@ describe('Importer', function () {
(1).should.eql(0, 'Allowed import of duplicate data.');
})
.catch(function (response) {
response.length.should.equal(7);
response.length.should.equal(6);
// NOTE: a duplicated tag.slug is a warning
response[0].errorType.should.equal('ValidationError');
@ -403,19 +400,16 @@ describe('Importer', function () {
response[1].message.should.eql('Validation (isEmail) failed for email');
response[2].errorType.should.equal('ValidationError');
response[2].message.should.eql('Value in [tags.name] cannot be blank.');
response[2].message.should.eql('Value in [tags.meta_title] exceeds maximum length of 300 characters.');
response[3].errorType.should.equal('ValidationError');
response[3].message.should.eql('Value in [tags.meta_title] exceeds maximum length of 300 characters.');
response[3].message.should.eql('Value in [posts.title] cannot be blank.');
response[3].errorType.should.eql('ValidationError');
response[4].message.should.eql('Value in [posts.title] cannot be blank.');
response[4].errorType.should.eql('ValidationError');
response[4].errorType.should.equal('ValidationError');
response[4].message.should.eql('Value in [posts.title] exceeds maximum length of 255 characters.');
response[5].errorType.should.equal('ValidationError');
response[5].message.should.eql('Value in [posts.title] exceeds maximum length of 255 characters.');
response[6].errorType.should.equal('ValidationError');
response[6].message.should.eql('Value in [settings.key] cannot be blank.');
response[5].message.should.eql('Value in [settings.key] cannot be blank.');
});
});

View File

@ -304,6 +304,98 @@ describe('Posts API (canary)', function () {
res.headers.location.should.equal(`http://127.0.0.1:2369${localUtils.API.getApiQuery('posts/')}${res.body.posts[0].id}/`);
});
});
it('can add with tags - array of strings with new names', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'Tags test 1',
tags: ['one', 'two']
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('Tags test 1');
res.body.posts[0].tags.length.should.equal(2);
res.body.posts[0].tags[0].slug.should.equal('one');
res.body.posts[0].tags[1].slug.should.equal('two');
});
});
it('can add with tags - array of strings with existing names', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'Tags test 2',
tags: ['one', 'two']
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('Tags test 2');
res.body.posts[0].tags.length.should.equal(2);
res.body.posts[0].tags[0].slug.should.equal('one');
res.body.posts[0].tags[1].slug.should.equal('two');
});
});
it('can add with tags - array of objects with existing slugs', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'Tags test 3',
tags: [{slug: 'one'}, {slug: 'two'}]
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('Tags test 3');
res.body.posts[0].tags.length.should.equal(2);
res.body.posts[0].tags[0].slug.should.equal('one');
res.body.posts[0].tags[1].slug.should.equal('two');
});
});
it('can add with tags - array of objects with new slugs', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'Tags test 4',
tags: [{slug: 'three'}, {slug: 'four'}]
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('Tags test 4');
res.body.posts[0].tags.length.should.equal(2);
res.body.posts[0].tags[0].slug.should.equal('three');
res.body.posts[0].tags[1].slug.should.equal('four');
});
});
});
describe('Edit', function () {

View File

@ -132,6 +132,98 @@ describe('Posts API (v2)', function () {
res.body.posts[0].title.should.equal('(Untitled)');
});
});
it('can add with tags - array of strings with new names', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'Tags test 1',
tags: ['one', 'two']
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('Tags test 1');
res.body.posts[0].tags.length.should.equal(2);
res.body.posts[0].tags[0].slug.should.equal('one');
res.body.posts[0].tags[1].slug.should.equal('two');
});
});
it('can add with tags - array of strings with existing names', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'Tags test 2',
tags: ['one', 'two']
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('Tags test 2');
res.body.posts[0].tags.length.should.equal(2);
res.body.posts[0].tags[0].slug.should.equal('one');
res.body.posts[0].tags[1].slug.should.equal('two');
});
});
it('can add with tags - array of objects with existing slugs', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'Tags test 3',
tags: [{slug: 'one'}, {slug: 'two'}]
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('Tags test 3');
res.body.posts[0].tags.length.should.equal(2);
res.body.posts[0].tags[0].slug.should.equal('one');
res.body.posts[0].tags[1].slug.should.equal('two');
});
});
it('can add with tags - array of objects with new slugs', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'Tags test 4',
tags: [{slug: 'three'}, {slug: 'four'}]
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('Tags test 4');
res.body.posts[0].tags.length.should.equal(2);
res.body.posts[0].tags[0].slug.should.equal('three');
res.body.posts[0].tags[1].slug.should.equal('four');
});
});
});
describe('Edit', function () {

View File

@ -298,6 +298,98 @@ describe('Posts API (v3)', function () {
res.headers.location.should.equal(`http://127.0.0.1:2369${localUtils.API.getApiQuery('posts/')}${res.body.posts[0].id}/`);
});
});
it('can add with tags - array of strings with new names', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'Tags test 1',
tags: ['one', 'two']
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('Tags test 1');
res.body.posts[0].tags.length.should.equal(2);
res.body.posts[0].tags[0].slug.should.equal('one');
res.body.posts[0].tags[1].slug.should.equal('two');
});
});
it('can add with tags - array of strings with existing names', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'Tags test 2',
tags: ['one', 'two']
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('Tags test 2');
res.body.posts[0].tags.length.should.equal(2);
res.body.posts[0].tags[0].slug.should.equal('one');
res.body.posts[0].tags[1].slug.should.equal('two');
});
});
it('can add with tags - array of objects with existing slugs', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'Tags test 3',
tags: [{slug: 'one'}, {slug: 'two'}]
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('Tags test 3');
res.body.posts[0].tags.length.should.equal(2);
res.body.posts[0].tags[0].slug.should.equal('one');
res.body.posts[0].tags[1].slug.should.equal('two');
});
});
it('can add with tags - array of objects with new slugs', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'Tags test 4',
tags: [{slug: 'three'}, {slug: 'four'}]
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('Tags test 4');
res.body.posts[0].tags.length.should.equal(2);
res.body.posts[0].tags[0].slug.should.equal('three');
res.body.posts[0].tags[1].slug.should.equal('four');
});
});
});
describe('Edit', function () {