diff --git a/core/server/models/tag.js b/core/server/models/tag.js index 9cbaed742a..51e95f0b3c 100644 --- a/core/server/models/tag.js +++ b/core/server/models/tag.js @@ -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'); diff --git a/test/integration/importer/v2.test.js b/test/integration/importer/v2.test.js index 65d337f58e..4a2eab0737 100644 --- a/test/integration/importer/v2.test.js +++ b/test/integration/importer/v2.test.js @@ -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.'); }); }); diff --git a/test/regression/api/canary/admin/posts.test.js b/test/regression/api/canary/admin/posts.test.js index b23b052389..7b3fc77801 100644 --- a/test/regression/api/canary/admin/posts.test.js +++ b/test/regression/api/canary/admin/posts.test.js @@ -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 () { diff --git a/test/regression/api/v2/admin/posts.test.js b/test/regression/api/v2/admin/posts.test.js index 02bf0c6dea..ccc4055083 100644 --- a/test/regression/api/v2/admin/posts.test.js +++ b/test/regression/api/v2/admin/posts.test.js @@ -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 () { diff --git a/test/regression/api/v3/admin/posts.test.js b/test/regression/api/v3/admin/posts.test.js index 4e80f74ffd..82ec2c6c2b 100644 --- a/test/regression/api/v3/admin/posts.test.js +++ b/test/regression/api/v3/admin/posts.test.js @@ -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 () {