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:
code_climate:
repo_token: 6c3a1b81a09b2338d6f30913c1bcad115026689752cbb499a0a25061cda6fbcf
after_script:
- grunt coverage
install:
- 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'),
nodemailer = require('nodemailer'),
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);
@ -113,16 +114,14 @@ exports.listSubmissions = function(req, res) {
}
res.json(_submissions);
});
};
/**
* Create a new form
*/
exports.create = function(req, res) {
if(!req.body.form){
return res.status(401).send({
return res.status(400).send({
message: 'Invalid Input'
});
}
@ -130,14 +129,15 @@ exports.create = function(req, res) {
var form = new Form(req.body.form);
form.admin = req.user._id;
form.save(function(err) {
form.save(function(err, createdForm) {
if (err) {
return res.status(500).send({
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) ){
readForRender(req, res);
} else {
var newForm = req.form.toJSON();
if (req.userId) {
if(req.form.admin._id+'' === req.userId+''){
return res.json(newForm);
}
if(!req.form){
return res.status(404).send({
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);
}
};
@ -173,9 +176,7 @@ var readForRender = exports.readForRender = function(req, res) {
});
}
delete newForm.lastModified;
delete newForm.__v;
delete newForm.created;
newForm = helpers.removeSensitiveModelData('public_form', newForm);
if(newForm.startPage && !newForm.startPage.showStart){
delete newForm.startPage;
@ -191,15 +192,12 @@ exports.update = function(req, res) {
var form = req.form;
var updatedForm = req.body.form;
if(form.form_fields === undefined){
form.form_fields = [];
}
if(form.analytics === undefined){
if(!form.analytics){
form.analytics = {
visitors: [],
gaCode: ''
}
};
}
if (req.body.changes) {
@ -217,11 +215,6 @@ exports.update = function(req, res) {
delete updatedForm.admin;
}
if(form.analytics === null){
form.analytics.visitors = [];
form.analytics.gaCode = '';
}
//Do this so we can create duplicate fields
var checkForValidId = new RegExp('^[0-9a-fA-F]{24}$');
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)
});
} else {
savedForm = helpers.removeSensitiveModelData('private_form', savedForm);
res.json(savedForm);
}
});
@ -280,6 +274,8 @@ exports.list = function(req, res) {
});
} else {
for(var i=0; i<forms.length; i++){
forms[i] = helpers.removeSensitiveModelData('private_form', forms[i]);
forms[i].numberOfResponses = 0;
if(forms[i].submissions){
forms[i].numberOfResponses = forms[i].submissions.length;
@ -300,6 +296,7 @@ exports.formByID = function(req, res, next, id) {
message: 'Form is invalid'
});
}
Form.findById(id)
.populate('admin')
.exec(function(err, form) {
@ -312,12 +309,7 @@ exports.formByID = function(req, res, next, id) {
}
else {
//Remove sensitive information from User object
var _form = form;
_form.admin.password = null;
_form.admin.salt = null;
_form.provider = null;
req.form = _form;
req.form = helpers.removeSensitiveModelData('private_form', form);
return next();
}
});
@ -345,13 +337,7 @@ exports.formByIDFast = function(req, res, next, id) {
}
else {
//Remove sensitive information from User object
var _form = form;
if(_form.admin){
_form.admin.password = null;
_form.admin.salt = null;
_form.provider = null;
}
req.form = _form;
req.form = helpers.removeSensitiveModelData('public_form', form);
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'),
i18n = require('i18n'),
async = require('async'),
pug = require('pug');
pug = require('pug'),
helpers = require('../helpers.server.controller');
var nev = require('email-verification')(mongoose);
@ -60,7 +61,7 @@ config_nev();
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 emailTemplate = {
@ -83,7 +84,7 @@ exports.validateVerificationToken = function(req, res){
};
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 emailTemplate = {
@ -117,7 +118,7 @@ exports.signup = function(req, res) {
var user = new User(req.body);
// Set language to visitor's language
user.language = req.cookies['userLang'];
user.language = req.cookies.userLang;
// Add missing user fields
user.provider = 'local';
@ -133,7 +134,7 @@ exports.signup = function(req, res) {
// new user created
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 URL = newTempUser[nev.options.URLFieldName];
@ -179,6 +180,8 @@ exports.signin = function(req, res, next) {
}
res.cookie('langCookie', user.language, { maxAge: 90000, httpOnly: true });
user = helpers.removeSensitiveModelData('private_user', user);
return res.json(user);
});
}
@ -190,7 +193,7 @@ exports.signin = function(req, res, next) {
*/
exports.signout = function(req, res) {
if(req.cookies.hasOwnProperty('userLang')){
res.destroyCookie('userLang');
res.clearCookie('userLang');
}
req.logout();
return res.status(200).send('You have successfully logged out.');
@ -198,16 +201,12 @@ exports.signout = function(req, res) {
/* Generate API Key for User */
exports.generateAPIKey = function(req, res) {
if (!req.isAuthenticated()){
return res.status(400).send({
message: 'User is not Authorized'
});
}
User.findById(req.user.id)
.exec( function(err, user) {
if (err) {
return res.status(400).send(err);
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
}
if (!user) {
@ -226,12 +225,8 @@ exports.generateAPIKey = function(req, res) {
}
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.
*/
var _ = require('lodash'),
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();
});
};
var auth = require('../../../config/passport_helpers');
/**
* Require login routing middleware
*/
@ -45,22 +16,3 @@ exports.requiresLogin = function(req, res, 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) {
const fn = pug.compileFile(__dirname + "/../../views/templates/reset-password-email.server.view.pug");
res.locals['url'] = 'http://' + req.headers.host + '/auth/reset/' + token;
const fn = pug.compileFile(__dirname + '/../../views/templates/reset-password-email.server.view.pug');
res.locals.url = 'http://' + req.headers.host + '/auth/reset/' + token;
console.log(res.locals);
var renderedHtml = fn(res.locals);
done(null, renderedHtml, user);
},
@ -98,10 +97,10 @@ exports.forgot = function(req, res) {
};
var userEmail = user.email;
var user = userEmail.split('@')[0];
var emailUsername = userEmail.split('@')[0];
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 tld = domain.split('.')[1];
@ -142,7 +141,7 @@ exports.validateResetToken = function(req, res) {
});
}
if (!user) {
return res.redirect('/#!/password/reset/invalid');
return res.redirect(400, '/#!/password/reset/invalid');
}
res.redirect('/#!/password/reset/' + req.params.token);
@ -187,13 +186,13 @@ exports.reset = function(req, res, next) {
done(null, savedUser);
});
} else {
done('Password reset token is invalid or has expired.', null);
done('invalid_reset_token', null);
}
});
},
function(user, done) {
const fn = pug.compileFile(__dirname + "/../../views/templates/reset-password-confirm-email.server.view.pug");
var renderedHtml = fn(res.locals);
const fn = pug.compileFile(__dirname + '/../../views/templates/reset-password-confirm-email.server.view.pug');
const renderedHtml = fn(res.locals);
done(null, renderedHtml, user);
},
// If valid email, send reset email using service
@ -211,12 +210,18 @@ exports.reset = function(req, res, next) {
}
], function(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
});
}
return res.json({
res.json({
message: 'Successfully changed your password!'
});
});

View File

@ -5,7 +5,8 @@
*/
var _ = require('lodash'),
errorHandler = require('../errors.server.controller.js'),
mongoose = require('mongoose');
mongoose = require('mongoose'),
helpers = require('../helpers.server.controller');
/**
* Update user details
@ -14,10 +15,9 @@ exports.update = function(req, res) {
// Init Variables
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;
if (user) {
// Merge existing user
user = _.extend(user, req.body);
user.updated = Date.now();
@ -32,29 +32,19 @@ exports.update = function(req, res) {
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
*/
exports.getUser = function(req, res) {
var _user = req.user;
delete _user.password;
delete _user.salt;
delete _user.provider;
delete _user.__v;
var user = helpers.removeSensitiveModelData('private_user', req.user);
res.json(req.user || null);
res.end();
return res.json(user);
};

View File

@ -11,25 +11,26 @@ module.exports = {
cc: emailSettings.recipients,
subject: parsedSubject,
html: parsedTemplate
}
};
smtpTransport.sendMail(mailOptions, cb);
},
parseTemplate: function(emailTemplate, emailAttrs, varFormat){
var resolvedTemplate = emailTemplate;
var that = this;
Object.keys(emailAttrs).forEach(function (key) {
resolvedTemplate = replaceTemplateVal(key, emailAttrs[key], resolvedTemplate);
resolvedTemplate = that.replaceTemplateVal(key, emailAttrs[key], resolvedTemplate);
});
return resolvedTemplate;
},
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){
var formFieldDict = {}
var formFieldDict = {};
form_fields.forEach(function(field){
if(field.hasOwnProperty('_id') && field.hasOwnProperty('fieldValue')){
formFieldDict[field._id] = field.fieldValue;
@ -37,4 +38,4 @@ module.exports = {
});
return formFieldDict;
}
}
};

