Merge branch '2.20' into emailNotifications

This commit is contained in:
David Baldwynn 2017-11-01 12:46:15 -07:00
commit ddd4c2b22c
54 changed files with 8692 additions and 4976 deletions

View File

@ -11,5 +11,9 @@ services:
addons: addons:
code_climate: code_climate:
repo_token: 6c3a1b81a09b2338d6f30913c1bcad115026689752cbb499a0a25061cda6fbcf repo_token: 6c3a1b81a09b2338d6f30913c1bcad115026689752cbb499a0a25061cda6fbcf
after_script: install:
- grunt coverage - npm install phantomjs
- npm install -g grunt
- npm install
script:
- yarn run travis

View File

@ -12,7 +12,8 @@ var mongoose = require('mongoose'),
_ = require('lodash'), _ = require('lodash'),
nodemailer = require('nodemailer'), nodemailer = require('nodemailer'),
emailNotifications = require('../libs/send-email-notifications'), emailNotifications = require('../libs/send-email-notifications'),
constants = require('../libs/constants'); constants = require('../libs/constants'),
helpers = require('./helpers.server.controller');
var smtpTransport = nodemailer.createTransport(config.mailer.options); var smtpTransport = nodemailer.createTransport(config.mailer.options);
@ -113,16 +114,14 @@ exports.listSubmissions = function(req, res) {
} }
res.json(_submissions); res.json(_submissions);
}); });
}; };
/** /**
* Create a new form * Create a new form
*/ */
exports.create = function(req, res) { exports.create = function(req, res) {
if(!req.body.form){ if(!req.body.form){
return res.status(401).send({ return res.status(400).send({
message: 'Invalid Input' message: 'Invalid Input'
}); });
} }
@ -130,14 +129,15 @@ exports.create = function(req, res) {
var form = new Form(req.body.form); var form = new Form(req.body.form);
form.admin = req.user._id; form.admin = req.user._id;
form.save(function(err) { form.save(function(err, createdForm) {
if (err) { if (err) {
return res.status(500).send({ return res.status(500).send({
message: errorHandler.getErrorMessage(err) message: errorHandler.getErrorMessage(err)
}); });
} }
return res.json(form); createdForm = helpers.removeSensitiveModelData('private_form', createdForm);
return res.json(createdForm);
}); });
}; };
@ -148,16 +148,19 @@ exports.read = function(req, res) {
if(!req.user || (req.form.admin.id !== req.user.id) ){ if(!req.user || (req.form.admin.id !== req.user.id) ){
readForRender(req, res); readForRender(req, res);
} else { } else {
var newForm = req.form.toJSON(); if(!req.form){
if (req.userId) {
if(req.form.admin._id+'' === req.userId+''){
return res.json(newForm);
}
return res.status(404).send({ return res.status(404).send({
message: 'Form Does Not Exist' message: 'Form Does Not Exist'
}); });
} }
var newForm = req.form.toJSON();
if(newForm.admin._id === req.user._id){
return res.json(newForm);
}
newForm = helpers.removeSensitiveModelData('private_form', newForm);
return res.json(newForm); return res.json(newForm);
} }
}; };
@ -173,9 +176,7 @@ var readForRender = exports.readForRender = function(req, res) {
}); });
} }
delete newForm.lastModified; newForm = helpers.removeSensitiveModelData('public_form', newForm);
delete newForm.__v;
delete newForm.created;
if(newForm.startPage && !newForm.startPage.showStart){ if(newForm.startPage && !newForm.startPage.showStart){
delete newForm.startPage; delete newForm.startPage;
@ -191,15 +192,12 @@ exports.update = function(req, res) {
var form = req.form; var form = req.form;
var updatedForm = req.body.form; var updatedForm = req.body.form;
if(form.form_fields === undefined){
form.form_fields = [];
}
if(form.analytics === undefined){ if(!form.analytics){
form.analytics = { form.analytics = {
visitors: [], visitors: [],
gaCode: '' gaCode: ''
} };
} }
if (req.body.changes) { if (req.body.changes) {
@ -217,11 +215,6 @@ exports.update = function(req, res) {
delete updatedForm.admin; delete updatedForm.admin;
} }
if(form.analytics === null){
form.analytics.visitors = [];
form.analytics.gaCode = '';
}
//Do this so we can create duplicate fields //Do this so we can create duplicate fields
var checkForValidId = new RegExp('^[0-9a-fA-F]{24}$'); var checkForValidId = new RegExp('^[0-9a-fA-F]{24}$');
for(var i=0; i < req.body.form.form_fields.length; i++){ for(var i=0; i < req.body.form.form_fields.length; i++){
@ -239,6 +232,7 @@ exports.update = function(req, res) {
message: errorHandler.getErrorMessage(err) message: errorHandler.getErrorMessage(err)
}); });
} else { } else {
savedForm = helpers.removeSensitiveModelData('private_form', savedForm);
res.json(savedForm); res.json(savedForm);
} }
}); });
@ -280,6 +274,8 @@ exports.list = function(req, res) {
}); });
} else { } else {
for(var i=0; i<forms.length; i++){ for(var i=0; i<forms.length; i++){
forms[i] = helpers.removeSensitiveModelData('private_form', forms[i]);
forms[i].numberOfResponses = 0; forms[i].numberOfResponses = 0;
if(forms[i].submissions){ if(forms[i].submissions){
forms[i].numberOfResponses = forms[i].submissions.length; forms[i].numberOfResponses = forms[i].submissions.length;
@ -300,6 +296,7 @@ exports.formByID = function(req, res, next, id) {
message: 'Form is invalid' message: 'Form is invalid'
}); });
} }
Form.findById(id) Form.findById(id)
.populate('admin') .populate('admin')
.exec(function(err, form) { .exec(function(err, form) {
@ -312,12 +309,7 @@ exports.formByID = function(req, res, next, id) {
} }
else { else {
//Remove sensitive information from User object //Remove sensitive information from User object
var _form = form; req.form = helpers.removeSensitiveModelData('private_form', form);
_form.admin.password = null;
_form.admin.salt = null;
_form.provider = null;
req.form = _form;
return next(); return next();
} }
}); });
@ -345,13 +337,7 @@ exports.formByIDFast = function(req, res, next, id) {
} }
else { else {
//Remove sensitive information from User object //Remove sensitive information from User object
var _form = form; req.form = helpers.removeSensitiveModelData('public_form', form);
if(_form.admin){
_form.admin.password = null;
_form.admin.salt = null;
_form.provider = null;
}
req.form = _form;
return next(); return next();
} }
}); });

View File

@ -0,0 +1,45 @@
'use strict';
module.exports = {
removeSensitiveModelData: function(type, object){
var privateFields = {
'public_form': ['__v', 'analytics.visitors', 'analytics.views', 'analytics.conversionRate', 'analytics.fields', 'lastModified', 'created'],
'private_form': ['__v'],
'public_user': ['passwordHash', 'password', 'provider', 'salt', 'lastModified', 'created', 'resetPasswordToken', 'resetPasswordExpires', 'token', 'apiKey', '__v'],
'private_user': ['passwordHash', 'password', 'provider', 'salt', 'resetPasswordToken', 'resetPasswordExpires', 'token', '__v']
};
function removeKeysFromDict(dict, keys){
for(var i=0; i<keys.length; i++){
var curr_key = keys[i];
if( dict.hasOwnProperty(curr_key) ){
delete dict[curr_key];
}
}
}
switch(type){
case 'private_form':
removeKeysFromDict(object, privateFields[type]);
if(object.admin){
removeKeysFromDict(object.admin, privateFields.private_user);
}
break;
case 'public_form':
removeKeysFromDict(object, privateFields[type]);
if(object.admin){
removeKeysFromDict(object.admin, privateFields.public_user);
}
break;
default:
if(privateFields.hasOwnProperty(type)){
removeKeysFromDict(object, privateFields[type]);
}
break;
}
return object;
}
};

View File

@ -12,7 +12,8 @@ var errorHandler = require('../errors.server.controller'),
fs = require('fs'), fs = require('fs'),
i18n = require('i18n'), i18n = require('i18n'),
async = require('async'), async = require('async'),
pug = require('pug'); pug = require('pug'),
helpers = require('../helpers.server.controller');
var nev = require('email-verification')(mongoose); var nev = require('email-verification')(mongoose);
@ -60,7 +61,7 @@ config_nev();
exports.validateVerificationToken = function(req, res){ exports.validateVerificationToken = function(req, res){
const fn = pug.compileFile(__dirname + "/../../views/welcome.email.view.pug"); const fn = pug.compileFile(__dirname + '/../../views/welcome.email.view.pug');
var renderedHtml = fn(res.locals); var renderedHtml = fn(res.locals);
var emailTemplate = { var emailTemplate = {
@ -83,7 +84,7 @@ exports.validateVerificationToken = function(req, res){
}; };
exports.resendVerificationEmail = function(req, res, next){ exports.resendVerificationEmail = function(req, res, next){
const fn = pug.compileFile(__dirname + "/../../views/verification.email.view.pug"); const fn = pug.compileFile(__dirname + '/../../views/verification.email.view.pug');
var renderedHtml = fn(res.locals); var renderedHtml = fn(res.locals);
var emailTemplate = { var emailTemplate = {
@ -117,7 +118,7 @@ exports.signup = function(req, res) {
var user = new User(req.body); var user = new User(req.body);
// Set language to visitor's language // Set language to visitor's language
user.language = req.cookies['userLang']; user.language = req.cookies.userLang;
// Add missing user fields // Add missing user fields
user.provider = 'local'; user.provider = 'local';
@ -133,7 +134,7 @@ exports.signup = function(req, res) {
// new user created // new user created
if (newTempUser) { if (newTempUser) {
const fn = pug.compileFile(__dirname + "/../../views/verification.email.view.pug"); const fn = pug.compileFile(__dirname + '/../../views/verification.email.view.pug');
var renderedHtml = fn(res.locals); var renderedHtml = fn(res.locals);
var URL = newTempUser[nev.options.URLFieldName]; var URL = newTempUser[nev.options.URLFieldName];
@ -179,6 +180,8 @@ exports.signin = function(req, res, next) {
} }
res.cookie('langCookie', user.language, { maxAge: 90000, httpOnly: true }); res.cookie('langCookie', user.language, { maxAge: 90000, httpOnly: true });
user = helpers.removeSensitiveModelData('private_user', user);
return res.json(user); return res.json(user);
}); });
} }
@ -190,7 +193,7 @@ exports.signin = function(req, res, next) {
*/ */
exports.signout = function(req, res) { exports.signout = function(req, res) {
if(req.cookies.hasOwnProperty('userLang')){ if(req.cookies.hasOwnProperty('userLang')){
res.destroyCookie('userLang'); res.clearCookie('userLang');
} }
req.logout(); req.logout();
return res.status(200).send('You have successfully logged out.'); return res.status(200).send('You have successfully logged out.');
@ -198,16 +201,12 @@ exports.signout = function(req, res) {
/* Generate API Key for User */ /* Generate API Key for User */
exports.generateAPIKey = function(req, res) { exports.generateAPIKey = function(req, res) {
if (!req.isAuthenticated()){
return res.status(400).send({
message: 'User is not Authorized'
});
}
User.findById(req.user.id) User.findById(req.user.id)
.exec( function(err, user) { .exec( function(err, user) {
if (err) { if (err) {
return res.status(400).send(err); return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} }
if (!user) { if (!user) {
@ -226,12 +225,8 @@ exports.generateAPIKey = function(req, res) {
} }
var newUser = _user.toObject(); var newUser = _user.toObject();
delete newUser.salt;
delete newUser.__v;
delete newUser.passwordHash;
delete newUser.provider;
return res.json(newUser); return res.json({ id: newUser._id, apiKey: newUser.apiKey });
}); });
}); });

View File

@ -3,36 +3,7 @@
/** /**
* Module dependencies. * Module dependencies.
*/ */
var _ = require('lodash'), var auth = require('../../../config/passport_helpers');
mongoose = require('mongoose'),
User = mongoose.model('User');
/**
* User middleware
*/
exports.userByID = function (req, res, next, id) {
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).send({
message: 'User is invalid'
});
}
User.findOne({
_id: id
}).exec(function (err, user) {
if (err) {
return next(err);
} else if (!user) {
return res.status(404).send({
message: 'User does not exist'
});
}
req.profile = user;
next();
});
};
/** /**
* Require login routing middleware * Require login routing middleware
*/ */
@ -45,22 +16,3 @@ exports.requiresLogin = function(req, res, next) {
return next(); return next();
} }
}; };
/**
* User authorizations routing middleware
*/
exports.hasAuthorization = function(roles) {
var _this = this;
return function(req, res, next) {
_this.requiresLogin(req, res, function() {
if (_.intersection(req.user.roles, roles).length) {
return next();
} else {
return res.status(403).send({
message: 'User is not authorized'
});
}
});
};
};

View File

@ -81,10 +81,9 @@ exports.forgot = function(req, res) {
} }
}, },
function(token, user, done) { function(token, user, done) {
const fn = pug.compileFile(__dirname + "/../../views/templates/reset-password-email.server.view.pug"); const fn = pug.compileFile(__dirname + '/../../views/templates/reset-password-email.server.view.pug');
res.locals['url'] = 'http://' + req.headers.host + '/auth/reset/' + token; res.locals.url = 'http://' + req.headers.host + '/auth/reset/' + token;
console.log(res.locals);
var renderedHtml = fn(res.locals); var renderedHtml = fn(res.locals);
done(null, renderedHtml, user); done(null, renderedHtml, user);
}, },
@ -98,10 +97,10 @@ exports.forgot = function(req, res) {
}; };
var userEmail = user.email; var userEmail = user.email;
var user = userEmail.split('@')[0]; var emailUsername = userEmail.split('@')[0];
var domain = userEmail.split('@')[1]; var domain = userEmail.split('@')[1];
var obfuscatedUser = user.substring(0, 1) + user.substring(1).replace(/./g, '*'); var obfuscatedUser = emailUsername.substring(0, 1) + emailUsername.substring(1).replace(/./g, '*');
var domainName = domain.split('.')[0]; var domainName = domain.split('.')[0];
var tld = domain.split('.')[1]; var tld = domain.split('.')[1];
@ -142,7 +141,7 @@ exports.validateResetToken = function(req, res) {
}); });
} }
if (!user) { if (!user) {
return res.redirect('/#!/password/reset/invalid'); return res.redirect(400, '/#!/password/reset/invalid');
} }
res.redirect('/#!/password/reset/' + req.params.token); res.redirect('/#!/password/reset/' + req.params.token);
@ -187,13 +186,13 @@ exports.reset = function(req, res, next) {
done(null, savedUser); done(null, savedUser);
}); });
} else { } else {
done('Password reset token is invalid or has expired.', null); done('invalid_reset_token', null);
} }
}); });
}, },
function(user, done) { function(user, done) {
const fn = pug.compileFile(__dirname + "/../../views/templates/reset-password-confirm-email.server.view.pug"); const fn = pug.compileFile(__dirname + '/../../views/templates/reset-password-confirm-email.server.view.pug');
var renderedHtml = fn(res.locals); const renderedHtml = fn(res.locals);
done(null, renderedHtml, user); done(null, renderedHtml, user);
}, },
// If valid email, send reset email using service // If valid email, send reset email using service
@ -211,12 +210,18 @@ exports.reset = function(req, res, next) {
} }
], function(err) { ], function(err) {
if (err) { if (err) {
res.status(500).send({ if(err === 'invalid_reset_token'){
return res.status(400).send({
message: 'Password reset token is invalid or has expired.'
});
}
return res.status(500).send({
message: err.message || err message: err.message || err
}); });
} }
return res.json({ res.json({
message: 'Successfully changed your password!' message: 'Successfully changed your password!'
}); });
}); });

