From 515618a5cad6b9765dc5b7722d84558254bfb4eb Mon Sep 17 00:00:00 2001 From: mrkvon Date: Sat, 8 Dec 2018 13:03:08 +0100 Subject: [PATCH] Rewrite References api in ES9 (#813) * rewrite references api with es9 and Promises - es9 .eslintrc which extends the default .eslintrc - start rewriting tests/server/reference-create.server.routes.tests.js - fix eslint var/let/const errors in remaining reference server tests * further es7ifying of reference-create.server.routes.tests * organize .eslintrc files, keep moving reference tests to es7+ * tests/server/reference-read-one.server.routes.tests to es7+ * reference.server.jobs.tests to es7+ - small fix in reference-read-many tests * reference.server.model.tests to es7+ * migrate modules/references/server to es7+ * use .eslintrc.js with overrides for es9 configuration * make eslint rules for es2018 more consistent and strict - fix eslint rules and add exceptions to eslint rules --- .eslintrc.js | 49 +- .../entries/pushMessagingServiceWorker.js | 2 +- modules/core/client/app/config.js | 1 + .../directives/tr-boards.client.directive.js | 1 + .../reference.server.controller.js | 405 ++++--- .../jobs/references-publish.server.job.js | 10 +- .../server/models/reference.server.model.js | 10 +- .../policies/references.server.policy.js | 35 +- .../server/routes/reference.server.routes.js | 8 +- .../reference-create.server.routes.tests.js | 1074 ++++++----------- ...reference-read-many.server.routes.tests.js | 336 ++---- .../reference-read-one.server.routes.tests.js | 291 ++--- .../server/reference.server.jobs.tests.js | 106 +- .../server/reference.server.model.tests.js | 142 +-- testutils/data.server.testutils.js | 170 +-- 15 files changed, 1110 insertions(+), 1530 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 82852cb3c9..75737b8632 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,8 +1,14 @@ +const es2018rules = { + 'no-var': 2, + 'prefer-const': 2, + 'arrow-spacing': [2, { before: true, after: true }] +}; + module.exports = { - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module' - }, + /* + * this would ideally belong to the react overrides, but overrides can't include extends + * https://github.com/eslint/eslint/issues/8813 + */ extends: [ 'plugin:react/recommended' ], @@ -86,5 +92,38 @@ module.exports = { __TESTING__: true, _: false, AppConfig: true - } + }, + /* + eventually, after the migration, these overrides will become the main rules + + it would be nice to keep the rules for client and server separate, + because eventually, they want to become independent codebases. + */ + overrides: [{ + // overrides for server code + // ES 2018 - specify migrated files and folders here + files: [ + 'testutils/data.server.testutils.js', + 'modules/references/server/**', + 'modules/references/tests/server/**' + ], + parserOptions: { + ecmaVersion: 2018 + }, + rules: es2018rules + }, { + // overrides for client/react code + files: [ + 'config/webpack/**', + 'modules/core/client/app/config.js', + 'modules/**/client/components/**', + 'modules/core/client/directives/tr-boards.client.directive.js', + 'modules/core/client/services/photos.service.js' + ], + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module' + }, + rules: es2018rules + }] }; diff --git a/config/webpack/entries/pushMessagingServiceWorker.js b/config/webpack/entries/pushMessagingServiceWorker.js index a2eec67014..18d5837fbb 100644 --- a/config/webpack/entries/pushMessagingServiceWorker.js +++ b/config/webpack/entries/pushMessagingServiceWorker.js @@ -7,7 +7,7 @@ firebase.initializeApp({ 'messagingSenderId': FCM_SENDER_ID }); -var messaging = firebase.messaging(); +const messaging = firebase.messaging(); messaging.setBackgroundMessageHandler(function (payload) { // not actually used, but without it here firefox does not receive messages... diff --git a/modules/core/client/app/config.js b/modules/core/client/app/config.js index 27ac02977c..4c50e4f441 100644 --- a/modules/core/client/app/config.js +++ b/modules/core/client/app/config.js @@ -1,4 +1,5 @@ 'use strict'; +/* eslint no-var: 0 */ import ngreact from 'ngreact'; diff --git a/modules/core/client/directives/tr-boards.client.directive.js b/modules/core/client/directives/tr-boards.client.directive.js index 50c037dda6..4a830f1693 100644 --- a/modules/core/client/directives/tr-boards.client.directive.js +++ b/modules/core/client/directives/tr-boards.client.directive.js @@ -1,3 +1,4 @@ +/* eslint no-var: 0 */ import photos from '@/modules/core/client/services/photos.service'; (function () { diff --git a/modules/references/server/controllers/reference.server.controller.js b/modules/references/server/controllers/reference.server.controller.js index 9bf4666122..98398a21c5 100644 --- a/modules/references/server/controllers/reference.server.controller.js +++ b/modules/references/server/controllers/reference.server.controller.js @@ -1,24 +1,24 @@ 'use strict'; -var mongoose = require('mongoose'), - _ = require('lodash'), - path = require('path'), - async = require('async'), - errorService = require(path.resolve('./modules/core/server/services/error.server.service')), - emailService = require(path.resolve('./modules/core/server/services/email.server.service')), - pushService = require(path.resolve('./modules/core/server/services/push.server.service')), - userProfile = require(path.resolve('./modules/users/server/controllers/users.profile.server.controller')), - Reference = mongoose.model('Reference'), - User = mongoose.model('User'); +const mongoose = require('mongoose'), + _ = require('lodash'), + path = require('path'), + util = require('util'), + errorService = require(path.resolve('./modules/core/server/services/error.server.service')), + emailService = require(path.resolve('./modules/core/server/services/email.server.service')), + pushService = require(path.resolve('./modules/core/server/services/push.server.service')), + userProfile = require(path.resolve('./modules/users/server/controllers/users.profile.server.controller')), + Reference = mongoose.model('Reference'), + User = mongoose.model('User'); /** * Validate the request body and data consistency * of Create a reference */ function validateCreate(req) { - var valid = true; - var details = {}; - var interactionErrors = {}; + let valid = true; + const details = {}; + const interactionErrors = {}; // Can't create a reference to oneself if (req.user._id.equals(req.body.userTo)) { @@ -27,7 +27,7 @@ function validateCreate(req) { } // Some interaction must have happened - var isInteraction = req.body.interactions && (req.body.interactions.met || req.body.interactions.hostedMe || req.body.interactions.hostedThem); + const isInteraction = req.body.interactions && (req.body.interactions.met || req.body.interactions.hostedMe || req.body.interactions.hostedThem); if (!isInteraction) { valid = false; interactionErrors.any = 'missing'; @@ -57,12 +57,12 @@ function validateCreate(req) { } // No unexpected fields - var allowedFields = ['userTo', 'interactions', 'recommend']; - var fields = Object.keys(req.body); - var unexpectedFields = _.difference(fields, allowedFields); - var allowedInteractions = ['met', 'hostedMe', 'hostedThem']; - var interactions = Object.keys(req.body.interactions || {}); - var unexpectedInteractions = _.difference(interactions, allowedInteractions); + const allowedFields = ['userTo', 'interactions', 'recommend']; + const fields = Object.keys(req.body); + const unexpectedFields = _.difference(fields, allowedFields); + const allowedInteractions = ['met', 'hostedMe', 'hostedThem']; + const interactions = Object.keys(req.body.interactions || {}); + const unexpectedInteractions = _.difference(interactions, allowedInteractions); if (unexpectedFields.length > 0 || unexpectedInteractions.length > 0) { valid = false; details.fields = 'unexpected'; @@ -70,10 +70,17 @@ function validateCreate(req) { if (Object.keys(interactionErrors).length > 0) details.interactions = interactionErrors; - return { valid: valid, details: details }; + return { valid, details }; } -var nonpublicReferenceFields = [ +class ResponseError { + constructor({ status, body }) { + this.status = status; + this.body = body; + } +} + +const nonpublicReferenceFields = [ '_id', 'public', 'userFrom', @@ -81,7 +88,7 @@ var nonpublicReferenceFields = [ 'created' ]; -var referenceFields = nonpublicReferenceFields.concat([ +const referenceFields = nonpublicReferenceFields.concat([ 'interactions.met', 'interactions.hostedMe', 'interactions.hostedThem', @@ -106,16 +113,12 @@ function formatReference(reference, isNonpublicFullyDisplayed) { /** * Check if the reference already exists. If it exists, return an error in a callback. */ -function checkDuplicate(req, done) { - Reference.findOne({ userFrom: req.user._id, userTo: req.body.userTo }).exec(function (err, ref) { - if (err) return done(err); +async function checkDuplicate(req) { + const ref = await Reference.findOne({ userFrom: req.user._id, userTo: req.body.userTo }).exec(); - if (ref === null) { - return done(); - } + if (ref === null) return; - return done({ status: 409, body: { errType: 'conflict' } }); - }); + throw new ResponseError({ status: 409, body: { errType: 'conflict' } }); } /** @@ -146,130 +149,134 @@ function processResponses(res, next, resOrErr) { * @param {object} req - Express Request object * @param {function} cb - callback function */ -function validate(validator, req, cb) { - var validation = validator(req); +function validate(validator, req) { + const validation = validator(req); if (validation.valid) { - return cb(); + return; } - return cb({ status: 400, body: { errType: 'bad-request', details: validation.details } }); + throw new ResponseError({ status: 400, body: { errType: 'bad-request', details: validation.details } }); +} + +async function isUserToPublic(req) { + const userTo = await User.findOne({ _id: req.body.userTo }).exec(); + + // Can't create a reference to a nonexistent user + // Can't create a reference to a nonpublic user + if (!userTo || !userTo.public) { + throw new ResponseError({ + status: 404, + body: { + errType: 'not-found', + details: { + userTo: 'not found' + } + } + }); + } + + return userTo; +} + +function validateReplyToPublicReference(otherReference, req) { + if (otherReference && otherReference.public && req.body.recommend !== 'yes') { + throw new ResponseError({ + status: 400, + body: { + errType: 'bad-request', + details: { + recommend: '\'yes\' expected - response to public' + } + } + }); + } +} + +async function saveNewReference(referenceData) { + const reference = new Reference(referenceData); + return await reference.save(); +} + +async function publishOtherReference(otherReference) { + if (otherReference && !otherReference.public) { + otherReference.set({ public: true }); + await otherReference.save(); + } +} + +async function sendEmailNotification(userFrom, userTo, savedReference, otherReference) { + if (!otherReference) { + return util.promisify(emailService.sendReferenceNotificationFirst)(userFrom, userTo); + } else { + return util.promisify(emailService.sendReferenceNotificationSecond)(userFrom, userTo, savedReference); + } +} + +async function sendPushNotification(userFrom, userTo, { isFirst }) { + return util.promisify(pushService.notifyNewReference)(userFrom, userTo, { isFirst }); } /** * Create a reference - express middleware */ -exports.create = function (req, res, next) { +exports.create = async function (req, res, next) { - var userTo; // not to have to pass found user in callbacks - - return async.waterfall([ + // each of the following functions throws a special response error when it wants to respond + // this special error gets processed within the catch {} + try { // Synchronous validation of the request data consistency - validate.bind(this, validateCreate, req), + validate(validateCreate, req); + // Check that the reference is not duplicate - _.partial(checkDuplicate, req), + await checkDuplicate(req); + // Check if the receiver of the reference exists and is public - function isUserToPublic(cb) { - User.findOne({ _id: req.body.userTo }).exec(function (err, foundUser) { - if (err) return cb(err); - - userTo = foundUser; - - // Can't create a reference to a nonexistent user - // Can't create a reference to a nonpublic user - if (!userTo || !userTo.public) { - return cb({ - status: 404, - body: { - errType: 'not-found', - details: { - userTo: 'not found' - } - } - }); - } + const userTo = await isUserToPublic(req); - return cb(); - }); - }, // Check if the opposite direction reference exists - // when it exists, we want to make both references public - function getOtherReference(cb) { - Reference.findOne({ userFrom: req.body.userTo, userTo: req.user._id }).exec(function (err, ref) { - cb(err, ref); - }); - }, - // save the reference... - function saveNewReference(otherReference, cb) { - - // ... when the other reference is public, this one can only have value of recommend: yes ... - if (otherReference && otherReference.public && req.body.recommend !== 'yes') { - return cb({ - status: 400, - body: { - errType: 'bad-request', - details: { - recommend: '\'yes\' expected - response to public' - } - } - }); - } + // when it exists, we will want to make both references public + const otherReference = await Reference.findOne({ userFrom: req.body.userTo, userTo: req.user._id }).exec(); - // ...and make it public if it is a reference reply - var reference = new Reference(_.merge(req.body, { userFrom: req.user._id, public: !!otherReference })); + // when the other reference is public, this one can only have value of recommend: yes + validateReplyToPublicReference(otherReference, req); + + // save the reference... + const savedReference = await saveNewReference({ + ...req.body, + userFrom: req.user._id, + public: !!otherReference + }); - reference.save(function (err, savedReference) { - return cb(err, savedReference, otherReference); - }); - }, // ...and if this is a reference reply, make the other reference public, too - function publishOtherReference(savedReference, otherReference, cb) { - if (otherReference && !otherReference.public) { - otherReference.set({ public: true }); - return otherReference.save(function (err) { - return cb(err, savedReference, otherReference); - }); - } + await publishOtherReference(otherReference); - return cb(null, savedReference, otherReference); - }, // send email notification - function sendEmailNotification(savedReference, otherReference, cb) { - if (!otherReference) { - return emailService.sendReferenceNotificationFirst(req.user, userTo, function (err) { - cb(err, savedReference, otherReference); - }); - } else { - return emailService.sendReferenceNotificationSecond(req.user, userTo, savedReference, function (err) { - cb(err, savedReference, otherReference); - }); - } - }, + await sendEmailNotification(req.user, userTo, savedReference, otherReference); + // send push notification - function sendPushNotification(savedReference, otherReference, cb) { - return pushService.notifyNewReference(req.user, userTo, { isFirst: !otherReference }, function (err) { - cb(err, savedReference); - }); - }, + await sendPushNotification(req.user, userTo, { isFirst: !otherReference }); + // finally, respond - function respond(savedReference, cb) { - return cb({ - status: 201, - body: formatReference(savedReference, true) - }); - } - ], processResponses.bind(this, res, next)); + throw new ResponseError({ + status: 201, + body: formatReference(savedReference, true) + }); + + } catch (e) { + processResponses(res, next, e); + } }; /** * Validator for readMany controller */ function validateReadMany(req) { - var valid = true; - var details = {}; + let valid = true; + const details = {}; // check that query contains userFrom or userTo - var isQueryWithFilter = req.query.userFrom || req.query.userTo; + const isQueryWithFilter = req.query.userFrom || req.query.userTo; if (!isQueryWithFilter) { valid = false; details.userFrom = 'missing'; @@ -280,34 +287,35 @@ function validateReadMany(req) { ['userFrom', 'userTo'].forEach(function (param) { if (!req.query[param]) return; - var isParamValid = mongoose.Types.ObjectId.isValid(req.query[param]); + const isParamValid = mongoose.Types.ObjectId.isValid(req.query[param]); if (!isParamValid) { valid = false; details[param] = 'invalid'; } }); - return { valid: valid, details: details }; + return { valid, details }; } /** * Read references filtered by userFrom or userTo */ -exports.readMany = function readMany(req, res, next) { - - return async.waterfall([ - +exports.readMany = async function readMany(req, res, next) { + try { // validate the query - validate.bind(this, validateReadMany, req), + validate(validateReadMany, req); + + const { userFrom, userTo } = req.query; + const self = req.user; - // build a query (synchronous) - function buildQuery(cb) { - var query = { }; + // build a query + const query = (function buildQuery() { + const query = { }; /** * Allow non-public references only when userFrom or userTo is self */ - var isSelfUserFromOrUserTo = req.user._id.equals(req.query.userFrom) || req.user._id.equals(req.query.userTo); + const isSelfUserFromOrUserTo = self._id.equals(userFrom) || self._id.equals(userTo); if (!isSelfUserFromOrUserTo) { query.public = true; } @@ -315,40 +323,37 @@ exports.readMany = function readMany(req, res, next) { /** * Filter by userFrom */ - if (req.query.userFrom) { - query.userFrom = req.query.userFrom; + if (userFrom) { + query.userFrom = userFrom; } /** * Filter by userTo */ - if (req.query.userTo) { - query.userTo = req.query.userTo; + if (userTo) { + query.userTo = userTo; } - cb(null, query); - }, + return query; + }()); // find references by query - function findReferences(query, cb) { - Reference.find(query) - .select(referenceFields) - .populate('userFrom userTo', userProfile.userMiniProfileFields) - .exec(cb); - }, - - // prepare success response - function prepareSuccessResponse(references, cb) { - var isSelfUserFrom = req.user._id.equals(req.query.userFrom); - - // when userFrom is self, we can see the nonpublic references in their full form - cb({ - status: 200, - body: references.map(_.partial(formatReference, _, isSelfUserFrom)) - }); - } - - ], processResponses.bind(this, res, next)); + const references = await Reference.find(query) + .select(referenceFields) + .populate('userFrom userTo', userProfile.userMiniProfileFields) + .exec(); + + // is the logged user userFrom? + const isSelfUserFrom = self._id.equals(userFrom); + + // when userFrom is self, we can see the nonpublic references in their full form + throw new ResponseError({ + status: 200, + body: references.map(reference => formatReference(reference, isSelfUserFrom)) + }); + } catch (e) { + processResponses(res, next, e); + } }; @@ -357,61 +362,63 @@ exports.readMany = function readMany(req, res, next) { * @param {string} id - referenceId */ function validateReadOne(id) { - var valid = true; - var details = {}; + let valid = true; + const details = {}; - var isIdValid = mongoose.Types.ObjectId.isValid(id); + const isIdValid = mongoose.Types.ObjectId.isValid(id); if (!isIdValid) { valid = false; details.referenceId = 'invalid'; } - return { valid: valid, details: details }; + return { valid, details }; } /** * Load a reference by id to request.reference */ -exports.referenceById = function referenceById(req, res, next, id) { // eslint-disable-line no-unused-vars - // don't bother fetching a reference for non-public users or guests - if (!req.user || !req.user.public) return next(); +exports.referenceById = async function referenceById(req, res, next, id) { // eslint-disable-line no-unused-vars + try { + // don't bother fetching a reference for non-public users or guests + if (!req.user || !req.user.public) return next(); - async.waterfall([ // validate - validate.bind(this, validateReadOne, id), + validate(validateReadOne, id); + + const selfId = req.user._id; + // find the reference by id - function (cb) { - Reference.findById(req.params.referenceId) - .select(referenceFields) - .populate('userFrom userTo', userProfile.userMiniProfileFields) - .exec(cb); - }, + const reference = await Reference.findById(req.params.referenceId) + .select(referenceFields) + .populate('userFrom userTo', userProfile.userMiniProfileFields) + .exec(); + + const userFromId = (reference) ? reference.userFrom._id : null; + const userToId = (reference) ? reference.userTo._id : null; + // make sure that nonpublic references are not exposed - // assign reference to request object - function (reference, cb) { - - // nonpublic reference can be exposed to userFrom or userTo only. - var isExistentPublicOrFromToSelf = reference - && (reference.public || reference.userFrom._id.equals(req.user._id) || reference.userTo.equals(req.user._id)); - if (!isExistentPublicOrFromToSelf) { - return cb({ - status: 404, - body: { - errType: 'not-found', - details: { - reference: 'not found' - } + // nonpublic reference can be exposed to userFrom or userTo only. + const isExistentPublicOrFromToSelf = reference && (reference.public || userFromId.equals(selfId) || userToId.equals(selfId)); + if (!isExistentPublicOrFromToSelf) { + throw new ResponseError({ + status: 404, + body: { + errType: 'not-found', + details: { + reference: 'not found' } - }); - } - - // assign the reference to the request object - // when reference is public, only userFrom can see it whole - var isUserFrom = reference.userFrom._id.equals(req.user._id); - req.reference = formatReference(reference, isUserFrom); - return cb(); + } + }); } - ], processResponses.bind(this, res, next)); + + // assign the reference to the request object + // when reference is not public, only userFrom can see it whole + const isUserFrom = userFromId === selfId; + req.reference = formatReference(reference, isUserFrom); + return next(); + } catch (e) { + processResponses(res, next, e); + } }; diff --git a/modules/references/server/jobs/references-publish.server.job.js b/modules/references/server/jobs/references-publish.server.job.js index 140a888be0..f5574734d0 100644 --- a/modules/references/server/jobs/references-publish.server.job.js +++ b/modules/references/server/jobs/references-publish.server.job.js @@ -1,10 +1,10 @@ 'use strict'; -var path = require('path'), - mongoose = require('mongoose'), - moment = require('moment'), - config = require(path.resolve('./config/config')), - Reference = mongoose.model('Reference'); +const path = require('path'), + mongoose = require('mongoose'), + moment = require('moment'), + config = require(path.resolve('./config/config')), + Reference = mongoose.model('Reference'); /** * Find all references that are older than timeToReply Reference and non-public. diff --git a/modules/references/server/models/reference.server.model.js b/modules/references/server/models/reference.server.model.js index ad9ae5277c..1e8f2f37ab 100644 --- a/modules/references/server/models/reference.server.model.js +++ b/modules/references/server/models/reference.server.model.js @@ -3,17 +3,17 @@ /** * Module dependencies. */ -var mongoose = require('mongoose'), - uniqueValidation = require('mongoose-beautiful-unique-validation'), - Schema = mongoose.Schema; +const mongoose = require('mongoose'), + uniqueValidation = require('mongoose-beautiful-unique-validation'), + Schema = mongoose.Schema; /** * ReferenceUser Schema */ -var ReferenceSchema = new Schema({ +const ReferenceSchema = new Schema({ created: { type: Date, - default: function () { return Date.now(); }, // Date.now is wrapped for sinon.useFakeTimers() + default: () => Date.now(), // Date.now is wrapped for sinon.useFakeTimers() required: true }, /* diff --git a/modules/references/server/policies/references.server.policy.js b/modules/references/server/policies/references.server.policy.js index 83ab646c75..786f83056d 100644 --- a/modules/references/server/policies/references.server.policy.js +++ b/modules/references/server/policies/references.server.policy.js @@ -1,8 +1,8 @@ 'use strict'; -var acl = require('acl'), - path = require('path'), - errorService = require(path.resolve('./modules/core/server/services/error.server.service')); +let acl = require('acl'); +const path = require('path'), + errorService = require(path.resolve('./modules/core/server/services/error.server.service')); acl = new acl(new acl.memoryBackend()); @@ -19,22 +19,21 @@ exports.invokeRolesPolicies = function () { }]); }; -exports.isAllowed = function (req, res, next) { +exports.isAllowed = async function (req, res, next) { + try { + const roles = (req.user && req.user.roles) ? req.user.roles : ['guest']; - var roles = (req.user && req.user.roles) ? req.user.roles : ['guest']; + const isAllowed = await acl.areAnyRolesAllowed(roles, req.route.path, req.method.toLowerCase()); - acl.areAnyRolesAllowed(roles, req.route.path, req.method.toLowerCase(), function (err, isAllowed) { - if (err) { - - } else { - if (isAllowed && req.user.public) { - // Access granted! Invoke next middleware - return next(); - } - - return res.status(403).json({ - message: errorService.getErrorMessageByKey('forbidden') - }); + if (isAllowed && req.user.public) { + // Access granted! Invoke next middleware + return next(); } - }); + + return res.status(403).json({ + message: errorService.getErrorMessageByKey('forbidden') + }); + } catch (e) { + return next(e); + } }; diff --git a/modules/references/server/routes/reference.server.routes.js b/modules/references/server/routes/reference.server.routes.js index 2bad38f0fc..67ceb11bd4 100644 --- a/modules/references/server/routes/reference.server.routes.js +++ b/modules/references/server/routes/reference.server.routes.js @@ -1,9 +1,9 @@ 'use strict'; -var path = require('path'), - config = require(path.resolve('./config/config')), - referencePolicy = require('../policies/references.server.policy'), - references = require('../controllers/reference.server.controller'); +const path = require('path'), + config = require(path.resolve('./config/config')), + referencePolicy = require('../policies/references.server.policy'), + references = require('../controllers/reference.server.controller'); module.exports = function (app) { if (config.featureFlags.reference) { diff --git a/modules/references/tests/server/reference-create.server.routes.tests.js b/modules/references/tests/server/reference-create.server.routes.tests.js index 9d2b81fdc1..aa4251e855 100644 --- a/modules/references/tests/server/reference-create.server.routes.tests.js +++ b/modules/references/tests/server/reference-create.server.routes.tests.js @@ -1,18 +1,16 @@ 'use strict'; -var should = require('should'), - _ = require('lodash'), - request = require('supertest'), - async = require('async'), - path = require('path'), - sinon = require('sinon'), - mongoose = require('mongoose'), - Reference = mongoose.model('Reference'), - testutils = require(path.resolve('./testutils/server.testutil')), - utils = require(path.resolve('./testutils/data.server.testutils')), - express = require(path.resolve('./config/lib/express')); - -describe('Create a reference', function () { +const should = require('should'), + request = require('supertest'), + path = require('path'), + sinon = require('sinon'), + mongoose = require('mongoose'), + Reference = mongoose.model('Reference'), + testutils = require(path.resolve('./testutils/server.testutil')), + utils = require(path.resolve('./testutils/data.server.testutils')), + express = require(path.resolve('./config/lib/express')); + +describe('Create a reference', () => { // user can leave a reference to anyone // - types of interaction @@ -27,52 +25,48 @@ describe('Create a reference', function () { // after the given time or after both left reference, both references become public // we'll catch email and push notifications - var jobs = testutils.catchJobs(); + const jobs = testutils.catchJobs(); - var user1, + let user1, user2, user3Nonpublic; - var app = express.init(mongoose.connection); - var agent = request.agent(app); + const app = express.init(mongoose.connection); + const agent = request.agent(app); - var _usersPublic = utils.generateUsers(2, { public: true }); - var _usersNonpublic = utils.generateUsers(1, { + const _usersPublic = utils.generateUsers(2, { public: true }); + const _usersNonpublic = utils.generateUsers(1, { public: false, username: 'nonpublic', email: 'nonpublic@example.com' }); - var _users = _.concat(_usersPublic, _usersNonpublic); - beforeEach(function () { + const _users = [..._usersPublic, ..._usersNonpublic]; + + beforeEach(() => { sinon.useFakeTimers({ now: 1500000000000, toFake: ['Date'] }); }); - afterEach(function () { + afterEach(() => { sinon.restore(); }); - beforeEach(function (done) { - utils.saveUsers(_users, function (err, usrs) { - user1 = usrs[0]; - user2 = usrs[1]; - user3Nonpublic = usrs[2]; - done(err); - }); + beforeEach(async () => { + [user1, user2, user3Nonpublic] = await utils.saveUsers(_users); }); afterEach(utils.clearDatabase); - context('logged in', function () { + context('logged in', () => { // Sign in and sign out beforeEach(utils.signIn.bind(this, _users[0], agent)); afterEach(utils.signOut.bind(this, agent)); - context('valid request', function () { - context('every reference', function () { + context('valid request', () => { + context('every reference', () => { - it('respond with 201 Created and the new reference in body', function (done) { - agent.post('/api/references') + it('respond with 201 Created and the new reference in body', async () => { + const { body } = await agent.post('/api/references') .send({ userTo: user2._id, interactions: { @@ -82,127 +76,86 @@ describe('Create a reference', function () { }, recommend: 'yes' }) - .expect(201) - .end(function (err, res) { - if (err) { - return done(err); - } - - should(res.body).match({ - public: false, - userFrom: user1._id.toString(), - userTo: user2._id.toString(), - created: new Date().toISOString(), - interactions: { - met: true, - hostedMe: true, - hostedThem: true - }, - recommend: 'yes', - _id: /^[0-9a-f]{24}$/ - }); - - return done(); - }); - }); + .expect(201); - it('save reference to database', function (done) { - async.waterfall([ - // before, reference shouldn't be found in the database - function (cb) { - Reference.find({ userFrom: user1._id, userTo: user2._id }).exec(cb); - }, - function (references, cb) { - try { - should(references).have.length(0); - cb(); - } catch (e) { - cb(e); - } - }, - // send request - function (cb) { - agent.post('/api/references') - .send({ - userTo: user2._id, - interactions: { - met: true, - hostedMe: true, - hostedThem: true - }, - recommend: 'yes' - }) - .expect(201) - .end(function (err) { - cb(err); - }); - }, - // after, reference should be found in the database - function (cb) { - Reference.find({ userFrom: user1._id, userTo: user2._id }).exec(cb); + should(body).match({ + public: false, + userFrom: user1._id.toString(), + userTo: user2._id.toString(), + created: new Date().toISOString(), + interactions: { + met: true, + hostedMe: true, + hostedThem: true }, - function (references, cb) { - try { - should(references).have.length(1); - should(references[0]).match({ - userFrom: user1._id, - userTo: user2._id, - interactions: { - met: true, - hostedMe: true, - hostedThem: true - } - }); - cb(); - } catch (e) { - cb(e); - } - } - ], done); + recommend: 'yes', + _id: /^[0-9a-f]{24}$/ + }); }); + it('save reference to database', async () => { + // before, reference shouldn't be found in the database + const beforeReferences = await Reference.find({ userFrom: user1._id, userTo: user2._id }).exec(); + should(beforeReferences).have.length(0); - it('[duplicate reference (the same (from, to) combination)] 409 Conflict', function (done) { - async.waterfall([ - // send the first request and expect 201 Created - function (cb) { - agent.post('/api/references') - .send({ - userTo: user2._id, - interactions: { - met: true, - hostedMe: true, - hostedThem: true - }, - recommend: 'yes' - }) - .expect(201) - .end(function (err) { - cb(err); - }); - }, - // send the second request and expect 409 Conflict - function (cb) { - agent.post('/api/references') - .send({ - userTo: user2._id, - interactions: { - met: false, - hostedMe: true, - hostedThem: false - }, - recommend: 'no' - }) - .expect(409) - .end(function (err) { - cb(err); - }); + // send request + await agent.post('/api/references') + .send({ + userTo: user2._id, + interactions: { + met: true, + hostedMe: true, + hostedThem: true + }, + recommend: 'yes' + }) + .expect(201); + + // after, reference should be found in the database + const afterReferences = await Reference.find({ userFrom: user1._id, userTo: user2._id }).exec(); + should(afterReferences).have.length(1); + should(afterReferences[0]).match({ + userFrom: user1._id, + userTo: user2._id, + interactions: { + met: true, + hostedMe: true, + hostedThem: true } - ], done); + }); }); - it('[creating a reference for self] 400', function (done) { - agent.post('/api/references') + + it('[duplicate reference (the same (from, to) combination)] 409 Conflict', async () => { + // send the first request and expect 201 Created + await agent.post('/api/references') + .send({ + userTo: user2._id, + interactions: { + met: true, + hostedMe: true, + hostedThem: true + }, + recommend: 'yes' + }) + .expect(201); + + // send the second request and expect 409 Conflict + await agent.post('/api/references') + .send({ + userTo: user2._id, + interactions: { + met: false, + hostedMe: true, + hostedThem: false + }, + recommend: 'no' + }) + .expect(409); + }); + + it('[creating a reference for self] 400', async () => { + const { body } = await agent.post('/api/references') .send({ userTo: user1._id, // the same user as logged in user interactions: { @@ -212,26 +165,18 @@ describe('Create a reference', function () { }, recommend: 'no' }) - .expect(400) - .end(function (err, response) { - if (err) return done(err); - - try { - should(response.body).match({ - message: 'Bad request.', - details: { - userTo: 'self' - } - }); - return done(); - } catch (e) { - return done(e); - } - }); + .expect(400); + + should(body).match({ + message: 'Bad request.', + details: { + userTo: 'self' + } + }); }); - it('[creating a reference for nonexistent user] 404', function (done) { - agent.post('/api/references') + it('[creating a reference for nonexistent user] 404', async () => { + const { body } = await agent.post('/api/references') .send({ userTo: '0'.repeat(24), // nonexistent user id interactions: { @@ -241,26 +186,18 @@ describe('Create a reference', function () { }, recommend: 'no' }) - .expect(404) - .end(function (err, response) { - if (err) return done(err); - - try { - should(response.body).match({ - message: 'Not found.', - details: { - userTo: 'not found' - } - }); - return done(); - } catch (e) { - return done(e); - } - }); + .expect(404); + + should(body).match({ + message: 'Not found.', + details: { + userTo: 'not found' + } + }); }); - it('[creating a reference for non-public user] 404', function (done) { - agent.post('/api/references') + it('[creating a reference for non-public user] 404', async () => { + const { body } = await agent.post('/api/references') .send({ userTo: user3Nonpublic._id, // non-public user id interactions: { @@ -270,76 +207,43 @@ describe('Create a reference', function () { }, recommend: 'no' }) - .expect(404) - .end(function (err, response) { - if (err) return done(err); - - try { - should(response.body).match({ - message: 'Not found.', - details: { - userTo: 'not found' - } - }); - return done(); - } catch (e) { - return done(e); - } - }); + .expect(404); + + should(body).match({ + message: 'Not found.', + details: { + userTo: 'not found' + } + }); }); }); - context('initial reference', function () { - it('the reference is saved as private', function (done) { - async.waterfall([ - // send request - function (cb) { - agent.post('/api/references') - .send({ - userTo: user2._id, - interactions: { - met: true, - hostedMe: true, - hostedThem: true - }, - recommend: 'yes' - }) - .expect(201) - .end(function (err, response) { - if (err) return cb(err); - - try { - should(response).have.propertyByPath('body', 'public').equal(false); - return cb(); - } catch (e) { - return cb(e); - } - }); - }, - // after, reference should be found in the database - function (cb) { - Reference.findOne({ userFrom: user1._id, userTo: user2._id }).exec(function (err, reference) { - if (err) return cb(err); - - try { - should(reference).have.property('public', false); - return cb(); - } catch (e) { - return cb(e); - } - }); - } - ], done); + context('initial reference', () => { + it('the reference is saved as private', async () => { + // send request + const { body } = await agent.post('/api/references') + .send({ + userTo: user2._id, + interactions: { + met: true, + hostedMe: true, + hostedThem: true + }, + recommend: 'yes' + }) + .expect(201); + + should(body).have.property('public', false); + + // after, reference should be found in the database + const reference = await Reference.findOne({ userFrom: user1._id, userTo: user2._id }).exec(); + should(reference).have.property('public', false); }); - it('send email notification to target user', function (done) { - try { - should(jobs.length).equal(0); - } catch (e) { - return done(e); - } + it('send email notification to target user', async () => { + should(jobs.length).equal(0); - agent.post('/api/references') + await agent.post('/api/references') .send({ userTo: user2._id, interactions: { @@ -349,33 +253,24 @@ describe('Create a reference', function () { }, recommend: 'yes' }) - .expect(201) - .end(function (err) { - if (err) return done(err); - - try { - var emailJobs = jobs.filter(function (job) { return job.type === 'send email'; }); - should(emailJobs.length).equal(1); - - var job = emailJobs[0]; - // @TODO design the email (subject, body, ...) - should(job.data.subject).equal('New reference from ' + user1.username); - should(job.data.to.address).equal(user2.email); - // @TODO add the right link - should(job.data.text) - .containEql('/profile/' + user1.username + '/references/new'); - should(job.data.html) - .containEql('/profile/' + user1.username + '/references/new'); - - return done(); - } catch (e) { - return done(e); - } - }); + .expect(201); + + const emailJobs = jobs.filter(job => job.type === 'send email'); + should(emailJobs.length).equal(1); + + const [job] = emailJobs; + // @TODO design the email (subject, body, ...) + should(job.data.subject).equal(`New reference from ${user1.username}`); + should(job.data.to.address).equal(user2.email); + // @TODO add the right link + should(job.data.text) + .containEql(`/profile/${user1.username}/references/new`); + should(job.data.html) + .containEql(`/profile/${user1.username}/references/new`); }); - it('push notification', function (done) { - agent.post('/api/references') + it('push notification', async () => { + await agent.post('/api/references') .send({ userTo: user2._id, interactions: { @@ -385,296 +280,195 @@ describe('Create a reference', function () { }, recommend: 'yes' }) - .expect(201) - .end(function (err) { - if (err) return done(err); - - try { - var pushJobs = jobs.filter(function (job) { return job.type === 'send push message'; }); - should(pushJobs.length).equal(1); - - var job = pushJobs[0]; - should(job.data.userId).equal(user2._id.toString()); - should(job.data.notification.title).equal('Trustroots'); - // @TODO design the notification text - should(job.data.notification.body) - .equal(user1.username + ' gave you a new reference. Give a reference back.'); - should(job.data.notification.click_action) - .containEql('/profile/' + user1.username + '/references/new'); - - return done(); - } catch (e) { - return done(e); - } - }); + .expect(201); + + const pushJobs = jobs.filter(job => job.type === 'send push message'); + should(pushJobs.length).equal(1); + + const [job] = pushJobs; + should(job.data.userId).equal(user2._id.toString()); + should(job.data.notification.title).equal('Trustroots'); + // @TODO design the notification text + should(job.data.notification.body) + .equal(`${user1.username} gave you a new reference. Give a reference back.`); + should(job.data.notification.click_action) + .containEql(`/profile/${user1.username}/references/new`); }); }); - context('reply reference', function () { + context('reply reference', () => { + + it('set both references as public', async () => { + // first create a non-public reference in the opposite direction + const reference = new Reference({ + userFrom: user2._id, + userTo: user1._id, + met: true, + recommend: 'no', + public: false + }); - it('set both references as public', function (done) { - async.waterfall([ - // first create a non-public reference in the opposite direction - function (cb) { - var reference = new Reference({ - userFrom: user2._id, - userTo: user1._id, + await reference.save(); + + // create the opposite direction reference + const { body } = await agent + .post('/api/references') + .send({ + userTo: user2._id, + interactions: { met: true, - recommend: 'no', - public: false - }); + hostedMe: true, + hostedThem: true + }, + recommend: 'yes' + }) + .expect(201); - return reference.save(function (err) { - return cb(err); - }); - }, - // create the opposite direction reference - function (cb) { - agent.post('/api/references') - .send({ - userTo: user2._id, - interactions: { - met: true, - hostedMe: true, - hostedThem: true - }, - recommend: 'yes' - }) - .expect(201) - .end(function (err, response) { - if (err) return cb(err); - - try { - should(response).have.propertyByPath('body', 'public').equal(true); - return cb(); - } catch (e) { - return cb(e); - } - }); - }, - // after, both references should be found in the database and public - function (cb) { - Reference.findOne({ userFrom: user2._id, userTo: user1._id }).exec(function (err, reference) { - if (err) return cb(err); - - try { - should(reference).have.property('public', true); - return cb(); - } catch (e) { - return cb(e); - } - }); - }, - function (cb) { - Reference.findOne({ userFrom: user1._id, userTo: user2._id }).exec(function (err, reference) { - if (err) return cb(err); - - try { - should(reference).have.property('public', true); - return cb(); - } catch (e) { - return cb(e); - } - }); - } - ], done); + should(body).have.property('public', true); + + // after, both references should be found in the database and public + const reference2To1 = await Reference.findOne({ userFrom: user2._id, userTo: user1._id }).exec(); + should(reference2To1).have.property('public', true); + + const reference1To2 = await Reference.findOne({ userFrom: user1._id, userTo: user2._id }).exec(); + should(reference1To2).have.property('public', true); }); - it('only positive recommendation is allowed when opposite-direction public reference exists', function (done) { - async.waterfall([ - // first create a public reference in the opposite direction - function (cb) { - var reference = new Reference({ - userFrom: user2._id, - userTo: user1._id, + it('only positive recommendation is allowed when opposite-direction public reference exists', async () => { + // first create a public reference in the opposite direction + const reference = new Reference({ + userFrom: user2._id, + userTo: user1._id, + met: true, + recommend: 'no', + public: true + }); + + await reference.save(); + + // create a response reference with recommend: 'no' + // should fail + const { body } = await agent + .post('/api/references') + .send({ + userTo: user2._id, + interactions: { met: true, - recommend: 'no', - public: true - }); + hostedMe: true, + hostedThem: true + }, + recommend: 'no' + }) + .expect(400); - return reference.save(function (err) { - return cb(err); - }); - }, - // create a response reference with recommend: 'no' - // should fail - function (cb) { - agent.post('/api/references') - .send({ - userTo: user2._id, - interactions: { - met: true, - hostedMe: true, - hostedThem: true - }, - recommend: 'no' - }) - .expect(400) - .end(function (err, response) { - if (err) return cb(err); - - try { - should(response).have.propertyByPath('body').match({ - message: 'Bad request.', - details: { - recommend: '\'yes\' expected - response to public' - } - }); - return cb(); - } catch (e) { - return cb(e); - } - }); - }, - // create a response reference with recommend: 'yes' - // should succeed - function (cb) { - agent.post('/api/references') - .send({ - userTo: user2._id, - interactions: { - met: true, - hostedMe: true, - hostedThem: true - }, - recommend: 'yes' - }) - .expect(201) - .end(function (err) { - return cb(err); - }); + should(body).match({ + message: 'Bad request.', + details: { + recommend: '\'yes\' expected - response to public' } - ], done); + }); + + // create a response reference with recommend: 'yes' + // should succeed + await agent.post('/api/references') + .send({ + userTo: user2._id, + interactions: { + met: true, + hostedMe: true, + hostedThem: true + }, + recommend: 'yes' + }) + .expect(201); }); - it('send email notification about the received reference', function (done) { - try { - should(jobs.length).equal(0); - } catch (e) { - return done(e); - } + it('send email notification about the received reference', async () => { + should(jobs.length).equal(0); - async.waterfall([ - // first create a reference in the opposite direction - function (cb) { - var reference = new Reference({ - userFrom: user2._id, - userTo: user1._id, - met: true, - recommend: 'no' - }); + // first create a reference in the opposite direction + const reference = new Reference({ + userFrom: user2._id, + userTo: user1._id, + met: true, + recommend: 'no' + }); - return reference.save(function (err) { - return cb(err); - }); - }, - function (cb) { - agent.post('/api/references') - .send({ - userTo: user2._id, - interactions: { - met: true, - hostedMe: true, - hostedThem: true - }, - recommend: 'yes' - }) - .expect(201) - .end(function (err) { - if (err) return cb(err); - - try { - var emailJobs = jobs.filter(function (job) { return job.type === 'send email'; }); - should(emailJobs.length).equal(1); - - var job = emailJobs[0]; - // @TODO design the email (subject, body, ...) - should(job.data.subject).equal('New reference from ' + user1.username); - should(job.data.to.address).equal(user2.email); - // @TODO add the right link - // this is a link to the own references - see my references - // because I already gave a reference - should(job.data.text) - .containEql('/profile/' + user2.username + '/references'); - should(job.data.html) - .containEql('/profile/' + user2.username + '/references'); - - return cb(); - } catch (e) { - return cb(e); - } - }); - } - ], done); + await reference.save(); + await agent.post('/api/references') + .send({ + userTo: user2._id, + interactions: { + met: true, + hostedMe: true, + hostedThem: true + }, + recommend: 'yes' + }) + .expect(201); + + const emailJobs = jobs.filter(job => job.type === 'send email'); + should(emailJobs.length).equal(1); + + const [job] = emailJobs; + // @TODO design the email (subject, body, ...) + should(job.data.subject).equal(`New reference from ${user1.username}`); + should(job.data.to.address).equal(user2.email); + // @TODO add the right link + // this is a link to the own references - see my references + // because I already gave a reference + should(job.data.text) + .containEql(`/profile/${user2.username}/references`); + should(job.data.html) + .containEql(`/profile/${user2.username}/references`); }); - it('push notification', function (done) { - try { - should(jobs.length).equal(0); - } catch (e) { - return done(e); - } + it('push notification', async () => { + should(jobs.length).equal(0); - async.waterfall([ - // first create a reference in the opposite direction - function (cb) { - var reference = new Reference({ - userFrom: user2._id, - userTo: user1._id, - interaction: { - met: true - }, - recommend: 'no' - }); - - return reference.save(function (err) { - return cb(err); - }); + // first create a reference in the opposite direction + const reference = new Reference({ + userFrom: user2._id, + userTo: user1._id, + interaction: { + met: true }, - function (cb) { - agent.post('/api/references') - .send({ - userTo: user2._id, - interactions: { - met: true, - hostedMe: true, - hostedThem: true - }, - recommend: 'yes' - }) - .expect(201) - .end(function (err) { - if (err) return cb(err); - - try { - var pushJobs = jobs.filter(function (job) { return job.type === 'send push message'; }); - should(pushJobs.length).equal(1); - - var job = pushJobs[0]; - should(job.data.userId).equal(user2._id.toString()); - should(job.data.notification.title).equal('Trustroots'); - // @TODO design the notification text - should(job.data.notification.body) - .equal(user1.username + ' gave you a new reference. You can see it.'); - should(job.data.notification.click_action) - .containEql('/profile/' + user2.username + '/references'); - - return cb(); - } catch (e) { - return cb(e); - } - }); - } - ], done); + recommend: 'no' + }); + + await reference.save(); + await agent.post('/api/references') + .send({ + userTo: user2._id, + interactions: { + met: true, + hostedMe: true, + hostedThem: true + }, + recommend: 'yes' + }) + .expect(201); + + const pushJobs = jobs.filter(job => job.type === 'send push message'); + should(pushJobs.length).equal(1); + + const [job] = pushJobs; + should(job.data.userId).equal(user2._id.toString()); + should(job.data.notification.title).equal('Trustroots'); + // @TODO design the notification text + should(job.data.notification.body) + .equal(`${user1.username} gave you a new reference. You can see it.`); + should(job.data.notification.click_action) + .containEql(`/profile/${user2.username}/references`); }); }); }); - context('invalid request', function () { - it('[invalid value in interaction types] 400', function (done) { - agent.post('/api/references') + context('invalid request', () => { + it('[invalid value in interaction types] 400', async () => { + const { body } = await agent.post('/api/references') .send({ userTo: user2._id, interactions: { @@ -683,28 +477,20 @@ describe('Create a reference', function () { }, recommend: 'unknown' }) - .expect(400) - .end(function (err, response) { - if (err) return done(err); - - try { - should(response.body).match({ - message: 'Bad request.', - details: { - interactions: { - met: 'boolean expected' - } - } - }); - return done(); - } catch (e) { - return done(e); + .expect(400); + + should(body).match({ + message: 'Bad request.', + details: { + interactions: { + met: 'boolean expected' } - }); + } + }); }); - it('[invalid recommendation] 400', function (done) { - agent.post('/api/references') + it('[invalid recommendation] 400', async () => { + const { body } = await agent.post('/api/references') .send({ userTo: user2._id, interactions: { @@ -713,26 +499,18 @@ describe('Create a reference', function () { }, recommend: 'invalid' }) - .expect(400) - .end(function (err, response) { - if (err) return done(err); - - try { - should(response.body).match({ - message: 'Bad request.', - details: { - recommend: 'one of \'yes\', \'no\', \'unknown\' expected' - } - }); - return done(); - } catch (e) { - return done(e); - } - }); + .expect(400); + + should(body).match({ + message: 'Bad request.', + details: { + recommend: 'one of \'yes\', \'no\', \'unknown\' expected' + } + }); }); - it('[invalid userTo] 400', function (done) { - agent.post('/api/references') + it('[invalid userTo] 400', async () => { + const { body } = await agent.post('/api/references') .send({ userTo: 'hello', interactions: { @@ -740,52 +518,36 @@ describe('Create a reference', function () { }, recommend: 'yes' }) - .expect(400) - .end(function (err, response) { - if (err) return done(err); - - try { - should(response.body).match({ - message: 'Bad request.', - details: { - userTo: 'userId expected' - } - }); - return done(); - } catch (e) { - return done(e); - } - }); + .expect(400); + + should(body).match({ + message: 'Bad request.', + details: { + userTo: 'userId expected' + } + }); }); - it('[missing userTo] 400', function (done) { - agent.post('/api/references') + it('[missing userTo] 400', async () => { + const { body } = await agent.post('/api/references') .send({ interactions: { hostedMe: true }, recommend: 'yes' }) - .expect(400) - .end(function (err, response) { - if (err) return done(err); - - try { - should(response.body).match({ - message: 'Bad request.', - details: { - userTo: 'missing' - } - }); - return done(); - } catch (e) { - return done(e); - } - }); + .expect(400); + + should(body).match({ + message: 'Bad request.', + details: { + userTo: 'missing' + } + }); }); - it('[unexpected fields] 400', function (done) { - agent.post('/api/references') + it('[unexpected fields] 400', async () => { + const { body } = await agent.post('/api/references') .send({ userTo: user2._id, interactions: { @@ -794,26 +556,18 @@ describe('Create a reference', function () { recommend: 'yes', foo: 'bar' }) - .expect(400) - .end(function (err, response) { - if (err) return done(err); - - try { - should(response.body).match({ - message: 'Bad request.', - details: { - fields: 'unexpected' - } - }); - return done(); - } catch (e) { - return done(e); - } - }); + .expect(400); + + should(body).match({ + message: 'Bad request.', + details: { + fields: 'unexpected' + } + }); }); - it('[all interaction types false or missing] 400', function (done) { - agent.post('/api/references') + it('[all interaction types false or missing] 400', async () => { + const { body } = await agent.post('/api/references') .send({ userTo: user2._id, met: false, @@ -822,63 +576,37 @@ describe('Create a reference', function () { }, recommend: 'yes' }) - .expect(400) - .end(function (err, response) { - if (err) return done(err); - - try { - should(response.body).match({ - message: 'Bad request.', - details: { - interactions: { - any: 'missing' - } - } - }); - return done(); - } catch (e) { - return done(e); + .expect(400); + + should(body).match({ + message: 'Bad request.', + details: { + interactions: { + any: 'missing' } - }); + } + }); }); }); }); - context('logged in as non-public user', function () { + context('logged in as non-public user', () => { // Sign in and sign out beforeEach(utils.signIn.bind(this, _usersNonpublic[0], agent)); afterEach(utils.signOut.bind(this, agent)); - it('403', function (done) { - agent.post('/api/references') - .send({ - - }) - .expect(403) - .end(function (err) { - if (err) { - return done(err); - } - - return done(); - }); + it('403', async () => { + await agent.post('/api/references') + .send({ }) + .expect(403); }); }); - context('not logged in', function () { - it('403', function (done) { - agent.post('/api/references') - .send({ - - }) - .expect(403) - .end(function (err) { - if (err) { - return done(err); - } - - return done(); - }); + context('not logged in', () => { + it('403', async () => { + await agent.post('/api/references') + .send({ }) + .expect(403); }); }); }); diff --git a/modules/references/tests/server/reference-read-many.server.routes.tests.js b/modules/references/tests/server/reference-read-many.server.routes.tests.js index 25e69bfdf5..e5267d1f43 100644 --- a/modules/references/tests/server/reference-read-many.server.routes.tests.js +++ b/modules/references/tests/server/reference-read-many.server.routes.tests.js @@ -1,16 +1,15 @@ 'use strict'; -var _ = require('lodash'), - mongoose = require('mongoose'), - path = require('path'), - request = require('supertest'), - should = require('should'), - sinon = require('sinon'), - utils = require(path.resolve('./testutils/data.server.testutils')), - userProfile = require(path.resolve('./modules/users/server/controllers/users.profile.server.controller')), - express = require(path.resolve('./config/lib/express')); - -describe('Read references by userFrom Id or userTo Id', function () { +const mongoose = require('mongoose'), + path = require('path'), + request = require('supertest'), + should = require('should'), + sinon = require('sinon'), + utils = require(path.resolve('./testutils/data.server.testutils')), + userProfile = require(path.resolve('./modules/users/server/controllers/users.profile.server.controller')), + express = require(path.resolve('./config/lib/express')); + +describe('Read references by userFrom Id or userTo Id', () => { // GET /references?userFrom=:UserId&userTo=:UserId // logged in public user can read all public references by userFrom @@ -19,32 +18,29 @@ describe('Read references by userFrom Id or userTo Id', function () { // ... can not read private references to self // ... can read a specific reference by specifying userFrom and userTo // when userFrom or userTo doesn't exist, we simply return empty list - var app = express.init(mongoose.connection); - var agent = request.agent(app); + const app = express.init(mongoose.connection); + const agent = request.agent(app); - var users; + let users; - var _usersPublic = utils.generateUsers(6, { public: true }); - var _usersPrivate = utils.generateUsers(3, { + const _usersPublic = utils.generateUsers(6, { public: true }); + const _usersPrivate = utils.generateUsers(3, { public: false, username: 'nonpublic', email: 'nonpublic@example.com' }); - var _users = _.concat(_usersPublic, _usersPrivate); + const _users = [..._usersPublic, ..._usersPrivate]; - beforeEach(function () { + beforeEach(() => { sinon.useFakeTimers({ now: new Date('2018-01-12'), toFake: ['Date'] }); }); - afterEach(function () { + afterEach(() => { sinon.restore(); }); - beforeEach(function (done) { - utils.saveUsers(_users, function (err, usrs) { - users = usrs; - return done(err); - }); + beforeEach(async () => { + users = await utils.saveUsers(_users); }); /** @@ -65,7 +61,7 @@ describe('Read references by userFrom Id or userTo Id', function () { * 4 F . . . . . * 5 T . . . . . */ - var referenceData = [ + const referenceData = [ [0, 1], [0, 2], [0, 3, { public: false }], [0, 4, { public: false }], [0, 5], [1, 0], [1, 2], [1, 3], [1, 5], [2, 0], [2, 3], [2, 4, { public: false }], [2, 5], @@ -74,220 +70,152 @@ describe('Read references by userFrom Id or userTo Id', function () { [5, 0] ]; - beforeEach(function (done) { - var _references = utils.generateReferences(users, referenceData); + beforeEach(async () => { + const _references = utils.generateReferences(users, referenceData); - utils.saveReferences(_references, done); + await utils.saveReferences(_references); }); afterEach(utils.clearDatabase); - context('logged in as public user', function () { + context('logged in as public user', () => { beforeEach(utils.signIn.bind(this, _usersPublic[0], agent)); afterEach(utils.signOut.bind(this, agent)); - it('[param userFrom] respond with all public references from userFrom', function (done) { - agent - .get('/api/references?userFrom=' + users[2]._id) - .expect(200) - .end(function (err, res) { - if (err) return done(err); - - try { - // user2 gave 3 public and 1 non-public references - should(res).have.property('body').which.is.Array().of.length(3); - return done(); - } catch (e) { - return done(e); - } - }); + it('[param userFrom] respond with all public references from userFrom', async () => { + const { body } = await agent + .get(`/api/references?userFrom=${users[2]._id}`) + .expect(200); + + // user2 gave 3 public and 1 non-public references + should(body).be.Array().of.length(3); }); - it('the references in response have expected structure, userFrom & userTo have miniProfile', function (done) { - agent - .get('/api/references?userFrom=' + users[2]._id) - .expect(200) - .end(function (err, res) { - if (err) return done(err); - - try { - res.body.forEach(function (ref) { - should(ref) - .have.property('userFrom') - .which.is.Object() - .with.properties(userProfile.userMiniProfileFields.split(' ').slice(2, -1)); - - should(ref) - .have.property('userTo') - .which.is.Object() - .with.properties(userProfile.userMiniProfileFields.split(' ').slice(2, -1)); - - should(ref).have.propertyByPath('interactions', 'met').Boolean(); - should(ref).have.propertyByPath('interactions', 'hostedMe').Boolean(); - should(ref).have.propertyByPath('interactions', 'hostedThem').Boolean(); - should(ref).have.property('public', true); - should(ref).have.property('created', new Date().toISOString()); - should(ref).have.property('recommend').oneOf('yes', 'no', 'unknown'); - should(ref).have.property('_id').String().match(/[0-9a-f]{24}/); - }); - - return done(); - } catch (e) { - return done(e); - } - }); + it('the references in response have expected structure, userFrom & userTo have miniProfile', async () => { + const { body } = await agent + .get(`/api/references?userFrom=${users[2]._id}`) + .expect(200); + + for (const ref of body) { + should(ref) + .have.property('userFrom') + .which.is.Object() + .with.properties(userProfile.userMiniProfileFields.split(' ').slice(2, -1)); + + should(ref) + .have.property('userTo') + .which.is.Object() + .with.properties(userProfile.userMiniProfileFields.split(' ').slice(2, -1)); + + should(ref).have.propertyByPath('interactions', 'met').Boolean(); + should(ref).have.propertyByPath('interactions', 'hostedMe').Boolean(); + should(ref).have.propertyByPath('interactions', 'hostedThem').Boolean(); + should(ref).have.property('public', true); + should(ref).have.property('created', new Date().toISOString()); + should(ref).have.property('recommend').oneOf('yes', 'no', 'unknown'); + should(ref).have.property('_id').String().match(/[0-9a-f]{24}/); + } }); - it('[param userTo] respond with all public references to userTo', function (done) { - agent - .get('/api/references?userTo=' + users[2]._id) - .expect(200) - .end(function (err, res) { - if (err) return done(err); - - try { - // user2 has received 2 public and 1 non-public reference - should(res).have.property('body').which.is.Array().of.length(2); - return done(); - } catch (e) { - return done(e); - } - }); + it('[param userTo] respond with all public references to userTo', async () => { + const { body } = await agent + .get(`/api/references?userTo=${users[2]._id}`) + .expect(200); + + // user2 has received 2 public and 1 non-public reference + should(body).be.Array().of.length(2); }); - it('[params userFrom and userTo] respond with 1 or 0 public reference from userFrom to userTo', function (done) { - agent - .get('/api/references?userFrom=' + users[2]._id + '&userTo=' + users[5]._id) - .expect(200) - .end(function (err, res) { - if (err) return done(err); - - try { - // there is 1 public reference from user2 to user5 - should(res).have.property('body').which.is.Array().of.length(1); - return done(); - } catch (e) { - return done(e); - } - }); + it('[params userFrom and userTo] respond with 1 or 0 public reference from userFrom to userTo', async () => { + const { body } = await agent + .get(`/api/references?userFrom=${users[2]._id}&userTo=${users[5]._id}`) + .expect(200); + + // there is 1 public reference from user2 to user5 + should(body).be.Array().of.length(1); }); - it('[userFrom is self] display all public and private references from userFrom', function (done) { - agent + it('[userFrom is self] display all public and private references from userFrom', async () => { + const { body } = await agent .get('/api/references?userFrom=' + users[0]._id) - .expect(200) - .end(function (err, res) { - if (err) return done(err); - - try { - // user0 has given 3 public and 2 non-public reference - // and should see all 5 of them - should(res).have.property('body').which.is.Array().of.length(5); - - var nonpublic = res.body.filter(function (ref) { return !ref.public; }); - should(nonpublic).length(2); - // the reference details are also present - should(nonpublic[0]).have.keys('recommend', 'interactions'); - should(nonpublic[0].interactions).have.keys('met', 'hostedMe', 'hostedThem'); - - return done(); - } catch (e) { - return done(e); - } - }); + .expect(200); + + // user0 has given 3 public and 2 non-public reference + // and should see all 5 of them + should(body).be.Array().of.length(5); + + const nonpublic = body.filter(ref => !ref.public); + should(nonpublic).length(2); + // the reference details are also present + should(nonpublic[0]).have.keys('recommend', 'interactions'); + should(nonpublic[0].interactions).have.keys('met', 'hostedMe', 'hostedThem'); }); - it('[userTo is self] private references are included in limited form (only userFrom, userTo, public, created)', function (done) { - agent - .get('/api/references?userTo=' + users[0]._id) - .expect(200) - .end(function (err, res) { - if (err) return done(err); - - try { - // user0 has received 4 public and 1 non-public reference - // and should see all 5 of them - // but the 1 non-public should have only fields userFrom, userTo, public, created - should(res).have.property('body').which.is.Array().of.length(5); - - var nonpublic = res.body.filter(function (ref) { return !ref.public; }); - should(nonpublic).be.Array().of.length(1); - should(nonpublic[0]).match({ - public: false, - created: new Date().toISOString() - }); - should(nonpublic[0]).have.only.keys('_id', 'userFrom', 'userTo', 'created', 'public'); - return done(); - } catch (e) { - return done(e); - } - }); + it('[userTo is self] private references are included in limited form (only userFrom, userTo, public, created)', async () => { + const { body } = await agent + .get(`/api/references?userTo=${users[0]._id}`) + .expect(200); + + // user0 has received 4 public and 1 non-public reference + // and should see all 5 of them + // but the 1 non-public should have only fields userFrom, userTo, public, created + should(body).be.Array().of.length(5); + + const nonpublic = body.filter(ref => !ref.public); + should(nonpublic).be.Array().of.length(1); + should(nonpublic[0]).match({ + public: false, + created: new Date().toISOString() + }); + should(nonpublic[0]).have.only.keys('_id', 'userFrom', 'userTo', 'created', 'public'); }); - it('[no params] 400 and error', function (done) { - agent + it('[no params] 400 and error', async () => { + const { body } = await agent .get('/api/references') - .expect(400) - .end(function (err, res) { - if (err) return done(err); - - try { - should(res.body).eql({ - message: 'Bad request.', - details: { - userFrom: 'missing', - userTo: 'missing' - } - }); - return done(); - } catch (e) { - return done(e); - } - }); + .expect(400); + + should(body).eql({ + message: 'Bad request.', + details: { + userFrom: 'missing', + userTo: 'missing' + } + }); }); - it('[invalid params] 400 and error', function (done) { - agent + it('[invalid params] 400 and error', async () => { + const { body } = await agent .get('/api/references?userFrom=asdf&userTo=1') - .expect(400) - .end(function (err, res) { - if (err) return done(err); - - try { - should(res.body).eql({ - message: 'Bad request.', - details: { - userFrom: 'invalid', - userTo: 'invalid' - } - }); - return done(); - } catch (e) { - return done(e); - } - }); + .expect(400); + + should(body).eql({ + message: 'Bad request.', + details: { + userFrom: 'invalid', + userTo: 'invalid' + } + }); }); }); - context('logged in as non-public user', function () { + context('logged in as non-public user', () => { beforeEach(utils.signIn.bind(this, _usersPrivate[0], agent)); afterEach(utils.signOut.bind(this, agent)); - it('403', function (done) { - agent - .get('/api/references?userFrom=' + users[2]._id) - .expect(403) - .end(done); + it('403', async () => { + await agent + .get(`/api/references?userFrom=${users[2]._id}`) + .expect(403); }); }); - context('not logged in', function () { - it('403', function (done) { - agent - .get('/api/references?userFrom=' + users[2]._id) - .expect(403) - .end(done); + context('not logged in', () => { + it('403', async () => { + await agent + .get(`/api/references?userFrom=${users[2]._id}`) + .expect(403); }); }); }); diff --git a/modules/references/tests/server/reference-read-one.server.routes.tests.js b/modules/references/tests/server/reference-read-one.server.routes.tests.js index 6752fc6264..dc8a0a52fe 100644 --- a/modules/references/tests/server/reference-read-one.server.routes.tests.js +++ b/modules/references/tests/server/reference-read-one.server.routes.tests.js @@ -1,43 +1,40 @@ 'use strict'; -var _ = require('lodash'), - mongoose = require('mongoose'), - path = require('path'), - request = require('supertest'), - should = require('should'), - sinon = require('sinon'), - utils = require(path.resolve('./testutils/data.server.testutils')), - userProfile = require(path.resolve('./modules/users/server/controllers/users.profile.server.controller')), - express = require(path.resolve('./config/lib/express')); - -describe('Read a single reference by reference id', function () { +const _ = require('lodash'), + mongoose = require('mongoose'), + path = require('path'), + request = require('supertest'), + should = require('should'), + sinon = require('sinon'), + utils = require(path.resolve('./testutils/data.server.testutils')), + userProfile = require(path.resolve('./modules/users/server/controllers/users.profile.server.controller')), + express = require(path.resolve('./config/lib/express')); + +describe('Read a single reference by reference id', () => { // GET /references/:referenceId // logged in public user can read a single public reference by id // ..... can read a single private reference if it is from self // logged in public user can not read other private references - var app = express.init(mongoose.connection); - var agent = request.agent(app); + const app = express.init(mongoose.connection); + const agent = request.agent(app); - var _usersPublic = utils.generateUsers(3, { public: true }); - var _usersPrivate = utils.generateUsers(1, { public: false, username: 'private', email: 'non@example.com' }); - var _users = _.concat(_usersPublic, _usersPrivate); + const _usersPublic = utils.generateUsers(3, { public: true }); + const _usersPrivate = utils.generateUsers(1, { public: false, username: 'private', email: 'non@example.com' }); + const _users = [..._usersPublic, ..._usersPrivate]; - var users, + let users, references; - beforeEach(function () { + beforeEach(() => { sinon.useFakeTimers({ now: new Date('2019-01-13 13:21:55.1'), toFake: ['Date'] }); }); - afterEach(function () { + afterEach(() => { sinon.restore(); }); - beforeEach(function (done) { - utils.saveUsers(_users, function (err, usrs) { - users = usrs; - return done(err); - }); + beforeEach(async () => { + users = await utils.saveUsers(_users); }); /** @@ -55,192 +52,132 @@ describe('Read a single reference by reference id', function () { * 1 F . T * 2 T F . */ - var referenceData = [ + const referenceData = [ [0, 1], [0, 2, { public: false }], [1, 0, { public: false }], [1, 2], [2, 0], [2, 1, { public: false }] ]; - beforeEach(function (done) { - var _references = utils.generateReferences(users, referenceData); - - utils.saveReferences(_references, function (err, refs) { - references = refs; - return done(err); - }); + beforeEach(async () => { + const _references = utils.generateReferences(users, referenceData); + references = await utils.saveReferences(_references); }); afterEach(utils.clearDatabase); - context('logged in as public user', function () { + context('logged in as public user', () => { beforeEach(utils.signIn.bind(this, _usersPublic[0], agent)); afterEach(utils.signOut.bind(this, agent)); - it('read a single public reference by id', function (done) { - agent - .get('/api/references/' + references[3]._id) - .expect(200) - .end(function (err, res) { - if (err) return done(err); - - try { - - // pre-collect expected values of users - var userFields = userProfile.userMiniProfileFields.split(' ').slice(2); - var userFromExp = _.pick(users[1], userFields); - userFromExp._id = users[1]._id.toString(); - var userToExp = _.pick(users[2], userFields); - userToExp._id = users[2]._id.toString(); - - should(res.body).eql({ - public: true, - userFrom: userFromExp, - userTo: userToExp, - created: new Date().toISOString(), - _id: references[3]._id.toString(), - interactions: { - met: references[3].interactions.met, - hostedMe: references[3].interactions.hostedMe, - hostedThem: references[3].interactions.hostedThem - }, - recommend: references[3].recommend - }); - return done(); - } catch (e) { - return done(e); - } - }); + it('read a single public reference by id', async () => { + const { body } = await agent + .get(`/api/references/${references[3]._id}`) + .expect(200); + + // pre-collect expected values of users + const userFields = userProfile.userMiniProfileFields.split(' ').slice(2); + const userFromExp = _.pick(users[1], userFields); + userFromExp._id = users[1]._id.toString(); + const userToExp = _.pick(users[2], userFields); + userToExp._id = users[2]._id.toString(); + + should(body).eql({ + public: true, + userFrom: userFromExp, + userTo: userToExp, + created: new Date().toISOString(), + _id: references[3]._id.toString(), + interactions: { + met: references[3].interactions.met, + hostedMe: references[3].interactions.hostedMe, + hostedThem: references[3].interactions.hostedThem + }, + recommend: references[3].recommend + }); }); - it('read a single private reference if it is from self', function (done) { - agent - .get('/api/references/' + references[1]._id) - .expect(200) - .end(function (err, res) { - if (err) return done(err); - - try { - should(res.body).match({ - public: false, - _id: references[1]._id.toString() - }); - - return done(); - } catch (e) { - return done(e); - } - }); + it('read a single private reference if it is from self', async () => { + const { body } = await agent + .get(`/api/references/${references[1]._id}`) + .expect(200); + + should(body).match({ + public: false, + _id: references[1]._id.toString() + }); }); - it('[private reference to self] display in limited form', function (done) { - agent - .get('/api/references/' + references[2]._id) - .expect(200) - .end(function (err, res) { - if (err) return done(err); - - try { - should(res.body).match({ - public: false, - _id: references[2]._id.toString(), - created: new Date().toISOString() - }); - - should(res.body).have.only.keys('userFrom', 'userTo', '_id', 'public', 'created'); - - return done(); - } catch (e) { - return done(e); - } - }); + it('[private reference to self] display in limited form', async () => { + const { body } = await agent + .get(`/api/references/${references[2]._id}`) + .expect(200); + + should(body).match({ + public: false, + _id: references[2]._id.toString(), + created: new Date().toISOString() + }); + + should(body).have.only.keys('userFrom', 'userTo', '_id', 'public', 'created'); }); - it('[private references not from self] 404', function (done) { - agent - .get('/api/references/' + references[5]._id) - .expect(404) - .end(function (err, res) { - if (err) return done(err); - - try { - should(res.body).eql({ - message: 'Not found.', - details: { - reference: 'not found' - } - }); - - return done(); - } catch (e) { - return done(e); - } - }); + it('[private references not from self] 404', async () => { + const { body } = await agent + .get(`/api/references/${references[5]._id}`) + .expect(404); + + should(body).eql({ + message: 'Not found.', + details: { + reference: 'not found' + } + }); }); - it('[reference doesn\'t exist] 404', function (done) { - agent - .get('/api/references/' + 'a'.repeat(24)) - .expect(404) - .end(function (err, res) { - if (err) return done(err); - - try { - should(res.body).eql({ - message: 'Not found.', - details: { - reference: 'not found' - } - }); - - return done(); - } catch (e) { - return done(e); - } - }); + it('[reference doesn\'t exist] 404', async () => { + const { body } = await agent + .get(`/api/references/${'a'.repeat(24)}`) + .expect(404); + + should(body).eql({ + message: 'Not found.', + details: { + reference: 'not found' + } + }); }); - it('[invalid referenceId] 400', function (done) { - agent + it('[invalid referenceId] 400', async () => { + const { body } = await agent .get('/api/references/foo') - .expect(400) - .end(function (err, res) { - if (err) return done(err); - - try { - should(res.body).eql({ - message: 'Bad request.', - details: { - referenceId: 'invalid' - } - }); - - return done(); - } catch (e) { - return done(e); - } - }); + .expect(400); + + should(body).eql({ + message: 'Bad request.', + details: { + referenceId: 'invalid' + } + }); }); }); - context('logged in as non-public user', function () { + context('logged in as non-public user', () => { beforeEach(utils.signIn.bind(this, _usersPrivate[0], agent)); afterEach(utils.signOut.bind(this, agent)); - it('403', function (done) { - agent - .get('/api/references/' + references[3]._id) - .expect(403) - .end(done); + it('403', async () => { + await agent + .get(`/api/references/${references[3]._id}`) + .expect(403); }); }); - context('not logged in', function () { - it('403', function (done) { - agent - .get('/api/references/' + references[3]._id) - .expect(403) - .end(done); + context('not logged in', () => { + it('403', async () => { + await agent + .get(`/api/references/${references[3]._id}`) + .expect(403); }); }); }); diff --git a/modules/references/tests/server/reference.server.jobs.tests.js b/modules/references/tests/server/reference.server.jobs.tests.js index b6b476f48a..c4afd86c54 100644 --- a/modules/references/tests/server/reference.server.jobs.tests.js +++ b/modules/references/tests/server/reference.server.jobs.tests.js @@ -1,26 +1,26 @@ 'use strict'; -var async = require('async'), - moment = require('moment'), - mongoose = require('mongoose'), - path = require('path'), - should = require('should'), - sinon = require('sinon'), - config = require(path.resolve('./config/config')), - jobPublishReference = require('../../server/jobs/references-publish.server.job'), - utils = require(path.resolve('./testutils/data.server.testutils')), - Reference = mongoose.model('Reference'); +const moment = require('moment'), + mongoose = require('mongoose'), + path = require('path'), + should = require('should'), + sinon = require('sinon'), + util = require('util'), + config = require(path.resolve('./config/config')), + jobPublishReference = require('../../server/jobs/references-publish.server.job'), + utils = require(path.resolve('./testutils/data.server.testutils')), + Reference = mongoose.model('Reference'); -describe('Job: Set reference to public after a given period of time', function () { +describe('Job: Set reference to public after a given period of time', () => { // fake Date // stub config.limits.timeToReplyReference with custom test value - beforeEach(function () { + beforeEach(() => { sinon.useFakeTimers({ now: new Date('2018-10-12 11:33:21.312'), toFake: ['Date'] }); sinon.stub(config.limits, 'timeToReplyReference').value({ days: 7 }); }); - afterEach(function () { + afterEach(() => { sinon.restore(); }); @@ -41,62 +41,52 @@ describe('Job: Set reference to public after a given period of time', function ( }; } - function createReferences(count, done) { - var references = []; + async function createReferences(count) { + const references = []; - for (var i = 0; i < count; ++i) { + for (let i = 0; i < count; ++i) { references.push(new Reference(generateReferenceData())); } - async.eachSeries(references, function (reference, cb) { reference.save(cb); }, done); + + for (const reference of references) { + await reference.save(); + } } - function countPublicReferences(done) { - return Reference.find({ public: true }).exec(function (err, resp) { - return done(err, resp.length); - }); + async function countPublicReferences() { + const references = await Reference.find({ public: true }).exec(); + return references.length; } - function waitWithCallback(duration, done) { + function wait(duration) { sinon.clock.tick(moment.duration(duration).asMilliseconds()); - return done(); } - function runJobAndExpectPublicReferences(expectedCount, done) { - jobPublishReference(null, function (errJob) { - if (errJob) return done(errJob); - - countPublicReferences(function (err, actualCount) { - if (err) return done(err); - - try { - should(actualCount).eql(expectedCount); - return done(); - } catch (e) { - return done(e); - } - }); - - }); + async function runJobAndExpectPublicReferences(expectedCount) { + // promisify job and run it + await util.promisify(jobPublishReference)(null); + // count the references which are public + const actualCount = await countPublicReferences(); + // test + should(actualCount).eql(expectedCount); } - it('non-public references older than a given period of time become public', function (done) { - return async.waterfall([ - // create some non-public references - createReferences.bind(this, 7), - // wait for some time less than the given period - waitWithCallback.bind(this, { days: 3 }), - // create some more references - createReferences.bind(this, 4), - // run the job and see that all the references are private - runJobAndExpectPublicReferences.bind(this, 0), - // wait so the older references can become public - waitWithCallback.bind(this, { days: 4, seconds: 1 }), - // run the job and see that the older references are public now - runJobAndExpectPublicReferences.bind(this, 7), - // wait longer - waitWithCallback.bind(this, { days: 3 }), - // run the job and see that all the references are public now - runJobAndExpectPublicReferences.bind(this, 11) - ], done); + it('non-public references older than a given period of time become public', async () => { + // create some non-public references + await createReferences(7); + // wait for some time less than the given period + wait({ days: 3 }); + // create some more references + await createReferences(4); + // run the job and see that all the references are private + await runJobAndExpectPublicReferences(0); + // wait so the older references can become public + wait({ days: 4, seconds: 1 }); + // run the job and see that the older references are public now + await runJobAndExpectPublicReferences(7); + // wait longer + wait({ days: 3 }); + // run the job and see that all the references are public now + await runJobAndExpectPublicReferences(11); }); }); diff --git a/modules/references/tests/server/reference.server.model.tests.js b/modules/references/tests/server/reference.server.model.tests.js index aa2af71d93..9dfb10f7b6 100644 --- a/modules/references/tests/server/reference.server.model.tests.js +++ b/modules/references/tests/server/reference.server.model.tests.js @@ -1,44 +1,28 @@ 'use strict'; -var should = require('should'), - mongoose = require('mongoose'), - path = require('path'), - utils = require(path.resolve('./testutils/data.server.testutils')), - User = mongoose.model('User'), - Reference = mongoose.model('Reference'); +const should = require('should'), + mongoose = require('mongoose'), + path = require('path'), + utils = require(path.resolve('./testutils/data.server.testutils')), + User = mongoose.model('User'), + Reference = mongoose.model('Reference'); -describe('Reference Model Unit Tests', function () { +describe('Reference Model Unit Tests', () => { - describe('Method Save', function () { + describe('Method Save', () => { - var user1, + let user1, user2, user3; - beforeEach(function () { - user1 = new User({ - username: 'user1', - email: 'user1@example.com', - password: 'correcthorsebatterystaples' - }); - - user2 = new User({ - username: 'user2', - email: 'user2@example.com', - password: 'correcthorsebatterystaples' - }); - - user3 = new User({ - username: 'user3', - email: 'user3@example.com', - password: 'correcthorsebatterystaples' - }); + beforeEach(() => { + [user1, user2, user3] = utils.generateUsers(3).map(_user => new User(_user)); }); afterEach(utils.clearDatabase); - it('save both directions without problems', function (done) { - var reference1 = new Reference({ + it('save both directions without problems', async () => { + const reference1 = new Reference({ userFrom: user1._id, userTo: user2._id, interactions: { @@ -49,7 +33,7 @@ describe('Reference Model Unit Tests', function () { recommend: 'no' }); - var reference2 = new Reference({ + const reference2 = new Reference({ userFrom: user2._id, userTo: user1._id, interactions: { @@ -60,25 +44,12 @@ describe('Reference Model Unit Tests', function () { recommend: 'yes' }); - reference1.save(function (err) { - try { - should.not.exist(err); - } catch (e) { - return done(e); - } - reference2.save(function (err) { - try { - should.not.exist(err); - } catch (e) { - return done(e); - } - return done(); - }); - }); + await should(reference1.save()).be.resolved(); + await should(reference2.save()).be.resolved(); }); - it('save multiple references from one user to different users without problems', function (done) { - var reference1 = new Reference({ + it('save multiple references from one user to different users without problems', async () => { + const reference1 = new Reference({ userFrom: user1._id, userTo: user2._id, interactions: { @@ -89,7 +60,7 @@ describe('Reference Model Unit Tests', function () { recommend: 'no' }); - var reference2 = new Reference({ + const reference2 = new Reference({ userFrom: user1._id, userTo: user3._id, interactions: { @@ -100,25 +71,12 @@ describe('Reference Model Unit Tests', function () { recommend: 'yes' }); - reference1.save(function (err) { - try { - should.not.exist(err); - } catch (e) { - return done(e); - } - reference2.save(function (err) { - try { - should.not.exist(err); - } catch (e) { - return done(e); - } - return done(); - }); - }); + await should(reference1.save()).be.resolved(); + await should(reference2.save()).be.resolved(); }); - it('show error when saving invalid values of \'met\', \'recommend\', \'hostedMe\', \'hostedThem\'', function (done) { - var reference = new Reference({ + it('show error when saving invalid values of \'met\', \'recommend\', \'hostedMe\', \'hostedThem\'', async () => { + const reference = new Reference({ userFrom: user1._id, userTo: user2._id, interactions: { @@ -129,52 +87,34 @@ describe('Reference Model Unit Tests', function () { recommend: 'bar' }); - reference.save(function (err) { - try { - should.exist(err); - should(err).match({ errors: { - 'interactions.met': { value: 'foo', kind: 'Boolean' }, - 'interactions.hostedMe': { value: 'foolme', kind: 'Boolean' }, - 'interactions.hostedThem': { value: 'foolthem', kind: 'Boolean' }, - recommend: { value: 'bar', kind: 'enum' } - } }); - } catch (e) { - return done(e); - } - - return done(); - }); + const err = await should(reference.save()).be.rejected(); + should(err).match({ errors: { + 'interactions.met': { value: 'foo', kind: 'Boolean' }, + 'interactions.hostedMe': { value: 'foolme', kind: 'Boolean' }, + 'interactions.hostedThem': { value: 'foolthem', kind: 'Boolean' }, + recommend: { value: 'bar', kind: 'enum' } + } }); }); - it('show error when saving duplicate reference (reference (from, to) already exists)', function (done) { - var reference1 = new Reference({ + it('show error when saving duplicate reference (reference (from, to) already exists)', async () => { + const reference1 = new Reference({ userFrom: user2._id, userTo: user1._id }); - var reference2 = new Reference({ + const reference2 = new Reference({ userFrom: user2._id, userTo: user1._id }); - reference1.save(function (err) { - try { - should.not.exist(err); - } catch (e) { - return done(e); - } - reference2.save(function (err) { - try { - should.exist(err); - should(err).have.property('errors').match({ - userFrom: { kind: 'unique' }, - userTo: { kind: 'unique' } - }); - } catch (e) { - return done(e); - } - return done(); - }); + // the first reference should be successfully saved + await should(reference1.save()).be.resolved(); + + // the second reference should fail with unique error + const err = await should(reference2.save()).be.rejected(); + should(err).have.property('errors').match({ + userFrom: { kind: 'unique' }, + userTo: { kind: 'unique' } }); }); }); diff --git a/testutils/data.server.testutils.js b/testutils/data.server.testutils.js index 12687f47ae..77903a4218 100644 --- a/testutils/data.server.testutils.js +++ b/testutils/data.server.testutils.js @@ -4,10 +4,9 @@ * Various functions that repeat in tests a lot */ -var _ = require('lodash'), - async = require('async'), - crypto = require('crypto'), - mongoose = require('mongoose'); +const _ = require('lodash'), + crypto = require('crypto'), + mongoose = require('mongoose'); /** * Get random integer within [0, exclusiveMaximum) @@ -32,32 +31,17 @@ function getRandInt(exclusiveMaximum) { * @param {string} [defs.password] - password (defaults to a random password) * @returns {object[]} array of user data */ -function generateUsers(count, defs) { - defs = _.defaultsDeep(defs, { - username: 'username', - firstName: 'GivenName', - lastName: 'FamilyName', - email: 'user@example.com' - }); - - return _.range(count).map(function (i) { - var username = defs.username + i; - var firstName = defs.firstName + i; - var lastName = defs.lastName + i; - var email = i + defs.email; - var publ = defs.hasOwnProperty('public') ? defs.public : !getRandInt(2); // public is reserved word - var password = defs.password || crypto.randomBytes(24).toString('base64'); - - return { - public: publ, - firstName: firstName, - lastName: lastName, - email: email, - username: username, - displayUsername: username, - password: password - }; - }); +function generateUsers(count, { username='username', firstName='GivenName', lastName='FamilyName', email='user@example.com', public: pub, password }={ }) { + + return _.range(count).map(i => ({ + public: (typeof pub === 'boolean') ? pub : !getRandInt(2), + firstName: firstName + i, + lastName: lastName + i, + email: i + email, + username: username + i, + displayUsername: username + i, + password: password || crypto.randomBytes(24).toString('base64') + })); } /** @@ -70,7 +54,7 @@ function generateUsers(count, defs) { */ function generateReferences(users, referenceData) { return referenceData.map(function (data) { - var defaultReference = { + const defaultReference = { userFrom: users[data[0]]._id, userTo: users[data[1]]._id, public: true, @@ -86,45 +70,72 @@ function generateReferences(users, referenceData) { }); } -/** - * @callback {saveDocumentsCallback} - * @param {error|null} error - * @param {object[]} documents - array of saved mongo documents - */ - /** * Save documents to mongodb * @param {string} collection - name of mongoose model (mongodb collection) * @param {object[]} _documents - array of document data - * @param {saveDocumentsCallback} done + * @returns {Promise} */ -function saveDocumentsToCollection(collection, _docs, done) { - var docs = _docs.map(function (_doc) { - var Model = mongoose.model(collection); +async function saveDocumentsToCollection(collection, _docs) { + const docs = _docs.map(_doc => { + const Model = mongoose.model(collection); return new Model(_doc); }); - async.eachSeries(docs, function (doc, cb) { - doc.save(cb); - }, function (err) { - return done(err, docs); - }); + for (const doc of docs) { + await doc.save(); + } + + return docs; +} + +/** + * save users to database calls callback if provided and returns Promise with saved documents + * @param {User[]} _docs - User documents to save + * @param {callback} [done] - optional callback + * @returns {Promise} + * the callback support can be removed when the whole codebase is migrated to ES6 + */ +async function saveUsers(_docs, done=() => {}) { + try { + const docs = await saveDocumentsToCollection('User', _docs); + done(null, docs); + return docs; + } catch (e) { + done(e); + throw e; + } } -var saveUsers = _.partial(saveDocumentsToCollection, 'User'); -var saveReferences = _.partial(saveDocumentsToCollection, 'Reference'); +/** + * save references to database calls callback if provided and returns Promise with saved documents + * @param {Reference[]} _docs - Reference documents to save + * @param {callback} [done] - optional callback + * @returns {Promise} + * the callback support can be removed when the whole codebase is migrated to ES6 + */ +async function saveReferences(_docs, done=() => {}) { + try { + const docs = await saveDocumentsToCollection('Reference', _docs); + done(null, docs); + return docs; + } catch (e) { + done(e); + throw e; + } +} /** * Clear specified database collections - * @param {string[]} - array of collection names to have all documents removed - * @param {function} - callback + * @param {string[]} collections - array of collection names to have all documents removed + * @returns {Promise} */ -function clearDatabaseCollections(collections, done) { - var models = collections.map(function (collection) { return mongoose.model(collection);}); +async function clearDatabaseCollections(collections) { + const models = collections.map(collection => mongoose.model(collection)); - async.eachSeries(models, function (Model, cb) { - Model.deleteMany().exec(cb); - }, done); + for (const Model of models) { + await Model.deleteMany().exec(); + } } /** @@ -132,7 +143,7 @@ function clearDatabaseCollections(collections, done) { * The new collections should be added as needed * Eventually this list shall become complete */ -var collections = [ +const collections = [ 'User', 'Reference' ]; @@ -140,45 +151,44 @@ var collections = [ /** * Clear all collections in a database * Usage in mocha: afterEach(clearDatabase) + * @returns {Promise} */ -function clearDatabase(done) { - clearDatabaseCollections(collections, done); +async function clearDatabase() { + await clearDatabaseCollections(collections); } /** * Sign in to app - * @param {object} credentials - * @param {string} credentials.username - * @param {string} credentials.password + * @param {object} user + * @param {string} user.username + * @param {string} user.password * @param {object} agent - supertest's agent - * @param {function} done - callback + * @returns {Promise} */ -function signIn(user, agent, done) { - var credentials = _.pick(user, ['username', 'password']); - agent.post('/api/auth/signin') - .send(credentials) - .expect(200) - .end(done); +async function signIn(user, agent) { + const { username, password } = user; + await agent.post('/api/auth/signin') + .send({ username, password }) + .expect(200); } /** * Sign out from app * @param {object} agent - supertest's agent - * @param {function} done - callback + * @returns {Promise} */ -function signOut(agent, done) { - agent.get('/api/auth/signout') - .expect(302) - .end(done); +async function signOut(agent) { + await agent.get('/api/auth/signout') + .expect(302); } module.exports = { - generateUsers: generateUsers, - saveUsers: saveUsers, - generateReferences: generateReferences, - saveReferences: saveReferences, - clearDatabase: clearDatabase, - signIn: signIn, - signOut: signOut + generateUsers, + saveUsers, + generateReferences, + saveReferences, + clearDatabase, + signIn, + signOut };