2887e416da
refs: TryGhost/Toolbox#147 * Replaces all references to isIgnitionError with isGhostError * Switches use of GhostError to InternalServerError - as GhostError is no longer public There are places where InternalServerError is not the valid error, and new errors should be added to the @tryghost/errors package to ensure that we can use semantically correct errors in those cases.
1909 lines
88 KiB
JavaScript
1909 lines
88 KiB
JavaScript
const errors = require('@tryghost/errors');
|
||
const should = require('should');
|
||
const sinon = require('sinon');
|
||
const testUtils = require('../../utils');
|
||
const moment = require('moment');
|
||
const _ = require('lodash');
|
||
const Promise = require('bluebird');
|
||
const {sequence} = require('@tryghost/promise');
|
||
const urlService = require('../../../core/server/services/url');
|
||
const ghostBookshelf = require('../../../core/server/models/base');
|
||
const models = require('../../../core/server/models');
|
||
const db = require('../../../core/server/data/db');
|
||
const settingsCache = require('../../../core/shared/settings-cache');
|
||
const events = require('../../../core/server/lib/common/events');
|
||
const configUtils = require('../../utils/configUtils');
|
||
const context = testUtils.context.owner;
|
||
const markdownToMobiledoc = testUtils.DataGenerator.markdownToMobiledoc;
|
||
|
||
/**
|
||
* IMPORTANT:
|
||
* - do not spy the events unit, because when we only spy, all listeners get the event
|
||
* - this can cause unexpected behaviour as the listeners execute code
|
||
* - using rewire is not possible, because each model self registers it's model registry in bookshelf
|
||
* - rewire would add 1 registry, a file who requires the models, tries to register the model another time
|
||
*/
|
||
describe('Post Model', function () {
|
||
let eventsTriggered = {};
|
||
|
||
before(testUtils.teardownDb);
|
||
before(testUtils.stopGhost);
|
||
after(testUtils.teardownDb);
|
||
|
||
before(testUtils.setup('users:roles'));
|
||
|
||
afterEach(function () {
|
||
sinon.restore();
|
||
});
|
||
|
||
beforeEach(function () {
|
||
sinon.stub(urlService, 'getUrlByResourceId').withArgs(testUtils.DataGenerator.Content.posts[0].id).returns('/html-ipsum/');
|
||
});
|
||
|
||
describe('Single author posts', function () {
|
||
afterEach(function () {
|
||
configUtils.restore();
|
||
});
|
||
|
||
describe('fetchOne/fetchAll/fetchPage', function () {
|
||
before(testUtils.fixtures.insertPostsAndTags);
|
||
after(function () {
|
||
return testUtils.truncate('posts_tags')
|
||
.then(function () {
|
||
return testUtils.truncate('tags');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts_meta');
|
||
});
|
||
});
|
||
|
||
describe('findOne', function () {
|
||
it('transforms legacy email_recipient_filter values on read', function (done) {
|
||
const postId = testUtils.DataGenerator.Content.posts[0].id;
|
||
|
||
db.knex('posts').where({id: postId}).update({
|
||
email_recipient_filter: 'paid'
|
||
}).then(() => {
|
||
return db.knex('posts').where({id: postId});
|
||
}).then((knexResult) => {
|
||
const [knexPost] = knexResult;
|
||
knexPost.email_recipient_filter.should.equal('paid');
|
||
|
||
return models.Post.findOne({id: postId});
|
||
}).then((result) => {
|
||
should.exist(result);
|
||
const post = result.toJSON();
|
||
post.email_recipient_filter.should.equal('status:-free');
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
});
|
||
|
||
describe('findPage', function () {
|
||
describe('with more posts/tags', function () {
|
||
beforeEach(function () {
|
||
return testUtils.truncate('posts_tags')
|
||
.then(function () {
|
||
return testUtils.truncate('tags');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts_meta');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts');
|
||
});
|
||
});
|
||
|
||
beforeEach(function () {
|
||
return testUtils.fixtures.insertPostsAndTags()
|
||
.then(function () {
|
||
return testUtils.fixtures.insertExtraPosts();
|
||
})
|
||
.then(function () {
|
||
return testUtils.fixtures.insertExtraPostsTags();
|
||
});
|
||
});
|
||
|
||
it('can findPage, with various options', function (done) {
|
||
models.Post.findPage({page: 2})
|
||
.then(function (paginationResult) {
|
||
paginationResult.meta.pagination.page.should.equal(2);
|
||
paginationResult.meta.pagination.limit.should.equal(15);
|
||
paginationResult.meta.pagination.pages.should.equal(4);
|
||
paginationResult.data.length.should.equal(15);
|
||
|
||
return models.Post.findPage({page: 5});
|
||
}).then(function (paginationResult) {
|
||
paginationResult.meta.pagination.page.should.equal(5);
|
||
paginationResult.meta.pagination.limit.should.equal(15);
|
||
paginationResult.meta.pagination.pages.should.equal(4);
|
||
paginationResult.data.length.should.equal(0);
|
||
|
||
return models.Post.findPage({limit: 30});
|
||
}).then(function (paginationResult) {
|
||
paginationResult.meta.pagination.page.should.equal(1);
|
||
paginationResult.meta.pagination.limit.should.equal(30);
|
||
paginationResult.meta.pagination.pages.should.equal(2);
|
||
paginationResult.data.length.should.equal(30);
|
||
|
||
// Test featured pages
|
||
return models.Post.findPage({limit: 10, filter: 'featured:true'});
|
||
}).then(function (paginationResult) {
|
||
paginationResult.meta.pagination.page.should.equal(1);
|
||
paginationResult.meta.pagination.limit.should.equal(10);
|
||
paginationResult.meta.pagination.pages.should.equal(1);
|
||
paginationResult.data.length.should.equal(2);
|
||
|
||
// Test both boolean formats for featured pages
|
||
return models.Post.findPage({limit: 10, filter: 'featured:1'});
|
||
}).then(function (paginationResult) {
|
||
paginationResult.meta.pagination.page.should.equal(1);
|
||
paginationResult.meta.pagination.limit.should.equal(10);
|
||
paginationResult.meta.pagination.pages.should.equal(1);
|
||
paginationResult.data.length.should.equal(2);
|
||
|
||
return models.Post.findPage({limit: 10, page: 2, status: 'all'});
|
||
}).then(function (paginationResult) {
|
||
paginationResult.meta.pagination.pages.should.equal(11);
|
||
|
||
return models.Post.findPage({limit: 'all', status: 'all'});
|
||
}).then(function (paginationResult) {
|
||
paginationResult.meta.pagination.page.should.equal(1);
|
||
paginationResult.meta.pagination.limit.should.equal('all');
|
||
paginationResult.meta.pagination.pages.should.equal(1);
|
||
paginationResult.data.length.should.equal(108);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('can findPage for tag, with various options', function (done) {
|
||
// Test tag filter
|
||
models.Post.findPage({page: 1, filter: 'tags:bacon'})
|
||
.then(function (paginationResult) {
|
||
paginationResult.meta.pagination.page.should.equal(1);
|
||
paginationResult.meta.pagination.limit.should.equal(15);
|
||
paginationResult.meta.pagination.pages.should.equal(1);
|
||
paginationResult.data.length.should.equal(2);
|
||
|
||
return models.Post.findPage({page: 1, filter: 'tags:kitchen-sink'});
|
||
}).then(function (paginationResult) {
|
||
paginationResult.meta.pagination.page.should.equal(1);
|
||
paginationResult.meta.pagination.limit.should.equal(15);
|
||
paginationResult.meta.pagination.pages.should.equal(1);
|
||
paginationResult.data.length.should.equal(2);
|
||
|
||
return models.Post.findPage({page: 1, filter: 'tags:injection'});
|
||
}).then(function (paginationResult) {
|
||
paginationResult.meta.pagination.page.should.equal(1);
|
||
paginationResult.meta.pagination.limit.should.equal(15);
|
||
paginationResult.meta.pagination.pages.should.equal(2);
|
||
paginationResult.data.length.should.equal(15);
|
||
|
||
return models.Post.findPage({page: 2, filter: 'tags:injection'});
|
||
}).then(function (paginationResult) {
|
||
paginationResult.meta.pagination.page.should.equal(2);
|
||
paginationResult.meta.pagination.limit.should.equal(15);
|
||
paginationResult.meta.pagination.pages.should.equal(2);
|
||
paginationResult.data.length.should.equal(10);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('edit', function () {
|
||
beforeEach(testUtils.fixtures.insertPostsAndTags);
|
||
|
||
afterEach(function () {
|
||
return testUtils.truncate('posts_tags')
|
||
.then(function () {
|
||
return testUtils.truncate('tags');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts_meta');
|
||
});
|
||
});
|
||
|
||
beforeEach(function () {
|
||
eventsTriggered = {};
|
||
|
||
sinon.stub(events, 'emit').callsFake(function (eventName, eventObj) {
|
||
if (!eventsTriggered[eventName]) {
|
||
eventsTriggered[eventName] = [];
|
||
}
|
||
|
||
eventsTriggered[eventName].push(eventObj);
|
||
});
|
||
});
|
||
|
||
it('[failure] multiple edits in one transaction', function () {
|
||
const options = _.cloneDeep(context);
|
||
|
||
const data = {
|
||
status: 'published'
|
||
};
|
||
|
||
return models.Base.transaction(function (txn) {
|
||
options.transacting = txn;
|
||
|
||
return models.Post.edit(data, _.merge({id: testUtils.DataGenerator.Content.posts[3].id}, options))
|
||
.then(function () {
|
||
return models.Post.edit(data, _.merge({id: testUtils.DataGenerator.Content.posts[5].id}, options));
|
||
})
|
||
.then(function () {
|
||
// force rollback
|
||
throw new Error();
|
||
});
|
||
}).catch(function () {
|
||
// txn was rolled back
|
||
Object.keys(eventsTriggered).length.should.eql(0);
|
||
});
|
||
});
|
||
|
||
it('multiple edits in one transaction', function () {
|
||
const options = _.cloneDeep(context);
|
||
|
||
const data = {
|
||
status: 'published'
|
||
};
|
||
|
||
return models.Base.transaction(function (txn) {
|
||
options.transacting = txn;
|
||
|
||
return models.Post.edit(data, _.merge({id: testUtils.DataGenerator.Content.posts[3].id}, options))
|
||
.then(function () {
|
||
return models.Post.edit(data, _.merge({id: testUtils.DataGenerator.Content.posts[5].id}, options));
|
||
});
|
||
}).then(function () {
|
||
// txn was successful
|
||
Object.keys(eventsTriggered).length.should.eql(4);
|
||
});
|
||
});
|
||
|
||
it('can change title', function (done) {
|
||
const postId = testUtils.DataGenerator.Content.posts[0].id;
|
||
|
||
models.Post.findOne({id: postId}).then(function (results) {
|
||
let post;
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.id.should.equal(postId);
|
||
post.title.should.not.equal('new title');
|
||
|
||
return models.Post.edit({title: 'new title'}, _.extend({}, context, {id: postId}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.title.should.equal('new title');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.published.edited']);
|
||
should.exist(eventsTriggered['post.edited']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('[failure] custom excerpt soft limit reached', function (done) {
|
||
const postId = testUtils.DataGenerator.Content.posts[0].id;
|
||
|
||
models.Post.findOne({id: postId}).then(function (results) {
|
||
let post;
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.id.should.equal(postId);
|
||
|
||
return models.Post.edit({
|
||
custom_excerpt: new Array(302).join('a')
|
||
}, _.extend({}, context, {id: postId}));
|
||
}).then(function () {
|
||
done(new Error('expected validation error'));
|
||
}).catch(function (err) {
|
||
err[0].name.should.eql('ValidationError');
|
||
done();
|
||
});
|
||
});
|
||
|
||
it('can publish draft post', function (done) {
|
||
const postId = testUtils.DataGenerator.Content.posts[3].id;
|
||
|
||
models.Post.findOne({id: postId, status: 'draft'}).then(function (results) {
|
||
let post;
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.id.should.equal(postId);
|
||
post.status.should.equal('draft');
|
||
|
||
return models.Post.edit({status: 'published'}, _.extend({}, context, {id: postId}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('published');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(4);
|
||
should.exist(eventsTriggered['post.published']);
|
||
should.exist(eventsTriggered['post.edited']);
|
||
should.exist(eventsTriggered['tag.attached']);
|
||
should.exist(eventsTriggered['user.attached']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('can unpublish published post', function (done) {
|
||
const postId = testUtils.DataGenerator.Content.posts[0].id;
|
||
|
||
models.Post.findOne({id: postId}).then(function (results) {
|
||
let post;
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.id.should.equal(postId);
|
||
post.status.should.equal('published');
|
||
|
||
return models.Post.edit({status: 'draft'}, _.extend({}, context, {id: postId}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('draft');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(4);
|
||
should.exist(eventsTriggered['post.unpublished']);
|
||
should.exist(eventsTriggered['post.edited']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('draft -> scheduled without published_at update', function (done) {
|
||
let post;
|
||
|
||
models.Post.findOne({status: 'draft'}).then(function (results) {
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.status.should.equal('draft');
|
||
|
||
results.set('published_at', null);
|
||
return results.save();
|
||
}).then(function () {
|
||
return models.Post.edit({
|
||
status: 'scheduled'
|
||
}, _.extend({}, context, {id: post.id}));
|
||
}).then(function () {
|
||
done(new Error('expected error'));
|
||
}).catch(function (err) {
|
||
should.exist(err);
|
||
(err instanceof errors.ValidationError).should.eql(true);
|
||
done();
|
||
});
|
||
});
|
||
|
||
it('draft -> scheduled: expect update of published_at', function (done) {
|
||
const newPublishedAt = moment().add(1, 'day').toDate();
|
||
|
||
models.Post.findOne({status: 'draft'}).then(function (results) {
|
||
let post;
|
||
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.status.should.equal('draft');
|
||
|
||
return models.Post.edit({
|
||
status: 'scheduled',
|
||
published_at: newPublishedAt
|
||
}, _.extend({}, context, {id: post.id}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('scheduled');
|
||
|
||
// mysql does not store ms
|
||
moment(edited.attributes.published_at).startOf('seconds').diff(moment(newPublishedAt).startOf('seconds')).should.eql(0);
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.scheduled']);
|
||
should.exist(eventsTriggered['post.edited']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('scheduled -> draft: expect unschedule', function (done) {
|
||
models.Post.findOne({status: 'scheduled'}).then(function (results) {
|
||
let post;
|
||
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.status.should.equal('scheduled');
|
||
|
||
return models.Post.edit({
|
||
status: 'draft'
|
||
}, _.extend({}, context, {id: post.id}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('draft');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.unscheduled']);
|
||
should.exist(eventsTriggered['post.edited']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('scheduled -> scheduled with updated published_at', function (done) {
|
||
models.Post.findOne({status: 'scheduled'}).then(function (results) {
|
||
let post;
|
||
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.status.should.equal('scheduled');
|
||
|
||
return models.Post.edit({
|
||
status: 'scheduled',
|
||
published_at: moment().add(20, 'days').toDate()
|
||
}, _.extend({}, context, {id: post.id}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('scheduled');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.rescheduled']);
|
||
should.exist(eventsTriggered['post.edited']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('scheduled -> scheduled with unchanged published_at', function (done) {
|
||
models.Post.findOne({status: 'scheduled'}).then(function (results) {
|
||
let post;
|
||
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.status.should.equal('scheduled');
|
||
|
||
return models.Post.edit({
|
||
status: 'scheduled'
|
||
}, _.extend({}, context, {id: post.id}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('scheduled');
|
||
|
||
// nothing has changed
|
||
Object.keys(eventsTriggered).length.should.eql(0);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('scheduled -> scheduled with unchanged published_at (within the 2 minutes window)', function (done) {
|
||
let post;
|
||
|
||
models.Post.findOne({status: 'scheduled'}).then(function (results) {
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.status.should.equal('scheduled');
|
||
|
||
results.set('published_at', moment().add(2, 'minutes').add(2, 'seconds').toDate());
|
||
return results.save();
|
||
}).then(function () {
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.edited']);
|
||
should.exist(eventsTriggered['post.rescheduled']);
|
||
eventsTriggered = {};
|
||
|
||
return Promise.delay(1000 * 3);
|
||
}).then(function () {
|
||
return models.Post.edit({
|
||
status: 'scheduled'
|
||
}, _.extend({}, context, {id: post.id}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('scheduled');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(1);
|
||
should.exist(eventsTriggered['post.edited']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('published -> scheduled and expect update of published_at', function (done) {
|
||
const postId = testUtils.DataGenerator.Content.posts[0].id;
|
||
|
||
models.Post.findOne({id: postId}).then(function (results) {
|
||
let post;
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.id.should.equal(postId);
|
||
post.status.should.equal('published');
|
||
|
||
return models.Post.edit({
|
||
status: 'scheduled',
|
||
published_at: moment().add(1, 'day').toDate()
|
||
}, _.extend({}, context, {id: postId}));
|
||
}).then(function () {
|
||
done(new Error('change status from published to scheduled is not allowed right now!'));
|
||
}).catch(function (err) {
|
||
should.exist(err);
|
||
(err instanceof errors.ValidationError).should.eql(true);
|
||
done();
|
||
});
|
||
});
|
||
|
||
it('can convert draft post to page and back', function (done) {
|
||
const postId = testUtils.DataGenerator.Content.posts[3].id;
|
||
|
||
models.Post.findOne({id: postId, status: 'draft'}).then(function (results) {
|
||
let post;
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.id.should.equal(postId);
|
||
post.status.should.equal('draft');
|
||
|
||
return models.Post.edit({type: 'page'}, _.extend({}, context, {id: postId}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('draft');
|
||
edited.attributes.type.should.equal('page');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.deleted']);
|
||
should.exist(eventsTriggered['page.added']);
|
||
|
||
return models.Post.edit({type: 'post'}, _.extend({}, context, {id: postId}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('draft');
|
||
edited.attributes.type.should.equal('post');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(4);
|
||
should.exist(eventsTriggered['post.deleted']);
|
||
should.exist(eventsTriggered['page.added']);
|
||
should.exist(eventsTriggered['post.deleted']);
|
||
should.exist(eventsTriggered['post.added']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('can convert draft to schedule AND post to page and back', function (done) {
|
||
models.Post.findOne({status: 'draft'}).then(function (results) {
|
||
let post;
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.status.should.equal('draft');
|
||
|
||
return models.Post.edit({
|
||
type: 'page',
|
||
status: 'scheduled',
|
||
published_at: moment().add(10, 'days')
|
||
}, _.extend({}, context, {id: post.id}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('scheduled');
|
||
edited.attributes.type.should.equal('page');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(3);
|
||
should.exist(eventsTriggered['post.deleted']);
|
||
should.exist(eventsTriggered['page.added']);
|
||
should.exist(eventsTriggered['page.scheduled']);
|
||
|
||
return models.Post.edit({type: 'post'}, _.extend({}, context, {id: edited.id}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('scheduled');
|
||
edited.attributes.type.should.equal('post');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(7);
|
||
should.exist(eventsTriggered['page.unscheduled']);
|
||
should.exist(eventsTriggered['page.deleted']);
|
||
should.exist(eventsTriggered['post.added']);
|
||
should.exist(eventsTriggered['post.scheduled']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('can convert published post to page and back', function (done) {
|
||
const postId = testUtils.DataGenerator.Content.posts[0].id;
|
||
|
||
models.Post.findOne({id: postId}).then(function (results) {
|
||
let post;
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.id.should.equal(postId);
|
||
post.status.should.equal('published');
|
||
|
||
return models.Post.edit({type: 'page'}, _.extend({}, context, {id: postId}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('published');
|
||
edited.attributes.type.should.equal('page');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(4);
|
||
should.exist(eventsTriggered['post.unpublished']);
|
||
should.exist(eventsTriggered['post.deleted']);
|
||
should.exist(eventsTriggered['page.added']);
|
||
should.exist(eventsTriggered['page.published']);
|
||
|
||
return models.Post.edit({type: 'post'}, _.extend({}, context, {id: postId}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('published');
|
||
edited.attributes.type.should.equal('post');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(8);
|
||
should.exist(eventsTriggered['page.unpublished']);
|
||
should.exist(eventsTriggered['page.deleted']);
|
||
should.exist(eventsTriggered['post.added']);
|
||
should.exist(eventsTriggered['post.published']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('can change type and status at the same time', function (done) {
|
||
const postId = testUtils.DataGenerator.Content.posts[3].id;
|
||
|
||
models.Post.findOne({id: postId, status: 'draft'}).then(function (results) {
|
||
let post;
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.id.should.equal(postId);
|
||
post.status.should.equal('draft');
|
||
|
||
return models.Post.edit({type: 'page', status: 'published'}, _.extend({}, context, {id: postId}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('published');
|
||
edited.attributes.type.should.equal('page');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(5);
|
||
should.exist(eventsTriggered['post.deleted']);
|
||
should.exist(eventsTriggered['page.added']);
|
||
should.exist(eventsTriggered['page.published']);
|
||
should.exist(eventsTriggered['tag.attached']);
|
||
should.exist(eventsTriggered['user.attached']);
|
||
|
||
return models.Post.edit({type: 'post', status: 'draft'}, _.extend({}, context, {id: postId}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('draft');
|
||
edited.attributes.type.should.equal('post');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(8);
|
||
should.exist(eventsTriggered['page.unpublished']);
|
||
should.exist(eventsTriggered['page.deleted']);
|
||
should.exist(eventsTriggered['post.added']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('cannot override the published_by setting', function (done) {
|
||
const postId = testUtils.DataGenerator.Content.posts[3].id;
|
||
|
||
models.Post.findOne({id: postId, status: 'draft'}).then(function (results) {
|
||
let post;
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.id.should.equal(postId);
|
||
post.status.should.equal('draft');
|
||
|
||
// Test changing status and published_by at the same time
|
||
return models.Post.edit({
|
||
status: 'published',
|
||
published_by: 4
|
||
}, _.extend({}, context, {id: postId}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('published');
|
||
edited.attributes.published_by.should.equal(context.context.user);
|
||
|
||
// Test changing status and published_by on its own
|
||
return models.Post.edit({published_by: 4}, _.extend({}, context, {id: postId}));
|
||
}).then(function (edited) {
|
||
should.exist(edited);
|
||
edited.attributes.status.should.equal('published');
|
||
edited.attributes.published_by.should.equal(context.context.user);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('transforms legacy email_recipient_filter values on save', function (done) {
|
||
const postId = testUtils.DataGenerator.Content.posts[3].id;
|
||
|
||
models.Post.findOne({id: postId}).then(() => {
|
||
return models.Post.edit({
|
||
email_recipient_filter: 'free'
|
||
}, _.extend({}, context, {id: postId}));
|
||
}).then((edited) => {
|
||
edited.attributes.email_recipient_filter.should.equal('status:free');
|
||
return db.knex('posts').where({id: edited.id});
|
||
}).then((knexResult) => {
|
||
const [knexPost] = knexResult;
|
||
knexPost.email_recipient_filter.should.equal('status:free');
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('transforms special-case visibility values on save', function (done) {
|
||
// status:-free === paid
|
||
// status:-free,status:free (+variations) === members
|
||
|
||
const postId = testUtils.DataGenerator.Content.posts[3].id;
|
||
|
||
models.Post.findOne({id: postId}).then(() => {
|
||
return models.Post.edit({
|
||
visibility: 'status:-free'
|
||
}, _.extend({}, context, {id: postId}));
|
||
}).then((edited) => {
|
||
edited.attributes.visibility.should.equal('paid');
|
||
return db.knex('posts').where({id: edited.id});
|
||
}).then((knexResult) => {
|
||
const [knexPost] = knexResult;
|
||
knexPost.visibility.should.equal('paid');
|
||
}).then(() => {
|
||
return models.Post.edit({
|
||
visibility: 'status:-free,status:free'
|
||
}, _.extend({}, context, {id: postId}));
|
||
}).then((edited) => {
|
||
edited.attributes.visibility.should.equal('members');
|
||
|
||
return models.Post.edit({
|
||
visibility: 'status:free,status:-free'
|
||
}, _.extend({}, context, {id: postId}));
|
||
}).then((edited) => {
|
||
edited.attributes.visibility.should.equal('members');
|
||
|
||
return models.Post.edit({
|
||
visibility: 'status:free,status:-free,label:vip'
|
||
}, _.extend({}, context, {id: postId}));
|
||
}).then((edited) => {
|
||
edited.attributes.visibility.should.equal('members');
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
});
|
||
|
||
describe('add', function () {
|
||
before(testUtils.fixtures.insertPostsAndTags);
|
||
|
||
after(function () {
|
||
return testUtils.truncate('posts_tags')
|
||
.then(function () {
|
||
return testUtils.truncate('tags');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts_meta');
|
||
});
|
||
});
|
||
|
||
beforeEach(function () {
|
||
eventsTriggered = {};
|
||
|
||
sinon.stub(events, 'emit').callsFake(function (eventName, eventObj) {
|
||
if (!eventsTriggered[eventName]) {
|
||
eventsTriggered[eventName] = [];
|
||
}
|
||
|
||
eventsTriggered[eventName].push(eventObj);
|
||
});
|
||
});
|
||
|
||
it('can add, defaults are all correct', function (done) {
|
||
let createdPostUpdatedDate;
|
||
const newPost = testUtils.DataGenerator.forModel.posts[2];
|
||
const newPostDB = testUtils.DataGenerator.Content.posts[2];
|
||
|
||
models.Post.add(newPost, _.merge({withRelated: ['author']}, context)).then(function (createdPost) {
|
||
return models.Post.findOne({id: createdPost.id, status: 'all'});
|
||
}).then(function (createdPost) {
|
||
should.exist(createdPost);
|
||
createdPost.has('uuid').should.equal(true);
|
||
createdPost.get('status').should.equal('draft');
|
||
createdPost.get('title').should.equal(newPost.title, 'title is correct');
|
||
createdPost.get('mobiledoc').should.equal(newPost.mobiledoc, 'mobiledoc is correct');
|
||
createdPost.has('html').should.equal(true);
|
||
createdPost.get('html').should.equal(newPostDB.html);
|
||
createdPost.has('plaintext').should.equal(true);
|
||
createdPost.get('plaintext').should.match(/^testing/);
|
||
createdPost.get('slug').should.equal(newPostDB.slug + '-2');
|
||
(!!createdPost.get('featured')).should.equal(false);
|
||
(!!createdPost.get('page')).should.equal(false);
|
||
|
||
should.equal(createdPost.get('locale'), null);
|
||
should.equal(createdPost.get('visibility'), 'public');
|
||
|
||
// testing for nulls
|
||
(createdPost.get('feature_image') === null).should.equal(true);
|
||
|
||
createdPost.get('created_at').should.be.above(new Date(0).getTime());
|
||
createdPost.get('created_by').should.equal(testUtils.DataGenerator.Content.users[0].id);
|
||
createdPost.get('author_id').should.equal(testUtils.DataGenerator.Content.users[0].id);
|
||
createdPost.has('author').should.equal(false);
|
||
createdPost.get('created_by').should.equal(createdPost.get('author_id'));
|
||
createdPost.get('updated_at').should.be.above(new Date(0).getTime());
|
||
createdPost.get('updated_by').should.equal(testUtils.DataGenerator.Content.users[0].id);
|
||
should.equal(createdPost.get('published_at'), null);
|
||
should.equal(createdPost.get('published_by'), null);
|
||
|
||
createdPostUpdatedDate = createdPost.get('updated_at');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.added']);
|
||
should.exist(eventsTriggered['user.attached']);
|
||
|
||
// Set the status to published to check that `published_at` is set.
|
||
return createdPost.save({status: 'published'}, context);
|
||
}).then(function (publishedPost) {
|
||
publishedPost.get('published_at').should.be.instanceOf(Date);
|
||
publishedPost.get('published_by').should.equal(testUtils.DataGenerator.Content.users[0].id);
|
||
publishedPost.get('updated_at').should.be.instanceOf(Date);
|
||
publishedPost.get('updated_by').should.equal(testUtils.DataGenerator.Content.users[0].id);
|
||
publishedPost.get('updated_at').should.not.equal(createdPostUpdatedDate);
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(4);
|
||
should.exist(eventsTriggered['post.published']);
|
||
should.exist(eventsTriggered['post.edited']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('can add, default visibility is taken from settings cache', function (done) {
|
||
const originalSettingsCacheGetFn = settingsCache.get;
|
||
sinon.stub(settingsCache, 'get')
|
||
.callsFake(function (key, options) {
|
||
if (key === 'labs') {
|
||
return {
|
||
members: true
|
||
};
|
||
} else if (key === 'default_content_visibility') {
|
||
return 'paid';
|
||
}
|
||
|
||
return originalSettingsCacheGetFn(key, options);
|
||
});
|
||
|
||
let createdPostUpdatedDate;
|
||
const newPost = testUtils.DataGenerator.forModel.posts[2];
|
||
const newPostDB = testUtils.DataGenerator.Content.posts[2];
|
||
|
||
models.Post.add(newPost, _.merge({withRelated: ['author']}, context)).then(function (createdPost) {
|
||
return models.Post.findOne({id: createdPost.id, status: 'all'});
|
||
}).then(function (createdPost) {
|
||
should.exist(createdPost);
|
||
createdPost.has('uuid').should.equal(true);
|
||
createdPost.get('status').should.equal('draft');
|
||
createdPost.get('title').should.equal(newPost.title, 'title is correct');
|
||
createdPost.get('mobiledoc').should.equal(newPost.mobiledoc, 'mobiledoc is correct');
|
||
createdPost.has('html').should.equal(true);
|
||
createdPost.get('html').should.equal(newPostDB.html);
|
||
createdPost.has('plaintext').should.equal(true);
|
||
createdPost.get('plaintext').should.match(/^testing/);
|
||
// createdPost.get('slug').should.equal(newPostDB.slug + '-3');
|
||
(!!createdPost.get('featured')).should.equal(false);
|
||
(!!createdPost.get('page')).should.equal(false);
|
||
|
||
should.equal(createdPost.get('locale'), null);
|
||
should.equal(createdPost.get('visibility'), 'paid');
|
||
|
||
// testing for nulls
|
||
(createdPost.get('feature_image') === null).should.equal(true);
|
||
|
||
createdPost.get('created_at').should.be.above(new Date(0).getTime());
|
||
createdPost.get('created_by').should.equal(testUtils.DataGenerator.Content.users[0].id);
|
||
createdPost.get('author_id').should.equal(testUtils.DataGenerator.Content.users[0].id);
|
||
createdPost.has('author').should.equal(false);
|
||
createdPost.get('created_by').should.equal(createdPost.get('author_id'));
|
||
createdPost.get('updated_at').should.be.above(new Date(0).getTime());
|
||
createdPost.get('updated_by').should.equal(testUtils.DataGenerator.Content.users[0].id);
|
||
should.equal(createdPost.get('published_at'), null);
|
||
should.equal(createdPost.get('published_by'), null);
|
||
|
||
createdPostUpdatedDate = createdPost.get('updated_at');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.added']);
|
||
should.exist(eventsTriggered['user.attached']);
|
||
|
||
// Set the status to published to check that `published_at` is set.
|
||
return createdPost.save({status: 'published'}, context);
|
||
}).then(function (publishedPost) {
|
||
publishedPost.get('published_at').should.be.instanceOf(Date);
|
||
publishedPost.get('published_by').should.equal(testUtils.DataGenerator.Content.users[0].id);
|
||
publishedPost.get('updated_at').should.be.instanceOf(Date);
|
||
publishedPost.get('updated_by').should.equal(testUtils.DataGenerator.Content.users[0].id);
|
||
publishedPost.get('updated_at').should.not.equal(createdPostUpdatedDate);
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(4);
|
||
should.exist(eventsTriggered['post.published']);
|
||
should.exist(eventsTriggered['post.edited']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('can add, with previous published_at date', function (done) {
|
||
const previousPublishedAtDate = new Date(2013, 8, 21, 12);
|
||
|
||
models.Post.add({
|
||
status: 'published',
|
||
published_at: previousPublishedAtDate,
|
||
title: 'published_at test',
|
||
mobiledoc: markdownToMobiledoc('This is some content')
|
||
}, context).then(function (newPost) {
|
||
should.exist(newPost);
|
||
new Date(newPost.get('published_at')).getTime().should.equal(previousPublishedAtDate.getTime());
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(3);
|
||
should.exist(eventsTriggered['post.added']);
|
||
should.exist(eventsTriggered['post.published']);
|
||
should.exist(eventsTriggered['user.attached']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('add draft post without published_at -> we expect no auto insert of published_at', function (done) {
|
||
models.Post.add({
|
||
status: 'draft',
|
||
title: 'draft 1',
|
||
mobiledoc: markdownToMobiledoc('This is some content')
|
||
}, context).then(function (newPost) {
|
||
should.exist(newPost);
|
||
should.not.exist(newPost.get('published_at'));
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.added']);
|
||
should.exist(eventsTriggered['user.attached']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('add multiple authors', function (done) {
|
||
models.Post.add({
|
||
status: 'draft',
|
||
title: 'draft 1',
|
||
mobiledoc: markdownToMobiledoc('This is some content'),
|
||
authors: [{
|
||
id: testUtils.DataGenerator.forKnex.users[0].id,
|
||
name: testUtils.DataGenerator.forKnex.users[0].name
|
||
}]
|
||
}, _.merge({withRelated: ['authors']}, context)).then(function (newPost) {
|
||
should.exist(newPost);
|
||
newPost.toJSON().author.should.eql(testUtils.DataGenerator.forKnex.users[0].id);
|
||
newPost.toJSON().authors.length.should.eql(1);
|
||
newPost.toJSON().authors[0].id.should.eql(testUtils.DataGenerator.forKnex.users[0].id);
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('add draft post with published_at -> we expect published_at to exist', function (done) {
|
||
models.Post.add({
|
||
status: 'draft',
|
||
published_at: moment().toDate(),
|
||
title: 'draft 1',
|
||
mobiledoc: markdownToMobiledoc('This is some content')
|
||
}, context).then(function (newPost) {
|
||
should.exist(newPost);
|
||
should.exist(newPost.get('published_at'));
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.added']);
|
||
should.exist(eventsTriggered['user.attached']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('add scheduled post without published_at -> we expect an error', function (done) {
|
||
models.Post.add({
|
||
status: 'scheduled',
|
||
title: 'scheduled 1',
|
||
mobiledoc: markdownToMobiledoc('This is some content')
|
||
}, context).catch(function (err) {
|
||
should.exist(err);
|
||
(err instanceof errors.ValidationError).should.eql(true);
|
||
Object.keys(eventsTriggered).length.should.eql(0);
|
||
done();
|
||
});
|
||
});
|
||
|
||
it('add scheduled post with published_at not in future-> we expect an error', function (done) {
|
||
models.Post.add({
|
||
status: 'scheduled',
|
||
published_at: moment().subtract(1, 'minute'),
|
||
title: 'scheduled 1',
|
||
mobiledoc: markdownToMobiledoc('This is some content')
|
||
}, context).catch(function (err) {
|
||
should.exist(err);
|
||
(err instanceof errors.ValidationError).should.eql(true);
|
||
Object.keys(eventsTriggered).length.should.eql(0);
|
||
done();
|
||
});
|
||
});
|
||
|
||
it('add scheduled post with published_at 1 minutes in future -> we expect an error', function (done) {
|
||
models.Post.add({
|
||
status: 'scheduled',
|
||
published_at: moment().add(1, 'minute'),
|
||
title: 'scheduled 1',
|
||
mobiledoc: markdownToMobiledoc('This is some content')
|
||
}, context).catch(function (err) {
|
||
(err instanceof errors.ValidationError).should.eql(true);
|
||
Object.keys(eventsTriggered).length.should.eql(0);
|
||
done();
|
||
});
|
||
});
|
||
|
||
it('add scheduled post with published_at 10 minutes in future -> we expect success', function (done) {
|
||
models.Post.add({
|
||
status: 'scheduled',
|
||
published_at: moment().add(10, 'minute'),
|
||
title: 'scheduled 1',
|
||
mobiledoc: markdownToMobiledoc('This is some content')
|
||
}, context).then(function (post) {
|
||
should.exist(post);
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(3);
|
||
should.exist(eventsTriggered['post.added']);
|
||
should.exist(eventsTriggered['post.scheduled']);
|
||
should.exist(eventsTriggered['user.attached']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('can generate a non conflicting slug', function (done) {
|
||
// Create 12 posts with the same title
|
||
sequence(_.times(12, function (i) {
|
||
return function () {
|
||
return models.Post.add({
|
||
title: 'Test Title',
|
||
mobiledoc: markdownToMobiledoc('Test Content ' + (i + 1))
|
||
}, context);
|
||
};
|
||
})).then(function (createdPosts) {
|
||
// Should have created 12 posts
|
||
createdPosts.length.should.equal(12);
|
||
|
||
// Should have unique slugs and contents
|
||
_(createdPosts).each(function (post, i) {
|
||
const num = i + 1;
|
||
|
||
// First one has normal title
|
||
if (num === 1) {
|
||
post.get('slug').should.equal('test-title');
|
||
return;
|
||
}
|
||
|
||
post.get('slug').should.equal('test-title-' + num);
|
||
JSON.parse(post.get('mobiledoc')).cards[0][1].markdown.should.equal('Test Content ' + num);
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.added']);
|
||
should.exist(eventsTriggered['user.attached']);
|
||
eventsTriggered['post.added'].length.should.eql(12);
|
||
});
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('can generate slugs without duplicate hyphens', function (done) {
|
||
const newPost = {
|
||
title: 'apprehensive titles have too many spaces—and m-dashes — – and also n-dashes ',
|
||
mobiledoc: markdownToMobiledoc('Test Content 1')
|
||
};
|
||
|
||
models.Post.add(newPost, context).then(function (createdPost) {
|
||
createdPost.get('slug').should.equal('apprehensive-titles-have-too-many-spaces-and-m-dashes-and-also-n-dashes');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.added']);
|
||
should.exist(eventsTriggered['user.attached']);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('can generate a safe slug when a protected keyword is used', function (done) {
|
||
const newPost = {
|
||
title: 'rss',
|
||
mobiledoc: markdownToMobiledoc('Test Content 1')
|
||
};
|
||
|
||
models.Post.add(newPost, context).then(function (createdPost) {
|
||
createdPost.get('slug').should.not.equal('rss');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.added']);
|
||
should.exist(eventsTriggered['user.attached']);
|
||
|
||
done();
|
||
});
|
||
});
|
||
|
||
it('can generate slugs without non-ascii characters', function (done) {
|
||
const newPost = {
|
||
title: 'भुते धडकी भरवणारा आहेत',
|
||
mobiledoc: markdownToMobiledoc('Test Content 1')
|
||
};
|
||
|
||
models.Post.add(newPost, context).then(function (createdPost) {
|
||
createdPost.get('slug').should.equal('bhute-dhddkii-bhrvnnaaraa-aahet');
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('detects duplicate slugs before saving', function (done) {
|
||
const firstPost = {
|
||
title: 'First post',
|
||
mobiledoc: markdownToMobiledoc('First content 1')
|
||
};
|
||
|
||
const secondPost = {
|
||
title: 'Second post',
|
||
mobiledoc: markdownToMobiledoc('Second content 1')
|
||
};
|
||
|
||
// Create the first post
|
||
models.Post.add(firstPost, context)
|
||
.then(function (createdFirstPost) {
|
||
// Store the slug for later
|
||
firstPost.slug = createdFirstPost.get('slug');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.added']);
|
||
should.exist(eventsTriggered['user.attached']);
|
||
|
||
// Create the second post
|
||
return models.Post.add(secondPost, context);
|
||
}).then(function (createdSecondPost) {
|
||
// Store the slug for comparison later
|
||
secondPost.slug = createdSecondPost.get('slug');
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['post.added']);
|
||
should.exist(eventsTriggered['user.attached']);
|
||
|
||
// Update with a conflicting slug from the first post
|
||
return createdSecondPost.save({
|
||
slug: firstPost.slug
|
||
}, context);
|
||
}).then(function (updatedSecondPost) {
|
||
// Should have updated from original
|
||
updatedSecondPost.get('slug').should.not.equal(secondPost.slug);
|
||
// Should not have a conflicted slug from the first
|
||
updatedSecondPost.get('slug').should.not.equal(firstPost.slug);
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(3);
|
||
should.exist(eventsTriggered['post.edited']);
|
||
|
||
return models.Post.findOne({
|
||
id: updatedSecondPost.id,
|
||
status: 'all'
|
||
});
|
||
}).then(function (foundPost) {
|
||
// Should have updated from original
|
||
foundPost.get('slug').should.not.equal(secondPost.slug);
|
||
// Should not have a conflicted slug from the first
|
||
foundPost.get('slug').should.not.equal(firstPost.slug);
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('it stores urls as transform-ready and reads as absolute', function (done) {
|
||
const post = {
|
||
title: 'Absolute->Transform-ready URL Transform Test',
|
||
mobiledoc: '{"version":"0.3.1","atoms":[],"cards":[["image",{"src":"http://127.0.0.1:2369/content/images/card.jpg"}]],"markups":[["a",["href","http://127.0.0.1:2369/test"]]],"sections":[[1,"p",[[0,[0],1,"Testing"]]],[10,0]]}',
|
||
custom_excerpt: 'Testing <a href="http://127.0.0.1:2369/internal">links</a> in custom excerpts',
|
||
codeinjection_head: '<script src="http://127.0.0.1:2369/assets/head.js"></script>',
|
||
codeinjection_foot: '<script src="http://127.0.0.1:2369/assets/foot.js"></script>',
|
||
feature_image: 'http://127.0.0.1:2369/content/images/feature.png',
|
||
canonical_url: 'http://127.0.0.1:2369/canonical',
|
||
posts_meta: {
|
||
og_image: 'http://127.0.0.1:2369/content/images/og.png',
|
||
twitter_image: 'http://127.0.0.1:2369/content/images/twitter.png'
|
||
}
|
||
};
|
||
|
||
models.Post.add(post, context).then((createdPost) => {
|
||
createdPost.get('mobiledoc').should.equal('{"version":"0.3.1","atoms":[],"cards":[["image",{"src":"http://127.0.0.1:2369/content/images/card.jpg"}]],"markups":[["a",["href","http://127.0.0.1:2369/test"]]],"sections":[[1,"p",[[0,[0],1,"Testing"]]],[10,0]]}');
|
||
createdPost.get('html').should.equal('<p><a href="http://127.0.0.1:2369/test">Testing</a></p><figure class="kg-card kg-image-card"><img src="http://127.0.0.1:2369/content/images/card.jpg" class="kg-image" alt loading="lazy"></figure>');
|
||
createdPost.get('plaintext').should.containEql('Testing [http://127.0.0.1:2369/test]');
|
||
createdPost.get('custom_excerpt').should.equal('Testing <a href="http://127.0.0.1:2369/internal">links</a> in custom excerpts');
|
||
createdPost.get('codeinjection_head').should.equal('<script src="http://127.0.0.1:2369/assets/head.js"></script>');
|
||
createdPost.get('codeinjection_foot').should.equal('<script src="http://127.0.0.1:2369/assets/foot.js"></script>');
|
||
createdPost.get('feature_image').should.equal('http://127.0.0.1:2369/content/images/feature.png');
|
||
createdPost.get('canonical_url').should.equal('http://127.0.0.1:2369/canonical');
|
||
|
||
const postMeta = createdPost.relations.posts_meta;
|
||
|
||
postMeta.get('og_image').should.equal('http://127.0.0.1:2369/content/images/og.png');
|
||
postMeta.get('twitter_image').should.equal('http://127.0.0.1:2369/content/images/twitter.png');
|
||
|
||
// ensure canonical_url is not transformed when protocol does not match
|
||
return createdPost.save({
|
||
canonical_url: 'https://127.0.0.1:2369/https-internal',
|
||
// sanity check for general absolute->relative transform during edits
|
||
feature_image: 'http://127.0.0.1:2369/content/images/updated_feature.png'
|
||
});
|
||
}).then((updatedPost) => {
|
||
updatedPost.get('canonical_url').should.equal('https://127.0.0.1:2369/https-internal');
|
||
updatedPost.get('feature_image').should.equal('http://127.0.0.1:2369/content/images/updated_feature.png');
|
||
|
||
return updatedPost;
|
||
}).then((updatedPost) => {
|
||
return db.knex('posts').where({id: updatedPost.id});
|
||
}).then((knexResult) => {
|
||
const [knexPost] = knexResult;
|
||
knexPost.mobiledoc.should.equal('{"version":"0.3.1","atoms":[],"cards":[["image",{"src":"__GHOST_URL__/content/images/card.jpg"}]],"markups":[["a",["href","__GHOST_URL__/test"]]],"sections":[[1,"p",[[0,[0],1,"Testing"]]],[10,0]]}');
|
||
knexPost.html.should.equal('<p><a href="__GHOST_URL__/test">Testing</a></p><figure class="kg-card kg-image-card"><img src="__GHOST_URL__/content/images/card.jpg" class="kg-image" alt loading="lazy"></figure>');
|
||
knexPost.plaintext.should.containEql('Testing [__GHOST_URL__/test]');
|
||
knexPost.custom_excerpt.should.equal('Testing <a href="__GHOST_URL__/internal">links</a> in custom excerpts');
|
||
knexPost.codeinjection_head.should.equal('<script src="__GHOST_URL__/assets/head.js"></script>');
|
||
knexPost.codeinjection_foot.should.equal('<script src="__GHOST_URL__/assets/foot.js"></script>');
|
||
knexPost.feature_image.should.equal('__GHOST_URL__/content/images/updated_feature.png');
|
||
knexPost.canonical_url.should.equal('https://127.0.0.1:2369/https-internal');
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
});
|
||
|
||
describe('destroy', function () {
|
||
beforeEach(testUtils.fixtures.insertPostsAndTags);
|
||
|
||
afterEach(function () {
|
||
return testUtils.truncate('posts_tags')
|
||
.then(function () {
|
||
return testUtils.truncate('tags');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts_meta');
|
||
});
|
||
});
|
||
|
||
beforeEach(function () {
|
||
eventsTriggered = {};
|
||
sinon.stub(events, 'emit').callsFake(function (eventName, eventObj) {
|
||
if (!eventsTriggered[eventName]) {
|
||
eventsTriggered[eventName] = [];
|
||
}
|
||
|
||
eventsTriggered[eventName].push(eventObj);
|
||
});
|
||
});
|
||
|
||
it('published post', function (done) {
|
||
// We're going to try deleting post id 1 which has tag id 1
|
||
const firstItemData = {id: testUtils.DataGenerator.Content.posts[0].id};
|
||
|
||
// Test that we have the post we expect, with exactly one tag
|
||
models.Post.findOne(firstItemData, {withRelated: ['tags']}).then(function (results) {
|
||
let post;
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.id.should.equal(firstItemData.id);
|
||
post.status.should.equal('published');
|
||
post.tags.should.have.length(2);
|
||
post.tags[0].id.should.equal(testUtils.DataGenerator.Content.tags[0].id);
|
||
|
||
// Destroy the post
|
||
return results.destroy();
|
||
}).then(function (response) {
|
||
const deleted = response.toJSON();
|
||
|
||
should.equal(deleted.author, undefined);
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(5);
|
||
should.exist(eventsTriggered['post.unpublished']);
|
||
should.exist(eventsTriggered['post.deleted']);
|
||
should.exist(eventsTriggered['user.detached']);
|
||
should.exist(eventsTriggered['tag.detached']);
|
||
should.exist(eventsTriggered['post.tag.detached']);
|
||
|
||
// Double check we can't find the post again
|
||
return models.Post.findOne(firstItemData);
|
||
}).then(function (newResults) {
|
||
should.equal(newResults, null);
|
||
|
||
// Double check we can't find any related tags
|
||
return ghostBookshelf.knex.select().table('posts_tags').where('post_id', firstItemData.id);
|
||
}).then(function (postsTags) {
|
||
postsTags.should.be.empty();
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('draft post', function (done) {
|
||
// We're going to try deleting post 4 which also has tag 4
|
||
const firstItemData = {id: testUtils.DataGenerator.Content.posts[3].id, status: 'draft'};
|
||
|
||
// Test that we have the post we expect, with exactly one tag
|
||
models.Post.findOne(firstItemData, {withRelated: ['tags']}).then(function (results) {
|
||
let post;
|
||
should.exist(results);
|
||
post = results.toJSON();
|
||
post.id.should.equal(firstItemData.id);
|
||
post.tags.should.have.length(1);
|
||
post.tags[0].id.should.equal(testUtils.DataGenerator.Content.tags[3].id);
|
||
|
||
// Destroy the post
|
||
return results.destroy(firstItemData);
|
||
}).then(function (response) {
|
||
const deleted = response.toJSON();
|
||
|
||
should.equal(deleted.author, undefined);
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(4);
|
||
should.exist(eventsTriggered['post.deleted']);
|
||
should.exist(eventsTriggered['tag.detached']);
|
||
should.exist(eventsTriggered['post.tag.detached']);
|
||
should.exist(eventsTriggered['user.detached']);
|
||
|
||
// Double check we can't find the post again
|
||
return models.Post.findOne(firstItemData);
|
||
}).then(function (newResults) {
|
||
should.equal(newResults, null);
|
||
|
||
// Double check we can't find any related tags
|
||
return ghostBookshelf.knex.select().table('posts_tags').where('post_id', firstItemData.id);
|
||
}).then(function (postsTags) {
|
||
postsTags.should.be.empty();
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('published page', function (done) {
|
||
// We're going to try deleting page 6 which has tag 1
|
||
const firstItemData = {id: testUtils.DataGenerator.Content.posts[5].id};
|
||
|
||
// Test that we have the post we expect, with exactly one tag
|
||
models.Post.findOne(firstItemData, {withRelated: ['tags']}).then(function (results) {
|
||
let page;
|
||
should.exist(results);
|
||
page = results.toJSON();
|
||
page.id.should.equal(firstItemData.id);
|
||
page.status.should.equal('published');
|
||
page.type.should.equal('page');
|
||
|
||
// Destroy the page
|
||
return results.destroy(firstItemData);
|
||
}).then(function (response) {
|
||
const deleted = response.toJSON();
|
||
|
||
should.equal(deleted.author, undefined);
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(3);
|
||
should.exist(eventsTriggered['page.unpublished']);
|
||
should.exist(eventsTriggered['page.deleted']);
|
||
should.exist(eventsTriggered['user.detached']);
|
||
|
||
// Double check we can't find the post again
|
||
return models.Post.findOne(firstItemData);
|
||
}).then(function (newResults) {
|
||
should.equal(newResults, null);
|
||
|
||
// Double check we can't find any related tags
|
||
return ghostBookshelf.knex.select().table('posts_tags').where('post_id', firstItemData.id);
|
||
}).then(function (postsTags) {
|
||
postsTags.should.be.empty();
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
|
||
it('draft page', function (done) {
|
||
// We're going to try deleting post 7 which has tag 4
|
||
const firstItemData = {id: testUtils.DataGenerator.Content.posts[6].id, status: 'draft'};
|
||
|
||
// Test that we have the post we expect, with exactly one tag
|
||
models.Post.findOne(firstItemData, {withRelated: ['tags']}).then(function (results) {
|
||
let page;
|
||
should.exist(results);
|
||
page = results.toJSON();
|
||
page.id.should.equal(firstItemData.id);
|
||
|
||
// Destroy the page
|
||
return results.destroy(firstItemData);
|
||
}).then(function (response) {
|
||
const deleted = response.toJSON();
|
||
|
||
should.equal(deleted.author, undefined);
|
||
|
||
Object.keys(eventsTriggered).length.should.eql(2);
|
||
should.exist(eventsTriggered['page.deleted']);
|
||
should.exist(eventsTriggered['user.detached']);
|
||
|
||
// Double check we can't find the post again
|
||
return models.Post.findOne(firstItemData);
|
||
}).then(function (newResults) {
|
||
should.equal(newResults, null);
|
||
|
||
// Double check we can't find any related tags
|
||
return ghostBookshelf.knex.select().table('posts_tags').where('post_id', firstItemData.id);
|
||
}).then(function (postsTags) {
|
||
postsTags.should.be.empty();
|
||
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
});
|
||
|
||
describe('Collision Protection', function () {
|
||
before(testUtils.fixtures.insertPostsAndTags);
|
||
|
||
after(function () {
|
||
return testUtils.truncate('posts_tags')
|
||
.then(function () {
|
||
return testUtils.truncate('tags');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts_meta');
|
||
});
|
||
});
|
||
|
||
it('update post title, but updated_at is out of sync', function () {
|
||
const postToUpdate = {id: testUtils.DataGenerator.Content.posts[1].id};
|
||
|
||
return models.Post.edit({
|
||
title: 'New Post Title',
|
||
updated_at: moment().subtract(1, 'day').format()
|
||
}, _.extend({}, context, {id: postToUpdate.id}))
|
||
.then(function () {
|
||
throw new Error('expected no success');
|
||
})
|
||
.catch(function (err) {
|
||
err.code.should.eql('UPDATE_COLLISION');
|
||
});
|
||
});
|
||
|
||
it('update post tags and updated_at is out of sync', function () {
|
||
const postToUpdate = {id: testUtils.DataGenerator.Content.posts[1].id};
|
||
|
||
return models.Post.edit({
|
||
tags: [{name: 'new-tag-1'}],
|
||
updated_at: moment().subtract(1, 'day').format()
|
||
}, _.extend({}, context, {id: postToUpdate.id}))
|
||
.then(function () {
|
||
throw new Error('expected no success');
|
||
})
|
||
.catch(function (err) {
|
||
err.code.should.eql('UPDATE_COLLISION');
|
||
});
|
||
});
|
||
|
||
it('update post authors and updated_at is out of sync', function () {
|
||
const postToUpdate = {id: testUtils.DataGenerator.Content.posts[1].id};
|
||
|
||
return models.Post.edit({
|
||
authors: [testUtils.DataGenerator.Content.users[3]],
|
||
updated_at: moment().subtract(1, 'day').format()
|
||
}, _.extend({}, context, {id: postToUpdate.id}))
|
||
.then(function () {
|
||
throw new Error('expected no success');
|
||
})
|
||
.catch(function (err) {
|
||
err.code.should.eql('UPDATE_COLLISION');
|
||
});
|
||
});
|
||
|
||
it('update post tags and updated_at is NOT out of sync', function () {
|
||
const postToUpdate = {id: testUtils.DataGenerator.Content.posts[1].id};
|
||
|
||
return models.Post.edit({
|
||
tags: [{name: 'new-tag-1'}]
|
||
}, _.extend({}, context, {id: postToUpdate.id}));
|
||
});
|
||
|
||
it('update post with no changes, but updated_at is out of sync', function () {
|
||
const postToUpdate = {id: testUtils.DataGenerator.Content.posts[1].id};
|
||
|
||
return models.Post.edit({
|
||
updated_at: moment().subtract(1, 'day').format()
|
||
}, _.extend({}, context, {id: postToUpdate.id}));
|
||
});
|
||
|
||
it('update post with old post title, but updated_at is out of sync', function () {
|
||
const postToUpdate = {
|
||
id: testUtils.DataGenerator.Content.posts[1].id,
|
||
title: testUtils.DataGenerator.forModel.posts[1].title
|
||
};
|
||
|
||
return models.Post.edit({
|
||
title: postToUpdate.title,
|
||
updated_at: moment().subtract(1, 'day').format()
|
||
}, _.extend({}, context, {id: postToUpdate.id}));
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('mobiledoc versioning', function () {
|
||
it('can create revisions', function () {
|
||
const newPost = {
|
||
mobiledoc: markdownToMobiledoc('a')
|
||
};
|
||
|
||
return models.Post.add(newPost, context)
|
||
.then((createdPost) => {
|
||
return models.Post.findOne({id: createdPost.id, status: 'all'});
|
||
})
|
||
.then((createdPost) => {
|
||
should.exist(createdPost);
|
||
|
||
return createdPost.save({mobiledoc: markdownToMobiledoc('b')}, context);
|
||
})
|
||
.then((updatedPost) => {
|
||
updatedPost.get('mobiledoc').should.equal(markdownToMobiledoc('b'));
|
||
|
||
return models.MobiledocRevision
|
||
.findAll({
|
||
filter: `post_id:${updatedPost.id}`
|
||
});
|
||
})
|
||
.then((mobiledocRevisions) => {
|
||
should.equal(mobiledocRevisions.length, 2);
|
||
|
||
mobiledocRevisions.toJSON()[0].mobiledoc.should.equal(markdownToMobiledoc('b'));
|
||
mobiledocRevisions.toJSON()[1].mobiledoc.should.equal(markdownToMobiledoc('a'));
|
||
});
|
||
});
|
||
|
||
it('keeps only 10 last revisions in FIFO style', function () {
|
||
let revisionedPost;
|
||
const newPost = {
|
||
mobiledoc: markdownToMobiledoc('revision: 0')
|
||
};
|
||
|
||
return models.Post.add(newPost, context)
|
||
.then((createdPost) => {
|
||
return models.Post.findOne({id: createdPost.id, status: 'all'});
|
||
})
|
||
.then((createdPost) => {
|
||
should.exist(createdPost);
|
||
revisionedPost = createdPost;
|
||
|
||
return sequence(_.times(11, (i) => {
|
||
return () => {
|
||
return models.Post.edit({
|
||
mobiledoc: markdownToMobiledoc('revision: ' + (i + 1))
|
||
}, _.extend({}, context, {id: createdPost.id}));
|
||
};
|
||
}));
|
||
})
|
||
.then(() => models.MobiledocRevision
|
||
.findAll({
|
||
filter: `post_id:${revisionedPost.id}`
|
||
})
|
||
)
|
||
.then((mobiledocRevisions) => {
|
||
should.equal(mobiledocRevisions.length, 10);
|
||
|
||
mobiledocRevisions.toJSON()[0].mobiledoc.should.equal(markdownToMobiledoc('revision: 11'));
|
||
mobiledocRevisions.toJSON()[9].mobiledoc.should.equal(markdownToMobiledoc('revision: 2'));
|
||
});
|
||
});
|
||
|
||
it('creates 2 revisions after first edit for previously unversioned post', function () {
|
||
let unversionedPost;
|
||
|
||
const newPost = {
|
||
title: 'post title',
|
||
mobiledoc: markdownToMobiledoc('a')
|
||
};
|
||
|
||
// passing 'migrating' flag to simulate unversioned post
|
||
const options = Object.assign(_.clone(context), {migrating: true});
|
||
|
||
return models.Post.add(newPost, options)
|
||
.then((createdPost) => {
|
||
should.exist(createdPost);
|
||
unversionedPost = createdPost;
|
||
createdPost.get('mobiledoc').should.equal(markdownToMobiledoc('a'));
|
||
|
||
return models.MobiledocRevision
|
||
.findAll({
|
||
filter: `post_id:${createdPost.id}`
|
||
});
|
||
})
|
||
.then((mobiledocRevisions) => {
|
||
should.equal(mobiledocRevisions.length, 0);
|
||
|
||
return models.Post.edit({
|
||
mobiledoc: markdownToMobiledoc('b')
|
||
}, _.extend({}, context, {id: unversionedPost.id}));
|
||
})
|
||
.then((editedPost) => {
|
||
should.exist(editedPost);
|
||
editedPost.get('mobiledoc').should.equal(markdownToMobiledoc('b'));
|
||
|
||
return models.MobiledocRevision
|
||
.findAll({
|
||
filter: `post_id:${editedPost.id}`
|
||
});
|
||
})
|
||
.then((mobiledocRevisions) => {
|
||
should.equal(mobiledocRevisions.length, 2);
|
||
|
||
mobiledocRevisions.toJSON()[0].mobiledoc.should.equal(markdownToMobiledoc('b'));
|
||
mobiledocRevisions.toJSON()[1].mobiledoc.should.equal(markdownToMobiledoc('a'));
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('Multiauthor Posts', function () {
|
||
before(testUtils.teardownDb);
|
||
|
||
after(function () {
|
||
return testUtils.teardownDb()
|
||
.then(function () {
|
||
return testUtils.setup('users:roles')();
|
||
});
|
||
});
|
||
|
||
before(testUtils.setup('posts:mu'));
|
||
|
||
it('can destroy multiple posts by author', function (done) {
|
||
// We're going to delete all posts by user 1
|
||
const authorData = {id: testUtils.DataGenerator.Content.users[0].id};
|
||
|
||
models.Post.findAll({context: {internal: true}}).then(function (found) {
|
||
// There are 10 posts created by posts:mu fixture
|
||
found.length.should.equal(10);
|
||
return models.Post.destroyByAuthor(authorData);
|
||
}).then(function (results) {
|
||
// User 1 has 2 posts in the database (each user has proportionate amount)
|
||
// 2 = 10 / 5 (posts / users)
|
||
results.length.should.equal(2);
|
||
return models.Post.findAll({context: {internal: true}});
|
||
}).then(function (found) {
|
||
// Only 8 should remain
|
||
// 8 = 10 - 2
|
||
found.length.should.equal(8);
|
||
done();
|
||
}).catch(done);
|
||
});
|
||
});
|
||
|
||
describe('Post tag handling edge cases', function () {
|
||
let postJSON;
|
||
let tagJSON;
|
||
let editOptions;
|
||
const createTag = testUtils.DataGenerator.forKnex.createTag;
|
||
|
||
beforeEach(function () {
|
||
return testUtils.truncate('posts_tags')
|
||
.then(function () {
|
||
return testUtils.truncate('tags');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts');
|
||
})
|
||
.then(function () {
|
||
return testUtils.truncate('posts_meta');
|
||
});
|
||
});
|
||
|
||
beforeEach(function () {
|
||
tagJSON = [];
|
||
|
||
const post = _.cloneDeep(testUtils.DataGenerator.forModel.posts[0]);
|
||
|
||
const postTags = [
|
||
createTag({name: 'tag1', slug: 'tag1'}),
|
||
createTag({name: 'tag2', slug: 'tag2'}),
|
||
createTag({name: 'tag3', slug: 'tag3'})
|
||
];
|
||
|
||
const extraTags = [
|
||
createTag({name: 'existing tag a', slug: 'existing-tag-a'}),
|
||
createTag({name: 'existing-tag-b', slug: 'existing-tag-b'}),
|
||
createTag({name: 'existing_tag_c', slug: 'existing_tag_c'})
|
||
];
|
||
|
||
post.tags = postTags;
|
||
post.status = 'published';
|
||
|
||
return Promise.props({
|
||
post: models.Post.add(post, _.extend({}, context, {withRelated: ['tags']})),
|
||
tag1: models.Tag.add(extraTags[0], context),
|
||
tag2: models.Tag.add(extraTags[1], context),
|
||
tag3: models.Tag.add(extraTags[2], context)
|
||
}).then(function (result) {
|
||
postJSON = result.post.toJSON({withRelated: ['tags']});
|
||
tagJSON.push(result.tag1.toJSON());
|
||
tagJSON.push(result.tag2.toJSON());
|
||
tagJSON.push(result.tag3.toJSON());
|
||
editOptions = _.extend({}, context, {id: postJSON.id, withRelated: ['tags']});
|
||
|
||
// reset the eventSpy here
|
||
sinon.restore();
|
||
});
|
||
});
|
||
|
||
it('should create the test data correctly', function (done) {
|
||
// creates a test tag
|
||
should.exist(tagJSON);
|
||
tagJSON.should.be.an.Array().with.lengthOf(3);
|
||
|
||
tagJSON[0].name.should.eql('existing tag a');
|
||
tagJSON[1].name.should.eql('existing-tag-b');
|
||
tagJSON[2].name.should.eql('existing_tag_c');
|
||
|
||
// creates a test post with an array of tags in the correct order
|
||
should.exist(postJSON);
|
||
postJSON.title.should.eql('HTML Ipsum');
|
||
should.exist(postJSON.tags);
|
||
postJSON.tags.should.be.an.Array().and.have.lengthOf(3);
|
||
|
||
postJSON.tags[0].name.should.eql('tag1');
|
||
postJSON.tags[1].name.should.eql('tag2');
|
||
postJSON.tags[2].name.should.eql('tag3');
|
||
|
||
done();
|
||
});
|
||
|
||
it('can edit slug of existing tag', function () {
|
||
const newJSON = _.cloneDeep(postJSON);
|
||
|
||
// Add an existing tag to the beginning of the array
|
||
newJSON.tags = [{id: postJSON.tags[0].id, slug: 'eins'}];
|
||
|
||
// Edit the post
|
||
return models.Post.edit(newJSON, editOptions).then(function (updatedPost) {
|
||
updatedPost = updatedPost.toJSON({withRelated: ['tags']});
|
||
|
||
updatedPost.tags.should.have.lengthOf(1);
|
||
updatedPost.tags[0].name.should.eql(postJSON.tags[0].name);
|
||
updatedPost.tags[0].slug.should.eql('eins');
|
||
updatedPost.tags[0].id.should.eql(postJSON.tags[0].id);
|
||
});
|
||
});
|
||
|
||
it('can\'t edit dates and authors of existing tag', function () {
|
||
const newJSON = _.cloneDeep(postJSON);
|
||
let updatedAtFormat;
|
||
let createdAtFormat;
|
||
|
||
// Add an existing tag to the beginning of the array
|
||
newJSON.tags = [_.cloneDeep(postJSON.tags[0])];
|
||
newJSON.tags[0].created_at = moment().add(2, 'days').format('YYYY-MM-DD HH:mm:ss');
|
||
newJSON.tags[0].updated_at = moment().add(2, 'days').format('YYYY-MM-DD HH:mm:ss');
|
||
|
||
// NOTE: this is currently only removed in the API layer
|
||
newJSON.tags[0].parent_id = newJSON.tags[0].parent;
|
||
delete newJSON.tags[0].parent;
|
||
|
||
// Edit the post
|
||
return Promise.delay(1000)
|
||
.then(function () {
|
||
return models.Post.edit(newJSON, editOptions);
|
||
})
|
||
.then(function (updatedPost) {
|
||
updatedPost = updatedPost.toJSON({withRelated: ['tags']});
|
||
|
||
updatedPost.tags.should.have.lengthOf(1);
|
||
updatedPost.tags[0].should.have.properties({
|
||
name: postJSON.tags[0].name,
|
||
slug: postJSON.tags[0].slug,
|
||
id: postJSON.tags[0].id,
|
||
created_by: postJSON.tags[0].created_by,
|
||
updated_by: postJSON.tags[0].updated_by
|
||
});
|
||
|
||
updatedAtFormat = moment(updatedPost.tags[0].updated_at).format('YYYY-MM-DD HH:mm:ss');
|
||
updatedAtFormat.should.eql(moment(postJSON.tags[0].updated_at).format('YYYY-MM-DD HH:mm:ss'));
|
||
updatedAtFormat.should.not.eql(moment(newJSON.tags[0].updated_at).format('YYYY-MM-DD HH:mm:ss'));
|
||
|
||
createdAtFormat = moment(updatedPost.tags[0].created_at).format('YYYY-MM-DD HH:mm:ss');
|
||
createdAtFormat.should.eql(moment(postJSON.tags[0].created_at).format('YYYY-MM-DD HH:mm:ss'));
|
||
createdAtFormat.should.not.eql(moment(newJSON.tags[0].created_at).format('YYYY-MM-DD HH:mm:ss'));
|
||
});
|
||
});
|
||
|
||
it('can reorder existing, added and deleted tags', function () {
|
||
const newJSON = _.cloneDeep(postJSON);
|
||
const lastTag = [postJSON.tags[2]];
|
||
|
||
// remove tag in the middle (tag1, tag2, tag3 -> tag1, tag3)
|
||
newJSON.tags.splice(1, 1);
|
||
|
||
// add a new one as first tag and reorder existing (tag4, tag3, tag1)
|
||
newJSON.tags = [{name: 'tag4'}].concat([newJSON.tags[1]]).concat([newJSON.tags[0]]);
|
||
|
||
// Edit the post
|
||
return models.Post.edit(newJSON, editOptions).then(function (updatedPost) {
|
||
updatedPost = updatedPost.toJSON({withRelated: ['tags']});
|
||
|
||
updatedPost.tags.should.have.lengthOf(3);
|
||
updatedPost.tags[0].should.have.properties({
|
||
name: 'tag4'
|
||
});
|
||
|
||
updatedPost.tags[1].should.have.properties({
|
||
name: 'tag3',
|
||
id: postJSON.tags[2].id
|
||
});
|
||
|
||
updatedPost.tags[2].should.have.properties({
|
||
name: 'tag1',
|
||
id: postJSON.tags[0].id
|
||
});
|
||
});
|
||
});
|
||
|
||
it('can add multiple tags with conflicting slugs', function () {
|
||
const newJSON = _.cloneDeep(postJSON);
|
||
|
||
// Add conflicting tags to the end of the array
|
||
newJSON.tags = [];
|
||
newJSON.tags.push({name: 'C'});
|
||
newJSON.tags.push({name: 'C++'});
|
||
newJSON.tags.push({name: 'C#'});
|
||
|
||
// Edit the post
|
||
return models.Post.edit(newJSON, editOptions).then(function (updatedPost) {
|
||
updatedPost = updatedPost.toJSON({withRelated: ['tags']});
|
||
|
||
updatedPost.tags.should.have.lengthOf(3);
|
||
|
||
updatedPost.tags[0].should.have.properties({name: 'C', slug: 'c'});
|
||
updatedPost.tags[1].should.have.properties({name: 'C++', slug: 'c-2'});
|
||
updatedPost.tags[2].should.have.properties({name: 'C#', slug: 'c-3'});
|
||
});
|
||
});
|
||
|
||
it('can handle lowercase/uppercase tags', function () {
|
||
const newJSON = _.cloneDeep(postJSON);
|
||
|
||
// Add conflicting tags to the end of the array
|
||
newJSON.tags = [];
|
||
newJSON.tags.push({name: 'test'});
|
||
newJSON.tags.push({name: 'tEst'});
|
||
|
||
// Edit the post
|
||
return models.Post.edit(newJSON, editOptions).then(function (updatedPost) {
|
||
updatedPost = updatedPost.toJSON({withRelated: ['tags']});
|
||
|
||
updatedPost.tags.should.have.lengthOf(1);
|
||
});
|
||
});
|
||
});
|
||
|
||
// disabling sanitization until we can implement a better version
|
||
// it('should sanitize the title', function (done) {
|
||
// new models.Post().fetch().then(function (model) {
|
||
// return model.set({'title': "</title></head><body><script>alert('blogtitle');</script>"}).save();
|
||
// }).then(function (saved) {
|
||
// saved.get('title').should.eql("</title></head><body>[removed]alert('blogtitle');[removed]");
|
||
// done();
|
||
// }).catch(done);
|
||
// });
|
||
});
|