View File

@ -5,7 +5,8 @@
*/ */
var _ = require('lodash'), var _ = require('lodash'),
errorHandler = require('../errors.server.controller.js'), errorHandler = require('../errors.server.controller.js'),
mongoose = require('mongoose'); mongoose = require('mongoose'),
helpers = require('../helpers.server.controller');
/** /**
* Update user details * Update user details
@ -14,47 +15,36 @@ exports.update = function(req, res) {
// Init Variables // Init Variables
var user = req.user; var user = req.user;
// For security measurement we remove the roles from the req.body object // To improve security we remove the roles from the req.body object
delete req.body.roles; delete req.body.roles;
if (user) { // Merge existing user
// Merge existing user user = _.extend(user, req.body);
user = _.extend(user, req.body); user.updated = Date.now();
user.updated = Date.now();
user.save(function(err) { user.save(function(err) {
if (err) { if (err) {
return res.status(500).send({ return res.status(500).send({
message: errorHandler.getErrorMessage(err) message: errorHandler.getErrorMessage(err)
});
}
req.login(user, function(loginErr) {
if (err) {
res.status(500).send(loginErr);
} else {
res.json(user);
}
}); });
}
req.login(user, function(loginErr) {
if (err) {
res.status(500).send(loginErr);
} else {
user = helpers.removeSensitiveModelData('private_user', user);
res.json(user);
}
});
}); });
} else {
res.status(401).send({
message: 'User is not signed in'
});
}
}; };
/** /**
* Send User * Send User
*/ */
exports.getUser = function(req, res) { exports.getUser = function(req, res) {
var _user = req.user; var user = helpers.removeSensitiveModelData('private_user', req.user);
delete _user.password;
delete _user.salt;
delete _user.provider;
delete _user.__v;
res.json(req.user || null); return res.json(user);
res.end();
}; };

View File

