diff --git a/.eslintrc.js b/.eslintrc.js index f28fcf29..164f675b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -41,7 +41,11 @@ module.exports = { 'one-var': [0, 'never'], 'one-var-declaration-per-line': [2, 'always'], 'padded-blocks': 0, - 'space-before-function-paren': 0, + 'space-before-function-paren': ['error', { + 'anonymous': 'always', + 'named': 'never', + 'asyncArrow': 'always' + }], 'space-in-parens': [2, 'never'], 'spaced-comment': 0, strict: 0, diff --git a/.travis.yml b/.travis.yml index 6d89aaa6..15ccf8dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ addons: - clang - google-chrome-stable before_install: + - npm install webdriver-manager -g && webdriver-manager update - npm install nsp -g # - npm install snyk -g - 'export PATH=$PATH:/usr/lib/chromium-browser/' @@ -44,7 +45,8 @@ after_script: - nsp check # - snyk test - gulp test:coverage - - node_modules/.bin/lcov-result-merger 'coverage/**/lcov.info' | node_modules/coveralls/bin/coveralls.js + - npm install lcov-result-merger@~1.2.0 -g + - lcov-result-merger 'coverage/**/lcov.info' | node_modules/coveralls/bin/coveralls.js notifications: webhooks: urls: diff --git a/config/env/development.js b/config/env/development.js index a326ecd3..bee1ec04 100644 --- a/config/env/development.js +++ b/config/env/development.js @@ -73,25 +73,53 @@ module.exports = { seedDB: { seed: process.env.MONGO_SEED === 'true', options: { - logResults: process.env.MONGO_SEED_LOG_RESULTS !== 'false', - seedUser: { - username: process.env.MONGO_SEED_USER_USERNAME || 'seeduser', - provider: 'local', - email: process.env.MONGO_SEED_USER_EMAIL || 'user@localhost.com', - firstName: 'User', - lastName: 'Local', - displayName: 'User Local', - roles: ['user'] + logResults: process.env.MONGO_SEED_LOG_RESULTS !== 'false' + }, + // Order of collections in configuration will determine order of seeding. + // i.e. given these settings, the User seeds will be complete before + // Article seed is performed. + collections: [{ + model: 'User', + docs: [{ + data: { + username: 'local-admin', + email: 'admin@localhost.com', + firstName: 'Admin', + lastName: 'Local', + roles: ['admin', 'user'] + } + }, { + // Set to true to overwrite this document + // when it already exists in the collection. + // If set to false, or missing, the seed operation + // will skip this document to avoid overwriting it. + overwrite: true, + data: { + username: 'local-user', + email: 'user@localhost.com', + firstName: 'User', + lastName: 'Local', + roles: ['user'] + } + }] + }, { + model: 'Article', + options: { + // Override log results setting at the + // collection level. + logResults: true }, - seedAdmin: { - username: process.env.MONGO_SEED_ADMIN_USERNAME || 'seedadmin', - provider: 'local', - email: process.env.MONGO_SEED_ADMIN_EMAIL || 'admin@localhost.com', - firstName: 'Admin', - lastName: 'Local', - displayName: 'Admin Local', - roles: ['user', 'admin'] - } - } + skip: { + // Skip collection when this query returns results. + // e.g. {}: Only seeds collection when it is empty. + when: {} // Mongoose qualified query + }, + docs: [{ + data: { + title: 'First Article', + content: 'This is a seeded Article for the development environment' + } + }] + }] } }; diff --git a/config/env/local.example.js b/config/env/local.example.js index 7a8641ff..2f4aeabf 100644 --- a/config/env/local.example.js +++ b/config/env/local.example.js @@ -20,10 +20,7 @@ module.exports = { db: { uri: 'mongodb://localhost/local-dev', - options: { - user: '', - pass: '' - } + options: {} }, sessionSecret: process.env.SESSION_SECRET || 'youshouldchangethistosomethingsecret', facebook: { diff --git a/config/env/production.js b/config/env/production.js index 2c27c010..39d1c4b6 100644 --- a/config/env/production.js +++ b/config/env/production.js @@ -15,22 +15,20 @@ module.exports = { db: { uri: process.env.MONGOHQ_URL || process.env.MONGODB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean-dev-v2', options: { - user: '', - pass: '' /** - * Uncomment to enable ssl certificate based authentication to mongodb - * servers. Adjust the settings below for your specific certificate - * setup. - * for connect to a replicaset, rename server:{...} to replset:{...} - server: { - ssl: true, - sslValidate: false, - checkServerIdentity: false, - sslCA: fs.readFileSync('./config/sslcerts/ssl-ca.pem'), - sslCert: fs.readFileSync('./config/sslcerts/ssl-cert.pem'), - sslKey: fs.readFileSync('./config/sslcerts/ssl-key.pem'), - sslPass: '1234' - } + * Uncomment to enable ssl certificate based authentication to mongodb + * servers. Adjust the settings below for your specific certificate + * setup. + * for connect to a replicaset, rename server:{...} to replset:{...} + + ssl: true, + sslValidate: false, + checkServerIdentity: false, + sslCA: fs.readFileSync('./config/sslcerts/ssl-ca.pem'), + sslCert: fs.readFileSync('./config/sslcerts/ssl-cert.pem'), + sslKey: fs.readFileSync('./config/sslcerts/ssl-key.pem'), + sslPass: '1234' + */ }, // Enable mongoose debug mode @@ -93,25 +91,19 @@ module.exports = { seedDB: { seed: process.env.MONGO_SEED === 'true', options: { - logResults: process.env.MONGO_SEED_LOG_RESULTS !== 'false', - seedUser: { - username: process.env.MONGO_SEED_USER_USERNAME || 'seeduser', - provider: 'local', - email: process.env.MONGO_SEED_USER_EMAIL || 'user@localhost.com', - firstName: 'User', - lastName: 'Local', - displayName: 'User Local', - roles: ['user'] - }, - seedAdmin: { - username: process.env.MONGO_SEED_ADMIN_USERNAME || 'seedadmin', - provider: 'local', - email: process.env.MONGO_SEED_ADMIN_EMAIL || 'admin@localhost.com', - firstName: 'Admin', - lastName: 'Local', - displayName: 'Admin Local', - roles: ['user', 'admin'] - } - } + logResults: process.env.MONGO_SEED_LOG_RESULTS !== 'false' + }, + collections: [{ + model: 'User', + docs: [{ + data: { + username: 'local-admin', + email: 'admin@localhost.com', + firstName: 'Admin', + lastName: 'Local', + roles: ['admin', 'user'] + } + }] + }] } }; diff --git a/config/env/test.js b/config/env/test.js index 845be38d..e2c74f01 100644 --- a/config/env/test.js +++ b/config/env/test.js @@ -5,10 +5,7 @@ var defaultEnvConfig = require('./default'); module.exports = { db: { uri: process.env.MONGOHQ_URL || process.env.MONGODB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean-test', - options: { - user: '', - pass: '' - }, + options: {}, // Enable mongoose debug mode debug: process.env.MONGODB_DEBUG || false }, @@ -83,25 +80,39 @@ module.exports = { seedDB: { seed: process.env.MONGO_SEED === 'true', options: { - logResults: process.env.MONGO_SEED_LOG_RESULTS !== 'false', - seedUser: { - username: process.env.MONGO_SEED_USER_USERNAME || 'seeduser', - provider: 'local', - email: process.env.MONGO_SEED_USER_EMAIL || 'user@localhost.com', - firstName: 'User', - lastName: 'Local', - displayName: 'User Local', - roles: ['user'] - }, - seedAdmin: { - username: process.env.MONGO_SEED_ADMIN_USERNAME || 'seedadmin', - provider: 'local', - email: process.env.MONGO_SEED_ADMIN_EMAIL || 'admin@localhost.com', - firstName: 'Admin', - lastName: 'Local', - displayName: 'Admin Local', - roles: ['user', 'admin'] - } - } + // Default to not log results for tests + logResults: process.env.MONGO_SEED_LOG_RESULTS === 'true' + }, + collections: [{ + model: 'User', + docs: [{ + overwrite: true, + data: { + username: 'seedadmin', + email: 'admin@localhost.com', + firstName: 'Admin', + lastName: 'Local', + roles: ['admin', 'user'] + } + }, { + overwrite: true, + data: { + username: 'seeduser', + email: 'user@localhost.com', + firstName: 'User', + lastName: 'Local', + roles: ['user'] + } + }] + }, { + model: 'Article', + docs: [{ + overwrite: true, + data: { + title: 'Test Article', + content: 'Code coverage test article!' + } + }] + }] } }; diff --git a/config/lib/app.js b/config/lib/app.js index 842cad82..60fa5c80 100644 --- a/config/lib/app.js +++ b/config/lib/app.js @@ -4,10 +4,10 @@ * Module dependencies. */ var config = require('../config'), - mongoose = require('./mongoose'), + mongooseService = require('./mongoose'), express = require('./express'), chalk = require('chalk'), - seed = require('./seed'), + seed = require('./mongo-seed'), ircConfig = config.meanTorrentConfig.ircAnnounce; function seedDB() { @@ -17,11 +17,11 @@ function seedDB() { } } -// Initialize Models -mongoose.loadModels(seedDB); - module.exports.init = function init(callback) { - mongoose.connect(function (db) { + mongooseService.connect(function (db) { + // Initialize Models + mongooseService.loadModels(seedDB); + // Initialize express var app = express.init(db); if (callback) callback(app, db, config); diff --git a/config/lib/express.js b/config/lib/express.js index fdb66715..0b0ad532 100644 --- a/config/lib/express.js +++ b/config/lib/express.js @@ -119,7 +119,7 @@ module.exports.initSession = function (app, db) { }, name: config.sessionKey, store: new MongoStore({ - mongooseConnection: db.connection, + db: db, collection: config.sessionCollection }) })); @@ -131,9 +131,9 @@ module.exports.initSession = function (app, db) { /** * Invoke modules server configuration */ -module.exports.initModulesConfiguration = function (app, db) { +module.exports.initModulesConfiguration = function (app) { config.files.server.configs.forEach(function (configPath) { - require(path.resolve(configPath))(app, db); + require(path.resolve(configPath))(app); }); }; diff --git a/config/lib/logger.js b/config/lib/logger.js index d4f3774e..a388a520 100644 --- a/config/lib/logger.js +++ b/config/lib/logger.js @@ -29,7 +29,7 @@ var logger = new winston.Logger({ // Useful for integrating with stream-related mechanism like Morgan's stream // option to log all HTTP requests to a file logger.stream = { - write: function(msg) { + write: function (msg) { logger.info(msg); } }; diff --git a/config/lib/mongo-seed.js b/config/lib/mongo-seed.js new file mode 100644 index 00000000..8762d1e8 --- /dev/null +++ b/config/lib/mongo-seed.js @@ -0,0 +1,153 @@ +'use strict'; + +var _ = require('lodash'), + config = require('../config'), + mongoose = require('mongoose'), + chalk = require('chalk'); + +exports.start = start; + +function start(seedConfig) { + return new Promise(function (resolve, reject) { + seedConfig = seedConfig || {}; + + var options = seedConfig.options || (config.seedDB ? _.clone(config.seedDB.options, true) : {}); + var collections = seedConfig.collections || (config.seedDB ? _.clone(config.seedDB.collections, true) : []); + + if (!collections.length) { + return resolve(); + } + + var seeds = collections + .filter(function (collection) { + return collection.model; + }); + + // Use the reduction pattern to ensure we process seeding in desired order. + seeds.reduce(function (p, item) { + return p.then(function () { + return seed(item, options); + }); + }, Promise.resolve()) // start with resolved promise for initial previous (p) item + .then(onSuccessComplete) + .catch(onError); + + // Local Promise handlers + + function onSuccessComplete() { + if (options.logResults) { + console.log(); + console.log(chalk.bold.green('Database Seeding: Mongo Seed complete!')); + console.log(); + } + + return resolve(); + } + + function onError(err) { + if (options.logResults) { + console.log(); + console.log(chalk.bold.red('Database Seeding: Mongo Seed Failed!')); + console.log(chalk.bold.red('Database Seeding: ' + err)); + console.log(); + } + + return reject(err); + } + + }); +} + +function seed(collection, options) { + // Merge options with collection options + options = _.merge(options || {}, collection.options || {}); + + return new Promise(function (resolve, reject) { + const Model = mongoose.model(collection.model); + const docs = collection.docs; + + var skipWhen = collection.skip ? collection.skip.when : null; + + if (!Model.seed) { + return reject(new Error('Database Seeding: Invalid Model Configuration - ' + collection.model + '.seed() not implemented')); + } + + if (!docs || !docs.length) { + return resolve(); + } + + // First check if we should skip this collection + // based on the collection's "skip.when" option. + // NOTE: If it exists, "skip.when" should be a qualified + // Mongoose query that will be used with Model.find(). + skipCollection() + .then(seedDocuments) + .then(function () { + return resolve(); + }) + .catch(function (err) { + return reject(err); + }); + + function skipCollection() { + return new Promise(function (resolve, reject) { + if (!skipWhen) { + return resolve(false); + } + + Model + .find(skipWhen) + .exec(function (err, results) { + if (err) { + return reject(err); + } + + if (results && results.length) { + return resolve(true); + } + + return resolve(false); + }); + }); + } + + function seedDocuments(skipCollection) { + return new Promise(function (resolve, reject) { + + if (skipCollection) { + return onComplete([{ message: chalk.yellow('Database Seeding: ' + collection.model + ' collection skipped') }]); + } + + var workload = docs + .filter(function (doc) { + return doc.data; + }) + .map(function (doc) { + return Model.seed(doc.data, { overwrite: doc.overwrite }); + }); + + Promise.all(workload) + .then(onComplete) + .catch(onError); + + // Local Closures + + function onComplete(responses) { + if (options.logResults) { + responses.forEach(function (response) { + if (response.message) { + console.log(chalk.magenta(response.message)); + } + }); + } + + return resolve(); + } + + function onError(err) { + return reject(err); + } + }); + } + }); +} diff --git a/config/lib/mongoose.js b/config/lib/mongoose.js index 17fae2c2..86554bda 100644 --- a/config/lib/mongoose.js +++ b/config/lib/mongoose.js @@ -3,7 +3,8 @@ /** * Module dependencies. */ -var config = require('../config'), +var _ = require('lodash'), + config = require('../config'), chalk = require('chalk'), path = require('path'), mongoose = require('mongoose'); @@ -19,30 +20,31 @@ module.exports.loadModels = function (callback) { }; // Initialize Mongoose -module.exports.connect = function (cb) { - var _this = this; - +module.exports.connect = function (callback) { mongoose.Promise = config.db.promise; - var db = mongoose.connect(config.db.uri, config.db.options, function (err) { - // Log Error - if (err) { - console.error(chalk.red('Could not connect to MongoDB!')); - console.log(err); - } else { + var options = _.merge(config.db.options || {}, { useMongoClient: true }); + mongoose + .connect(config.db.uri, options) + .then(function (connection) { // Enabling mongoose debug mode if required mongoose.set('debug', config.db.debug); // Call callback FN - if (cb) cb(db); - } - }); + if (callback) callback(connection.db); + }) + .catch(function (err) { + console.error(chalk.red('Could not connect to MongoDB!')); + console.log(err); + }); + }; module.exports.disconnect = function (cb) { - mongoose.disconnect(function (err) { - console.info(chalk.yellow('Disconnected from MongoDB.')); - cb(err); - }); + mongoose.connection.db + .close(function (err) { + console.info(chalk.yellow('Disconnected from MongoDB.')); + return cb(err); + }); }; diff --git a/config/lib/seed.js b/config/lib/seed.js deleted file mode 100644 index eca41888..00000000 --- a/config/lib/seed.js +++ /dev/null @@ -1,158 +0,0 @@ -'use strict'; - -var _ = require('lodash'), - config = require('../config'), - mongoose = require('mongoose'), - chalk = require('chalk'), - crypto = require('crypto'); - -// global seed options object -var seedOptions = {}; - -function removeUser (user) { - return new Promise(function (resolve, reject) { - var User = mongoose.model('User'); - User.find({ username: user.username }).remove(function (err) { - if (err) { - reject(new Error('Failed to remove local ' + user.username)); - } - resolve(); - }); - }); -} - -function saveUser (user) { - return function() { - return new Promise(function (resolve, reject) { - // Then save the user - user.save(function (err, theuser) { - if (err) { - reject(new Error('Failed to add local ' + user.username)); - } else { - resolve(theuser); - } - }); - }); - }; -} - -function checkUserNotExists (user) { - return new Promise(function (resolve, reject) { - var User = mongoose.model('User'); - User.find({ username: user.username }, function (err, users) { - if (err) { - reject(new Error('Failed to find local account ' + user.username)); - } - - if (users.length === 0) { - resolve(); - } else { - reject(new Error('Failed due to local account already exists: ' + user.username)); - } - }); - }); -} - -function reportSuccess (password) { - return function (user) { - return new Promise(function (resolve, reject) { - if (seedOptions.logResults) { - console.log(chalk.bold.red('Database Seeding:\t\t\tLocal ' + user.username + ' added with password set to ' + password)); - } - resolve(); - }); - }; -} - -// save the specified user with the password provided from the resolved promise -function seedTheUser (user) { - return function (password) { - return new Promise(function (resolve, reject) { - - var User = mongoose.model('User'); - // set the new password - user.password = password; - - if (user.username === seedOptions.seedAdmin.username && process.env.NODE_ENV === 'production') { - checkUserNotExists(user) - .then(saveUser(user)) - .then(reportSuccess(password)) - .then(function () { - resolve(); - }) - .catch(function (err) { - reject(err); - }); - } else { - removeUser(user) - .then(saveUser(user)) - .then(reportSuccess(password)) - .then(function () { - resolve(); - }) - .catch(function (err) { - reject(err); - }); - } - }); - }; -} - -// report the error -function reportError (reject) { - return function (err) { - if (seedOptions.logResults) { - console.log(); - console.log('Database Seeding:\t\t\t' + err); - console.log(); - } - reject(err); - }; -} - -module.exports.start = function start(options) { - // Initialize the default seed options - seedOptions = _.clone(config.seedDB.options, true); - - // Check for provided options - - if (_.has(options, 'logResults')) { - seedOptions.logResults = options.logResults; - } - - if (_.has(options, 'seedUser')) { - seedOptions.seedUser = options.seedUser; - } - - if (_.has(options, 'seedAdmin')) { - seedOptions.seedAdmin = options.seedAdmin; - } - - var User = mongoose.model('User'); - return new Promise(function (resolve, reject) { - - var adminAccount = new User(seedOptions.seedAdmin); - var userAccount = new User(seedOptions.seedUser); - - // If production only seed admin if it does not exist - if (process.env.NODE_ENV === 'production') { - User.generateRandomPassphrase() - .then(seedTheUser(adminAccount)) - .then(function () { - resolve(); - }) - .catch(reportError(reject)); - } else { - // Add both Admin and User account - - User.generateRandomPassphrase() - .then(seedTheUser(userAccount)) - .then(User.generateRandomPassphrase) - .then(seedTheUser(adminAccount)) - .then(function () { - resolve(); - }) - .catch(reportError(reject)); - } - }); -}; diff --git a/config/lib/socket.io.js b/config/lib/socket.io.js index f120f147..64bc1112 100644 --- a/config/lib/socket.io.js +++ b/config/lib/socket.io.js @@ -74,7 +74,7 @@ module.exports = function (app, db) { // Create a MongoDB storage object var mongoStore = new MongoStore({ - mongooseConnection: db.connection, + db: db, collection: config.sessionCollection }); diff --git a/gulpfile.js b/gulpfile.js index a9320c50..9b368682 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -109,11 +109,10 @@ gulp.task('watch:server:run-tests', function () { if (filePath === path.resolve(file.path)) { changedTestFiles.push(f); + plugins.refresh.changed(f); } }); }); - - plugins.refresh.changed(); }); }); @@ -153,7 +152,7 @@ gulp.task('uglify', function () { return gulp.src(assets) .pipe(plugins.ngAnnotate()) .pipe(plugins.uglify({ - mangle: false + mangle: true })) .pipe(plugins.concat('application.min.js')) .pipe(plugins.rev()) @@ -304,15 +303,15 @@ gulp.task('templatecache', function () { // Mocha tests task gulp.task('mocha', function (done) { - // Open mongoose connections - var mongoose = require('./config/lib/mongoose.js'); + var mongooseService = require('./config/lib/mongoose'); var testSuites = changedTestFiles.length ? changedTestFiles : testAssets.tests.server; var error; // Connect mongoose - mongoose.connect(function () { - mongoose.loadModels(); - // Run the tests + mongooseService.connect(function (db) { + // Load mongoose models + mongooseService.loadModels(); + gulp.src(testSuites) .pipe(plugins.mocha({ reporter: 'spec', @@ -323,9 +322,13 @@ gulp.task('mocha', function (done) { error = err; }) .on('end', function () { - // When the tests are done, disconnect mongoose and pass the error state back to gulp - mongoose.disconnect(function () { - done(error); + mongooseService.disconnect(function (err) { + if (err) { + console.log('Error disconnecting from database'); + console.log(err); + } + + return done(error); }); }); }); @@ -360,7 +363,7 @@ gulp.task('karma', function (done) { }); // Run karma with coverage options set and write report -gulp.task('karma:coverage', function(done) { +gulp.task('karma:coverage', function (done) { new KarmaServer({ configFile: __dirname + '/karma.conf.js', preprocessors: { @@ -389,20 +392,54 @@ gulp.task('karma:coverage', function(done) { // Drops the MongoDB database, used in e2e testing gulp.task('dropdb', function (done) { // Use mongoose configuration - var mongoose = require('./config/lib/mongoose.js'); + var mongooseService = require('./config/lib/mongoose'); - mongoose.connect(function (db) { - db.connection.db.dropDatabase(function (err) { + mongooseService.connect(function (db) { + db.dropDatabase(function (err) { if (err) { console.error(err); } else { - console.log('Successfully dropped db: ', db.connection.db.databaseName); + console.log('Successfully dropped db: ', db.databaseName); } - db.connection.db.close(done); + + mongooseService.disconnect(done); }); }); }); +// Seed Mongo database based on configuration +gulp.task('mongo-seed', function (done) { + var db = require('./config/lib/mongoose'); + var seed = require('./config/lib/mongo-seed'); + + // Open mongoose database connection + db.connect(function () { + db.loadModels(); + + seed + .start({ + options: { + logResults: true + } + }) + .then(function () { + // Disconnect and finish task + db.disconnect(done); + }) + .catch(function (err) { + db.disconnect(function (disconnectError) { + if (disconnectError) { + console.log('Error disconnecting from the database, but was preceded by a Mongo Seed error.'); + } + + // Finish task with error + done(err); + }); + }); + }); + +}); + // Downloads the selenium webdriver if protractor version is compatible gulp.task('webdriver_update', webdriver_update); @@ -417,12 +454,12 @@ gulp.task('protractor', ['webdriver_update'], function () { .pipe(protractor({ configFile: 'protractor.conf.js' })) - .on('end', function() { + .on('end', function () { console.log('E2E Testing complete'); // exit with success. process.exit(0); }) - .on('error', function(err) { + .on('error', function (err) { console.error('E2E Tests failed:'); console.error(err); process.exit(1); @@ -475,3 +512,17 @@ gulp.task('default', function (done) { gulp.task('prod', function (done) { runSequence(['copyLocalEnvConfig', 'makeUploadsDir', 'templatecache'], 'build', 'env:prod', 'lint', ['nodemon-nodebug', 'watch'], done); }); + +// Run Mongo Seed with default environment config +gulp.task('seed', function (done) { + runSequence('env:dev', 'mongo-seed', done); +}); + +// Run Mongo Seed with production environment config +gulp.task('seed:prod', function (done) { + runSequence('env:prod', 'mongo-seed', done); +}); + +gulp.task('seed:test', function (done) { + runSequence('env:test', 'mongo-seed', done); +}); diff --git a/modules/articles/client/config/articles-admin.client.routes.js b/modules/articles/client/config/articles-admin.client.routes.js index b493d0e0..877aeadb 100644 --- a/modules/articles/client/config/articles-admin.client.routes.js +++ b/modules/articles/client/config/articles-admin.client.routes.js @@ -41,7 +41,8 @@ controller: 'ArticlesAdminController', controllerAs: 'vm', data: { - roles: ['admin'] + roles: ['admin'], + pageTitle: '{{ articleResolve.title }}' }, resolve: { articleResolve: getArticle diff --git a/modules/articles/client/config/articles.client.routes.js b/modules/articles/client/config/articles.client.routes.js index 4a94fa2e..2638234d 100644 --- a/modules/articles/client/config/articles.client.routes.js +++ b/modules/articles/client/config/articles.client.routes.js @@ -18,10 +18,7 @@ url: '', templateUrl: '/modules/articles/client/views/list-articles.client.view.html', controller: 'ArticlesListController', - controllerAs: 'vm', - data: { - pageTitle: 'Articles List' - } + controllerAs: 'vm' }) .state('articles.view', { url: '/:articleId', @@ -32,7 +29,7 @@ articleResolve: getArticle }, data: { - pageTitle: 'Article {{ articleResolve.title }}' + pageTitle: '{{ articleResolve.title }}' } }); } diff --git a/modules/articles/client/controllers/admin/article.client.controller.js b/modules/articles/client/controllers/admin/article.client.controller.js index 97f730d3..6fd3438e 100644 --- a/modules/articles/client/controllers/admin/article.client.controller.js +++ b/modules/articles/client/controllers/admin/article.client.controller.js @@ -1,4 +1,4 @@ -(function () { +(function () { 'use strict'; angular @@ -19,7 +19,7 @@ // Remove existing Article function remove() { if ($window.confirm('Are you sure you want to delete?')) { - vm.article.$remove(function() { + vm.article.$remove(function () { $state.go('admin.articles.list'); Notification.success({ message: ' Article deleted successfully!' }); }); diff --git a/modules/articles/server/config/articles.server.config.js b/modules/articles/server/config/articles.server.config.js index 629c3606..1e5355ca 100644 --- a/modules/articles/server/config/articles.server.config.js +++ b/modules/articles/server/config/articles.server.config.js @@ -9,6 +9,6 @@ var path = require('path'), /** * Module init function. */ -module.exports = function (app, db) { +module.exports = function (app) { }; diff --git a/modules/articles/server/models/article.server.model.js b/modules/articles/server/models/article.server.model.js index 1610a9c0..5bc87394 100644 --- a/modules/articles/server/models/article.server.model.js +++ b/modules/articles/server/models/article.server.model.js @@ -4,7 +4,10 @@ * Module dependencies */ var mongoose = require('mongoose'), - Schema = mongoose.Schema; + Schema = mongoose.Schema, + path = require('path'), + config = require(path.resolve('./config/config')), + chalk = require('chalk'); /** * Article Schema @@ -35,6 +38,105 @@ var ArticleSchema = new Schema({ } }); -ArticleSchema.index({user: -1, created: -1}); +ArticleSchema.statics.seed = seed; mongoose.model('Article', ArticleSchema); + +/** +* Seeds the User collection with document (Article) +* and provided options. +*/ +function seed(doc, options) { + var Article = mongoose.model('Article'); + + return new Promise(function (resolve, reject) { + + skipDocument() + .then(findAdminUser) + .then(add) + .then(function (response) { + return resolve(response); + }) + .catch(function (err) { + return reject(err); + }); + + function findAdminUser(skip) { + var User = mongoose.model('User'); + + return new Promise(function (resolve, reject) { + if (skip) { + return resolve(true); + } + + User + .findOne({ + roles: { $in: ['admin'] } + }) + .exec(function (err, admin) { + if (err) { + return reject(err); + } + + doc.user = admin; + + return resolve(); + }); + }); + } + + function skipDocument() { + return new Promise(function (resolve, reject) { + Article + .findOne({ + title: doc.title + }) + .exec(function (err, existing) { + if (err) { + return reject(err); + } + + if (!existing) { + return resolve(false); + } + + if (existing && !options.overwrite) { + return resolve(true); + } + + // Remove Article (overwrite) + + existing.remove(function (err) { + if (err) { + return reject(err); + } + + return resolve(false); + }); + }); + }); + } + + function add(skip) { + return new Promise(function (resolve, reject) { + if (skip) { + return resolve({ + message: chalk.yellow('Database Seeding: Article\t' + doc.title + ' skipped') + }); + } + + var article = new Article(doc); + + article.save(function (err) { + if (err) { + return reject(err); + } + + return resolve({ + message: 'Database Seeding: Article\t' + article.title + ' added' + }); + }); + }); + } + }); +} diff --git a/modules/articles/tests/server/admin.article.server.routes.tests.js b/modules/articles/tests/server/admin.article.server.routes.tests.js index ea9b9e14..ba080eb2 100644 --- a/modules/articles/tests/server/admin.article.server.routes.tests.js +++ b/modules/articles/tests/server/admin.article.server.routes.tests.js @@ -23,7 +23,7 @@ var app, describe('Article Admin CRUD tests', function () { before(function (done) { // Get application - app = express.init(mongoose); + app = express.init(mongoose.connection.db); agent = request.agent(app); done(); diff --git a/modules/articles/tests/server/article.server.routes.tests.js b/modules/articles/tests/server/article.server.routes.tests.js index 0c23a521..6bc05235 100644 --- a/modules/articles/tests/server/article.server.routes.tests.js +++ b/modules/articles/tests/server/article.server.routes.tests.js @@ -24,7 +24,7 @@ describe('Article CRUD tests', function () { before(function (done) { // Get application - app = express.init(mongoose); + app = express.init(mongoose.connection.db); agent = request.agent(app); done(); @@ -119,7 +119,7 @@ describe('Article CRUD tests', function () { // Save the article articleObj.save(function () { // Request articles - request(app).get('/api/articles') + agent.get('/api/articles') .end(function (req, res) { // Set assertion res.body.should.be.instanceof(Array).and.have.lengthOf(1); @@ -137,7 +137,7 @@ describe('Article CRUD tests', function () { // Save the article articleObj.save(function () { - request(app).get('/api/articles/' + articleObj._id) + agent.get('/api/articles/' + articleObj._id) .end(function (req, res) { // Set assertion res.body.should.be.instanceof(Object).and.have.property('title', article.title); @@ -150,7 +150,7 @@ describe('Article CRUD tests', function () { it('should return proper error for single article with an invalid Id, if not signed in', function (done) { // test is not a valid mongoose Id - request(app).get('/api/articles/test') + agent.get('/api/articles/test') .end(function (req, res) { // Set assertion res.body.should.be.instanceof(Object).and.have.property('message', 'Article is invalid'); @@ -162,7 +162,7 @@ describe('Article CRUD tests', function () { it('should return proper error for single article which doesnt exist, if not signed in', function (done) { // This is a valid mongoose Id but a non-existent article - request(app).get('/api/articles/559e9cd815f80b4c256a8f41') + agent.get('/api/articles/559e9cd815f80b4c256a8f41') .end(function (req, res) { // Set assertion res.body.should.be.instanceof(Object).and.have.property('message', 'No article with that identifier has been found'); @@ -202,7 +202,7 @@ describe('Article CRUD tests', function () { // Save the article articleObj.save(function () { // Try deleting article - request(app).delete('/api/articles/' + articleObj._id) + agent.delete('/api/articles/' + articleObj._id) .expect(403) .end(function (articleDeleteErr, articleDeleteRes) { // Set message assertion @@ -312,7 +312,7 @@ describe('Article CRUD tests', function () { if (err) { return done(err); } - request(app).get('/api/articles/' + articleObj._id) + agent.get('/api/articles/' + articleObj._id) .end(function (req, res) { // Set assertion res.body.should.be.instanceof(Object).and.have.property('title', article.title); diff --git a/modules/chat/client/config/chat.client.routes.js b/modules/chat/client/config/chat.client.routes.js index 64ec7245..d8b745ff 100644 --- a/modules/chat/client/config/chat.client.routes.js +++ b/modules/chat/client/config/chat.client.routes.js @@ -15,8 +15,7 @@ controller: 'ChatController', controllerAs: 'vm', data: { - roles: ['user', 'oper', 'admin'], - pageTitle: 'Chat' + roles: ['user', 'oper', 'admin'] } }); } diff --git a/modules/core/client/config/core.client.routes.js b/modules/core/client/config/core.client.routes.js index c00f8807..69593f05 100644 --- a/modules/core/client/config/core.client.routes.js +++ b/modules/core/client/config/core.client.routes.js @@ -39,13 +39,12 @@ controller: 'ErrorController', controllerAs: 'vm', params: { - message: function($stateParams) { + message: function ($stateParams) { return $stateParams.message; } }, data: { - ignoreState: true, - pageTitle: 'Not Found' + ignoreState: true } }) .state('bad-request', { @@ -54,21 +53,19 @@ controller: 'ErrorController', controllerAs: 'vm', params: { - message: function($stateParams) { + message: function ($stateParams) { return $stateParams.message; } }, data: { - ignoreState: true, - pageTitle: 'Bad Request' + ignoreState: true } }) .state('forbidden', { url: '/forbidden', templateUrl: '/modules/core/client/views/403.client.view.html', data: { - ignoreState: true, - pageTitle: 'Forbidden' + ignoreState: true } }); } diff --git a/modules/core/client/directives/page-title.client.directive.js b/modules/core/client/directives/page-title.client.directive.js index 0ccf7fda..6e27f016 100644 --- a/modules/core/client/directives/page-title.client.directive.js +++ b/modules/core/client/directives/page-title.client.directive.js @@ -19,13 +19,17 @@ function listener(event, toState) { var applicationCoreTitle = 'CHD.im', - separeteBy = ' - '; + separator = ' - ', + stateTitle = applicationCoreTitle + separator; + + toState.name.split('.').forEach(function (value, index) { + stateTitle = stateTitle + value.charAt(0).toUpperCase() + value.slice(1) + separator; + }); if (toState.data && toState.data.pageTitle) { - var stateTitle = $interpolate(toState.data.pageTitle)($state.$current.locals.globals); - element.html(applicationCoreTitle + separeteBy + $translate.instant(stateTitle)); - } else { - element.html(applicationCoreTitle); + stateTitle = $interpolate(stateTitle + $translate.instant(toState.data.pageTitle) + separator)(($state.$current.locals.globals)); } + stateTitle = stateTitle.slice(0, 0 - separator.length); + element.text(stateTitle); } } } diff --git a/modules/core/client/services/menu.client.service.js b/modules/core/client/services/menu.client.service.js index 29f8c146..dbcf80bd 100644 --- a/modules/core/client/services/menu.client.service.js +++ b/modules/core/client/services/menu.client.service.js @@ -43,11 +43,11 @@ // Add menu item object function addMenuItem(menuId, options) { - options = options || {}; - // Validate that the menu exists service.validateMenuExistence(menuId); + options = options || {}; + // Push new menu item service.menus[menuId].items.push({ title: options.title || '', @@ -64,11 +64,9 @@ // Add submenu items if (options.items) { - for (var i in options.items) { - if (options.items.hasOwnProperty(i)) { - service.addSubMenuItem(menuId, options.state, options.items[i]); - } - } + options.items.forEach(function (subMenuItem) { + service.addSubMenuItem(menuId, options.state, subMenuItem); + }); } // Return the menu object @@ -83,21 +81,20 @@ service.validateMenuExistence(menuId); // Search for menu item - for (var itemIndex in service.menus[menuId].items) { - if (service.menus[menuId].items[itemIndex].state === parentItemState) { - // Push new submenu item - service.menus[menuId].items[itemIndex].items.push({ - title: options.title || '', - state: options.state || '', - params: options.params || {}, - roles: ((options.roles === null || typeof options.roles === 'undefined') ? service.menus[menuId].items[itemIndex].roles : options.roles), - position: options.position || 0, - shouldRender: shouldRender, - target: options.target || undefined, - divider: options.divider || false - }); - } - } + service.menus[menuId].items.filter(function (item) { + return item.state === parentItemState; + }).forEach(function (item) { + item.items.push({ + title: options.title || '', + state: options.state || '', + params: options.params || {}, + roles: ((options.roles === null || typeof options.roles === 'undefined') ? item.roles : options.roles), + position: options.position || 0, + shouldRender: shouldRender, + target: options.target || undefined, + divider: options.divider || false + }); + }); // Return the menu object return service.menus[menuId]; @@ -117,23 +114,17 @@ shouldRender = function (user) { if (this.roles.indexOf('*') !== -1) { return true; - } else { - if (!user) { - return false; - } - - for (var userRoleIndex in user.roles) { - if (user.roles.hasOwnProperty(userRoleIndex)) { - for (var roleIndex in this.roles) { - if (this.roles.hasOwnProperty(roleIndex) && this.roles[roleIndex] === user.roles[userRoleIndex]) { - return true; - } - } - } - } } - return false; + if (!user) { + return false; + } + + var matchingRoles = user.roles.filter(function (userRole) { + return this.roles.indexOf(userRole) !== -1; + }, this); + + return matchingRoles.length > 0; }; // Adding the topbar menu @@ -155,48 +146,40 @@ // Validate that the menu exists service.validateMenuExistence(menuId); - // Search for menu item to remove - for (var itemIndex in service.menus[menuId].items) { - if (service.menus[menuId].items.hasOwnProperty(itemIndex) && service.menus[menuId].items[itemIndex].state === menuItemState) { - service.menus[menuId].items.splice(itemIndex, 1); - } - } + // Filter out menu items that do not match the current menu item state. + service.menus[menuId].items = service.menus[menuId].items.filter(function (item) { + return item.state !== menuItemState; + }); // Return the menu object return service.menus[menuId]; } // Remove existing menu object by menu id - function removeSubMenuItem(menuId, submenuItemState) { + function removeSubMenuItem(menuId, subMenuItemState) { // Validate that the menu exists service.validateMenuExistence(menuId); - // Search for menu item to remove - for (var itemIndex in service.menus[menuId].items) { - if (this.menus[menuId].items.hasOwnProperty(itemIndex)) { - for (var subitemIndex in service.menus[menuId].items[itemIndex].items) { - if (this.menus[menuId].items[itemIndex].items.hasOwnProperty(subitemIndex) && service.menus[menuId].items[itemIndex].items[subitemIndex].state === submenuItemState) { - service.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1); - } - } - } - } + // Filter out sub-menu items that do not match the current subMenuItemState + service.menus[menuId].items.forEach(function (parentMenuItem) { + parentMenuItem.items = parentMenuItem.items.filter(function (subMenuItem) { + return subMenuItem.state !== subMenuItemState; + }); + }); // Return the menu object return service.menus[menuId]; } - // Validate menu existance + // Validate menu existence function validateMenuExistence(menuId) { - if (menuId && menuId.length) { - if (service.menus[menuId]) { - return true; - } else { - throw new Error('Menu does not exist'); - } - } else { + if (!(menuId && menuId.length)) { throw new Error('MenuId was not provided'); } + if (!service.menus[menuId]) { + throw new Error('Menu does not exist'); + } + return true; } } }()); diff --git a/modules/core/tests/client/interceptors/auth-interceptor.client.tests.js b/modules/core/tests/client/interceptors/auth-interceptor.client.tests.js index 0d877b61..ba6ae0a3 100644 --- a/modules/core/tests/client/interceptors/auth-interceptor.client.tests.js +++ b/modules/core/tests/client/interceptors/auth-interceptor.client.tests.js @@ -1,7 +1,7 @@ 'use strict'; -(function() { - describe('authInterceptor', function() { +(function () { + describe('authInterceptor', function () { // Initialize global variables var authInterceptor, $q, @@ -13,11 +13,11 @@ beforeEach(module(ApplicationConfiguration.applicationModuleName)); // Load httpProvider - beforeEach(module(function($httpProvider) { + beforeEach(module(function ($httpProvider) { httpProvider = $httpProvider; })); - beforeEach(inject(function(_authInterceptor_, _$q_, _$state_, _Authentication_) { + beforeEach(inject(function (_authInterceptor_, _$q_, _$state_, _Authentication_) { authInterceptor = _authInterceptor_; $q = _$q_; $state = _$state_; @@ -26,19 +26,19 @@ spyOn($state, 'transitionTo'); })); - it('Auth Interceptor should be object', function() { + it('Auth Interceptor should be object', function () { expect(typeof authInterceptor).toEqual('object'); }); - it('Auth Interceptor should contain responseError function', function() { + it('Auth Interceptor should contain responseError function', function () { expect(typeof authInterceptor.responseError).toEqual('function'); }); - it('httpProvider Interceptor should have authInterceptor', function() { + it('httpProvider Interceptor should have authInterceptor', function () { expect(httpProvider.interceptors).toContain('authInterceptor'); }); - describe('Forbidden Interceptor', function() { + describe('Forbidden Interceptor', function () { it('should redirect to forbidden route', function () { var response = { status: 403, @@ -50,7 +50,7 @@ }); }); - describe('Authorization Interceptor', function() { + describe('Authorization Interceptor', function () { it('should redirect to signIn page for unauthorized access', function () { var response = { status: 401, @@ -63,9 +63,9 @@ }); }); - describe('Unresponsive Interceptor', function() { + describe('Unresponsive Interceptor', function () { var Notification; - beforeEach(inject(function(_Notification_) { + beforeEach(inject(function (_Notification_) { Notification = _Notification_; spyOn(Notification, 'error'); })); diff --git a/modules/core/tests/client/menus.client.service.tests.js b/modules/core/tests/client/menus.client.service.tests.js index 11773539..4e253ebc 100644 --- a/modules/core/tests/client/menus.client.service.tests.js +++ b/modules/core/tests/client/menus.client.service.tests.js @@ -1,7 +1,7 @@ 'use strict'; -(function() { - describe('Menus', function() { +(function () { + describe('Menus', function () { // Initialize global variables var scope, menuService; @@ -9,78 +9,78 @@ // Load the main application module beforeEach(module(ApplicationConfiguration.applicationModuleName)); - beforeEach(inject(function(_menuService_) { + beforeEach(inject(function (_menuService_) { menuService = _menuService_; })); - it('should have topbar added', function() { + it('should have topbar added', function () { expect(menuService.menus.topbar).toBeDefined(); }); - it('should have default roles to user and admin', function() { + it('should have default roles to user and admin', function () { expect(menuService.defaultRoles).toEqual(['user', 'admin']); }); - describe('addMenu', function() { - describe('with no options', function() { + describe('addMenu', function () { + describe('with no options', function () { var menuId = 'menu1', menu; - beforeEach(function() { + beforeEach(function () { menu = menuService.addMenu(menuId); }); - it('should return menu object', function() { + it('should return menu object', function () { expect(menu).toBeDefined(); }); - it('should default roles', function() { + it('should default roles', function () { expect(menu.roles).toEqual(menuService.defaultRoles); }); - it('should have empty items', function() { + it('should have empty items', function () { expect(menu.items).toEqual([]); }); - it('should set shouldRender to shouldRender function handle', function() { + it('should set shouldRender to shouldRender function handle', function () { expect(menu.shouldRender()).toBeFalsy(); }); }); - describe('with options', function() { + describe('with options', function () { var menu, options = { roles: ['a', 'b', 'c'], items: ['d', 'e', 'f'] }; - beforeEach(function() { + beforeEach(function () { menu = menuService.addMenu('menu1', options); }); - it('should set items to options.items list', function() { + it('should set items to options.items list', function () { expect(menu.items).toBe(options.items); }); - it('should set roles to options.roles list', function() { + it('should set roles to options.roles list', function () { expect(menu.roles).toBe(options.roles); }); }); }); - describe('shouldRender', function() { + describe('shouldRender', function () { var menuOptions = { roles: ['*', 'menurole'] }, menu; - beforeEach(function() { + beforeEach(function () { menu = menuService.addMenu('menu1', menuOptions); }); - describe('when logged out', function() { - it('should render if menu is public', function() { + describe('when logged out', function () { + it('should render if menu is public', function () { expect(menu.shouldRender()).toBeTruthy(); }); - it('should not render if menu is private', function() { + it('should not render if menu is private', function () { menu = menuService.addMenu('menu1', { isPublic: false }); @@ -88,28 +88,28 @@ }); }); - describe('when logged in', function() { + describe('when logged in', function () { var user = { roles: ['1', 'menurole', '2'] }; - describe('menu with * role', function() { - it('should render', function() { + describe('menu with * role', function () { + it('should render', function () { expect(menu.shouldRender(user)).toBeTruthy(); }); }); - describe('menu without * role', function() { - beforeEach(function() { + describe('menu without * role', function () { + beforeEach(function () { menu = menuService.addMenu('menu1', { roles: ['b', 'menurole', 'c'] }); }); - it('should render if user has same role as menu', function() { + it('should render if user has same role as menu', function () { expect(menu.shouldRender(user)).toBeTruthy(); }); - it('should not render if user has different roles', function() { + it('should not render if user has different roles', function () { user = { roles: ['1', '2', '3'] }; @@ -119,54 +119,54 @@ }); }); - describe('validateMenuExistence', function() { - describe('when menuId not provided', function() { - it('should throw menuId error', function() { + describe('validateMenuExistence', function () { + describe('when menuId not provided', function () { + it('should throw menuId error', function () { expect(menuService.validateMenuExistence).toThrowError('MenuId was not provided'); }); }); - describe('when menu does not exist', function() { - it('should throw no menu error', function() { - var target = function() { + describe('when menu does not exist', function () { + it('should throw no menu error', function () { + var target = function () { menuService.validateMenuExistence('noMenuId'); }; expect(target).toThrowError('Menu does not exist'); }); }); - describe('when menu exists', function() { + describe('when menu exists', function () { var menuId = 'menuId'; - beforeEach(function() { + beforeEach(function () { menuService.menus[menuId] = {}; }); - it('should return truthy', function() { + it('should return truthy', function () { expect(menuService.validateMenuExistence(menuId)).toBeTruthy(); }); }); }); - describe('removeMenu', function() { + describe('removeMenu', function () { var menu = { id: 'menuId' }; - beforeEach(function() { + beforeEach(function () { menuService.menus[menu.id] = menu; menuService.validateMenuExistence = jasmine.createSpy(); menuService.removeMenu(menu.id); }); - it('should remove existing menu from menus', function() { + it('should remove existing menu from menus', function () { expect(menuService.menus).not.toContain(menu.id); }); - it('validates menu existance before removing', function() { + it('validates menu existance before removing', function () { expect(menuService.validateMenuExistence).toHaveBeenCalledWith(menu.id); }); }); - describe('addMenuItem', function() { + describe('addMenuItem', function () { var menuId = 'menu1', subMenuItem1 = { title: 'sub1' @@ -187,7 +187,7 @@ menu, menuItem; - beforeEach(function() { + beforeEach(function () { menuService.validateMenuExistence = jasmine.createSpy(); menuService.addSubMenuItem = jasmine.createSpy(); menuService.addMenu(menuId, { @@ -197,84 +197,84 @@ menuItem = menu.items[0]; }); - it('should validate menu existance', function() { + it('should validate menu existance', function () { expect(menuService.validateMenuExistence).toHaveBeenCalledWith(menuId); }); - it('should return the menu', function() { + it('should return the menu', function () { expect(menu).toBeDefined(); }); - it('should set menu item shouldRender function', function() { + it('should set menu item shouldRender function', function () { expect(menuItem.shouldRender).toBeDefined(); }); - describe('with options set', function() { - it('should add menu item to menu', function() { + describe('with options set', function () { + it('should add menu item to menu', function () { expect(menu.items.length).toBe(1); }); - it('should set menu item title to options title', function() { + it('should set menu item title to options title', function () { expect(menuItem.title).toBe(menuItemOptions.title); }); - it('should set menu item state to options state', function() { + it('should set menu item state to options state', function () { expect(menuItem.state).toBe(menuItemOptions.state); }); - it('should set menu item type to options type', function() { + it('should set menu item type to options type', function () { expect(menuItem.type).toBe(menuItemOptions.type); }); - it('should set menu item class to options class', function() { + it('should set menu item class to options class', function () { expect(menuItem.class).toBe(menuItemOptions.class); }); - it('should set menu item position to options position', function() { + it('should set menu item position to options position', function () { expect(menuItem.position).toBe(menuItemOptions.position); }); - it('should call addSubMenuItem for each item in options', function() { + it('should call addSubMenuItem for each item in options', function () { expect(menuService.addSubMenuItem).toHaveBeenCalledWith(menuId, menuItemOptions.state, subMenuItem1); expect(menuService.addSubMenuItem).toHaveBeenCalledWith(menuId, menuItemOptions.state, subMenuItem2); }); }); - describe('without options set', function() { - beforeEach(function() { + describe('without options set', function () { + beforeEach(function () { menu = menuService.addMenuItem(menuId); menuItem = menu.items[1]; }); - it('should set menu item type to item', function() { + it('should set menu item type to item', function () { expect(menuItem.type).toBe('item'); }); - it('should set menu item title to empty', function() { + it('should set menu item title to empty', function () { expect(menuItem.title).toBe(''); }); - it('should set menu item isPublic to false', function() { + it('should set menu item isPublic to false', function () { expect(menuItem.isPublic).toBeFalsy(); }); - it('should set menu item roles to default roles', function() { + it('should set menu item roles to default roles', function () { expect(menuItem.roles).toEqual(menuService.defaultRoles); }); - it('should set menu item position to 0', function() { + it('should set menu item position to 0', function () { expect(menuItem.position).toBe(0); }); }); }); - describe('removeMenuItem', function() { + describe('removeMenuItem', function () { var menuId = 'menuId', menuItemState = 'menu.state1', menuItemState2 = 'menu.state2', menu; - beforeEach(function() { + beforeEach(function () { menuService.addMenu(menuId); menuService.addMenuItem(menuId, { state: menuItemState }); menuService.addMenuItem(menuId, { state: menuItemState2 }); @@ -282,21 +282,21 @@ menu = menuService.removeMenuItem(menuId, menuItemState); }); - it('should return menu object', function() { + it('should return menu object', function () { expect(menu).not.toBeNull(); }); - it('should validate menu existance', function() { + it('should validate menu existance', function () { expect(menuService.validateMenuExistence).toHaveBeenCalledWith(menuId); }); - it('should remove sub menu items with same state', function() { + it('should remove sub menu items with same state', function () { expect(menu.items.length).toBe(1); expect(menu.items[0].state).toBe(menuItemState2); }); }); - describe('addSubMenuItem', function() { + describe('addSubMenuItem', function () { var subItemOptions = { title: 'title', state: 'sub.state', @@ -324,7 +324,7 @@ subItem2, menu; - beforeEach(function() { + beforeEach(function () { menuService.validateMenuExistence = jasmine.createSpy(); menuService.addMenu(menuId); menuService.addMenuItem(menuId, menuItem1Options); @@ -339,93 +339,93 @@ subItem2 = menuItem1.items[1]; }); - afterEach(function() { + afterEach(function () { menuService.removeMenu(menuId); }); - it('should return menu object', function() { + it('should return menu object', function () { expect(menu).not.toBeNull(); }); - it('should validate menu existance', function() { + it('should validate menu existance', function () { expect(menuService.validateMenuExistence).toHaveBeenCalledWith(menuId); }); - it('should not add sub menu item to menu item of different state', function() { + it('should not add sub menu item to menu item of different state', function () { expect(menuItem3.items.length).toBe(0); }); - it('should set shouldRender', function() { + it('should set shouldRender', function () { expect(subItem1.shouldRender).toBeDefined(); }); - describe('with options set', function() { - it('should add sub menu item to menu item', function() { + describe('with options set', function () { + it('should add sub menu item to menu item', function () { expect(subItem1).toBeDefined(); }); - it('should set title to options title', function() { + it('should set title to options title', function () { expect(subItem1.title).toBe(subItemOptions.title); }); - it('should set state to options state', function() { + it('should set state to options state', function () { expect(subItem1.state).toBe(subItemOptions.state); }); - it('should set roles to options roles', function() { + it('should set roles to options roles', function () { expect(subItem1.roles).toEqual(subItemOptions.roles); }); - it('should set position to options position', function() { + it('should set position to options position', function () { expect(subItem1.position).toEqual(subItemOptions.position); }); - it('should set params to options params', function() { + it('should set params to options params', function () { expect(subItem1.params).toEqual(subItemOptions.params); }); }); - describe('without optoins set', function() { - it('should add sub menu item to menu item', function() { + describe('without optoins set', function () { + it('should add sub menu item to menu item', function () { expect(subItem2).toBeDefined(); }); - it('should set isPublic to parent isPublic', function() { + it('should set isPublic to parent isPublic', function () { expect(subItem2.isPublic).toBe(menuItem1.isPublic); }); - it('should set title to blank', function() { + it('should set title to blank', function () { expect(subItem2.title).toBe(''); }); - it('should set state to blank', function() { + it('should set state to blank', function () { expect(subItem2.state).toBe(''); }); - it('should set roles to parent roles', function() { + it('should set roles to parent roles', function () { expect(subItem2.roles).toEqual(menuItem1.roles); }); - it('should set position to 0', function() { + it('should set position to 0', function () { expect(subItem2.position).toBe(0); }); }); - describe('then removeSubMenuItem', function() { - beforeEach(function() { + describe('then removeSubMenuItem', function () { + beforeEach(function () { menuService.validateMenuExistence = jasmine.createSpy(); menu = menuService.removeSubMenuItem(menuId, subItem1.state); }); - it('should validate menu existance', function() { + it('should validate menu existance', function () { expect(menuService.validateMenuExistence).toHaveBeenCalledWith(menuId); }); - it('should return menu object', function() { + it('should return menu object', function () { expect(menu).toBeDefined(); }); - it('should remove sub menu item', function() { + it('should remove sub menu item', function () { expect(menuItem1.items.length).toBe(1); expect(menuItem1.items[0].state).toEqual(subItem2.state); }); diff --git a/modules/core/tests/client/socket.io.client.service.tests.js b/modules/core/tests/client/socket.io.client.service.tests.js index 02def5d0..802b42ff 100644 --- a/modules/core/tests/client/socket.io.client.service.tests.js +++ b/modules/core/tests/client/socket.io.client.service.tests.js @@ -1,4 +1,4 @@ -(function() { +(function () { 'use strict'; /* Creates a mock of socket.io for the browser. diff --git a/modules/core/tests/server/core.server.config.tests.js b/modules/core/tests/server/core.server.config.tests.js index ab8e2368..3509906c 100644 --- a/modules/core/tests/server/core.server.config.tests.js +++ b/modules/core/tests/server/core.server.config.tests.js @@ -12,8 +12,9 @@ var _ = require('lodash'), request = require('supertest'), config = require(path.resolve('./config/config')), logger = require(path.resolve('./config/lib/logger')), - seed = require(path.resolve('./config/lib/seed')), - express = require(path.resolve('./config/lib/express')); + seed = require(path.resolve('./config/lib/mongo-seed')), + express = require(path.resolve('./config/lib/express')), + Article = mongoose.model('Article'); /** * Globals @@ -28,355 +29,666 @@ var app, describe('Configuration Tests:', function () { - describe('Testing default seedDB', function () { - before(function(done) { - User.remove(function(err) { - should.not.exist(err); + describe('Testing Mongo Seed', function () { + var _seedConfig = _.clone(config.seedDB, true); + var articleSeedConfig; + var userSeedConfig; + var _admin; + var _user; + var _article; - user1 = { - username: 'user_config_test', - provider: 'local', - email: 'user_config_test_@localhost.com', - firstName: 'User', - lastName: 'Local', - displayName: 'User Local', - roles: ['user'] - }; + before(function (done) { + _admin = { + username: 'test-seed-admin', + email: 'test-admin@localhost.com', + firstName: 'Admin', + lastName: 'Test', + roles: ['admin', 'user'] + }; - admin1 = { - username: 'admin_config_test', - provider: 'local', - email: 'admin_config_test_@localhost.com', - firstName: 'Admin', - lastName: 'Local', - displayName: 'Admin Local', - roles: ['user', 'admin'] - }; + _user = { + username: 'test-seed-user', + email: 'test-user@localhost.com', + firstName: 'User', + lastName: 'Test', + roles: ['user'] + }; - userFromSeedConfig = config.seedDB.options.seedUser; - adminFromSeedConfig = config.seedDB.options.seedAdmin; - - return done(); + _article = { + title: 'Testing Database Seed Article', + content: 'Testing Article Seed right now!' + }; + var articleCollections = _.filter(_seedConfig.collections, function (collection) { + return collection.model === 'Article'; }); - }); - after(function(done) { - User.remove(function(err) { - should.not.exist(err); - return done(); + // articleCollections.should.be.instanceof(Array).and.have.lengthOf(1); + articleSeedConfig = articleCollections[0]; + + var userCollections = _.filter(_seedConfig.collections, function (collection) { + return collection.model === 'User'; }); + + // userCollections.should.be.instanceof(Array).and.have.lengthOf(1); + userSeedConfig = userCollections[0]; + + return done(); }); - it('should have seedDB configuration set for "regular" user', function() { - (typeof userFromSeedConfig).should.not.equal('undefined'); - should.exist(userFromSeedConfig.username); - should.exist(userFromSeedConfig.email); - }); - - it('should have seedDB configuration set for admin user', function() { - (typeof adminFromSeedConfig).should.not.equal('undefined'); - should.exist(adminFromSeedConfig.username); - should.exist(adminFromSeedConfig.email); - }); - - it('should not be an admin user to begin with', function(done) { - User.find({ username: 'seedadmin' }, function(err, users) { - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(0); - return done(); - }); - }); - - it('should not be a "regular" user to begin with', function(done) { - User.find({ username: 'seeduser' }, function(err, users) { - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(0); - return done(); - }); - }); - - it('should seed ONLY the admin user account when NODE_ENV is set to "production"', function(done) { - - // Save original value - var nodeEnv = process.env.NODE_ENV; - // Set node env ro production environment - process.env.NODE_ENV = 'production'; - - User.find({ username: adminFromSeedConfig.username }, function(err, users) { - - // There shouldn't be any errors - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(0); - - seed - .start({ logResults: false }) - .then(function() { - User.find({ username: adminFromSeedConfig.username }, function(err, users) { - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(1); - - var _admin = users.pop(); - _admin.username.should.equal(adminFromSeedConfig.username); - - // Restore original NODE_ENV environment variable - process.env.NODE_ENV = nodeEnv; - - User.remove(function(err) { - should.not.exist(err); - return done(); - }); - }); - }); - }); - }); - - it('should seed admin, and "regular" user accounts when NODE_ENV is set to "test"', function(done) { - - // Save original value - var nodeEnv = process.env.NODE_ENV; - // Set node env ro production environment - process.env.NODE_ENV = 'test'; - - User.find({ username: adminFromSeedConfig.username }, function(err, users) { - - // There shouldn't be any errors - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(0); - - seed - .start({ logResults: false }) - .then(function() { - User.find({ username: adminFromSeedConfig.username }, function(err, users) { - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(1); - - var _admin = users.pop(); - _admin.username.should.equal(adminFromSeedConfig.username); - - User.find({ username: userFromSeedConfig.username }, function(err, users) { - - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(1); - - var _user = users.pop(); - _user.username.should.equal(userFromSeedConfig.username); - - // Restore original NODE_ENV environment variable - process.env.NODE_ENV = nodeEnv; - - User.remove(function(err) { - should.not.exist(err); - return done(); - }); - }); - }); - }); - }); - }); - - it('should seed admin, and "regular" user accounts when NODE_ENV is set to "test" when they already exist', function (done) { - - // Save original value - var nodeEnv = process.env.NODE_ENV; - // Set node env ro production environment - process.env.NODE_ENV = 'test'; - - var _user = new User(userFromSeedConfig); - var _admin = new User(adminFromSeedConfig); - - _admin.save(function (err) { - // There shouldn't be any errors - should.not.exist(err); - _user.save(function (err) { - // There shouldn't be any errors - should.not.exist(err); - - User.find({ username: { $in: [adminFromSeedConfig.username, userFromSeedConfig.username] } }, function (err, users) { - - // There shouldn't be any errors - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(2); - - seed - .start({ logResults: false }) - .then(function () { - User.find({ username: { $in: [adminFromSeedConfig.username, userFromSeedConfig.username] } }, function (err, users) { - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(2); - - // Restore original NODE_ENV environment variable - process.env.NODE_ENV = nodeEnv; - - User.remove(function (err) { - should.not.exist(err); - return done(); - }); - }); - }); - }); + afterEach(function (done) { + Article.remove().exec() + .then(function () { + return User.remove().exec(); + }) + .then(function () { + return done(); + }) + .catch(function (err) { + return done(err); }); - }); }); - it('should ONLY seed admin user account when NODE_ENV is set to "production" with custom admin', function(done) { + it('should have default seed configuration set for articles', function (done) { + articleSeedConfig.should.be.instanceof(Object); + articleSeedConfig.docs.should.be.instanceof(Array).and.have.lengthOf(1); + should.exist(articleSeedConfig.docs[0].data.title); + should.exist(articleSeedConfig.docs[0].data.content); - // Save original value - var nodeEnv = process.env.NODE_ENV; - // Set node env ro production environment - process.env.NODE_ENV = 'production'; + return done(); + }); - User.find({ username: admin1.username }, function(err, users) { + it('should have default seed configuration set for users', function (done) { + userSeedConfig.should.be.instanceof(Object); + userSeedConfig.docs.should.be.instanceof(Array).and.have.lengthOf(2); - // There shouldn't be any errors - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(0); + should.exist(userSeedConfig.docs[0].data.username); + should.exist(userSeedConfig.docs[0].data.email); + should.exist(userSeedConfig.docs[0].data.firstName); + should.exist(userSeedConfig.docs[0].data.lastName); + should.exist(userSeedConfig.docs[0].data.roles); - seed - .start({ logResults: false, seedAdmin: admin1 }) - .then(function() { - User.find({ username: admin1.username }, function(err, users) { - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(1); + should.exist(userSeedConfig.docs[1].data.username); + should.exist(userSeedConfig.docs[1].data.email); + should.exist(userSeedConfig.docs[1].data.firstName); + should.exist(userSeedConfig.docs[1].data.lastName); + should.exist(userSeedConfig.docs[1].data.roles); - var _admin = users.pop(); - _admin.username.should.equal(admin1.username); + return done(); + }); - // Restore original NODE_ENV environment variable - process.env.NODE_ENV = nodeEnv; + it('should seed data from default config', function (done) { - User.remove(function(err) { - should.not.exist(err); - return done(); - }); - }); + seed.start() + .then(function () { + // Check Articles Seed + return Article.find().exec(); + }) + .then(function (articles) { + articles.should.be.instanceof(Array).and.have.lengthOf(articleSeedConfig.docs.length); + // Check Users Seed + return User.find().exec(); + }) + .then(function (users) { + users.should.be.instanceof(Array).and.have.lengthOf(userSeedConfig.docs.length); + return done(); + }) + .catch(done); + }); + + it('should overwrite existing article by default', function (done) { + articleSeedConfig.docs.should.be.instanceof(Array).and.have.lengthOf(1); + + var article = new Article(articleSeedConfig.docs[0].data); + article.content = '_temp_test_article_'; + + // save temp article + article.save() + .then(function () { + return seed.start(); + }) + .then(function () { + return Article.find().exec(); + }) + .then(function (articles) { + articles.should.be.instanceof(Array).and.have.lengthOf(1); + + var newArticle = articles.pop(); + articleSeedConfig.docs[0].data.title.should.equal(newArticle.title); + articleSeedConfig.docs[0].data.content.should.equal(newArticle.content); + + return done(); + }) + .catch(done); + }); + + it('should overwrite existing users by default', function (done) { + userSeedConfig.docs.should.be.instanceof(Array).and.have.lengthOf(2); + + var admin = new User(userSeedConfig.docs[0].data); + admin.email = 'temp-admin@localhost.com'; + admin.provider = 'local'; + + var user = new User(userSeedConfig.docs[1].data); + user.email = 'temp-user@localhost.com'; + user.provider = 'local'; + + admin.save() + .then(function () { + return user.save(); + }) + .then(function () { + return User.find().exec(); + }) + .then(function (users) { + users.should.be.instanceof(Array).and.have.lengthOf(2); + // Start Seed + return seed.start(); + }) + .then(function () { + return User.find().exec(); + }) + .then(function (users) { + // Should still only be two users, since we removed + // the existing users before seeding again. + users.should.be.instanceof(Array).and.have.lengthOf(2); + + return User.find({ username: admin.username }).exec(); + }) + .then(function (users) { + users.should.be.instanceof(Array).and.have.lengthOf(1); + + var newAdmin = users.pop(); + userSeedConfig.docs[0].data.username.should.equal(newAdmin.username); + userSeedConfig.docs[0].data.email.should.equal(newAdmin.email); + + return User.find({ username: user.username }).exec(); + }) + .then(function (users) { + users.should.be.instanceof(Array).and.have.lengthOf(1); + + var newUser = users.pop(); + userSeedConfig.docs[1].data.username.should.equal(newUser.username); + userSeedConfig.docs[1].data.email.should.equal(newUser.email); + + return done(); + }) + .catch(done); + }); + + it('should seed single article with custom options', function (done) { + seed + .start({ + collections: [{ + model: 'Article', + docs: [{ + overwrite: true, + data: _article + }] + }] + }) + .then(function () { + return Article.find().exec(); + }) + .then(function (articles) { + articles.should.be.instanceof(Array).and.have.lengthOf(1); + + var newArticle = articles.pop(); + _article.title.should.equal(newArticle.title); + _article.content.should.equal(newArticle.content); + + return done(); + }) + .catch(done); + }); + + it('should seed single article with user set to custom seeded admin user', function (done) { + seed + .start({ + collections: [{ + model: 'User', + docs: [{ + data: _admin + }] + }, { + model: 'Article', + docs: [{ + overwrite: true, + data: _article + }] + }] + }) + .then(function () { + return User.find().exec(); + }) + .then(function (users) { + users.should.be.instanceof(Array).and.have.lengthOf(1); + + return Article + .find() + .populate('user', 'firstName lastName username email roles') + .exec(); + }) + .then(function (articles) { + articles.should.be.instanceof(Array).and.have.lengthOf(1); + + var newArticle = articles.pop(); + _article.title.should.equal(newArticle.title); + _article.content.should.equal(newArticle.content); + + should.exist(newArticle.user); + should.exist(newArticle.user._id); + + _admin.username.should.equal(newArticle.user.username); + _admin.email.should.equal(newArticle.user.email); + _admin.firstName.should.equal(newArticle.user.firstName); + _admin.lastName.should.equal(newArticle.user.lastName); + + should.exist(newArticle.user.roles); + newArticle.user.roles.indexOf('admin').should.equal(_admin.roles.indexOf('admin')); + + return done(); + }) + .catch(done); + }); + + it('should seed single article with NO user set due to seed order', function (done) { + seed + .start({ + collections: [{ + model: 'Article', + docs: [{ + overwrite: true, + data: _article + }] + }, { + model: 'User', + docs: [{ + data: _admin + }] + }] + }) + .then(function () { + return User.find().exec(); + }) + .then(function (users) { + users.should.be.instanceof(Array).and.have.lengthOf(1); + + return Article + .find() + .populate('user', 'firstName lastName username email roles') + .exec(); + }) + .then(function (articles) { + articles.should.be.instanceof(Array).and.have.lengthOf(1); + + var newArticle = articles.pop(); + _article.title.should.equal(newArticle.title); + _article.content.should.equal(newArticle.content); + + should.not.exist(newArticle.user); + + return done(); + }) + .catch(done); + }); + + it('should seed admin and user accounts with custom options', function (done) { + seed + .start({ + collections: [{ + model: 'User', + docs: [{ + data: _admin + }, { + data: _user + }] + }] + }) + .then(function () { + return User.find().exec(); + }) + .then(function (users) { + users.should.be.instanceof(Array).and.have.lengthOf(2); + return User.find({ username: _admin.username }).exec(); + }) + .then(function (users) { + users.should.be.instanceof(Array).and.have.lengthOf(1); + + var newAdmin = users.pop(); + _admin.username.should.equal(newAdmin.username); + _admin.email.should.equal(newAdmin.email); + + return User.find({ username: _user.username }).exec(); + }) + .then(function (users) { + users.should.be.instanceof(Array).and.have.lengthOf(1); + + var newUser = users.pop(); + _user.username.should.equal(newUser.username); + _user.email.should.equal(newUser.email); + + return done(); + }) + .catch(done); + }); + + it('should NOT overwrite existing article with custom options', function (done) { + + var article = new Article(_article); + article.content = '_temp_article_content_'; + + article.save() + .then(function () { + return seed.start({ + collections: [{ + model: 'Article', + docs: [{ + overwrite: false, + data: _article + }] + }] }); - }); + }) + .then(function () { + return Article.find().exec(); + }) + .then(function (articles) { + articles.should.be.instanceof(Array).and.have.lengthOf(1); + + var existingArticle = articles.pop(); + article.title.should.equal(existingArticle.title); + article.content.should.equal(existingArticle.content); + + return done(); + }) + .catch(done); }); - it('should seed admin, and "regular" user accounts when NODE_ENV is set to "test" with custom options', function(done) { + it('should NOT overwrite existing user with custom options', function (done) { + var user = new User(_user); + user.provider = 'local'; + user.email = 'temp-test-user@localhost.com'; - // Save original value - var nodeEnv = process.env.NODE_ENV; - // Set node env ro production environment - process.env.NODE_ENV = 'test'; - - User.find({ username: admin1.username }, function(err, users) { - - // There shouldn't be any errors - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(0); - - seed - .start({ logResults: false, seedAdmin: admin1, seedUser: user1 }) - .then(function() { - User.find({ username: admin1.username }, function(err, users) { - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(1); - - var _admin = users.pop(); - _admin.username.should.equal(admin1.username); - - User.find({ username: user1.username }, function(err, users) { - - should.not.exist(err); - users.should.be.instanceof(Array).and.have.lengthOf(1); - - var _user = users.pop(); - _user.username.should.equal(user1.username); - - // Restore original NODE_ENV environment variable - process.env.NODE_ENV = nodeEnv; - - User.remove(function(err) { - should.not.exist(err); - return done(); - }); - }); - }); + user.save() + .then(function () { + return seed.start({ + collections: [{ + model: 'User', + docs: [{ + overwrite: false, + data: _user + }] + }] }); - }); + }) + .then(function () { + return User.find().exec(); + }) + .then(function (users) { + users.should.be.instanceof(Array).and.have.lengthOf(1); + + var existingUser = users.pop(); + user.username.should.equal(existingUser.username); + user.email.should.equal(existingUser.email); + + return done(); + }) + .catch(done); }); - it('should NOT seed admin user account if it already exists when NODE_ENV is set to "production"', function(done) { - - // Save original value - var nodeEnv = process.env.NODE_ENV; - // Set node env ro production environment - process.env.NODE_ENV = 'production'; - - var _admin = new User(adminFromSeedConfig); - - _admin.save(function(err, user) { - // There shouldn't be any errors - should.not.exist(err); - user.username.should.equal(adminFromSeedConfig.username); - - seed - .start({ logResults: false }) - .then(function () { - // we don't ever expect to make it here but we don't want to timeout - User.remove(function(err) { - should.not.exist(err); - // force this test to fail since we should never be here - should.exist(undefined); - // Restore original NODE_ENV environment variable - process.env.NODE_ENV = nodeEnv; - - return done(); - }); - }) - .catch(function (err) { - should.exist(err); - err.message.should.equal('Failed due to local account already exists: ' + adminFromSeedConfig.username); - - // Restore original NODE_ENV environment variable - process.env.NODE_ENV = nodeEnv; - - User.remove(function(removeErr) { - should.not.exist(removeErr); - - return done(); - }); - }); - }); - }); - - it('should NOT seed "regular" user account if missing email when NODE_ENV set to "test"', function (done) { - - // Save original value - var nodeEnv = process.env.NODE_ENV; - // Set node env ro test environment - process.env.NODE_ENV = 'test'; - - var _user = new User(user1); - _user.email = ''; + it('should NOT seed article when missing title with custom options', function (done) { + var invalid = { + content: '_temp_article_content_' + }; seed - .start({ logResults: false, seedUser: _user }) + .start({ + collections: [{ + model: 'Article', + docs: [{ + data: invalid + }] + }] + }) .then(function () { - // we don't ever expect to make it here but we don't want to timeout - User.remove(function(err) { - // force this test to fail since we should never be here - should.exist(undefined); - // Restore original NODE_ENV environment variable - process.env.NODE_ENV = nodeEnv; - - return done(); - }); + // We should not make it here so we + // force an assert failure to prevent hangs. + should.exist(undefined); + return done(); }) .catch(function (err) { should.exist(err); - err.message.should.equal('Failed to add local ' + user1.username); + err.message.should.equal('Article validation failed: title: Title cannot be blank'); - // Restore original NODE_ENV environment variable - process.env.NODE_ENV = nodeEnv; + return done(); + }); + }); - User.remove(function(removeErr) { - should.not.exist(removeErr); + it('should NOT seed user when missing username with custom options', function (done) { + var invalid = _.clone(_user, true); + invalid.username = undefined; - return done(); + seed + .start({ + collections: [{ + model: 'User', + docs: [{ + data: invalid + }] + }] + }) + .then(function () { + // We should not make it here so we + // force an assert failure to prevent hangs. + should.exist(undefined); + return done(); + }) + .catch(function (err) { + should.exist(err); + err.message.should.equal('User validation failed: username: Please fill in a username'); + + return done(); + }); + }); + + it('should NOT seed user when missing email with custom options', function (done) { + var invalid = _.clone(_user, true); + invalid.email = undefined; + + seed + .start({ + collections: [{ + model: 'User', + docs: [{ + data: invalid + }] + }] + }) + .then(function () { + // We should not make it here so we + // force an assert failure to prevent hangs. + should.exist(undefined); + return done(); + }) + .catch(function (err) { + should.exist(err); + err.message.should.equal('User validation failed: email: Please fill a valid email address'); + + return done(); + }); + }); + + it('should NOT seed user with invalid email with custom options', function (done) { + var invalid = _.clone(_user, true); + invalid.email = '...invalid-email...'; + + seed + .start({ + collections: [{ + model: 'User', + docs: [{ + data: invalid + }] + }] + }) + .then(function () { + // We should not make it here so we + // force an assert failure to prevent hangs. + should.exist(undefined); + return done(); + }) + .catch(function (err) { + should.exist(err); + err.message.should.equal('User validation failed: email: Please fill a valid email address'); + + return done(); + }); + }); + + it('should NOT continue seed when empty collections config', function (done) { + seed + .start({ + collections: [] + }) + .then(function () { + return Article.find().exec(); + }) + .then(function (articles) { + articles.should.be.instanceof(Array).and.have.lengthOf(0); + + return User.find().exec(); + }) + .then(function (users) { + users.should.be.instanceof(Array).and.have.lengthOf(0); + + return done(); + }) + .catch(done); + }); + + it('should NOT seed any data when empty docs config', function (done) { + seed + .start({ + collections: [{ + model: 'Article', + docs: [] + }] + }) + .then(function () { + return Article.find().exec(); + }) + .then(function (articles) { + articles.should.be.instanceof(Array).and.have.lengthOf(0); + + return User.find().exec(); + }) + .then(function (users) { + users.should.be.instanceof(Array).and.have.lengthOf(0); + + return done(); + }) + .catch(done); + }); + + it('should seed article with custom options & skip.when results are empty', function (done) { + seed + .start({ + collections: [{ + model: 'Article', + skip: { + when: { title: 'should-not-find-this-title' } + }, + docs: [{ + data: _article + }] + }] + }) + .then(function () { + return Article.find().exec(); + }) + .then(function (articles) { + articles.should.be.instanceof(Array).and.have.lengthOf(1); + + var newArticle = articles.pop(); + _article.title.should.be.equal(newArticle.title); + _article.content.should.be.equal(newArticle.content); + + return done(); + }) + .catch(done); + }); + + it('should skip seed on collection with custom options & skip.when has results', function (done) { + var article = new Article({ + title: 'temp-article-title', + content: 'temp-article-content' + }); + + article + .save() + .then(function () { + return Article.find().exec(); + }) + .then(function (articles) { + articles.should.be.instanceof(Array).and.have.lengthOf(1); + + var newArticle = articles.pop(); + article.title.should.equal(newArticle.title); + article.content.should.equal(newArticle.content); + + return seed.start({ + collections: [{ + model: 'Article', + skip: { + when: { title: newArticle.title } + }, + docs: [{ + data: _article + }] + }] }); + }) + .then(function () { + return Article.find().exec(); + }) + .then(function (articles) { + // We should have the same article added at start of this unit test. + articles.should.be.instanceof(Array).and.have.lengthOf(1); + + var existingArticle = articles.pop(); + article.title.should.equal(existingArticle.title); + article.content.should.equal(existingArticle.content); + + return done(); + }) + .catch(done); + }); + + it('should fail seed with custom options & invalid skip.when query', function (done) { + seed + .start({ + collections: [{ + model: 'Article', + skip: { + when: { created: 'not-a-valid-date' } + }, + docs: [{ + data: _article + }] + }] + }) + .then(function () { + // We should not get here + should.exist(undefined); + return done(); + }) + .catch(function (err) { + should.exist(err); + // We expect the error message to include + err.message.indexOf('Cast to date failed').should.equal(0); + + return done(); }); }); }); @@ -511,13 +823,13 @@ describe('Configuration Tests:', function () { describe('Testing exposing environment as a variable to layout', function () { - ['development', 'production', 'test'].forEach(function(env) { + ['development', 'production', 'test'].forEach(function (env) { it('should expose environment set to ' + env, function (done) { // Set env to development for this test process.env.NODE_ENV = env; // Gget application - app = express.init(mongoose); + app = express.init(mongoose.connection.db); agent = request.agent(app); // Get rendered layout diff --git a/modules/users/client/views/password/forgot-password.client.view.html b/modules/users/client/views/password/forgot-password.client.view.html index c29f45e2..6946cb5c 100644 --- a/modules/users/client/views/password/forgot-password.client.view.html +++ b/modules/users/client/views/password/forgot-password.client.view.html @@ -6,12 +6,12 @@ autocomplete="off">
- -
-

Enter a username.

+
+

Enter a username or email.

@@ -21,4 +21,3 @@
- diff --git a/modules/users/server/config/users.server.config.js b/modules/users/server/config/users.server.config.js index ab211e73..cfce8d42 100644 --- a/modules/users/server/config/users.server.config.js +++ b/modules/users/server/config/users.server.config.js @@ -11,7 +11,7 @@ var passport = require('passport'), /** * Module init function */ -module.exports = function (app, db) { +module.exports = function (app) { // Serialize sessions passport.serializeUser(function (user, done) { user.updateSignedTime(); diff --git a/modules/users/server/controllers/users/users.password.server.controller.js b/modules/users/server/controllers/users/users.password.server.controller.js index f57f78a5..afda726b 100644 --- a/modules/users/server/controllers/users/users.password.server.controller.js +++ b/modules/users/server/controllers/users/users.password.server.controller.js @@ -31,17 +31,23 @@ exports.forgot = function (req, res, next) { }, // Lookup user by username function (token, done) { - if (req.body.username) { + if (req.body.usernameOrEmail) { + + var usernameOrEmail = String(req.body.usernameOrEmail).toLowerCase(); + User.findOne({ - username: req.body.username.toLowerCase() + $or: [ + { username: usernameOrEmail }, + { email: usernameOrEmail } + ] }, '-salt -password', function (err, user) { if (err || !user) { return res.status(400).send({ - message: 'No account with that username has been found' + message: 'No account with that username or email has been found' }); } else if (user.provider !== 'local') { return res.status(400).send({ - message: 'It seems like you signed up using your ' + user.provider + ' account' + message: 'It seems like you signed up using your ' + user.provider + ' account, please sign in using that provider.' }); } else { user.resetPasswordToken = token; @@ -54,7 +60,7 @@ exports.forgot = function (req, res, next) { }); } else { return res.status(422).send({ - message: 'Username field must not be blank' + message: 'Username/email field must not be blank' }); } }, diff --git a/modules/users/server/controllers/users/users.profile.server.controller.js b/modules/users/server/controllers/users/users.profile.server.controller.js index 52fc23cd..4e7962dd 100644 --- a/modules/users/server/controllers/users/users.profile.server.controller.js +++ b/modules/users/server/controllers/users/users.profile.server.controller.js @@ -81,7 +81,7 @@ exports.changeProfilePicture = function (req, res) { }); } - function uploadImage () { + function uploadImage() { return new Promise(function (resolve, reject) { upload(req, res, function (uploadError) { if (uploadError) { @@ -93,7 +93,7 @@ exports.changeProfilePicture = function (req, res) { }); } - function updateUser () { + function updateUser() { return new Promise(function (resolve, reject) { user.profileImageURL = config.uploads.profile.image.dest + req.file.filename; user.save(function (err, theuser) { @@ -106,12 +106,20 @@ exports.changeProfilePicture = function (req, res) { }); } - function deleteOldImage () { + function deleteOldImage() { return new Promise(function (resolve, reject) { if (existingImageUrl !== User.schema.path('profileImageURL').defaultValue) { fs.unlink(existingImageUrl, function (unlinkError) { if (unlinkError) { - console.log(unlinkError); + + // If file didn't exist, no need to reject promise + if (unlinkError.code === 'ENOENT') { + console.log('Removing profile image failed because file did not exist.'); + return resolve(); + } + + console.error(unlinkError); + reject({ message: 'Error occurred while deleting old profile picture' }); @@ -125,7 +133,7 @@ exports.changeProfilePicture = function (req, res) { }); } - function login () { + function login() { return new Promise(function (resolve, reject) { req.login(user, function (err) { if (err) { diff --git a/modules/users/server/models/user.server.model.js b/modules/users/server/models/user.server.model.js index b87fe1e6..7584dbaf 100644 --- a/modules/users/server/models/user.server.model.js +++ b/modules/users/server/models/user.server.model.js @@ -11,7 +11,8 @@ var mongoose = require('mongoose'), validator = require('validator'), generatePassword = require('generate-password'), owasp = require('owasp-password-strength-test'), - moment = require('moment'); + moment = require('moment'), + chalk = require('chalk'); owasp.config(config.shared.owasp); @@ -424,4 +425,92 @@ UserSchema.statics.generateRandomPassphrase = function () { }); }; +UserSchema.statics.seed = seed; + mongoose.model('User', UserSchema); + +/** +* Seeds the User collection with document (User) +* and provided options. +*/ +function seed(doc, options) { + var User = mongoose.model('User'); + + return new Promise(function (resolve, reject) { + + skipDocument() + .then(add) + .then(function (response) { + return resolve(response); + }) + .catch(function (err) { + return reject(err); + }); + + function skipDocument() { + return new Promise(function (resolve, reject) { + User + .findOne({ + username: doc.username + }) + .exec(function (err, existing) { + if (err) { + return reject(err); + } + + if (!existing) { + return resolve(false); + } + + if (existing && !options.overwrite) { + return resolve(true); + } + + // Remove User (overwrite) + + existing.remove(function (err) { + if (err) { + return reject(err); + } + + return resolve(false); + }); + }); + }); + } + + function add(skip) { + return new Promise(function (resolve, reject) { + + if (skip) { + return resolve({ + message: chalk.yellow('Database Seeding: User\t\t' + doc.username + ' skipped') + }); + } + + User.generateRandomPassphrase() + .then(function (passphrase) { + var user = new User(doc); + + user.provider = 'local'; + user.displayName = user.firstName + ' ' + user.lastName; + user.password = passphrase; + + user.save(function (err) { + if (err) { + return reject(err); + } + + return resolve({ + message: 'Database Seeding: User\t\t' + user.username + ' added with password set to ' + passphrase + }); + }); + }) + .catch(function (err) { + return reject(err); + }); + }); + } + + }); +} diff --git a/modules/users/tests/client/password-validator.client.directive.tests.js b/modules/users/tests/client/password-validator.client.directive.tests.js index c6f31834..8ab12315 100644 --- a/modules/users/tests/client/password-validator.client.directive.tests.js +++ b/modules/users/tests/client/password-validator.client.directive.tests.js @@ -1,8 +1,8 @@ 'use strict'; -(function() { +(function () { // Password Validator Directive Spec - describe('PasswordValidatorDirective', function() { + describe('PasswordValidatorDirective', function () { // Initialize global variables var scope, element, @@ -12,7 +12,7 @@ // Load the main application module beforeEach(module(ApplicationConfiguration.applicationModuleName)); - beforeEach(inject(function(_$rootScope_, _$compile_) { + beforeEach(inject(function (_$rootScope_, _$compile_) { // Set a new global scope scope = _$rootScope_.$new(); $compile = _$compile_; @@ -30,7 +30,7 @@ // inject allows you to use AngularJS dependency injection // to retrieve and use other services - inject(function($compile) { + inject(function ($compile) { var form = $compile(template)(scope); element = form.find('div'); @@ -39,7 +39,7 @@ }); } - describe('Initialize', function() { + describe('Initialize', function () { beforeEach(function () { compileDirective(); }); @@ -65,7 +65,7 @@ expect(scope.requirementsProgress).toEqual(undefined); }); - it('should be valid when password meets requirements - "P@ssw0rd!!""', function() { + it('should be valid when password meets requirements - "P@ssw0rd!!""', function () { scope.passwordMock.password = 'P@ssw0rd!!'; compileDirective(); scope.$digest(); @@ -76,7 +76,7 @@ expect(scope.requirementsProgress).toEqual('100'); }); - it('should be valid when password meets requirements with a passphrase', function() { + it('should be valid when password meets requirements with a passphrase', function () { scope.passwordMock.password = 'Open-Source Full-Stack Solution for MEAN'; compileDirective(); scope.$digest(); @@ -87,7 +87,7 @@ expect(scope.requirementsProgress).toEqual('100'); }); - it('should not allow a less than 10 characters long - "P@$$w0rd!"', function() { + it('should not allow a less than 10 characters long - "P@$$w0rd!"', function () { scope.passwordMock.password = 'P@$$w0rd!'; compileDirective(); scope.$digest(); @@ -99,7 +99,7 @@ expect(scope.requirementsProgress).toEqual('80'); }); - it('should not allow a greater than 128 characters long', function() { + it('should not allow a greater than 128 characters long', function () { scope.passwordMock.password = ')!/uLT="lh&:`6X!]|15o!$!TJf,.13l?vG].-j],lFPe/QhwN#{Z<[*1nX@n1^?WW-%_.*D)m$toB+N7z}kcN#B_d(f41h%w@0F!]igtSQ1gl~6sEV&r~}~1ub>If1c+'; compileDirective(); scope.$digest(); @@ -111,7 +111,7 @@ expect(scope.requirementsProgress).toEqual('80'); }); - it('should not allow more than 3 or more repeating characters - "P@$$w0rd!!!"', function() { + it('should not allow more than 3 or more repeating characters - "P@$$w0rd!!!"', function () { scope.passwordMock.password = 'P@$$w0rd!!!'; compileDirective(); scope.$digest(); @@ -123,7 +123,7 @@ expect(scope.requirementsProgress).toEqual('80'); }); - it('should not allow a password with no uppercase letters - "p@$$w0rd!!"', function() { + it('should not allow a password with no uppercase letters - "p@$$w0rd!!"', function () { scope.passwordMock.password = 'p@$$w0rd!!'; compileDirective(); scope.$digest(); @@ -135,7 +135,7 @@ expect(scope.requirementsProgress).toEqual('80'); }); - it('should not allow a password with less than one number - "P@$$word!!"', function() { + it('should not allow a password with less than one number - "P@$$word!!"', function () { scope.passwordMock.password = 'P@$$word!!'; compileDirective(); scope.$digest(); @@ -147,7 +147,7 @@ expect(scope.requirementsProgress).toEqual('80'); }); - it('should not allow a password with less than one special character - "Passw0rdss"', function() { + it('should not allow a password with less than one special character - "Passw0rdss"', function () { scope.passwordMock.password = 'Passw0rdss'; compileDirective(); scope.$digest(); @@ -159,7 +159,7 @@ expect(scope.requirementsProgress).toEqual('80'); }); - it('should show 20% progress and "danger" color', function() { + it('should show 20% progress and "danger" color', function () { scope.passwordMock.password = 'P'; compileDirective(); scope.$digest(); @@ -168,7 +168,7 @@ expect(scope.requirementsProgress).toEqual('20'); }); - it('should show 40% progress and "warning" color', function() { + it('should show 40% progress and "warning" color', function () { scope.passwordMock.password = 'Pa'; compileDirective(); scope.$digest(); @@ -177,7 +177,7 @@ expect(scope.requirementsProgress).toEqual('40'); }); - it('should show 60% progress and "info" color', function() { + it('should show 60% progress and "info" color', function () { scope.passwordMock.password = 'Pa$'; compileDirective(); scope.$digest(); @@ -186,7 +186,7 @@ expect(scope.requirementsProgress).toEqual('60'); }); - it('should show 80% progress and "primary" color', function() { + it('should show 80% progress and "primary" color', function () { scope.passwordMock.password = 'Pa$$w0rd'; compileDirective(); scope.$digest(); @@ -195,7 +195,7 @@ expect(scope.requirementsProgress).toEqual('80'); }); - it('should show 100% progress and "success" color', function() { + it('should show 100% progress and "success" color', function () { scope.passwordMock.password = 'Pa$$w0rd!!'; compileDirective(); scope.$digest(); diff --git a/modules/users/tests/client/password-verify.client.directive.tests.js b/modules/users/tests/client/password-verify.client.directive.tests.js index 24b41dcd..29b35bcd 100644 --- a/modules/users/tests/client/password-verify.client.directive.tests.js +++ b/modules/users/tests/client/password-verify.client.directive.tests.js @@ -1,8 +1,8 @@ 'use strict'; -(function() { +(function () { // Password Verify Directive Spec - describe('PasswordVerifyDirective', function() { + describe('PasswordVerifyDirective', function () { // Initialize global variables var scope, element, @@ -12,7 +12,7 @@ // Load the main application module beforeEach(module(ApplicationConfiguration.applicationModuleName)); - beforeEach(inject(function(_$rootScope_, _$compile_) { + beforeEach(inject(function (_$rootScope_, _$compile_) { // Set a new global scope scope = _$rootScope_.$new(); $compile = _$compile_; @@ -32,7 +32,7 @@ // inject allows you to use AngularJS dependency injection // to retrieve and use other services - inject(function($compile) { + inject(function ($compile) { var form = $compile(template)(scope); element = form.find('div'); @@ -41,7 +41,7 @@ }); } - describe('Initialize', function() { + describe('Initialize', function () { beforeEach(function () { compileDirective(); }); diff --git a/modules/users/tests/client/password.client.controller.tests.js b/modules/users/tests/client/password.client.controller.tests.js index 3bb87cc5..43d855a1 100644 --- a/modules/users/tests/client/password.client.controller.tests.js +++ b/modules/users/tests/client/password.client.controller.tests.js @@ -1,8 +1,8 @@ 'use strict'; -(function() { +(function () { // Password controller Spec - describe('PasswordController', function() { + describe('PasswordController', function () { // Initialize global variables var PasswordController, scope, @@ -12,11 +12,11 @@ $window, Notification; - beforeEach(function() { + beforeEach(function () { jasmine.addMatchers({ - toEqualData: function(util, customEqualityTesters) { + toEqualData: function (util, customEqualityTesters) { return { - compare: function(actual, expected) { + compare: function (actual, expected) { return { pass: angular.equals(actual, expected) }; @@ -29,8 +29,8 @@ // Load the main application module beforeEach(module(ApplicationConfiguration.applicationModuleName)); - describe('Logged in user', function() { - beforeEach(inject(function($controller, $rootScope, _UsersService_, _Authentication_, _$stateParams_, _$httpBackend_, _$location_) { + describe('Logged in user', function () { + beforeEach(inject(function ($controller, $rootScope, _UsersService_, _Authentication_, _$stateParams_, _$httpBackend_, _$location_) { // Set a new global scope scope = $rootScope.$new(); @@ -55,13 +55,13 @@ }); })); - it('should redirect logged in user to home', function() { + it('should redirect logged in user to home', function () { expect($location.path).toHaveBeenCalledWith('/'); }); }); - describe('Logged out user', function() { - beforeEach(inject(function($controller, $rootScope, _$window_, _$stateParams_, _$httpBackend_, _$location_, _Notification_) { + describe('Logged out user', function () { + beforeEach(inject(function ($controller, $rootScope, _$window_, _$stateParams_, _$httpBackend_, _$location_, _Notification_) { // Set a new global scope scope = $rootScope.$new(); @@ -87,22 +87,22 @@ }); })); - it('should not redirect to home', function() { + it('should not redirect to home', function () { expect($location.path).not.toHaveBeenCalledWith('/'); }); - describe('askForPasswordReset', function() { + describe('askForPasswordReset', function () { var credentials = { username: 'test', password: 'P@ssw0rd!!' }; - beforeEach(function() { + beforeEach(function () { scope.vm.credentials = credentials; }); - describe('POST error', function() { + describe('POST error', function () { var errorMessage = 'No account with that username has been found'; - beforeEach(function() { + beforeEach(function () { $httpBackend.when('POST', '/api/auth/forgot', credentials).respond(400, { 'message': errorMessage }); @@ -111,18 +111,18 @@ $httpBackend.flush(); }); - it('should clear form', function() { + it('should clear form', function () { expect(scope.vm.credentials).toBe(null); }); - it('should call Notification.error with response message', function() { + it('should call Notification.error with response message', function () { expect(Notification.error).toHaveBeenCalledWith({ message: errorMessage, title: ' Failed to send password reset email!', delay: 4000 }); }); }); - describe('POST success', function() { + describe('POST success', function () { var successMessage = 'An email has been sent to the provided email with further instructions.'; - beforeEach(function() { + beforeEach(function () { $httpBackend.when('POST', '/api/auth/forgot', credentials).respond({ 'message': successMessage }); @@ -131,27 +131,27 @@ $httpBackend.flush(); }); - it('should clear form', function() { + it('should clear form', function () { expect(scope.vm.credentials).toBe(null); }); - it('should call Notification.success with response message', function() { + it('should call Notification.success with response message', function () { expect(Notification.success).toHaveBeenCalledWith({ message: successMessage, title: ' Password reset email sent successfully!' }); }); }); }); - describe('resetUserPassword', function() { + describe('resetUserPassword', function () { var token = 'testToken'; var passwordDetails = { password: 'test' }; - beforeEach(function() { + beforeEach(function () { $stateParams.token = token; scope.vm.passwordDetails = passwordDetails; }); - it('POST error should call Notification.error with response message', function() { + it('POST error should call Notification.error with response message', function () { var errorMessage = 'Passwords do not match'; $httpBackend.when('POST', '/api/auth/reset/' + token, passwordDetails).respond(400, { 'message': errorMessage @@ -163,26 +163,26 @@ expect(Notification.error).toHaveBeenCalledWith({ message: errorMessage, title: ' Password reset failed!', delay: 4000 }); }); - describe('POST success', function() { + describe('POST success', function () { var user = { username: 'test' }; - beforeEach(function() { + beforeEach(function () { $httpBackend.when('POST', '/api/auth/reset/' + token, passwordDetails).respond(user); scope.vm.resetUserPassword(true); $httpBackend.flush(); }); - it('should clear password form', function() { + it('should clear password form', function () { expect(scope.vm.passwordDetails).toBe(null); }); - it('should attach user profile', function() { + it('should attach user profile', function () { expect(scope.vm.authentication.user.username).toEqual(user.username); }); - it('should redirect to password reset success view with Notification.success', function() { + it('should redirect to password reset success view with Notification.success', function () { expect(Notification.success).toHaveBeenCalledWith({ message: ' Password reset successful!' }); expect($location.path).toHaveBeenCalledWith('/password/reset/success'); }); diff --git a/modules/users/tests/e2e/users.e2e.tests.js b/modules/users/tests/e2e/users.e2e.tests.js index 02d27ede..05141305 100644 --- a/modules/users/tests/e2e/users.e2e.tests.js +++ b/modules/users/tests/e2e/users.e2e.tests.js @@ -421,7 +421,7 @@ describe('Users E2E Tests:', function () { expect(element.all(by.css('.error-text')).get(1).getText()).toBe('Password is required.'); }); - it('Verify that the user is logged in', function() { + it('Verify that the user is logged in', function () { // Make sure user is signed out first signout(); // Sign in diff --git a/modules/users/tests/server/user.server.model.tests.js b/modules/users/tests/server/user.server.model.tests.js index bac9e8b2..43c8168e 100644 --- a/modules/users/tests/server/user.server.model.tests.js +++ b/modules/users/tests/server/user.server.model.tests.js @@ -186,7 +186,7 @@ describe('User Model Unit Tests:', function () { _user3.email = _user1.email; _user3.save(function (err) { should.exist(err); - _user1.remove(function(err) { + _user1.remove(function (err) { should.not.exist(err); done(); }); @@ -224,7 +224,7 @@ describe('User Model Unit Tests:', function () { _user1.save(function (err) { should.not.exist(err); _user1.password.should.not.equal(passwordBeforeSave); - _user1.remove(function(err) { + _user1.remove(function (err) { should.not.exist(err); done(); }); @@ -238,7 +238,7 @@ describe('User Model Unit Tests:', function () { _user1.save(function (err) { should.not.exist(err); _user1.password.should.not.equal(passwordBeforeSave); - _user1.remove(function(err) { + _user1.remove(function (err) { should.not.exist(err); done(); }); @@ -246,7 +246,7 @@ describe('User Model Unit Tests:', function () { }); }); - describe('User Password Validation Tests', function() { + describe('User Password Validation Tests', function () { it('should validate when the password strength passes - "P@$$w0rd!!"', function () { var _user1 = new User(user1); _user1.password = 'P@$$w0rd!!'; @@ -351,7 +351,7 @@ describe('User Model Unit Tests:', function () { }); }); - describe('User E-mail Validation Tests', function() { + describe('User E-mail Validation Tests', function () { it('should not allow invalid email address - "123"', function (done) { var _user1 = new User(user1); @@ -645,12 +645,12 @@ describe('User Model Unit Tests:', function () { }); - describe('Username Validation', function() { - it('should show error to save username beginning with .', function(done) { + describe('Username Validation', function () { + it('should show error to save username beginning with .', function (done) { var _user = new User(user1); _user.username = '.login'; - _user.save(function(err) { + _user.save(function (err) { should.exist(err); done(); }); @@ -660,67 +660,67 @@ describe('User Model Unit Tests:', function () { var _user = new User(user1); _user.username = config.illegalUsernames[Math.floor(Math.random() * config.illegalUsernames.length)]; - _user.save(function(err) { + _user.save(function (err) { should.exist(err); done(); }); }); - it('should show error to save username end with .', function(done) { + it('should show error to save username end with .', function (done) { var _user = new User(user1); _user.username = 'login.'; - _user.save(function(err) { + _user.save(function (err) { should.exist(err); done(); }); }); - it('should show error to save username with ..', function(done) { + it('should show error to save username with ..', function (done) { var _user = new User(user1); _user.username = 'log..in'; - _user.save(function(err) { + _user.save(function (err) { should.exist(err); done(); }); }); - it('should show error to save username shorter than 3 character', function(done) { + it('should show error to save username shorter than 3 character', function (done) { var _user = new User(user1); _user.username = 'lo'; - _user.save(function(err) { + _user.save(function (err) { should.exist(err); done(); }); }); - it('should show error saving a username without at least one alphanumeric character', function(done) { + it('should show error saving a username without at least one alphanumeric character', function (done) { var _user = new User(user1); _user.username = '-_-'; - _user.save(function(err) { + _user.save(function (err) { should.exist(err); done(); }); }); - it('should show error saving a username longer than 34 characters', function(done) { + it('should show error saving a username longer than 34 characters', function (done) { var _user = new User(user1); _user.username = 'l'.repeat(35); - _user.save(function(err) { + _user.save(function (err) { should.exist(err); done(); }); }); - it('should save username with dot', function(done) { + it('should save username with dot', function (done) { var _user = new User(user1); _user.username = 'log.in'; - _user.save(function(err) { + _user.save(function (err) { should.not.exist(err); done(); }); diff --git a/modules/users/tests/server/user.server.routes.tests.js b/modules/users/tests/server/user.server.routes.tests.js index 6a51d633..6142c966 100644 --- a/modules/users/tests/server/user.server.routes.tests.js +++ b/modules/users/tests/server/user.server.routes.tests.js @@ -6,6 +6,7 @@ var semver = require('semver'), path = require('path'), mongoose = require('mongoose'), User = mongoose.model('User'), + config = require(path.resolve('./config/config')), express = require(path.resolve('./config/lib/express')); /** @@ -26,7 +27,7 @@ describe('User CRUD tests', function () { before(function (done) { // Get application - app = express.init(mongoose); + app = express.init(mongoose.connection.db); agent = request.agent(app); done(); @@ -325,7 +326,7 @@ describe('User CRUD tests', function () { should.not.exist(err); agent.post('/api/auth/forgot') .send({ - username: 'some_username_that_doesnt_exist' + usernameOrEmail: 'some_username_that_doesnt_exist' }) .expect(400) .end(function (err, res) { @@ -334,13 +335,13 @@ describe('User CRUD tests', function () { return done(err); } - res.body.message.should.equal('No account with that username has been found'); + res.body.message.should.equal('No account with that username or email has been found'); return done(); }); }); }); - it('forgot password should return 400 for no username provided', function (done) { + it('forgot password should return 400 for empty username/email', function (done) { var provider = 'facebook'; user.provider = provider; user.roles = ['user']; @@ -349,7 +350,7 @@ describe('User CRUD tests', function () { should.not.exist(err); agent.post('/api/auth/forgot') .send({ - username: '' + usernameOrEmail: '' }) .expect(422) .end(function (err, res) { @@ -358,7 +359,29 @@ describe('User CRUD tests', function () { return done(err); } - res.body.message.should.equal('Username field must not be blank'); + res.body.message.should.equal('Username/email field must not be blank'); + return done(); + }); + }); + }); + + it('forgot password should return 400 for no username or email provided', function (done) { + var provider = 'facebook'; + user.provider = provider; + user.roles = ['user']; + + user.save(function (err) { + should.not.exist(err); + agent.post('/api/auth/forgot') + .send({}) + .expect(422) + .end(function (err, res) { + // Handle error + if (err) { + return done(err); + } + + res.body.message.should.equal('Username/email field must not be blank'); return done(); }); }); @@ -373,7 +396,7 @@ describe('User CRUD tests', function () { should.not.exist(err); agent.post('/api/auth/forgot') .send({ - username: user.username + usernameOrEmail: user.username }) .expect(400) .end(function (err, res) { @@ -382,20 +405,20 @@ describe('User CRUD tests', function () { return done(err); } - res.body.message.should.equal('It seems like you signed up using your ' + user.provider + ' account'); + res.body.message.should.equal('It seems like you signed up using your ' + user.provider + ' account, please sign in using that provider.'); return done(); }); }); }); - it('forgot password should be able to reset password for user password reset request', function (done) { + it('forgot password should be able to reset password for user password reset request using username', function (done) { user.roles = ['user']; user.save(function (err) { should.not.exist(err); agent.post('/api/auth/forgot') .send({ - username: user.username + usernameOrEmail: user.username }) .expect(400) .end(function (err, res) { @@ -404,7 +427,33 @@ describe('User CRUD tests', function () { return done(err); } - User.findOne({ username: user.username.toLowerCase() }, function(err, userRes) { + User.findOne({ username: user.username.toLowerCase() }, function (err, userRes) { + userRes.resetPasswordToken.should.not.be.empty(); + should.exist(userRes.resetPasswordExpires); + res.body.message.should.be.equal('Failure sending email'); + return done(); + }); + }); + }); + }); + + it('forgot password should be able to reset password for user password reset request using email', function (done) { + user.roles = ['user']; + + user.save(function (err) { + should.not.exist(err); + agent.post('/api/auth/forgot') + .send({ + usernameOrEmail: user.email + }) + .expect(400) + .end(function (err, res) { + // Handle error + if (err) { + return done(err); + } + + User.findOne({ username: user.username.toLowerCase() }, function (err, userRes) { userRes.resetPasswordToken.should.not.be.empty(); should.exist(userRes.resetPasswordExpires); res.body.message.should.be.equal('Failure sending email'); @@ -421,7 +470,7 @@ describe('User CRUD tests', function () { should.not.exist(err); agent.post('/api/auth/forgot') .send({ - username: user.username + usernameOrEmail: user.username }) .expect(400) .end(function (err, res) { @@ -430,7 +479,7 @@ describe('User CRUD tests', function () { return done(err); } - User.findOne({ username: user.username.toLowerCase() }, function(err, userRes) { + User.findOne({ username: user.username.toLowerCase() }, function (err, userRes) { userRes.resetPasswordToken.should.not.be.empty(); should.exist(userRes.resetPasswordExpires); @@ -458,7 +507,7 @@ describe('User CRUD tests', function () { should.not.exist(err); agent.post('/api/auth/forgot') .send({ - username: user.username + usernameOrEmail: user.username }) .expect(400) .end(function (err, res) { @@ -1030,6 +1079,39 @@ describe('User CRUD tests', function () { }); }); + it('should be able to change profile picture and not fail if existing picture file does not exist', function (done) { + + user.profileImageURL = config.uploads.profile.image.dest + 'non-existing.png'; + + user.save(function (saveErr) { + // Handle error + if (saveErr) { + return done(saveErr); + } + + agent.post('/api/auth/signin') + .send(credentials) + .expect(200) + .end(function (signinErr) { + // Handle signin error + if (signinErr) { + return done(signinErr); + } + + agent.post('/api/users/picture') + .attach('newProfilePicture', './modules/users/client/img/profile/default.png') + .expect(200) + .end(function (userInfoErr) { + + should.not.exist(userInfoErr); + + return done(); + }); + }); + + }); + }); + afterEach(function (done) { User.remove().exec(done); }); diff --git a/package.json b/package.json index c11428a9..b0bda61f 100644 --- a/package.json +++ b/package.json @@ -32,35 +32,35 @@ "test:coverage": "gulp test:coverage", "postinstall": "npm run bower", "generate-ssl-certs": "scripts/generate-ssl-certs.sh", - "snyk-protect": "snyk protect", - "prepublish": "npm run snyk-protect" + "seed": "gulp seed", + "seed:prod": "gulp seed:prod", + "seed:test": "gulp seed:test" }, "dependencies": { "acl": "~0.4.10", - "async": "~2.3.0", + "async": "~2.5.0", "bencoding": "0.0.1", "body-parser": "~1.17.1", "bower": "~1.8.0", - "chalk": "~2.0.1", + "chalk": "~2.1.0", "compression": "~1.7.0", "connect-flash": "~0.1.1", "connect-mongo": "~1.3.2", "cookie-parser": "~1.4.1", - "crypto": "0.0.3", "express": "~4.15.2", "express-hbs": "^1.0.4", "express-session": "~1.15.2", "generate-password": "~1.3.0", "glob": "~7.1.1", - "helmet": "~3.6.1", + "helmet": "~3.8.1", "irc": "^0.5.2", - "jasmine-core": "~2.5.2", + "jasmine-core": "~2.7.0", "lodash": "~4.17.4", - "lusca": "~1.4.1", + "lusca": "~1.5.1", "method-override": "~2.3.8", "mocha": "~3.4.2", "moment": "^2.18.1", - "mongoose": "~4.10.2", + "mongoose": "~4.11.3", "morgan": "~1.8.1", "moviedb": "^0.2.8", "multer": "~1.3.0", @@ -87,16 +87,15 @@ "devDependencies": { "coveralls": "~2.13.0", "del": "^3.0.0", - "eslint": "~2.2.0", "eslint-config-airbnb": "~6.0.2", "gulp": "~3.9.1", "gulp-angular-templatecache": "~2.0.0", - "gulp-autoprefixer": "~3.1.0", + "gulp-autoprefixer": "~4.0.0", "gulp-concat": "~2.6.0", "gulp-csslint": "~1.0.0", "gulp-csso": "~3.0.0", "gulp-eslint": "~3.0.1", - "gulp-imagemin": "~3.2.0", + "gulp-imagemin": "~3.3.0", "gulp-istanbul": "~1.1.1", "gulp-less": "~3.3.0", "gulp-load-plugins": "~1.5.0", @@ -106,23 +105,19 @@ "gulp-protractor": "^4.0.0", "gulp-refresh": "~1.1.0", "gulp-rename": "~1.2.2", - "gulp-rev": "^7.1.2", + "gulp-rev": "~8.0.0", "gulp-sass": "~3.1.0", - "gulp-uglify": "~2.1.2", - "gulp-util": "~3.0.7", + "gulp-uglify": "~3.0.0", "imagemin-pngquant": "~5.0.0", - "istanbul": "~0.4.2", - "karma": "~1.6.0", - "karma-chrome-launcher": "~2.0.0", + "karma": "~1.7.0", + "karma-chrome-launcher": "~2.2.0", "karma-coverage": "~1.1.1", "karma-jasmine": "~1.1.0", "karma-mocha-reporter": "~2.2.3", "karma-ng-html2js-preprocessor": "~1.0.0", - "lcov-result-merger": "~1.2.0", - "run-sequence": "~2.0.0", - "semver": "~5.3.0", + "run-sequence": "~2.1.0", + "semver": "~5.4.1", "should": "~11.2.1", "supertest": "~3.0.0" - }, - "snyk": true + } }