View File

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

View File

@ -73,7 +73,6 @@ var VisitorDataSchema = new Schema({
userAgent: {
type: String
}
});
var formSchemaOptions = {
@ -104,12 +103,17 @@ var FormSchema = new Schema({
visitors: [VisitorDataSchema]
},
form_fields: [FieldSchema],
submissions: [{
form_fields: {
type: [FieldSchema],
default: []
},
submissions: {
type: [{
type: Schema.Types.ObjectId,
ref: 'FormSubmission'
}],
dfeault: []
},
admin: {
type: Schema.Types.ObjectId,
ref: 'User',
@ -197,6 +201,7 @@ var FormSchema = new Schema({
type: Boolean,
default: false
},
isLive: {
type: Boolean,
default: true
@ -262,7 +267,7 @@ FormSchema.virtual('analytics.fields').get(function () {
var visitors = this.analytics.visitors;
var that = this;
if(this.form_fields.length === 0) {
if(!this.form_fields || this.form_fields.length === 0) {
return null;
}
@ -332,26 +337,6 @@ FormSchema.plugin(timeStampPlugin, {
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){
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;
}
delete form_fields[i].validFieldTypes;
delete form_fields[i].disabled;
delete form_fields[i].required;
delete form_fields[i].isSubmission;
delete form_fields[i].title;
delete form_fields[i].fieldOptions;
delete form_fields[i].ratingOptions;
delete form_fields[i].logicJump;
delete form_fields[i].description;
delete form_fields[i].created;
delete form_fields[i].lastModified;
delete form_fields[i].deletePreserved;
delete this.form_fields[i].validFieldTypes;
delete this.form_fields[i].disabled;
delete this.form_fields[i].required;
delete this.form_fields[i].isSubmission;
delete this.form_fields[i].title;
delete this.form_fields[i].fieldOptions;
delete this.form_fields[i].ratingOptions;
delete this.form_fields[i].logicJump;
delete this.form_fields[i].description;
delete this.form_fields[i].created;
delete this.form_fields[i].lastModified;
delete this.form_fields[i].deletePreserved;
}
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
*/
@ -78,8 +64,6 @@ var UserSchema = new Schema({
type: String,
default: 'local'
},
providerData: {},
additionalProvidersData: {},
roles: {
type: [{
type: String,
@ -116,10 +100,6 @@ var UserSchema = new Schema({
}
});
UserSchema.virtual('displayName').get(function () {
return this.firstName + ' ' + this.lastName;
});
UserSchema.plugin(timeStampPlugin, {
createdPath: 'created',
modifiedPath: 'lastModified',

View File

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

View File

@ -12,6 +12,7 @@ module.exports = function(app) {
var users = require('../../app/controllers/users.server.controller');
// 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').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').post(users.resendVerificationEmail);
// Setting up the users password api
app.route('/users/password').post(users.requiresLogin, users.changePassword);
// Setting up the password reset api
app.route('/auth/forgot').post(users.forgot);
app.route('/auth/reset/:token').get(users.validateResetToken);
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/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'),
Form = mongoose.model('Form'),
Field = mongoose.model('Field'),
FormSubmission = mongoose.model('FormSubmission');
FormSubmission = mongoose.model('FormSubmission'),
async = require('async');
/**
* Globals
@ -68,7 +69,6 @@ describe('Form Routes Unit tests', function() {
.send({form: myForm})
.expect(401)
.end(function(FormSaveErr, FormSaveRes) {
console.log(FormSaveRes.text)
// Call the assertion callback
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
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) {
// Set Form 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) {
// Set Form with a invalid title field
myForm.title = '';
@ -165,10 +192,22 @@ describe('Form Routes Unit tests', function() {
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
loginSession.post('/forms')
@ -182,7 +221,7 @@ describe('Form Routes Unit tests', function() {
}
// Update Form title
myForm.title = 'WHY YOU GOTTA BE SO MEAN?';
myForm.title = 'WHY YOU GOTTA BE SO FORMULAIC?';
// Update an existing Form
loginSession.put('/forms/' + FormSaveRes.body._id)
@ -197,13 +236,12 @@ describe('Form Routes Unit tests', function() {
// Set assertions
(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
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();
});
});
});
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
authenticatedSession.post('/forms')
.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){
authenticatedSession.get('/auth/signout')
.expect(200)
.end(function(signoutErr, signoutRes) {
// Handle signout error
if (signoutErr) return done(signoutErr);
if (signoutErr) {
return done(signoutErr);
}
authenticatedSession.destroy();
done();
});

View File

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

View File

@ -21,8 +21,7 @@ var credentials, user;
* Form routes tests
*/
describe('Form Submission Routes Unit tests', function() {
var FormObj, _Submission, submissionSession, _SubmissionBody
var FormObj, _Submission, submissionSession, _SubmissionBody;
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:'link', title:'Your Website', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'},
{fieldType:'number', title:'Your Age'}
]
];
/**
* Unit tests

View File

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

View File

@ -6,24 +6,23 @@ var should = require('should'),
mongoose = require('mongoose'),
User = mongoose.model('User'),
config = require('../../config/config'),
tmpUser = mongoose.model(config.tempUserCollection);
tmpUser = mongoose.model(config.tempUserCollection),
async = require('async');
/**
* Globals
*/
var credentials, _User, activateToken, userSession;
var credentials, _User, userSession;
/**
* Form routes tests
*/
describe('User CRUD tests', function() {
this.timeout(30000);
beforeEach(function() {
before(function() {
// Create user credentials
credentials = {
email: 'test732@test.com',
username: 'test732',
email: 'test099@test.com',
username: 'test099',
password: 'password3223'
};
@ -31,77 +30,424 @@ describe('User CRUD tests', function() {
_User = {
email: credentials.email,
username: credentials.username,
password: credentials.password
password: credentials.password,
firstName: 'John',
lastName: 'Smith'
};
//Initialize Session
userSession = Session(app);
});
it(' > Create, Verify and Activate a User > ', function() {
it('should be able to create a temporary (non-activated) User', function(done) {
describe(' > Create, Verify and Activate a User > ', function() {
this.timeout(10000);
it('should be able to create and activate a User', function(done) {
async.waterfall([
function(callback) {
userSession.post('/auth/signup')
.send(_User)
.expect(200)
.end(function(FormSaveErr) {
// Handle error
should.not.exist(FormSaveErr);
tmpUser.findOne({username: _User.username}, function (err, user) {
should.not.exist(err);
.end(function(err) {
callback(err);
});
},
function(callback) {
tmpUser.findOne({username: _User.username})
.lean()
.exec(function (err, user) {
should.exist(user);
_User.username.should.equal(user.username);
_User.firstName.should.equal(user.firstName);
_User.lastName.should.equal(user.lastName);
activateToken = user.GENERATED_VERIFYING_URL;
callback(err, user.GENERATED_VERIFYING_URL);
});
},
function(activateToken, callback) {
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');
.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(signinErr, signinRes) {
// Handle signin error
if (signinErr) {
return done(signinErr);
}
var user = signinRes.body;
(user.username).should.equal(credentials.username);
.end(function(err, res) {
(res.body.username).should.equal(credentials.username);
callback(err);
});
},
function(callback) {
userSession.get('/auth/signout')
.expect(200)
.end(function(signoutErr, signoutRes) {
// Handle signout error
if (signoutErr) {
return done(signoutErr);
.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);
});
}
(signoutRes.text).should.equal('You have successfully logged out.');
done();
});
});
});
});
], function (err) {
done(err);
});
});
after(function(done){
User.remove().exec(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 () {
tmpUser.remove().exec(function(){
userSession.destroy();

View File

@ -6,7 +6,6 @@ block content
h3.col-md-12.text-center=__('500_HEADER')
div.col-md-4.col-md-offset-4
if process.env.NODE_ENV == 'development' || process.env.NODE_ENV == 'test'
div.col-md-12.text-center(style="padding-bottom: 50px;")
| #{error}
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'];
function containsAnySupportedLanguages(preferredLanguages){
for (var i = 0; i < preferredLanguages.length; i++) {
var currIndex = supportedLanguages.indexOf(preferredLanguages[i]);
var i, currIndex;
for (i = 0; i < preferredLanguages.length; i++) {
currIndex = supportedLanguages.indexOf(preferredLanguages[i]);
if (currIndex > -1) {
return supportedLanguages[currIndex];
}
@ -148,8 +149,6 @@ module.exports = function(db) {
// reassign url
req.url = subdomainPath;
req.userId = user._id;
// Q.E.D.
return next();
});
@ -200,7 +199,7 @@ module.exports = function(db) {
app.use(morgan(logger.getLogFormat(), logger.getMorganOptions()));
// Environment dependent middleware
if (process.env.NODE_ENV === 'development') {
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
// Disable views cache
app.set('view cache', false);
} else if (process.env.NODE_ENV === 'production') {
@ -228,7 +227,6 @@ module.exports = function(db) {
// Setting the app router and static folder
app.use('/static', express.static(path.resolve('./public')));
app.use('/uploads', express.static(path.resolve('./uploads')));
// CookieParser should be above session
app.use(cookieParser());
@ -263,18 +261,20 @@ module.exports = function(db) {
//Visitor Language Detection
app.use(function(req, res, next) {
var acceptLanguage = req.headers['accept-language'];
if(acceptLanguage){
var languages = acceptLanguage.match(/[a-z]{2}(?!-)/g) || [];
var supportedLanguage = containsAnySupportedLanguages(languages);
var languages, supportedLanguage;
if(acceptLanguage){
languages = acceptLanguage.match(/[a-z]{2}(?!-)/g) || [];
supportedLanguage = containsAnySupportedLanguages(languages);
}
if(!req.user && supportedLanguage !== null){
var currLanguage = res.cookie('userLang');
if(currLanguage && currLanguage !== supportedLanguage || !currLanguage){
res.clearCookie('userLang');
res.cookie('userLang', supportedLanguage, { maxAge: 90000, httpOnly: true });
}
} else if(req.user && (!req.cookies.hasOwnProperty('userLang') || req.cookies['userLang'] !== req.user.language) ){
} else if(req.user && (!req.cookies.hasOwnProperty('userLang') || req.cookies.userLang !== req.user.language) ){
res.cookie('userLang', req.user.language, { maxAge: 90000, httpOnly: true });
}
}
@ -290,7 +290,7 @@ module.exports = function(db) {
app.use(function (req, res, next) {
// 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
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
@ -322,6 +322,7 @@ module.exports = function(db) {
// Log it
client.captureError(err);
// Error page
res.status(500).render('500', {
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);
// Return Express server instance

View File

@ -63,7 +63,6 @@ logger.setupFileLogger = function setupFileLogger() {
return false;
}
};
/**
@ -76,7 +75,7 @@ logger.getLogOptions = function getLogOptions() {
var _config = _.clone(config, true);
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');
return false;
}
@ -97,7 +96,6 @@ logger.getLogOptions = function getLogOptions() {
handleExceptions: true,
humanReadableUnhandledException: true
};
};
/**

View File

@ -6,14 +6,24 @@ module.exports.isAuthenticatedOrApiKey = function isAuthenticated(req, res, next
if (req.isAuthenticated()) {
return next();
}
// Try authenticate with API KEY
if (req.headers.apikey || req.query.apikey || req.body.apikey) {
passport.authenticate('localapikey', function (err, user, info) {
if (err)
return res.sendStatus(500);
if(!req.body.apikey && req.headers.apikey){
req.body.apikey = req.headers.apikey;
} 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 || '');
}
req.login(user, function(loginErr) {
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({
'apiKey': apiKey
}, function(err, user) {
if (err)
if (err) {
return done(err);
}
if (!user)
if (!user){
return done(null, false, {
message: 'Unknown API Key'
});
}
return done(null, user);
});

View File

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

View File

@ -204,62 +204,22 @@ module.exports = function(grunt) {
singleRun: true
}
},
protractor: {
options: {
configFile: 'protractor.conf.js',
keepAlive: true,
noColor: false
},
e2e: {
options: {
args: {} // Target-specific arguments
}
}
},
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: {
src: watchFiles.serverTests,
options: {
coverageFolder: 'coverageServer',
mask: '*.test.js',
require: ['server.js']
}
},
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']
reportFormats: ['html','lcovonly']
}
}
},
istanbul_check_coverage: {
default: {
lcovMerge: {
options: {
coverageFolder: 'coverage*', // will check both coverage folders and merge the coverage results
check: {
lines: 80,
statements: 80
}
}
}
emitters: ['event'],
},
src: ['./coverageServer/*.info', './coverageClient/lcov-report/*.info']
},
html2js: {
options: {
@ -287,7 +247,7 @@ module.exports = function(grunt) {
options: {
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'
}
},
@ -323,9 +283,7 @@ module.exports = function(grunt) {
});
// Code coverage tasks.
grunt.registerTask('coveralls', ['env:test','mocha_istanbul:coveralls']);
grunt.registerTask('coverage', ['env:test', 'mocha_istanbul:coverage']);
grunt.registerTask('coverage:client', ['env:test', 'mocha_istanbul:coverageClient']);
grunt.registerTask('coveralls', ['test:client', 'karma:unit', 'mocha_istanbul:coverageServer', 'lcovMerge']);
grunt.registerTask('coverage:server', ['env:test', 'mocha_istanbul:coverageServer']);
// Default task(s).
@ -339,7 +297,7 @@ module.exports = function(grunt) {
grunt.registerTask('secure', ['env:secure', 'lint', 'html2js:main', 'html2js:forms', 'concurrent:default']);
// Lint task(s).
grunt.registerTask('lint', ['jshint', 'csslint', 'i18nlint:client', 'i18nlint:server']);
grunt.registerTask('lint', ['jshint', 'csslint']);
grunt.registerTask('lint:tests', ['jshint:allTests']);
// Build task(s).
@ -349,9 +307,11 @@ module.exports = function(grunt) {
grunt.registerTask('setup', ['execute']);
// 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: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']);
};

View File

@ -29,9 +29,18 @@ module.exports = function(config) {
'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/modules/*/*.js': ['coverage'],
//'public/modules/*/*[!tests]*/*.js': ['coverage'],
'public/form_modules/forms/base/views/*.html': ['ng-html2js'],
'public/modules/*/*.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: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,13 +10,33 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
myform: '='
},
controller: function($scope){
$scope.table = {
masterChecker: false,
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({
method: 'GET',
url: '/forms/'+$scope.myform._id+'/submissions'
@ -36,10 +56,19 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
}
$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({
method: 'GET',
url: '/forms/'+$scope.myform._id+'/visitors'
@ -52,8 +81,23 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
});
};
getSubmissions();
getVisitors();
$scope.handleSubmissionsRefresh();
$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
@ -72,14 +116,48 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
return (totalTime/numSubmissions).toFixed(0);
})();
var updateFields = $interval(getSubmissions, 100000);
var updateFields = $interval(getVisitors, 1000000);
$scope.DeviceStatistics = (function(){
var newStatItem = function(){
return {
visits: 0,
responses: 0,
completion: 0,
average_time: 0,
total_time: 0
};
};
$scope.$on('$destroy', function() {
if (updateFields) {
$interval.cancel($scope.updateFields);
var stats = {
desktop: newStatItem(),
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
@ -109,25 +187,24 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
//Delete selected submissions of Form
$scope.deleteSelectedSubmissions = function(){
$scope.deletionInProgress = true;
var delete_ids = _.chain($scope.table.rows).filter(function(row){
return !!row.selected;
}).pluck('_id').value();
$http({ url: '/forms/'+$scope.myform._id+'/submissions',
return $http({ url: '/forms/'+$scope.myform._id+'/submissions',
method: 'DELETE',
data: {deleted_submissions: delete_ids},
headers: {'Content-Type': 'application/json;charset=utf-8'}
}).success(function(data, status){
$scope.deletionInProgress = true;
//Remove deleted ids from table
var tmpArray = [];
for(var i=0; i<$scope.table.rows.length; i++){
if(!$scope.table.rows[i].selected){
tmpArray.push($scope.table.rows[i]);
}
}
$scope.table.rows = tmpArray;
$scope.table.rows = $scope.table.rows.filter(function(field){
return !field.selected;
});
})
.error(function(err){
$scope.deletionInProgress = true;
console.error(err);
});
};

View File

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

View File

@ -57,22 +57,7 @@
_id: '525a8422f6d0f87f0e407a33'
};
var newFakeModal = function(){
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
//Mock myForm Service
beforeEach(module(function($provide) {
$provide.service('myForm', function($q) {
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
beforeEach(inject(function($uibModal) {
@ -199,7 +205,7 @@
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();
//Set $state transition
@ -214,7 +220,7 @@
$httpBackend.flush();
$state.ensureAllTransitionsHappened();
});
}));
it('$scope.update() should send a PUT request with the id of form', function() {
var controller = createAdminFormController();

View File

@ -86,7 +86,6 @@
});
}));
// 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
// with the same name as the service.
@ -106,25 +105,13 @@
// Initialize the Forms controller.
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() {
var dupSampleForm = sampleFormList[2],
@ -135,12 +122,6 @@
var controller = createListFormsController();
// Set GET response
$httpBackend.expectGET(/^(\/forms)$/).respond(200, sampleFormList);
// Run controller functionality
scope.findAll();
$httpBackend.flush();
// Set GET response
$httpBackend.expect('POST', '/forms').respond(200, dupSampleForm);
// Run controller functionality
@ -164,13 +145,6 @@
var controller = createListFormsController();
// Set GET response
$httpBackend.expectGET(/^(\/forms)$/).respond(200, sampleFormList);
// Run controller functionality
scope.findAll();
$httpBackend.flush();
// Set GET response
$httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, delSampleForm);

View File

@ -2,7 +2,7 @@
(function() {
// Forms Controller Spec
describe('EditSubmissions Directive-Controller Tests', function() {
describe('EditFormSubmissions Directive-Controller Tests', function() {
// Initialize global variables
var el, scope, controller, $httpBackend;
@ -10,13 +10,25 @@
firstName: 'Full',
lastName: 'Name',
email: 'test@test.com',
username: 'test@test.com',
username: 'test1234',
password: 'password',
provider: 'local',
roles: ['user'],
_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 = {
title: 'Form Title',
admin: 'ed873933b1f1dea0ce12fab9',
@ -27,7 +39,18 @@
{fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'}
],
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: [],
startPage: {
@ -61,7 +84,8 @@
],
admin: sampleUser,
form: sampleForm,
timeElapsed: 10.33
timeElapsed: 10.33,
selected: false
},
{
form_fields: [
@ -71,7 +95,8 @@
],
admin: sampleUser,
form: sampleForm,
timeElapsed: 2.33
timeElapsed: 2.33,
selected: false
},
{
form_fields: [
@ -81,7 +106,8 @@
],
admin: sampleUser,
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.
@ -118,10 +144,12 @@
$httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})$/).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})\/submissions$/).respond(200, sampleSubmissions);
$httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})\/visitors$/).respond(200, sampleVisitors);
//Instantiate directive.
var tmp_scope = $rootScope.$new();
tmp_scope.myform = sampleForm;
tmp_scope.myform.submissions = sampleSubmissions;
tmp_scope.user = sampleUser;
//gotacha: Controller and link functions will execute.
@ -141,6 +169,7 @@
it('$scope.toggleAllCheckers should toggle all checkboxes in table', function(){
//Run Controller Logic to Test
scope.table.rows = sampleSubmissions;
scope.table.masterChecker = true;
scope.toggleAllCheckers();
@ -151,6 +180,7 @@
});
it('$scope.isAtLeastOneChecked should return true when at least one checkbox is selected', function(){
scope.table.rows = sampleSubmissions;
scope.table.masterChecker = true;
scope.toggleAllCheckers();
@ -161,17 +191,23 @@
});
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.getSubmissions(function(err){
scope.toggleAllCheckers();
$httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})(\/submissions)$/).respond(200);
//Run Controller Logic to Test
scope.deleteSelectedSubmissions();
$httpBackend.flush();
scope.deleteSelectedSubmissions().then(function(){
expect(scope.table.rows.length).toEqual(0);
});
expect(err).not.toBeDefined();
});
$httpBackend.flush();
});
});
});

View File

@ -62,6 +62,52 @@
beforeEach(module('module-templates'));
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_) {
//Instantiate directive.
var tmp_scope = $rootScope.$new();
@ -97,26 +143,12 @@
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
scope.addNewField(true, 'textfield');
scope.addNewField('textfield');
var expectedFormField = {
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);
expect(scope.editFieldModal.opened).toBeTruthy();
});
it('$scope.deleteField() should DELETE a field to $scope.myform.form_fields', function() {

View File

@ -5,7 +5,6 @@
describe('FieldIcon Directive Tests', function() {
// Initialize global variables
var scope,
FormFields,
faClasses = {
'textfield': 'fa fa-pencil-square-o',
'dropdown': 'fa fa-th-list',
@ -28,9 +27,67 @@
// Load the main application module
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_) {
scope = $rootScope.$new();
FormFields = _FormFields_;
}));
it('should be able render all field-icon types', inject(function($compile) {

View File

@ -5,11 +5,63 @@
describe('Field Directive Tests', function() {
// Initialize global variables
var scope,
FormFields,
$templateCache,
$httpBackend,
$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 = {
firstName: 'Full',
lastName: 'Name',
@ -65,9 +117,15 @@
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_) {
scope = $rootScope.$new();
FormFields = _FormFields_;
// Point global variables to injected services
$httpBackend = _$httpBackend_;
@ -76,6 +134,7 @@
$compile = _$compile_;
}));
it('should be able to render all field types in html', inject(function($rootScope) {
scope.fields = sampleFields;

View File

@ -4,15 +4,73 @@
// Forms Controller Spec
describe('onFinishRender Directive Tests', function() {
// Initialize global variables
var scope,
FormFields;
var scope;
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
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();
FormFields = _FormFields_;
spyOn($rootScope, '$broadcast');
}));

View File

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

View File

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

View File

@ -3,12 +3,15 @@
* Module dependencies.
*/
require('dotenv').config({path: './.env'});
if(!process.env.NODE_ENV){
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;
@ -62,9 +65,6 @@ console.log('--');
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('Database:\t\t\t' + config.db.uri));
if (process.env.NODE_ENV === 'secure') {
console.log(chalk.green('HTTPs:\t\t\t\ton'));
}
console.log('--');
process.on('uncaughtException', function (err) {

7203
yarn.lock Normal file

File diff suppressed because it is too large Load Diff