@ -11,25 +11,26 @@ module.exports = {
cc: emailSettings.recipients, cc: emailSettings.recipients,
subject: parsedSubject, subject: parsedSubject,
html: parsedTemplate html: parsedTemplate
} };
smtpTransport.sendMail(mailOptions, cb); smtpTransport.sendMail(mailOptions, cb);
}, },
parseTemplate: function(emailTemplate, emailAttrs, varFormat){ parseTemplate: function(emailTemplate, emailAttrs, varFormat){
var resolvedTemplate = emailTemplate; var resolvedTemplate = emailTemplate;
var that = this;
Object.keys(emailAttrs).forEach(function (key) { Object.keys(emailAttrs).forEach(function (key) {
resolvedTemplate = replaceTemplateVal(key, emailAttrs[key], resolvedTemplate); resolvedTemplate = that.replaceTemplateVal(key, emailAttrs[key], resolvedTemplate);
}); });
return resolvedTemplate; return resolvedTemplate;
}, },
replaceTemplateVal: function(key, val, template, varFormat){ replaceTemplateVal: function(key, val, template, varFormat){
return template.replace( new RegExp(varFormat[0] + key + varFormat[1], 'g'), value ) return template.replace( new RegExp(varFormat[0] + key + varFormat[1], 'g'), val);
}, },
createFieldDict: function(form_fields){ createFieldDict: function(form_fields){
var formFieldDict = {} var formFieldDict = {};
form_fields.forEach(function(field){ form_fields.forEach(function(field){
if(field.hasOwnProperty('_id') && field.hasOwnProperty('fieldValue')){ if(field.hasOwnProperty('_id') && field.hasOwnProperty('fieldValue')){
formFieldDict[field._id] = field.fieldValue; formFieldDict[field._id] = field.fieldValue;
@ -37,4 +38,4 @@ module.exports = {
}); });
return formFieldDict; return formFieldDict;
} }
} };

View File

@ -2,38 +2,36 @@
// Plugin // Plugin
module.exports = function timestamp (schema, options) { module.exports = function timestamp (schema, options) {
options || (options = {}) options = options || (options === {});
// Options // Options
var fields = {} var fields = {},
, createdPath = options.createdPath || 'created' createdPath = options.createdPath || 'created',
, modifiedPath = options.modifiedPath || 'modified' modifiedPath = options.modifiedPath || 'modified',
, useVirtual = (options.useVirtual !== undefined) useVirtual = (options.useVirtual !== undefined) ? options.useVirtual : true;
? options.useVirtual
: true
// Add paths to schema if not present // Add paths to schema if not present
if (!schema.paths[createdPath]) { if (!schema.paths[createdPath]) {
fields[modifiedPath] = { type: Date } fields[modifiedPath] = { type: Date };
} }
if (useVirtual) { if (useVirtual) {
// Use the ObjectID for extracting the created time // Use the ObjectID for extracting the created time
schema.virtual(createdPath).get(function () { schema.virtual(createdPath).get(function () {
return new Date(this._id.generationTime * 1000) return new Date(this._id.generationTime * 1000);
}) });
} else { } else {
if (!schema.paths[createdPath]) { if (!schema.paths[createdPath]) {
fields[createdPath] = { fields[createdPath] = {
type: Date type: Date,
, default: Date.now default: Date.now
} };
} }
} }
schema.add(fields) schema.add(fields);
// Update the modified timestamp on save // Update the modified timestamp on save
schema.pre('save', function (next) { schema.pre('save', function (next) {
this[modifiedPath] = new Date this[modifiedPath] = new Date();
next() next();
}) });
} };

View File

@ -73,7 +73,6 @@ var VisitorDataSchema = new Schema({
userAgent: { userAgent: {
type: String type: String
} }
}); });
var formSchemaOptions = { var formSchemaOptions = {
@ -104,12 +103,17 @@ var FormSchema = new Schema({
visitors: [VisitorDataSchema] visitors: [VisitorDataSchema]
}, },
form_fields: [FieldSchema], form_fields: {
submissions: [{ type: [FieldSchema],
type: Schema.Types.ObjectId, default: []
ref: 'FormSubmission' },
}], submissions: {
type: [{
type: Schema.Types.ObjectId,
ref: 'FormSubmission'
}],
dfeault: []
},
admin: { admin: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'User', ref: 'User',
@ -197,6 +201,7 @@ var FormSchema = new Schema({
type: Boolean, type: Boolean,
default: false default: false
}, },
isLive: { isLive: {
type: Boolean, type: Boolean,
default: true default: true
@ -262,7 +267,7 @@ FormSchema.virtual('analytics.fields').get(function () {
var visitors = this.analytics.visitors; var visitors = this.analytics.visitors;
var that = this; var that = this;
if(this.form_fields.length === 0) { if(!this.form_fields || this.form_fields.length === 0) {
return null; return null;
} }
@ -332,26 +337,6 @@ FormSchema.plugin(timeStampPlugin, {
useVirtual: false useVirtual: false
}); });
FormSchema.pre('save', function (next) {
switch(this.language){
case 'spanish':
this.language = 'es';
break;
case 'french':
this.language = 'fr';
break;
case 'italian':
this.language = 'it';
break;
case 'german':
this.language = 'de';
break;
default:
break;
}
next();
});
function getDeletedIndexes(needle, haystack){ function getDeletedIndexes(needle, haystack){
var deletedIndexes = []; var deletedIndexes = [];

View File

@ -55,18 +55,18 @@ FormSubmissionSchema.pre('save', function (next) {
this.form_fields[i].fieldValue = this.form_fields[i].fieldValue.option_value; this.form_fields[i].fieldValue = this.form_fields[i].fieldValue.option_value;
} }
delete form_fields[i].validFieldTypes; delete this.form_fields[i].validFieldTypes;
delete form_fields[i].disabled; delete this.form_fields[i].disabled;
delete form_fields[i].required; delete this.form_fields[i].required;
delete form_fields[i].isSubmission; delete this.form_fields[i].isSubmission;
delete form_fields[i].title; delete this.form_fields[i].title;
delete form_fields[i].fieldOptions; delete this.form_fields[i].fieldOptions;
delete form_fields[i].ratingOptions; delete this.form_fields[i].ratingOptions;
delete form_fields[i].logicJump; delete this.form_fields[i].logicJump;
delete form_fields[i].description; delete this.form_fields[i].description;
delete form_fields[i].created; delete this.form_fields[i].created;
delete form_fields[i].lastModified; delete this.form_fields[i].lastModified;
delete form_fields[i].deletePreserved; delete this.form_fields[i].deletePreserved;
} }
next(); next();
}); });

View File

@ -1,26 +0,0 @@
'use strict';
const constants = require('../../libs/constants'),
config = require('../../../config/config');
module.exports = exports = function lastModifiedPlugin (schema, options) {
schema.add({
language: {
type: String,
enum: constants.languageTypes,
default: config.defaultLanguage,
required: options.required || 'Must be a valid language'
}
});
schema.pre('save', function (next) {
var currWord = this.language;
//English is the default backup language
this.language = 'en';
if(constants.wordToLangCode.has(currWord)){
this.language = constants.wordToLangCode[currWord];
}
next();
});
};

View File

@ -24,20 +24,6 @@ smtpTransport.verify(function(error, success) {
} }
}); });
/**
* A Validation function for local strategy properties
*/
var validateLocalStrategyProperty = function(property) {
var propHasLength;
if (property) {
propHasLength = !!property.length;
} else {
propHasLength = false;
}
return ((this.provider !== 'local' && !this.updated) || propHasLength);
};
/** /**
* User Schema * User Schema
*/ */
@ -78,8 +64,6 @@ var UserSchema = new Schema({
type: String, type: String,
default: 'local' default: 'local'
}, },
providerData: {},
additionalProvidersData: {},
roles: { roles: {
type: [{ type: [{
type: String, type: String,
@ -116,10 +100,6 @@ var UserSchema = new Schema({
} }
}); });
UserSchema.virtual('displayName').get(function () {
return this.firstName + ' ' + this.lastName;
});
UserSchema.plugin(timeStampPlugin, { UserSchema.plugin(timeStampPlugin, {
createdPath: 'created', createdPath: 'created',
modifiedPath: 'lastModified', modifiedPath: 'lastModified',

View File

@ -31,7 +31,7 @@ module.exports = function(app) {
} }
app.route('/forms/:formIdFast([a-zA-Z0-9]+)') app.route('/forms/:formIdFast([a-zA-Z0-9]+)')
.post(forms.createSubmission) .post(forms.createSubmission);
app.route('/forms') app.route('/forms')
.get(auth.isAuthenticatedOrApiKey, forms.list) .get(auth.isAuthenticatedOrApiKey, forms.list)

View File

@ -12,6 +12,7 @@ module.exports = function(app) {
var users = require('../../app/controllers/users.server.controller'); var users = require('../../app/controllers/users.server.controller');
// Setting up the users profile api // Setting up the users profile api
app.route('/users/password').post(users.requiresLogin, users.changePassword);
app.route('/users/me').get(auth.isAuthenticatedOrApiKey, users.getUser); app.route('/users/me').get(auth.isAuthenticatedOrApiKey, users.getUser);
app.route('/users').put(auth.isAuthenticatedOrApiKey, users.update); app.route('/users').put(auth.isAuthenticatedOrApiKey, users.update);
@ -19,8 +20,7 @@ module.exports = function(app) {
app.route('/auth/verify/:token').get(users.validateVerificationToken); app.route('/auth/verify/:token').get(users.validateVerificationToken);
app.route('/auth/verify').post(users.resendVerificationEmail); app.route('/auth/verify').post(users.resendVerificationEmail);
// Setting up the users password api // Setting up the password reset api
app.route('/users/password').post(users.requiresLogin, users.changePassword);
app.route('/auth/forgot').post(users.forgot); app.route('/auth/forgot').post(users.forgot);
app.route('/auth/reset/:token').get(users.validateResetToken); app.route('/auth/reset/:token').get(users.validateResetToken);
app.route('/auth/reset/:token').post(users.reset); app.route('/auth/reset/:token').post(users.reset);
@ -33,7 +33,4 @@ module.exports = function(app) {
app.route('/auth/signout').get(users.signout); app.route('/auth/signout').get(users.signout);
app.route('/auth/genkey').get(users.requiresLogin, users.generateAPIKey); app.route('/auth/genkey').get(users.requiresLogin, users.generateAPIKey);
// Finish by binding the user middleware
app.param('userId', users.userByID);
}; };

View File

@ -9,7 +9,8 @@ var should = require('should'),
User = mongoose.model('User'), User = mongoose.model('User'),
Form = mongoose.model('Form'), Form = mongoose.model('Form'),
Field = mongoose.model('Field'), Field = mongoose.model('Field'),
FormSubmission = mongoose.model('FormSubmission'); FormSubmission = mongoose.model('FormSubmission'),
async = require('async');
/** /**
* Globals * Globals
@ -68,7 +69,6 @@ describe('Form Routes Unit tests', function() {
.send({form: myForm}) .send({form: myForm})
.expect(401) .expect(401)
.end(function(FormSaveErr, FormSaveRes) { .end(function(FormSaveErr, FormSaveRes) {
console.log(FormSaveRes.text)
// Call the assertion callback // Call the assertion callback
done(FormSaveErr); done(FormSaveErr);
}); });
@ -83,7 +83,7 @@ describe('Form Routes Unit tests', function() {
}); });
}); });
it(' > should be able to read/get a Form if not signed in', function(done) { it(' > should be able to read/get a live Form if not signed in', function(done) {
// Create new Form model instance // Create new Form model instance
var FormObj = new Form(myForm); var FormObj = new Form(myForm);
@ -105,6 +105,23 @@ describe('Form Routes Unit tests', function() {
}); });
}); });
it(' > should be able to read/get a non-live Form if not signed in', function(done) {
// Create new Form model instance
var FormObj = new Form(myForm);
FormObj.isLive = false;
// Save the Form
FormObj.save(function(err, form) {
if(err) return done(err);
userSession.get('/subdomain/' + credentials.username + '/forms/' + form._id + '/render')
.expect(401, {message: 'Form is Not Public'})
.end(function(err, res) {
done(err);
});
});
});
it(' > should not be able to delete an Form if not signed in', function(done) { it(' > should not be able to delete an Form if not signed in', function(done) {
// Set Form user // Set Form user
myForm.admin = user; myForm.admin = user;
@ -146,6 +163,16 @@ describe('Form Routes Unit tests', function() {
}); });
}); });
it(' > should not be able to create a Form if body is empty', function(done) {
loginSession.post('/forms')
.send({form: null})
.expect(400, {'message':'Invalid Input'})
.end(function(FormSaveErr, FormSaveRes) {
// Call the assertion callback
done(FormSaveErr);
});
});
it(' > should not be able to save a Form if no title is provided', function(done) { it(' > should not be able to save a Form if no title is provided', function(done) {
// Set Form with a invalid title field // Set Form with a invalid title field
myForm.title = ''; myForm.title = '';
@ -165,10 +192,22 @@ describe('Form Routes Unit tests', function() {
done(); done();
}); });
}); });
it(' > should be able to update a Form if signed in', function(done) { it(' > should be able to create a Form if form_fields are undefined', function(done) {
myForm.analytics = null;
myForm.form_fields = null;
loginSession.post('/forms')
.send({form: myForm})
.expect(200)
.end(function(FormSaveErr, FormSaveRes) {
// Call the assertion callback
done(FormSaveErr);
});
});
it(' > should be able to update a Form if signed in and Form is valid', function(done) {
// Save a new Form // Save a new Form
loginSession.post('/forms') loginSession.post('/forms')
@ -182,7 +221,7 @@ describe('Form Routes Unit tests', function() {
} }
// Update Form title // Update Form title
myForm.title = 'WHY YOU GOTTA BE SO MEAN?'; myForm.title = 'WHY YOU GOTTA BE SO FORMULAIC?';
// Update an existing Form // Update an existing Form
loginSession.put('/forms/' + FormSaveRes.body._id) loginSession.put('/forms/' + FormSaveRes.body._id)
@ -197,13 +236,12 @@ describe('Form Routes Unit tests', function() {
// Set assertions // Set assertions
(FormUpdateRes.body._id).should.equal(FormSaveRes.body._id); (FormUpdateRes.body._id).should.equal(FormSaveRes.body._id);
(FormUpdateRes.body.title).should.match('WHY YOU GOTTA BE SO MEAN?'); (FormUpdateRes.body.title).should.match(myForm.title);
// Call the assertion callback // Call the assertion callback
done(); done();
}); });
}); });
}); });
it(' > should be able to delete a Form if signed in', function(done) { it(' > should be able to delete a Form if signed in', function(done) {
@ -238,10 +276,9 @@ describe('Form Routes Unit tests', function() {
done(); done();
}); });
}); });
}); });
it('should be able to save new form while logged in', function(done){ it(' > should be able to save new form while logged in', function(done){
// Save a new Form // Save a new Form
authenticatedSession.post('/forms') authenticatedSession.post('/forms')
.send({form: myForm}) .send({form: myForm})
@ -271,12 +308,70 @@ describe('Form Routes Unit tests', function() {
}); });
}); });
it(' > should be able to get list of users\' forms sorted by date created while logged in', function(done) {
var myForm1 = {
title: 'First Form',
language: 'en',
admin: user.id,
form_fields: [
new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'nascar', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'hockey', 'fieldValue': ''})
],
isLive: true
};
var myForm2 = {
title: 'Second Form',
language: 'en',
admin: user.id,
form_fields: [
new Field({'fieldType':'textfield', 'title':'Last Name', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'formula one', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'football', 'fieldValue': ''})
],
isLive: true
};
var FormObj1 = new Form(myForm1);
var FormObj2 = new Form(myForm2);
async.waterfall([
function(callback) {
FormObj1.save(function(err){
callback(err);
});
},
function(callback) {
FormObj2.save(function(err){
callback(err);
});
},
function(callback) {
loginSession.get('/forms')
.expect(200)
.end(function(err, res) {
res.body.length.should.equal(2);
res.body[0].title.should.equal('Second Form');
res.body[1].title.should.equal('First Form');
// Call the assertion callback
callback(err);
});
}
], function (err) {
done(err);
});
});
afterEach('should be able to signout user', function(done){ afterEach('should be able to signout user', function(done){
authenticatedSession.get('/auth/signout') authenticatedSession.get('/auth/signout')
.expect(200) .expect(200)
.end(function(signoutErr, signoutRes) { .end(function(signoutErr, signoutRes) {
// Handle signout error // Handle signout error
if (signoutErr) return done(signoutErr); if (signoutErr) {
return done(signoutErr);
}
authenticatedSession.destroy(); authenticatedSession.destroy();
done(); done();
}); });

View File

@ -17,7 +17,6 @@ var exampleDemo = {
address: '880-9650 Velit. St.', address: '880-9650 Velit. St.',
city: '', city: '',
dateOfBirth: '10', dateOfBirth: '10',
displayName: 'Test User',
email: 'polydaic@gmail.com', email: 'polydaic@gmail.com',
firstName: 'Test User', firstName: 'Test User',
hin: '', hin: '',
@ -83,7 +82,7 @@ describe('FormSubmission Model Unit Tests:', function() {
firstName: 'Full', firstName: 'Full',
lastName: 'Name', lastName: 'Name',
email: 'test1@test.com', email: 'test1@test.com',
username: 'test1'+Date.now(), username: 'test1',
password: 'password', password: 'password',
provider: 'local' provider: 'local'
}); });
@ -198,6 +197,7 @@ describe('FormSubmission Model Unit Tests:', function() {
it('should preserve deleted form_fields that have submissions without any problems', function(done) { it('should preserve deleted form_fields that have submissions without any problems', function(done) {
var fieldPropertiesToOmit = ['deletePreserved', 'globalId', 'lastModified', 'created', '_id', 'submissionId', 'isSubmission', 'validFieldTypes', 'title'];
var old_fields = myForm.toObject().form_fields; var old_fields = myForm.toObject().form_fields;
var new_form_fields = _.clone(myForm.toObject().form_fields); var new_form_fields = _.clone(myForm.toObject().form_fields);
new_form_fields.splice(0, 1); new_form_fields.splice(0, 1);
@ -209,8 +209,8 @@ describe('FormSubmission Model Unit Tests:', function() {
should.not.exist(err); should.not.exist(err);
should.exist(_form.form_fields); should.exist(_form.form_fields);
var actual_fields = _.deepOmit(_form.toObject().form_fields, ['deletePreserved', 'globalId', 'lastModified', 'created', '_id', 'submissionId', 'isSubmission', 'validFieldTypes', 'title']); var actual_fields = _.deepOmit(_form.toObject().form_fields, fieldPropertiesToOmit);
old_fields = _.deepOmit(old_fields, ['deletePreserved', 'globalId', 'lastModified', 'created', '_id', 'submissionId', 'isSubmission', 'validFieldTypes', 'title']); old_fields = _.deepOmit(old_fields, fieldPropertiesToOmit);
should.deepEqual(actual_fields, old_fields, 'old form_fields not equal to newly saved form_fields'); should.deepEqual(actual_fields, old_fields, 'old form_fields not equal to newly saved form_fields');
done(); done();

View File

@ -21,8 +21,7 @@ var credentials, user;
* Form routes tests * Form routes tests
*/ */
describe('Form Submission Routes Unit tests', function() { describe('Form Submission Routes Unit tests', function() {
var FormObj, _Submission, submissionSession, _SubmissionBody var FormObj, _Submission, submissionSession, _SubmissionBody;
beforeEach(function(done) { beforeEach(function(done) {
@ -237,6 +236,4 @@ describe('Form Submission Routes Unit tests', function() {
}); });
}); });
}); });
}); });

View File

