Skip to content

Commit

Permalink
#1160 #1161 Adds validation to prevent articles and pages with duplic…
Browse files Browse the repository at this point in the history
…ate headlines.
  • Loading branch information
brianhyder committed Oct 18, 2016
1 parent 298cdac commit 17b1d7b
Show file tree
Hide file tree
Showing 6 changed files with 345 additions and 149 deletions.
17 changes: 8 additions & 9 deletions include/service/entities/content/article_service_v2.js
Expand Up @@ -29,11 +29,13 @@ module.exports = function (pb) {

/**
* Provides functions to interact with articles
*
* @class ArticleServiceV2
* @constructor
* @extends BaseObjectService
* @extends ContentObjectService
* @param {Object} context
* @param {object} [context.contentSettings]
* @param {string} context.site
* @param {boolean} context.onlyThisSite
*/
function ArticleServiceV2(context) {
if (!util.isObject(context)) {
Expand Down Expand Up @@ -105,7 +107,7 @@ module.exports = function (pb) {
//add where clause to search based on section
var section = sectionId;
if (util.isObject(section)) {
section = section[pb.DAO.getIdField()] + '';
section = section[DAO.getIdField()] + '';
}
options.where.article_sections = section;

Expand Down Expand Up @@ -191,9 +193,10 @@ module.exports = function (pb) {
* @method validate
* @param {Object} context
* @param {Object} context.data The DTO that was provided for persistence
* @param {Array} context.validationErrors
* @param {ArticleServiceV2} context.service An instance of the service that triggered
* the event that called this handler
* @param {Function} cb A callback that takes a single parameter: an error if occurred
* @param {Function} cb (Error) A callback that takes a single parameter: an error if occurred
*/
ArticleServiceV2.validate = function (context, cb) {
var obj = context.data;
Expand Down Expand Up @@ -280,10 +283,6 @@ module.exports = function (pb) {
}
}

if (!ValidationService.isNonEmptyStr(obj.headline, true)) {
errors.push(BaseObjectService.validationFailure('headline', 'The headline is required'));
}

if (!ValidationService.isNonEmptyStr(obj.subheading, false)) {
errors.push(BaseObjectService.validationFailure('subheading', 'An invalid subheading was provided'));
}
Expand Down Expand Up @@ -316,7 +315,7 @@ module.exports = function (pb) {
errors.push(BaseObjectService.validationFailure('article_layout', 'The layout is required'));
}

cb(null);
context.service.validateHeadline(context, cb);
};

/**
Expand Down
28 changes: 28 additions & 0 deletions include/service/entities/content/content_object_service.js
Expand Up @@ -41,6 +41,7 @@ module.exports = function(pb) {
* @param {object} [context.contentSettings]
* @param {string} context.site
* @param {boolean} context.onlyThisSite
* @param {string} context.type
*/
function ContentObjectService(context){
if (!util.isObject(context)) {
Expand Down Expand Up @@ -488,6 +489,33 @@ module.exports = function(pb) {
throw new Error('getTopicsForContent must be overriden by the extending prototype');
};

/**
* Validates that a headline is provided and is unique
* @param {object} context
* @param {object} context.data
* @param {Array} context.validationErrors
* @param {function} cb (Error)
* @returns {*}
*/
ContentObjectService.prototype.validateHeadline = function(context, cb) {
var obj = context.data;
var errors = context.validationErrors;

//quick check on format
if (!ValidationService.isNonEmptyStr(obj.headline, true)) {
errors.push(BaseObjectService.validationFailure('headline', 'The headline is required'));
return cb();
}

//now ensure it is unique
this.dao.unique(this.type, {headline: obj.headline}, obj[DAO.getIdField()], function(err, unique) {
if (!unique) {
errors.push(BaseObjectService.validationFailure('headline', 'The headline must be unique'));
}
cb(err);
});
};

/**
*
* @static
Expand Down
10 changes: 3 additions & 7 deletions include/service/entities/content/page_service.js
Expand Up @@ -31,7 +31,7 @@ module.exports = function(pb) {
* Provides functions to interact with pages
*
* @class PageService
* @extends BaseObjectService
* @extends ContentObjectService
* @constructor
* @param {object} context
* @param {object} [context.contentSettings]
Expand Down Expand Up @@ -65,7 +65,7 @@ module.exports = function(pb) {
* @param {Boolean} isMultiple
* @return {Object}
*/
PageService.prototype.getRenderOptions = function(options, isMultiple) {
PageService.prototype.getRenderOptions = function(options/*, isMultiple*/) {
if (!util.isObject(options)) {
options = {};
}
Expand Down Expand Up @@ -230,10 +230,6 @@ module.exports = function(pb) {
errors.push(BaseObjectService.validationFailure('url', 'An invalid URL slug was provided'));
}

if (!ValidationService.isNonEmptyStr(obj.headline, true)) {
errors.push(BaseObjectService.validationFailure('headline', 'The headline is required'));
}

if (!ValidationService.isNonEmptyStr(obj.subheading, false)) {
errors.push(BaseObjectService.validationFailure('subheading', 'An invalid subheading was provided'));
}
Expand Down Expand Up @@ -266,7 +262,7 @@ module.exports = function(pb) {
errors.push(BaseObjectService.validationFailure('page_layout', 'The layout is required'));
}

cb(null);
context.service.validateHeadline(context, cb);
};

/**
Expand Down
162 changes: 95 additions & 67 deletions test/include/service/entities/content/article_service_v2_tests.js
@@ -1,5 +1,6 @@
'use strict';

//depedencies
//dependencies
var should = require('should');
var pb = {};
pb.DAO = require('../../../../../include/dao/dao.js')(pb);
Expand All @@ -8,84 +9,111 @@ pb.ValidationService = require('../../../../../include/validation/validation_ser
pb.ContentObjectService = require('../../../../../include/service/entities/content/content_object_service.js')(pb);
var ArticleServiceV2 = require('../../../../../include/service/entities/content/article_service_v2.js')(pb);

describe('ArticleServiceV2', function() {
describe('ArticleServiceV2.format', function () {
it('should allow valid values', function () {
var article = getArticle();
var article2 = getArticle();
describe('ArticleServiceV2', function () {

// Set expected date to the parsed version of the date being passed to the service
article2.publish_date = pb.BaseObjectService.getDate(article2.publish_date);
describe('ArticleServiceV2.format', function () {

ArticleServiceV2.format({data: article}, function() {});
should.deepEqual(article, article2);
});
it('should allow valid values', function () {
var article = getArticle();
var article2 = getArticle();

it('should sanitize keywords', function () {
var article = getArticle();
article.meta_keywords = ["A Keyword With <div>HTML</div>", "Another <a href='http://www.test.com'>HTML</a> keyword"];
// Set expected date to the parsed version of the date being passed to the service
article2.publish_date = pb.BaseObjectService.getDate(article2.publish_date);

ArticleServiceV2.format({data: article}, function() {});
article.meta_keywords[0].should.eql("A Keyword With HTML");
article.meta_keywords[1].should.eql("Another HTML keyword");
});
});
ArticleServiceV2.format({data: article}, function () {
});
should.deepEqual(article, article2);
});

describe('ArticleServiceV2.merge', function () {
it('should copy all properties', function () {
var article = getArticle();
var article2 = {};
it('should sanitize keywords', function () {
var article = getArticle();
article.meta_keywords = ["A Keyword With <div>HTML</div>", "Another <a href='http://www.test.com'>HTML</a> keyword"];

ArticleServiceV2.merge({data: article, object: article2}, function() {});
should.deepEqual(article, article2);
ArticleServiceV2.format({data: article}, function () {
});
article.meta_keywords[0].should.eql("A Keyword With HTML");
article.meta_keywords[1].should.eql("Another HTML keyword");
});
});
});

describe('ArticleServiceV2.validate', function () {
it('should allow valid values', function () {
var article = getArticle();
var errors = [];
describe('ArticleServiceV2.merge', function () {

// Set date to the parsed version of the date being passed to the service
article.publish_date = pb.BaseObjectService.getDate(article.publish_date);
it('should copy all properties', function () {
var article = getArticle();
var article2 = {};

ArticleServiceV2.validate({data: article, validationErrors: errors}, function() {});
errors.length.should.eql(0);
ArticleServiceV2.merge({data: article, object: article2}, function () {
});
should.deepEqual(article, article2);
});
});

it('should find errors', function () {
var article = getArticle();
var errors = [];

article.meta_keywords = "This should be an array, not a string";

ArticleServiceV2.validate({data: article, validationErrors: errors}, function() {});

errors.length.should.eql(2);
errors[0].field.should.eql("publish_date");
errors[1].field.should.eql("meta_keywords");
describe('ArticleServiceV2.validate', function () {

it('should allow valid values', function (next) {
// Set date to the parsed version of the date being passed to the service
var article = getArticle();
article.publish_date = pb.BaseObjectService.getDate(article.publish_date);

var context = {
validationErrors: [],
data: article,
service: {
validateHeadline: function(context, cb) {
cb();
}
}
};
ArticleServiceV2.validate(context, function () {

context.validationErrors.length.should.eql(0);
next();
});
});

it('should find errors', function (next) {
var article = getArticle();
article.meta_keywords = "This should be an array, not a string";

var context = {
validationErrors: [],
data: article,
service: {
validateHeadline: function(context, cb) {
cb();
}
}
};
ArticleServiceV2.validate(context, function () {
context.validationErrors.length.should.eql(2);
context.validationErrors[0].field.should.eql("publish_date");
context.validationErrors[1].field.should.eql("meta_keywords");
next();
});


});
});
});
});

var getArticle = function() {
return {
allow_comments: false,
draft: 0,
article_media: undefined,
article_sections: undefined,
article_topics: undefined,
author: "507f191e810c19729de860ea", // Random MongoDB compatible object ID
template: undefined,
thumbnail: null,
headline: "Test Headline",
subheading: "Test Subheading",
article_layout: "Simple Page Layout",
focus_keyword: "Testing",
seo_title: "SEO Title",
meta_desc: "This is a test article",
url: "http://test-size.com/article/test-article",
publish_date: "2015-09-08T01:55:28+00:00", // ISO Format
meta_keywords: ["Keyword One", "Keyword Two", "Keyword Three"]
}
};
var getArticle = function () {
return {
allow_comments: false,
draft: 0,
article_media: undefined,
article_sections: undefined,
article_topics: undefined,
author: "507f191e810c19729de860ea", // Random MongoDB compatible object ID
template: undefined,
thumbnail: null,
headline: "Test Headline",
subheading: "Test Subheading",
article_layout: "Simple Page Layout",
focus_keyword: "Testing",
seo_title: "SEO Title",
meta_desc: "This is a test article",
url: "http://test-size.com/article/test-article",
publish_date: "2015-09-08T01:55:28+00:00", // ISO Format
meta_keywords: ["Keyword One", "Keyword Two", "Keyword Three"]
};
};

0 comments on commit 17b1d7b

Please sign in to comment.