@ -24,7 +24,7 @@ var invalidFormFields = [
{fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false}, {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false},
{fieldType:'link', title:'Your Website', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'}, {fieldType:'link', title:'Your Website', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'},
{fieldType:'number', title:'Your Age'} {fieldType:'number', title:'Your Age'}
] ];
/** /**
* Unit tests * Unit tests

View File

@ -1,70 +1,72 @@
'use strict';
// Dependencies // Dependencies
var util = require('util') var util = require('util'),
, assert = require('assert') assert = require('assert'),
, mongoose = require('mongoose') mongoose = require('mongoose'),
, timestamp = require('../../libs/timestamp.server.plugin') timestamp = require('../../libs/timestamp.server.plugin'),
, Schema = mongoose.Schema Schema = mongoose.Schema,
, ObjectId = Schema.ObjectId ObjectId = Schema.ObjectId;
// Run tests // Run tests
describe('Timestamp', function () { describe('Timestamp', function () {
describe('#default()', function () { describe('#default()', function () {
var FooSchema = new Schema() var FooSchema = new Schema();
FooSchema.plugin(timestamp) FooSchema.plugin(timestamp);
var FooModel = mongoose.model('timeFoo', FooSchema) var FooModel = mongoose.model('timeFoo', FooSchema),
, bar = new FooModel() bar = new FooModel();
before(function () { before(function () {
FooModel.remove(function (err) { FooModel.remove(function (err) {
assert.strictEqual(err, null) assert.strictEqual(err, null);
}) });
}) });
it('should have custom properties', function (done) { it('should have custom properties', function (done) {
assert.strictEqual(typeof FooSchema.virtuals.created, 'object') assert.strictEqual(typeof FooSchema.virtuals.created, 'object');
assert.strictEqual(typeof FooSchema.paths.modified, 'object') assert.strictEqual(typeof FooSchema.paths.modified, 'object');
done() done();
}) });
it('should create the default attributes', function (done) { it('should create the default attributes', function (done) {
bar.save(function (err, doc) { bar.save(function (err, doc) {
assert.strictEqual(err, null) assert.strictEqual(err, null);
assert.strictEqual(util.isDate(doc.created), true) assert.strictEqual(util.isDate(doc.created), true);
assert.strictEqual(util.isDate(doc.modified), true) assert.strictEqual(util.isDate(doc.modified), true);
done() done();
}) });
}) });
}) });
describe('#custom()', function () { describe('#custom()', function () {
var FooSchema = new Schema() var FooSchema = new Schema();
FooSchema.plugin(timestamp, { FooSchema.plugin(timestamp, {
createdPath: 'oh' createdPath: 'oh',
, modifiedPath: 'hai' modifiedPath: 'hai',
, useVirtual: false useVirtual: false
}) });
var BarModel = mongoose.model('timeBar', FooSchema) var BarModel = mongoose.model('timeBar', FooSchema),
, bar = new BarModel() bar = new BarModel();
before(function () { before(function () {
BarModel.remove(function (err) { BarModel.remove(function (err) {
assert.strictEqual(err, null) assert.strictEqual(err, null);
}) });
}) });
it('should have custom properties', function (done) { it('should have custom properties', function (done) {
assert.strictEqual(typeof FooSchema.paths.oh, 'object') assert.strictEqual(typeof FooSchema.paths.oh, 'object');
assert.strictEqual(typeof FooSchema.paths.hai, 'object') assert.strictEqual(typeof FooSchema.paths.hai, 'object');
done() done();
}) });
it('should create custom attributes', function (done) { it('should create custom attributes', function (done) {
bar.save(function (err, doc) { bar.save(function (err, doc) {
assert.strictEqual(err, null) assert.strictEqual(err, null);
assert.strictEqual(util.isDate(doc.oh), true) assert.strictEqual(util.isDate(doc.oh), true);
assert.strictEqual(util.isDate(doc.hai), true) assert.strictEqual(util.isDate(doc.hai), true);
done() done();
}) });
}) });
}) });
}) });

View File

@ -6,24 +6,23 @@ var should = require('should'),
mongoose = require('mongoose'), mongoose = require('mongoose'),
User = mongoose.model('User'), User = mongoose.model('User'),
config = require('../../config/config'), config = require('../../config/config'),
tmpUser = mongoose.model(config.tempUserCollection); tmpUser = mongoose.model(config.tempUserCollection),
async = require('async');
/** /**
* Globals * Globals
*/ */
var credentials, _User, activateToken, userSession; var credentials, _User, userSession;
/** /**
* Form routes tests * Form routes tests
*/ */
describe('User CRUD tests', function() { describe('User CRUD tests', function() {
this.timeout(30000); before(function() {
beforeEach(function() {
// Create user credentials // Create user credentials
credentials = { credentials = {
email: 'test732@test.com', email: 'test099@test.com',
username: 'test732', username: 'test099',
password: 'password3223' password: 'password3223'
}; };
@ -31,77 +30,424 @@ describe('User CRUD tests', function() {
_User = { _User = {
email: credentials.email, email: credentials.email,
username: credentials.username, username: credentials.username,
password: credentials.password password: credentials.password,
firstName: 'John',
lastName: 'Smith'
}; };
//Initialize Session //Initialize Session
userSession = Session(app); userSession = Session(app);
}); });
it(' > Create, Verify and Activate a User > ', function() { describe(' > Create, Verify and Activate a User > ', function() {
this.timeout(10000);
it('should be able to create a temporary (non-activated) User', function(done) { it('should be able to create and activate a User', function(done) {
userSession.post('/auth/signup') async.waterfall([
.send(_User) function(callback) {
.expect(200) userSession.post('/auth/signup')
.end(function(FormSaveErr) { .send(_User)
// Handle error .expect(200)
should.not.exist(FormSaveErr); .end(function(err) {
callback(err);
tmpUser.findOne({username: _User.username}, function (err, user) { });
should.not.exist(err); },
function(callback) {
tmpUser.findOne({username: _User.username})
.lean()
.exec(function (err, user) {
should.exist(user); should.exist(user);
_User.username.should.equal(user.username); _User.username.should.equal(user.username);
_User.firstName.should.equal(user.firstName); _User.firstName.should.equal(user.firstName);
_User.lastName.should.equal(user.lastName); _User.lastName.should.equal(user.lastName);
activateToken = user.GENERATED_VERIFYING_URL; callback(err, user.GENERATED_VERIFYING_URL);
userSession.get('/auth/verify/'+activateToken)
.expect(200)
.end(function(VerifyErr, VerifyRes) {
// Handle error
if (VerifyErr) {
return done(VerifyErr);
}
(VerifyRes.text).should.equal('User successfully verified');
userSession.post('/auth/signin')
.send(credentials)
.expect('Content-Type', /json/)
.expect(200)
.end(function(signinErr, signinRes) {
// Handle signin error
if (signinErr) {
return done(signinErr);
}
var user = signinRes.body;
(user.username).should.equal(credentials.username);
userSession.get('/auth/signout')
.expect(200)
.end(function(signoutErr, signoutRes) {
// Handle signout error
if (signoutErr) {
return done(signoutErr);
}
(signoutRes.text).should.equal('You have successfully logged out.');
done();
});
});
});
}); });
}); },
function(activateToken, callback) {
userSession.get('/auth/verify/' + activateToken)
.expect(200)
.end(function(err, res) {
(res.text).should.equal('User successfully verified');
callback(err);
});
},
function(callback) {
userSession.post('/auth/signin')
.send(credentials)
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
(res.body.username).should.equal(credentials.username);
callback(err);
});
},
function(callback) {
userSession.get('/auth/signout')
.expect(200)
.end(function(err, res) {
(res.text).should.equal('You have successfully logged out.');
callback(err);
});
},
function(callback) {
User.findOne({ username: _User.username })
.lean()
.exec(function(err, user){
should.exist(user);
callback(err);
});
}
], function (err) {
done(err);
});
}); });
after(function(done){
User.remove().exec(done);
});
}); });
afterEach(function(done) { describe(' > Reset Password > ', function(){
this.timeout(10000);
beforeEach(function(done){
var UserObj = new User(_User);
UserObj.save(function(err){
done(err);
});
});
it('should be able to reset password of a created User with a valid passwordResetToken', function(done) {
var changedPassword = 'password1234';
var resetPasswordToken;
async.waterfall([
function(callback) {
userSession.post('/auth/forgot')
.send({ username: _User.username })
.expect(200)
.end(function(err) {
callback(err);
});
},
function(callback) {
User.findOne({ username: _User.username })
.lean()
.exec(function(err, user){
if(err){
callback(err);
}
callback(null, user.resetPasswordToken);
});
},
function(resetPasswordToken, callback) {
userSession.get('/auth/reset/' + resetPasswordToken)
.expect(302)
.end(function(err) {
callback(err, resetPasswordToken);
});
},
function(resetPasswordToken, callback) {
userSession.post('/auth/reset/' + resetPasswordToken)
.send({
newPassword: changedPassword,
verifyPassword: changedPassword
})
.expect(200)
.end(function(err, res) {
callback(err, resetPasswordToken);
});
},
function(resetPasswordToken, callback) {
User.findOne({ username: _User.username })
.exec(function(err, user){
should.exist(user);
user.authenticate(changedPassword).should.be.true();
should.not.exist(user.resetPasswordToken);
callback(err);
});
}
], function (err, result) {
credentials.password = changedPassword;
done(err);
});
});
it('should be not able to reset password of a created User with a invalid passwordResetToken', function(done) {
var changedPassword = 'password4321';
var resetPasswordToken = 'thisIsNotAValidToken';
async.waterfall([
function(callback) {
userSession.post('/auth/forgot')
.send({ username: credentials.username })
.expect(200)
.end(function(err, res) {
callback(err);
});
},
function(callback) {
userSession.get('/auth/reset/' + resetPasswordToken)
.expect(400)
.end(function(err) {
callback(err);
});
},
function(callback) {
userSession.post('/auth/reset/' + resetPasswordToken)
.send({
newPassword: changedPassword,
verifyPassword: changedPassword
})
.expect(400)
.end(function(err, res) {
callback(err);
});
},
function(callback) {
User.findOne({ username: _User.username })
.exec(function(err, user){
should.exist(user);
user.authenticate(changedPassword).should.be.false();
callback(err);
});
}
], function (err, result) {
done(err);
});
});
afterEach(function(done){
User.remove({ username: credentials.username }).exec(done);
});
});
describe(' > User Profile Changes > ', function(){
var profileSession = new Session(app);
this.timeout(10000);
beforeEach(function(done){
var UserObj = new User(_User);
UserObj.save(function(err, user){
done(err);
});
});
it('should be able to change password when logged in', function(done) {
var changedPassword = 'aVeryBadPassword';
async.waterfall([
function(callback) {
userSession.post('/auth/signin')
.send({
username: _User.username,
password: _User.password
})
.expect(200)
.end(function(err, res) {
callback(err);
});
},
function(callback) {
userSession.post('/users/password')
.send({
currentPassword: _User.password,
newPassword: changedPassword,
verifyPassword: changedPassword
})
.expect(200)
.end(function(err, res) {
callback(err);
});
},
function(callback) {
User.findOne({ username: _User.username })
.exec(function(err, user){
user.authenticate(changedPassword).should.be.true();
callback(err);
});
}
], function (err) {
done(err);
});
});
it('should be able to update user when logged in', function(done) {
var newUser = {};
newUser.firstName = 'goodnight';
newUser.lastName = 'everyone';
newUser.email = 'grcg@gcrc.com';
newUser.username = 'grcg';
async.waterfall([
function(callback) {
userSession.post('/auth/signin')
.send({
username: _User.username,
password: _User.password
})
.expect(200)
.end(function(err, res) {
callback(err);
});
},
function(callback) {
userSession.put('/users')
.send(newUser)
.expect(200)
.end(function(err, res) {
callback(err);
});
},
function(callback) {
User.findOne({ username: newUser.username })
.exec(function(err, user){
user.firstName.should.equal(newUser.firstName);
user.lastName.should.equal(newUser.lastName);
user.email.should.equal(newUser.email);
user.username.should.equal(newUser.username);
callback(err);
});
}
], function (err) {
done(err);
});
});
it('should be able to fetch user when logged in', function(done) {
async.waterfall([
function(callback) {
userSession.post('/auth/signin')
.send({
username: _User.username,
password: _User.password
})
.expect(200)
.end(function(err, res) {
callback(err);
});
},
function(callback) {
userSession.get('/users/me')
.expect(200)
.end(function(err, res) {
var user = res.body;
user.firstName.should.equal(_User.firstName);
user.lastName.should.equal(_User.lastName);
user.email.should.equal(_User.email);
user.username.should.equal(_User.username);
callback(err);
});
}
], function (err) {
done(err);
});
});
afterEach(function(done){
userSession.get('/auth/signout')
.end(function(err, res) {
User.remove().exec(done);
});
});
});
describe(' > User API > ', function(){
var apiKey;
this.timeout(10000);
before(function(done){
var UserObj = new User(_User);
UserObj.save(function(err, user){
done(err);
});
});
it('should be able to request API Key', function(done) {
async.waterfall([
function(callback) {
userSession.post('/auth/signin')
.send({
username: _User.username,
password: _User.password
})
.expect(200)
.end(function(err, res) {
callback(err);
});
},
function(callback) {
userSession.get('/auth/genkey')
.expect(200)
.end(function(err, res) {
apiKey = res.body.apiKey;
callback(err);
});
},
function(callback) {
userSession.get('/auth/signout')
.expect(200)
.end(function(err, res) {
callback(err);
});
},
function(callback) {
userSession.get('/users/me?apikey=' + apiKey)
.expect(200)
.end(function(err, res) {
var user = res.body;
user.firstName.should.equal(_User.firstName);
user.lastName.should.equal(_User.lastName);
user.email.should.equal(_User.email);
user.username.should.equal(_User.username);
callback(err);
});
},
], function (err) {
done(err);
});
});
it('should be able to update user with API key', function(done) {
var newUser = {};
newUser.firstName = 'goodnight';
newUser.lastName = 'everyone';
newUser.email = 'grcg@gcrc.com';
newUser.username = 'grcg';
async.waterfall([
function(callback) {
userSession.put('/users?apikey=' + apiKey)
.send(newUser)
.expect(200)
.end(function(err, res) {
callback(err);
});
},
function(callback) {
User.findOne({ username: newUser.username })
.exec(function(err, user){
user.firstName.should.equal(newUser.firstName);
user.lastName.should.equal(newUser.lastName);
user.email.should.equal(newUser.email);
user.username.should.equal(newUser.username);
callback(err);
});
}
], function (err) {
done(err);
});
});
after(function(done){
User.remove().exec(done);
});
});
after(function(done) {
User.remove().exec(function () { User.remove().exec(function () {
tmpUser.remove().exec(function(){ tmpUser.remove().exec(function(){
userSession.destroy(); userSession.destroy();

View File

@ -6,7 +6,6 @@ block content
h3.col-md-12.text-center=__('500_HEADER') h3.col-md-12.text-center=__('500_HEADER')
div.col-md-4.col-md-offset-4 div.col-md-4.col-md-offset-4
if process.env.NODE_ENV == 'development' || process.env.NODE_ENV == 'test' if process.env.NODE_ENV == 'development' || process.env.NODE_ENV == 'test'
div.col-md-12.text-center(style="padding-bottom: 50px;") div.col-md-12.text-center(style="padding-bottom: 50px;")
| #{error} | #{error}
else else

60
config/env/secure.js vendored
View File

@ -1,60 +0,0 @@
'use strict';
module.exports = {
baseUrl: 'https://forms.polydaic.com',
port: 8443,
db: {
uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || process.env.MONGODB_URI || 'mongodb://127.0.0.1/mean',
options: {
user: '',
pass: ''
}
},
log: {
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
format: 'combined',
// Stream defaults to process.stdout
// Uncomment to enable logging to a log on the file system
options: {
stream: 'access.log'
}
},
sessionCookie: {
path: '/',
httpOnly: false,
// If secure is set to true then it will cause the cookie to be set
// only when SSL-enabled (HTTPS) is used, and otherwise it won't
// set a cookie. 'true' is recommended yet it requires the above
// mentioned pre-requisite.
secure: true,
// Only set the maxAge to null if the cookie shouldn't be expired
// at all. The cookie will expunge when the browser is closed.
maxAge: 7200,
// To set the cookie in a specific domain uncomment the following
// setting:
domain: process.env.BASE_URL || 'localhost:3000'
},
assets: {
css: 'public/dist/application.min.css',
js: 'public/dist/application.min.js'
},
mailer: {
from: process.env.MAILER_FROM || '',
options: process.env.MAILER_SMTP_HOST ? { //Uses custom SMTP if MAILER_SMTP_HOST is set
host: process.env.MAILER_SMTP_HOST || '',
port: process.env.MAILER_SMTP_PORT || 587,
secure: process.env.MAILER_SMTP_SECURE || true,
auth: {
user: process.env.MAILER_EMAIL_ID || '',
pass: process.env.MAILER_PASSWORD || ''
}
} : {
service: process.env.MAILER_SERVICE_PROVIDER || '',
auth: {
user: process.env.MAILER_EMAIL_ID || '',
pass: process.env.MAILER_PASSWORD || ''
}
}
}
};

View File

@ -39,8 +39,9 @@ var configureSocketIO = function (app, db) {
var supportedLanguages = ['en', 'de', 'fr', 'it', 'es']; var supportedLanguages = ['en', 'de', 'fr', 'it', 'es'];
function containsAnySupportedLanguages(preferredLanguages){ function containsAnySupportedLanguages(preferredLanguages){
for (var i = 0; i < preferredLanguages.length; i++) { var i, currIndex;
var currIndex = supportedLanguages.indexOf(preferredLanguages[i]); for (i = 0; i < preferredLanguages.length; i++) {
currIndex = supportedLanguages.indexOf(preferredLanguages[i]);
if (currIndex > -1) { if (currIndex > -1) {
return supportedLanguages[currIndex]; return supportedLanguages[currIndex];
} }
@ -148,8 +149,6 @@ module.exports = function(db) {
// reassign url // reassign url
req.url = subdomainPath; req.url = subdomainPath;
req.userId = user._id;
// Q.E.D. // Q.E.D.
return next(); return next();
}); });
@ -200,7 +199,7 @@ module.exports = function(db) {
app.use(morgan(logger.getLogFormat(), logger.getMorganOptions())); app.use(morgan(logger.getLogFormat(), logger.getMorganOptions()));
// Environment dependent middleware // Environment dependent middleware
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
// Disable views cache // Disable views cache
app.set('view cache', false); app.set('view cache', false);
} else if (process.env.NODE_ENV === 'production') { } else if (process.env.NODE_ENV === 'production') {
@ -228,7 +227,6 @@ module.exports = function(db) {
// Setting the app router and static folder // Setting the app router and static folder
app.use('/static', express.static(path.resolve('./public'))); app.use('/static', express.static(path.resolve('./public')));
app.use('/uploads', express.static(path.resolve('./uploads')));
// CookieParser should be above session // CookieParser should be above session
app.use(cookieParser()); app.use(cookieParser());
@ -263,18 +261,20 @@ module.exports = function(db) {
//Visitor Language Detection //Visitor Language Detection
app.use(function(req, res, next) { app.use(function(req, res, next) {
var acceptLanguage = req.headers['accept-language']; var acceptLanguage = req.headers['accept-language'];
var languages, supportedLanguage;
if(acceptLanguage){ if(acceptLanguage){
var languages = acceptLanguage.match(/[a-z]{2}(?!-)/g) || []; languages = acceptLanguage.match(/[a-z]{2}(?!-)/g) || [];
supportedLanguage = containsAnySupportedLanguages(languages);
}
var supportedLanguage = containsAnySupportedLanguages(languages); if(!req.user && supportedLanguage !== null){
if(!req.user && supportedLanguage !== null){ var currLanguage = res.cookie('userLang');
var currLanguage = res.cookie('userLang'); if(currLanguage && currLanguage !== supportedLanguage || !currLanguage){
res.clearCookie('userLang');
if(currLanguage && currLanguage !== supportedLanguage || !currLanguage){ res.cookie('userLang', supportedLanguage, { maxAge: 90000, httpOnly: true });
res.clearCookie('userLang'); } else if(req.user && (!req.cookies.hasOwnProperty('userLang') || req.cookies.userLang !== req.user.language) ){
res.cookie('userLang', supportedLanguage, { maxAge: 90000, httpOnly: true });
}
} else if(req.user && (!req.cookies.hasOwnProperty('userLang') || req.cookies['userLang'] !== req.user.language) ){
res.cookie('userLang', req.user.language, { maxAge: 90000, httpOnly: true }); res.cookie('userLang', req.user.language, { maxAge: 90000, httpOnly: true });
} }
} }
@ -290,7 +290,7 @@ module.exports = function(db) {
app.use(function (req, res, next) { app.use(function (req, res, next) {
// Website you wish to allow to connect // Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', 'https://sentry.polydaic.com'); res.setHeader('Access-Control-Allow-Origin', 'https://sentry.io');
// Request methods you wish to allow // Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
@ -322,6 +322,7 @@ module.exports = function(db) {
// Log it // Log it
client.captureError(err); client.captureError(err);
// Error page
res.status(500).render('500', { res.status(500).render('500', {
error: err.stack error: err.stack
}); });
@ -336,22 +337,6 @@ module.exports = function(db) {
}); });
}); });
if (process.env.NODE_ENV === 'secure') {
// Load SSL key and certificate
var privateKey = fs.readFileSync('./config/sslcerts/key.pem', 'utf8');
var certificate = fs.readFileSync('./config/sslcerts/cert.pem', 'utf8');
// Create HTTPS Server
var httpsServer = https.createServer({
key: privateKey,
cert: certificate
}, app);
// Return HTTPS server instance
return httpsServer;
}
app = configureSocketIO(app, db); app = configureSocketIO(app, db);
// Return Express server instance // Return Express server instance

View File

@ -63,7 +63,6 @@ logger.setupFileLogger = function setupFileLogger() {
return false; return false;
} }
}; };
/** /**
@ -76,7 +75,7 @@ logger.getLogOptions = function getLogOptions() {
var _config = _.clone(config, true); var _config = _.clone(config, true);
var configFileLogger = _config.log.fileLogger; var configFileLogger = _config.log.fileLogger;
if (!_.has(_config, 'log.fileLogger.directoryPath') || !_.has(_config, 'log.fileLogger.fileName')) { if (process.env.NODE_ENV !== 'test' && !_.has(_config, 'log.fileLogger.directoryPath') || !_.has(_config, 'log.fileLogger.fileName')) {
console.log('unable to find logging file configuration'); console.log('unable to find logging file configuration');
return false; return false;
} }
@ -97,7 +96,6 @@ logger.getLogOptions = function getLogOptions() {
handleExceptions: true, handleExceptions: true,
humanReadableUnhandledException: true humanReadableUnhandledException: true
}; };
}; };
/** /**

View File

@ -6,14 +6,24 @@ module.exports.isAuthenticatedOrApiKey = function isAuthenticated(req, res, next
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
return next(); return next();
} }
// Try authenticate with API KEY // Try authenticate with API KEY
if (req.headers.apikey || req.query.apikey || req.body.apikey) { if (req.headers.apikey || req.query.apikey || req.body.apikey) {
passport.authenticate('localapikey', function (err, user, info) { if(!req.body.apikey && req.headers.apikey){
if (err) req.body.apikey = req.headers.apikey;
return res.sendStatus(500); } else if(!req.query.apikey && req.headers.apikey){
req.query.apikey = req.headers.apikey;
}
if (!user) passport.authenticate('localapikey', function (err, user, info) {
if (err) {
return res.status(500).send('Internal Server Error with API. Sorry about that!');
}
if (!user) {
console.log('no user for apikey');
return res.status(401).send(info.message || ''); return res.status(401).send(info.message || '');
}
req.login(user, function(loginErr) { req.login(user, function(loginErr) {
if (loginErr) return res.sendStatus(500); if (loginErr) return res.sendStatus(500);
@ -28,23 +38,3 @@ module.exports.isAuthenticatedOrApiKey = function isAuthenticated(req, res, next
} }
}; };
module.exports.hasRole = function hasRole(roleRequired) {
if (!roleRequired) {
throw new Error('Required role needs to be set');
}
return function(req, res, next) {
return module.exports.isAuthenticated(req, res, function() {
if (req.user && req.user.roles && req.user.roles.indexOf(roleRequired) !== -1){
return next();
}
return res.sendStatus(403);
});
};
};
module.exports.hasAdminRole = function hasAdminRole() {
return module.exports.hasRole('admin');
};

View File

@ -11,13 +11,15 @@ module.exports = function() {
return User.findOne({ return User.findOne({
'apiKey': apiKey 'apiKey': apiKey
}, function(err, user) { }, function(err, user) {
if (err) if (err) {
return done(err); return done(err);
}
if (!user) if (!user){
return done(null, false, { return done(null, false, {
message: 'Unknown API Key' message: 'Unknown API Key'
}); });
}
return done(null, user); return done(null, user);
}); });

View File

@ -14,8 +14,6 @@ module.exports = function () {
passwordField: 'password' passwordField: 'password'
}, },
function (username, password, done) { function (username, password, done) {
console.log('\n\n\n\n\nusername: '+username);
console.log('password: '+password)
User.findOne({ User.findOne({
$or: [ $or: [
{'username': username.toLowerCase()}, {'username': username.toLowerCase()},

View File

@ -204,63 +204,23 @@ module.exports = function(grunt) {
singleRun: true singleRun: true
} }
}, },
protractor: {
options: {
configFile: 'protractor.conf.js',
keepAlive: true,
noColor: false
},
e2e: {
options: {
args: {} // Target-specific arguments
}
}
},
mocha_istanbul: { mocha_istanbul: {
coverage: {
src: watchFiles.allTests, // a folder works nicely
options: {
mask: '*.test.js',
require: ['server.js']
}
},
coverageClient: {
src: watchFiles.clientTests, // specifying file patterns works as well
options: {
coverageFolder: 'coverageClient',
mask: '*.test.js',
require: ['server.js']
}
},
coverageServer: { coverageServer: {
src: watchFiles.serverTests, src: watchFiles.serverTests,
options: { options: {
coverageFolder: 'coverageServer', coverageFolder: 'coverageServer',
mask: '*.test.js', mask: '*.test.js',
require: ['server.js'] require: ['server.js'],
} reportFormats: ['html','lcovonly']
},
coveralls: {
src: watchFiles.allTests, // multiple folders also works
options: {
require: ['server.js'],
coverage: true, // this will make the grunt.event.on('coverage') event listener to be triggered
root: './lib', // define where the cover task should consider the root of libraries that are covered by tests
reportFormats: ['cobertura','lcovonly']
} }
} }
}, },
istanbul_check_coverage: { lcovMerge: {
default: { options: {
options: { emitters: ['event'],
coverageFolder: 'coverage*', // will check both coverage folders and merge the coverage results },
check: { src: ['./coverageServer/*.info', './coverageClient/lcov-report/*.info']
lines: 80, },
statements: 80
}
}
}
},
html2js: { html2js: {
options: { options: {
base: 'public', base: 'public',
@ -287,7 +247,7 @@ module.exports = function(grunt) {
options: { options: {
module: 'TellForm.templates' module: 'TellForm.templates'
}, },
src: ['public/modules/**/views/**.html', 'public/modules/**/views/**/*.html', 'public/form_modules/forms/base/**/*.html', '!public/modules/forms/base/**/*.html'], src: ['public/modules/**/views/**.html', 'public/modules/**/views/**/*.html', 'public/form_modules/forms/base/**/*.html'],
dest: 'public/dist/populate_template_cache.js' dest: 'public/dist/populate_template_cache.js'
} }
}, },
@ -323,9 +283,7 @@ module.exports = function(grunt) {
}); });
// Code coverage tasks. // Code coverage tasks.
grunt.registerTask('coveralls', ['env:test','mocha_istanbul:coveralls']); grunt.registerTask('coveralls', ['test:client', 'karma:unit', 'mocha_istanbul:coverageServer', 'lcovMerge']);
grunt.registerTask('coverage', ['env:test', 'mocha_istanbul:coverage']);
grunt.registerTask('coverage:client', ['env:test', 'mocha_istanbul:coverageClient']);
grunt.registerTask('coverage:server', ['env:test', 'mocha_istanbul:coverageServer']); grunt.registerTask('coverage:server', ['env:test', 'mocha_istanbul:coverageServer']);
// Default task(s). // Default task(s).
@ -339,7 +297,7 @@ module.exports = function(grunt) {
grunt.registerTask('secure', ['env:secure', 'lint', 'html2js:main', 'html2js:forms', 'concurrent:default']); grunt.registerTask('secure', ['env:secure', 'lint', 'html2js:main', 'html2js:forms', 'concurrent:default']);
// Lint task(s). // Lint task(s).
grunt.registerTask('lint', ['jshint', 'csslint', 'i18nlint:client', 'i18nlint:server']); grunt.registerTask('lint', ['jshint', 'csslint']);
grunt.registerTask('lint:tests', ['jshint:allTests']); grunt.registerTask('lint:tests', ['jshint:allTests']);
// Build task(s). // Build task(s).
@ -349,9 +307,11 @@ module.exports = function(grunt) {
grunt.registerTask('setup', ['execute']); grunt.registerTask('setup', ['execute']);
// Test task(s). // Test task(s).
grunt.registerTask('test', ['lint:tests', 'test:server', 'test:client']); grunt.registerTask('test', ['test:server', 'test:client']);
grunt.registerTask('test:server', ['lint:tests', 'env:test', 'mochaTest']); grunt.registerTask('test:server', ['lint:tests', 'env:test', 'mochaTest']);
grunt.registerTask('test:client', ['lint:tests', 'html2js:main', 'html2js:forms', 'env:test', 'karma:unit']); grunt.registerTask('test:client', ['lint:tests', 'html2js:main', 'html2js:forms', 'env:test', 'karma:unit']);
grunt.registerTask('test:travis', ['coverage:server', 'test:client', 'lcovMerge']);
grunt.registerTask('testdebug', ['env:test', 'karma:debug']); grunt.registerTask('testdebug', ['env:test', 'karma:debug']);
}; };

View File

@ -29,11 +29,20 @@ module.exports = function(config) {
'public/modules/**/views/**/*.html': ['ng-html2js'], 'public/modules/**/views/**/*.html': ['ng-html2js'],
'public/modules/**/views/*.html': ['ng-html2js'], 'public/modules/**/views/*.html': ['ng-html2js'],
'public/form_modules/forms/base/views/**/*.html': ['ng-html2js'], 'public/form_modules/forms/base/views/**/*.html': ['ng-html2js'],
'public/form_modules/forms/base/views/*.html': ['ng-html2js'] 'public/form_modules/forms/base/views/*.html': ['ng-html2js'],
//'public/modules/*/*.js': ['coverage'], 'public/modules/*/*.js': ['coverage'],
//'public/modules/*/*[!tests]*/*.js': ['coverage'], 'public/modules/*/*[!tests]*/*.js': ['coverage']
}, },
// configure coverage reporter
coverageReporter: {
reporters: [
{ type: 'html', subdir: 'report-html' },
{ type: 'lcov', subdir: 'report-lcov' },
],
dir : 'coverageClient/'
},
ngHtml2JsPreprocessor: { ngHtml2JsPreprocessor: {
stripPrefix: 'public/', stripPrefix: 'public/',
prependPrefix: 'static/', prependPrefix: 'static/',

View File

@ -21,12 +21,13 @@
"generate": "all-contributors generate", "generate": "all-contributors generate",
"start": "grunt", "start": "grunt",
"test": "grunt test", "test": "grunt test",
"travis": "grunt test:travis",
"postinstall": "bower install --config.interactive=false; grunt build;", "postinstall": "bower install --config.interactive=false; grunt build;",
"init": "node scripts/setup.js" "init": "node scripts/setup.js"
}, },
"dependencies": { "dependencies": {
"async": "^1.4.2", "async": "^1.4.2",
"bcrypt": "^0.8.7", "bcrypt": "~1.0.3",
"body-parser": "~1.14.1", "body-parser": "~1.14.1",
"bower": "~1.6.5", "bower": "~1.6.5",
"chalk": "^1.1.3", "chalk": "^1.1.3",
@ -72,7 +73,6 @@
"request": "^2.83.0", "request": "^2.83.0",
"socket.io": "^1.4.6", "socket.io": "^1.4.6",
"socket.io-redis": "^1.0.0", "socket.io-redis": "^1.0.0",
"swig": "~1.4.1",
"uuid-token-generator": "^0.5.0", "uuid-token-generator": "^0.5.0",
"wildcard-subdomains": "github:tellform/wildcard-subdomains", "wildcard-subdomains": "github:tellform/wildcard-subdomains",
"winston": "^2.3.1", "winston": "^2.3.1",
@ -91,8 +91,8 @@
"grunt-contrib-uglify": "^0.11.1", "grunt-contrib-uglify": "^0.11.1",
"grunt-contrib-watch": "~0.6.1", "grunt-contrib-watch": "~0.6.1",
"grunt-execute": "^0.2.2", "grunt-execute": "^0.2.2",
"grunt-i18nlint": "github:jwarby/grunt-i18nlint",
"grunt-karma": "~0.12.1", "grunt-karma": "~0.12.1",
"grunt-lcov-merge": "^1.2.3",
"grunt-mocha-istanbul": "^3.0.1", "grunt-mocha-istanbul": "^3.0.1",
"grunt-mocha-test": "~0.12.1", "grunt-mocha-test": "~0.12.1",
"grunt-newer": "~1.1.1", "grunt-newer": "~1.1.1",
@ -100,7 +100,7 @@
"grunt-usemin": "^3.1.1", "grunt-usemin": "^3.1.1",
"grunt-wiredep": "^3.0.1", "grunt-wiredep": "^3.0.1",
"istanbul": "^0.4.0", "istanbul": "^0.4.0",
"jasmine-core": "^2.4.1", "jasmine-core": "^2.6",
"karma": "~0.13.14", "karma": "~0.13.14",
"karma-chrome-launcher": "~0.2.1", "karma-chrome-launcher": "~0.2.1",
"karma-coverage": "~0.5.3", "karma-coverage": "~0.5.3",
@ -113,7 +113,7 @@
"mocha": "^3.1.2", "mocha": "^3.1.2",
"mocha-lcov-reporter": "^1.0.0", "mocha-lcov-reporter": "^1.0.0",
"nightwatch": "^0.9.8", "nightwatch": "^0.9.8",
"phantomjs": "^1.9.18", "phantomjs-prebuilt": "^2.1.15",
"selenium-server": "^3.0.1", "selenium-server": "^3.0.1",
"should": "~7.1.1", "should": "~7.1.1",
"supertest": "~1.2.0", "supertest": "~1.2.0",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,9 +6,38 @@
var scope, var scope,
HeaderController; HeaderController;
var sampleUser = {
firstName: 'Full',
lastName: 'Name',
email: 'test@test.com',
username: 'test@test.com',
language: 'en',
password: 'password',
provider: 'local',
roles: ['user'],
_id: 'ed873933b1f1dea0ce12fab9'
};
// Load the main application module // Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName)); beforeEach(module(ApplicationConfiguration.applicationModuleName));
//Mock Authentication Service
beforeEach(module(function($provide) {
$provide.service('Auth', function() {
return {
ensureHasCurrentUser: function() {
return sampleUser;
},
isAuthenticated: function() {
return true;
},
getUserState: function() {
return true;
}
};
});
}));
beforeEach(inject(function($controller, $rootScope) { beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new(); scope = $rootScope.$new();

View File

@ -161,7 +161,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
MULTIPLE_CHOICE: 'Multiple Choice', MULTIPLE_CHOICE: 'Multiple Choice',
DROPDOWN: 'Dropdown', DROPDOWN: 'Dropdown',
DATE: 'Date', DATE: 'Date',
PARAGRAPH_T: 'Paragraph', PARAGRAPH_FIELD: 'Paragraph',
YES_NO: 'Yes/No', YES_NO: 'Yes/No',
LEGAL: 'Legal', LEGAL: 'Legal',
RATING: 'Rating', RATING: 'Rating',

View File

@ -157,7 +157,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
MULTIPLE_CHOICE: 'Choix multiple', MULTIPLE_CHOICE: 'Choix multiple',
DROPDOWN: 'Menu Déroulant', DROPDOWN: 'Menu Déroulant',
DATE: 'Date', DATE: 'Date',
PARAGRAPH_T: "Paragraphe", PARAGRAPH_FIELD: "Paragraphe",
OUI_NON: 'Oui / Non', OUI_NON: 'Oui / Non',
LEGAL: 'Légal', LEGAL: 'Légal',
RATING: "Évaluation", RATING: "Évaluation",

View File

@ -89,7 +89,6 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
ADD_OPTION: 'Option hinzufügen', ADD_OPTION: 'Option hinzufügen',
NUM_OF_STEPS: 'Anzahl der Schritte', NUM_OF_STEPS: 'Anzahl der Schritte',
CLICK_FIELDS_FOOTER: 'Klicken Sie auf Felder, um sie hier hinzuzufügen', CLICK_FIELDS_FOOTER: 'Klicken Sie auf Felder, um sie hier hinzuzufügen',
FORM: 'Formular',
IF_THIS_FIELD: 'Wenn dieses Feld', IF_THIS_FIELD: 'Wenn dieses Feld',
IS_EQUAL_TO: 'ist gleich', IS_EQUAL_TO: 'ist gleich',
IS_NOT_EQUAL_TO: 'ist nicht gleich', IS_NOT_EQUAL_TO: 'ist nicht gleich',
@ -157,7 +156,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
MULTIPLE_CHOICE: 'Mehrfachauswahl', MULTIPLE_CHOICE: 'Mehrfachauswahl',
DROPDOWN: 'Dropdown-Liste', DROPDOWN: 'Dropdown-Liste',
DATE: 'Datum', DATE: 'Datum',
PARAGRAPH_T: "Absatz", PARAGRAPH_FIELD: "Absatz",
YES_NO: 'Ja / Nein', YES_NO: 'Ja / Nein',
LEGAL: "Rechtliche", LEGAL: "Rechtliche",
RATING: 'Bewertung', RATING: 'Bewertung',

View File

@ -157,7 +157,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
MULTIPLE_CHOICE: 'Scelta multipla', MULTIPLE_CHOICE: 'Scelta multipla',
DROPDOWN: 'Dropdown', DROPDOWN: 'Dropdown',
DATE: 'Data', DATE: 'Data',
PARAGRAPH_T: 'Paragrafo', PARAGRAPH_FIELD: 'Paragrafo',
YES_NO: 'Sì / no', YES_NO: 'Sì / no',
LEGAL: 'Legale', LEGAL: 'Legale',
RATING: 'Valutazione', RATING: 'Valutazione',

View File

@ -158,7 +158,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
MULTIPLE_CHOICE: 'Opciones múltiples', MULTIPLE_CHOICE: 'Opciones múltiples',
DROPDOWN: 'Desplegable', DROPDOWN: 'Desplegable',
DATE: 'Fecha', DATE: 'Fecha',
PARAGRAPH_T: 'Párrafo', PARAGRAPH_FIELD: 'Párrafo',
YES_NO: 'Si/No', YES_NO: 'Si/No',
LEGAL: 'Legal', LEGAL: 'Legal',
RATING: 'Puntaje', RATING: 'Puntaje',

View File

@ -10,13 +10,33 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
myform: '=' myform: '='
}, },
controller: function($scope){ controller: function($scope){
$scope.table = { $scope.table = {
masterChecker: false, masterChecker: false,
rows: [] rows: []
}; };
var getSubmissions = function(){ $scope.deletionInProgress = false;
$scope.waitingForDeletion = false;
//Waits until deletionInProgress is false before running getSubmissions
$scope.$watch("deletionInProgress",function(newVal, oldVal){
if(newVal === oldVal) return;
if(newVal === false && $scope.waitingForDeletion) {
$scope.getSubmissions();
$scope.waitingForDeletion = false;
}
});
$scope.handleSubmissionsRefresh = function(){
if(!$scope.deletionInProgress) {
$scope.getSubmissions();
} else {
$scope.waitingForDeletion = true;
}
};
$scope.getSubmissions = function(cb){
$http({ $http({
method: 'GET', method: 'GET',
url: '/forms/'+$scope.myform._id+'/submissions' url: '/forms/'+$scope.myform._id+'/submissions'
@ -36,10 +56,19 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
} }
$scope.table.rows = submissions; $scope.table.rows = submissions;
if(cb && typeof cb === 'function'){
cb();
}
}, function errorCallback(err){
console.error(err);
if(cb && typeof cb === 'function'){
cb(err);
}
}); });
}; };
var getVisitors = function(){ $scope.getVisitors = function(){
$http({ $http({
method: 'GET', method: 'GET',
url: '/forms/'+$scope.myform._id+'/visitors' url: '/forms/'+$scope.myform._id+'/visitors'
@ -52,8 +81,23 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
}); });
}; };
getSubmissions(); $scope.handleSubmissionsRefresh();
getVisitors(); $scope.getVisitors();
//Fetch submissions and visitor data every 1.67 min
var updateSubmissions = $interval($scope.handleSubmissionsRefresh, 100000);
var updateVisitors = $interval($scope.getVisitors, 1000000);
//Prevent $intervals from running after directive is destroyed
$scope.$on('$destroy', function() {
if (updateSubmissions) {
$interval.cancel($scope.updateSubmissions);
}
if (updateVisitors) {
$interval.cancel($scope.updateVisitors);
}
});
/* /*
** Analytics Functions ** Analytics Functions
@ -72,14 +116,48 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
return (totalTime/numSubmissions).toFixed(0); return (totalTime/numSubmissions).toFixed(0);
})(); })();
var updateFields = $interval(getSubmissions, 100000); $scope.DeviceStatistics = (function(){
var updateFields = $interval(getVisitors, 1000000); var newStatItem = function(){
return {
visits: 0,
responses: 0,
completion: 0,
average_time: 0,
total_time: 0
};
};
$scope.$on('$destroy', function() { var stats = {
if (updateFields) { desktop: newStatItem(),
$interval.cancel($scope.updateFields); tablet: newStatItem(),
phone: newStatItem(),
other: newStatItem()
};
if($scope.myform.analytics && $scope.myform.analytics.visitors) {
var visitors = $scope.myform.analytics.visitors;
for (var i = 0; i < visitors.length; i++) {
var visitor = visitors[i];
var deviceType = visitor.deviceType;
stats[deviceType].visits++;
if (visitor.isSubmitted) {
stats[deviceType].total_time = stats[deviceType].total_time + visitor.timeElapsed;
stats[deviceType].responses++;
}
if(stats[deviceType].visits) {
stats[deviceType].completion = 100*(stats[deviceType].responses / stats[deviceType].visits).toFixed(2);
}
if(stats[deviceType].responses){
stats[deviceType].average_time = (stats[deviceType].total_time / stats[deviceType].responses).toFixed(0);
}
}
} }
}); return stats;
})();
/* /*
** Table Functions ** Table Functions
@ -109,25 +187,24 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
//Delete selected submissions of Form //Delete selected submissions of Form
$scope.deleteSelectedSubmissions = function(){ $scope.deleteSelectedSubmissions = function(){
$scope.deletionInProgress = true;
var delete_ids = _.chain($scope.table.rows).filter(function(row){ var delete_ids = _.chain($scope.table.rows).filter(function(row){
return !!row.selected; return !!row.selected;
}).pluck('_id').value(); }).pluck('_id').value();
$http({ url: '/forms/'+$scope.myform._id+'/submissions', return $http({ url: '/forms/'+$scope.myform._id+'/submissions',
method: 'DELETE', method: 'DELETE',
data: {deleted_submissions: delete_ids}, data: {deleted_submissions: delete_ids},
headers: {'Content-Type': 'application/json;charset=utf-8'} headers: {'Content-Type': 'application/json;charset=utf-8'}
}).success(function(data, status){ }).success(function(data, status){
$scope.deletionInProgress = true;
//Remove deleted ids from table //Remove deleted ids from table
var tmpArray = []; $scope.table.rows = $scope.table.rows.filter(function(field){
for(var i=0; i<$scope.table.rows.length; i++){ return !field.selected;
if(!$scope.table.rows[i].selected){ });
tmpArray.push($scope.table.rows[i]);
}
}
$scope.table.rows = tmpArray;
}) })
.error(function(err){ .error(function(err){
$scope.deletionInProgress = true;
console.error(err); console.error(err);
}); });
}; };

View File

@ -28,7 +28,7 @@ angular.module('forms').service('FormFields', [ '$rootScope', '$translate', '$wi
}, },
{ {
name : 'textarea', name : 'textarea',
value : $translate.instant('PARAGRAPH'), value : $translate.instant('PARAGRAPH_FIELD'),
}, },
{ {
name : 'yes_no', name : 'yes_no',

View File

@ -57,22 +57,7 @@
_id: '525a8422f6d0f87f0e407a33' _id: '525a8422f6d0f87f0e407a33'
}; };
var newFakeModal = function(){ //Mock myForm Service
var result = {
opened: true,
close: function( item ) {
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
this.opened = false;
},
dismiss: function( type ) {
//The user clicked cancel on the modal dialog, call the stored cancel callback
this.opened = false;
}
};
return result;
};
//Mock Users Service
beforeEach(module(function($provide) { beforeEach(module(function($provide) {
$provide.service('myForm', function($q) { $provide.service('myForm', function($q) {
return sampleForm; return sampleForm;
@ -159,6 +144,27 @@
}); });
})); }));
var newFakeModal = function(){
var modal = {
opened: true,
close: function( item ) {
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
this.opened = false;
},
dismiss: function( type ) {
//The user clicked cancel on the modal dialog, call the stored cancel callback
this.opened = false;
},
result: {
then: function (cb) {
if(cb && typeof cb === 'function'){
cb();
}
}
}
};
return modal;
};
//Mock $uibModal //Mock $uibModal
beforeEach(inject(function($uibModal) { beforeEach(inject(function($uibModal) {
@ -199,7 +205,7 @@
expect(scope.myform).toEqualData(sampleForm); expect(scope.myform).toEqualData(sampleForm);
}); });
it('$scope.removeCurrentForm() with valid form data should send a DELETE request with the id of form', function() { it('$scope.removeCurrentForm() with valid form data should send a DELETE request with the id of form', inject(function($uibModal) {
var controller = createAdminFormController(); var controller = createAdminFormController();
//Set $state transition //Set $state transition
@ -214,7 +220,7 @@
$httpBackend.flush(); $httpBackend.flush();
$state.ensureAllTransitionsHappened(); $state.ensureAllTransitionsHappened();
}); }));
it('$scope.update() should send a PUT request with the id of form', function() { it('$scope.update() should send a PUT request with the id of form', function() {
var controller = createAdminFormController(); var controller = createAdminFormController();

View File

@ -86,7 +86,6 @@
}); });
})); }));
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable // This allows us to inject a service but then attach it to a variable
// with the same name as the service. // with the same name as the service.
@ -106,25 +105,13 @@
// Initialize the Forms controller. // Initialize the Forms controller.
createListFormsController = function(){ createListFormsController = function(){
return $controller('ListFormsController', { $scope: scope }); return $controller('ListFormsController', {
$scope: scope,
myForms: sampleFormList
});
}; };
})); }));
it('$scope.findAll() should query all User\'s Forms', inject(function() {
var controller = createListFormsController();
// Set GET response
$httpBackend.expectGET(/^(\/forms)$/).respond(200, sampleFormList);
// Run controller functionality
scope.findAll();
$httpBackend.flush();
// Test scope value
expect( scope.myforms ).toEqualData(sampleFormList);
}));
it('$scope.duplicateForm() should duplicate a Form', inject(function() { it('$scope.duplicateForm() should duplicate a Form', inject(function() {
var dupSampleForm = sampleFormList[2], var dupSampleForm = sampleFormList[2],
@ -135,12 +122,6 @@
var controller = createListFormsController(); var controller = createListFormsController();
// Set GET response
$httpBackend.expectGET(/^(\/forms)$/).respond(200, sampleFormList);
// Run controller functionality
scope.findAll();
$httpBackend.flush();
// Set GET response // Set GET response
$httpBackend.expect('POST', '/forms').respond(200, dupSampleForm); $httpBackend.expect('POST', '/forms').respond(200, dupSampleForm);
// Run controller functionality // Run controller functionality
@ -164,13 +145,6 @@
var controller = createListFormsController(); var controller = createListFormsController();
// Set GET response
$httpBackend.expectGET(/^(\/forms)$/).respond(200, sampleFormList);
// Run controller functionality
scope.findAll();
$httpBackend.flush();
// Set GET response // Set GET response
$httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, delSampleForm); $httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, delSampleForm);

View File

@ -2,7 +2,7 @@
(function() { (function() {
// Forms Controller Spec // Forms Controller Spec
describe('EditSubmissions Directive-Controller Tests', function() { describe('EditFormSubmissions Directive-Controller Tests', function() {
// Initialize global variables // Initialize global variables
var el, scope, controller, $httpBackend; var el, scope, controller, $httpBackend;
@ -10,13 +10,25 @@
firstName: 'Full', firstName: 'Full',
lastName: 'Name', lastName: 'Name',
email: 'test@test.com', email: 'test@test.com',
username: 'test@test.com', username: 'test1234',
password: 'password', password: 'password',
provider: 'local', provider: 'local',
roles: ['user'], roles: ['user'],
_id: 'ed873933b1f1dea0ce12fab9' _id: 'ed873933b1f1dea0ce12fab9'
}; };
var sampleVisitors = [{
socketId: '33b1f1dea0ce12fab9ed8739',
referrer: 'https://tellform.com/examples',
lastActiveField: 'ed873933b0ce121f1deafab9',
timeElapsed: 100000,
isSubmitted: true,
language: 'en',
ipAddr: '192.168.1.1',
deviceType: 'desktop',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4'
}]
var sampleForm = { var sampleForm = {
title: 'Form Title', title: 'Form Title',
admin: 'ed873933b1f1dea0ce12fab9', admin: 'ed873933b1f1dea0ce12fab9',
@ -27,7 +39,18 @@
{fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'} {fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'}
], ],
analytics: { analytics: {
visitors: [] visitors: sampleVisitors,
conversionRate: 80.5,
fields: [
{
dropoffViews: 0,
responses: 1,
totalViews: 1,
continueRate: 100,
dropoffRate: 0,
field: {fieldType:'textfield', title:'First Name', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'}
}
]
}, },
submissions: [], submissions: [],
startPage: { startPage: {
@ -61,7 +84,8 @@
], ],
admin: sampleUser, admin: sampleUser,
form: sampleForm, form: sampleForm,
timeElapsed: 10.33 timeElapsed: 10.33,
selected: false
}, },
{ {
form_fields: [ form_fields: [
@ -71,7 +95,8 @@
], ],
admin: sampleUser, admin: sampleUser,
form: sampleForm, form: sampleForm,
timeElapsed: 2.33 timeElapsed: 2.33,
selected: false
}, },
{ {
form_fields: [ form_fields: [
@ -81,7 +106,8 @@
], ],
admin: sampleUser, admin: sampleUser,
form: sampleForm, form: sampleForm,
timeElapsed: 11.11 timeElapsed: 11.11,
selected: false
}]; }];
// The $resource service augments the response object with methods for updating and deleting the resource. // The $resource service augments the response object with methods for updating and deleting the resource.
@ -118,10 +144,12 @@
$httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm); $httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm);
$httpBackend.whenGET('/forms').respond(200, sampleForm); $httpBackend.whenGET('/forms').respond(200, sampleForm);
$httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm); $httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm);
//Instantiate directive. $httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})\/submissions$/).respond(200, sampleSubmissions);
$httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})\/visitors$/).respond(200, sampleVisitors);
//Instantiate directive.
var tmp_scope = $rootScope.$new(); var tmp_scope = $rootScope.$new();
tmp_scope.myform = sampleForm; tmp_scope.myform = sampleForm;
tmp_scope.myform.submissions = sampleSubmissions;
tmp_scope.user = sampleUser; tmp_scope.user = sampleUser;
//gotacha: Controller and link functions will execute. //gotacha: Controller and link functions will execute.
@ -141,6 +169,7 @@
it('$scope.toggleAllCheckers should toggle all checkboxes in table', function(){ it('$scope.toggleAllCheckers should toggle all checkboxes in table', function(){
//Run Controller Logic to Test //Run Controller Logic to Test
scope.table.rows = sampleSubmissions;
scope.table.masterChecker = true; scope.table.masterChecker = true;
scope.toggleAllCheckers(); scope.toggleAllCheckers();
@ -151,6 +180,7 @@
}); });
it('$scope.isAtLeastOneChecked should return true when at least one checkbox is selected', function(){ it('$scope.isAtLeastOneChecked should return true when at least one checkbox is selected', function(){
scope.table.rows = sampleSubmissions;
scope.table.masterChecker = true; scope.table.masterChecker = true;
scope.toggleAllCheckers(); scope.toggleAllCheckers();
@ -161,16 +191,22 @@
}); });
it('$scope.deleteSelectedSubmissions should delete all submissions that are selected', function(){ it('$scope.deleteSelectedSubmissions should delete all submissions that are selected', function(){
$httpBackend.expect('GET', /^(\/forms\/)([0-9a-fA-F]{24})(\/submissions)$/).respond(200, sampleSubmissions);
scope.table.masterChecker = true; scope.table.masterChecker = true;
scope.toggleAllCheckers(); scope.getSubmissions(function(err){
scope.toggleAllCheckers();
$httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})(\/submissions)$/).respond(200); $httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})(\/submissions)$/).respond(200);
//Run Controller Logic to Test //Run Controller Logic to Test
scope.deleteSelectedSubmissions(); scope.deleteSelectedSubmissions().then(function(){
expect(scope.table.rows.length).toEqual(0);
});
expect(err).not.toBeDefined();
});
$httpBackend.flush(); $httpBackend.flush();
expect(scope.table.rows.length).toEqual(0);
}); });
}); });

View File

@ -62,6 +62,52 @@
beforeEach(module('module-templates')); beforeEach(module('module-templates'));
beforeEach(module('stateMock')); beforeEach(module('stateMock'));
//Mock FormFields Service
beforeEach(module(function($provide) {
$provide.service('FormFields', function() {
return {
types: [
{
name : 'textfield',
value : 'Short Text'
},
{
name : 'email',
value : 'Email'
}
]
};
});
}));
var newFakeModal = function(){
var modal = {
opened: true,
close: function( item ) {
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
this.opened = false;
},
dismiss: function( type ) {
//The user clicked cancel on the modal dialog, call the stored cancel callback
this.opened = false;
},
result: {
then: function (cb) {
if(cb && typeof cb === 'function'){
cb();
}
}
}
};
return modal;
};
//Mock $uibModal
beforeEach(inject(function($uibModal) {
var modal = newFakeModal();
spyOn($uibModal, 'open').and.returnValue(modal);
}));
beforeEach(inject(function($compile, $controller, $rootScope, _$httpBackend_) { beforeEach(inject(function($compile, $controller, $rootScope, _$httpBackend_) {
//Instantiate directive. //Instantiate directive.
var tmp_scope = $rootScope.$new(); var tmp_scope = $rootScope.$new();
@ -97,26 +143,12 @@
scope.myform = _.cloneDeep(sampleForm); scope.myform = _.cloneDeep(sampleForm);
}); });
it('$scope.addNewField() should ADD a new field to $scope.myform.form_fields', function() { it('$scope.addNewField() should open the new field modal', function() {
//Run controller methods //Run controller methods
scope.addNewField(true, 'textfield'); scope.addNewField('textfield');
var expectedFormField = { expect(scope.editFieldModal.opened).toBeTruthy();
title: 'Short Text2',
fieldType: 'textfield',
fieldValue: '',
required: true,
disabled: false,
deletePreserved: false,
logicJump: Object({ })
};
var actualFormField = _.cloneDeep(_.last(scope.myform.form_fields));
delete actualFormField._id;
expect(scope.myform.form_fields.length).toEqual(sampleForm.form_fields.length+1);
expect(actualFormField).toEqualData(expectedFormField);
}); });
it('$scope.deleteField() should DELETE a field to $scope.myform.form_fields', function() { it('$scope.deleteField() should DELETE a field to $scope.myform.form_fields', function() {

View File

@ -5,7 +5,6 @@
describe('FieldIcon Directive Tests', function() { describe('FieldIcon Directive Tests', function() {
// Initialize global variables // Initialize global variables
var scope, var scope,
FormFields,
faClasses = { faClasses = {
'textfield': 'fa fa-pencil-square-o', 'textfield': 'fa fa-pencil-square-o',
'dropdown': 'fa fa-th-list', 'dropdown': 'fa fa-th-list',
@ -28,10 +27,68 @@
// Load the main application module // Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName)); beforeEach(module(ApplicationConfiguration.applicationModuleName));
//Mock FormFields Service
var FormFields = {
types: [
{
name : 'textfield',
value : 'Short Text'
},
{
name : 'email',
value : 'Email'
},
{
name : 'radio',
value : 'Muliple Choice'
},
{
name : 'dropdown',
value : 'Dropdown'
},
{
name : 'date',
value : 'Date'
},
{
name : 'textarea',
value : 'Paragraph',
},
{
name : 'yes_no',
value : 'Yes/No',
},
{
name : 'legal',
value : 'Legal',
},
{
name : 'rating',
value : 'Rating',
},
{
name : 'link',
value : 'Link',
},
{
name : 'number',
value : 'Numbers',
},
{
name : 'statement',
value : 'Statement'
}
]
};
beforeEach(module(function($provide) {
$provide.service('FormFields', function() {
return FormFields;
});
}));
beforeEach(inject(function ($rootScope, _FormFields_) { beforeEach(inject(function ($rootScope, _FormFields_) {
scope = $rootScope.$new(); scope = $rootScope.$new();
FormFields = _FormFields_; }));
}));
it('should be able render all field-icon types', inject(function($compile) { it('should be able render all field-icon types', inject(function($compile) {
var currType, currClass; var currType, currClass;

View File

@ -5,11 +5,63 @@
describe('Field Directive Tests', function() { describe('Field Directive Tests', function() {
// Initialize global variables // Initialize global variables
var scope, var scope,
FormFields,
$templateCache, $templateCache,
$httpBackend, $httpBackend,
$compile; $compile;
var FormFields = {
types: [
{
name : 'textfield',
value : 'Short Text'
},
{
name : 'email',
value : 'Email'
},
{
name : 'radio',
value : 'Muliple Choice'
},
{
name : 'dropdown',
value : 'Dropdown'
},
{
name : 'date',
value : 'Date'
},
{
name : 'textarea',
value : 'Paragraph',
},
{
name : 'yes_no',
value : 'Yes/No',
},
{
name : 'legal',
value : 'Legal',
},
{
name : 'rating',
value : 'Rating',
},
{
name : 'link',
value : 'Link',
},
{
name : 'number',
value : 'Numbers',
},
{
name : 'statement',
value : 'Statement'
}
]
};
var sampleUser = { var sampleUser = {
firstName: 'Full', firstName: 'Full',
lastName: 'Name', lastName: 'Name',
@ -65,9 +117,15 @@
beforeEach(module('ngSanitize', 'ui.select')); beforeEach(module('ngSanitize', 'ui.select'));
//Mock FormFields Service
beforeEach(module(function($provide) {
$provide.service('FormFields', function() {
return FormFields;
});
}));
beforeEach(inject(function($rootScope, _FormFields_, _$compile_, _$httpBackend_) { beforeEach(inject(function($rootScope, _FormFields_, _$compile_, _$httpBackend_) {
scope = $rootScope.$new(); scope = $rootScope.$new();
FormFields = _FormFields_;
// Point global variables to injected services // Point global variables to injected services
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
@ -76,6 +134,7 @@
$compile = _$compile_; $compile = _$compile_;
})); }));
it('should be able to render all field types in html', inject(function($rootScope) { it('should be able to render all field types in html', inject(function($rootScope) {
scope.fields = sampleFields; scope.fields = sampleFields;

View File

@ -4,15 +4,73 @@
// Forms Controller Spec // Forms Controller Spec
describe('onFinishRender Directive Tests', function() { describe('onFinishRender Directive Tests', function() {
// Initialize global variables // Initialize global variables
var scope, var scope;
FormFields;
var FormFields = {
types: [
{
name : 'textfield',
value : 'Short Text'
},
{
name : 'email',
value : 'Email'
},
{
name : 'radio',
value : 'Muliple Choice'
},
{
name : 'dropdown',
value : 'Dropdown'
},
{
name : 'date',
value : 'Date'
},
{
name : 'textarea',
value : 'Paragraph',
},
{
name : 'yes_no',
value : 'Yes/No',
},
{
name : 'legal',
value : 'Legal',
},
{
name : 'rating',
value : 'Rating',
},
{
name : 'link',
value : 'Link',
},
{
name : 'number',
value : 'Numbers',
},
{
name : 'statement',
value : 'Statement'
}
]
};
// Load the main application module // Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName)); beforeEach(module(ApplicationConfiguration.applicationModuleName));
beforeEach(inject(function ($rootScope, _FormFields_) { //Mock FormFields Service
beforeEach(module(function($provide) {
$provide.service('FormFields', function() {
return FormFields;
});
}));
beforeEach(inject(function ($rootScope) {
scope = $rootScope.$new(); scope = $rootScope.$new();
FormFields = _FormFields_;
spyOn($rootScope, '$broadcast'); spyOn($rootScope, '$broadcast');
})); }));

View File

@ -9,7 +9,7 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$loca
$scope.forms = {}; $scope.forms = {};
$scope.signin = function() { $scope.signin = function() {
if(!$scope.forms.signinForm.$invalid){ if($scope.forms && $scope.forms.hasOwnProperty('siginForm') && !$scope.forms.signinForm.$invalid){
User.login($scope.credentials).then( User.login($scope.credentials).then(
function(response) { function(response) {
Auth.login(response); Auth.login(response);

View File

@ -80,7 +80,7 @@
// Load the main application module // Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName)); beforeEach(module(ApplicationConfiguration.applicationModuleName));
beforeEach(module('module-templates'));
beforeEach(module('stateMock')); beforeEach(module('stateMock'));
// Mock Users Service // Mock Users Service

View File

@ -3,12 +3,15 @@
* Module dependencies. * Module dependencies.
*/ */
require('dotenv').config({path: './.env'});
if(!process.env.NODE_ENV){ if(!process.env.NODE_ENV){
process.env.NODE_ENV = 'development'; process.env.NODE_ENV = 'development';
} }
//Don't check .env file if we are in travis-ci
if(!process.env.TRAVIS){
require('dotenv').config({path: './.env'});
}
require('events').EventEmitter.prototype._maxListeners = 0; require('events').EventEmitter.prototype._maxListeners = 0;
@ -62,9 +65,6 @@ console.log('--');
console.log(chalk.green('Environment:\t\t\t' + process.env.NODE_ENV)); console.log(chalk.green('Environment:\t\t\t' + process.env.NODE_ENV));
console.log(chalk.green('Port:\t\t\t\t' + config.port)); console.log(chalk.green('Port:\t\t\t\t' + config.port));
console.log(chalk.green('Database:\t\t\t' + config.db.uri)); console.log(chalk.green('Database:\t\t\t' + config.db.uri));
if (process.env.NODE_ENV === 'secure') {
console.log(chalk.green('HTTPs:\t\t\t\ton'));
}
console.log('--'); console.log('--');
process.on('uncaughtException', function (err) { process.on('uncaughtException', function (err) {

7203
yarn.lock Normal file

File diff suppressed because it is too large Load Diff