From 9a02d96a7fc8ebd88016feb3abb3fa54ca609c47 Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sun, 16 Dec 2018 22:12:34 -0500 Subject: [PATCH 1/9] Write stats route --- constants/role.constant.js | 2 +- constants/routes.constant.js | 10 +++++ controllers/hacker.controller.js | 11 ++++++ middlewares/hacker.middleware.js | 7 ++++ models/account.model.js | 13 ++++++- package-lock.json | 9 ++++- package.json | 3 +- routes/api/hacker.js | 39 +++++++++++++++++++ services/hacker.service.js | 67 ++++++++++++++++++++++++++++++-- 9 files changed, 152 insertions(+), 9 deletions(-) diff --git a/constants/role.constant.js b/constants/role.constant.js index 8b2f7870..0d0d8dec 100644 --- a/constants/role.constant.js +++ b/constants/role.constant.js @@ -6,7 +6,7 @@ const Constants = { const mongoose = require("mongoose"); const accountRole = { - "_id": mongoose.Types.ObjectId(0), + "_id": mongoose.Types.ObjectId.createFromTime(0), "name": "account", "routes": [ Constants.Routes.authRoutes.login, diff --git a/constants/routes.constant.js b/constants/routes.constant.js index 30504705..64f63bfe 100644 --- a/constants/routes.constant.js +++ b/constants/routes.constant.js @@ -159,6 +159,14 @@ const volunteerRoutes = { }, }; + +const staffRoutes = { + "hackerStats": { + requestType: Constants.REQUEST_TYPES.GET, + uri: "/api/hacker/stats", + } +} + const allRoutes = { "Auth": authRoutes, "Account": accountRoutes, @@ -166,6 +174,7 @@ const allRoutes = { "Sponsor": sponsorRoutes, "Team": teamRoutes, "Volunteer": volunteerRoutes, + "Staff": staffRoutes, }; /** @@ -201,6 +210,7 @@ module.exports = { sponsorRoutes: sponsorRoutes, teamRoutes: teamRoutes, volunteerRoutes: volunteerRoutes, + staffRoutes: staffRoutes, allRoutes: allRoutes, listAllRoutes: listAllRoutes, }; \ No newline at end of file diff --git a/controllers/hacker.controller.js b/controllers/hacker.controller.js index 3c292a65..dd5164a1 100644 --- a/controllers/hacker.controller.js +++ b/controllers/hacker.controller.js @@ -97,6 +97,16 @@ function downloadedResume(req, res) { }); } +function gotStats(req, res) { + return res.status(200).json({ + message: "Retrieved stats", + data: { + stats: req.body.stats, + } + }); + +} + module.exports = { updatedHacker: updatedHacker, findById: Util.asyncMiddleware(findById), @@ -104,4 +114,5 @@ module.exports = { uploadedResume: uploadedResume, downloadedResume: downloadedResume, showHacker: showHacker, + gotStats: gotStats, }; \ No newline at end of file diff --git a/middlewares/hacker.middleware.js b/middlewares/hacker.middleware.js index 88d25935..67064621 100644 --- a/middlewares/hacker.middleware.js +++ b/middlewares/hacker.middleware.js @@ -462,6 +462,12 @@ async function findSelf(req, res, next) { } } +async function getStats(req, res, next) { + const stats = await Services.Hacker.getStats(); + req.body.stats = stats; + next(); +} + module.exports = { parsePatch: parsePatch, parseHacker: parseHacker, @@ -480,4 +486,5 @@ module.exports = { parseConfirmation: parseConfirmation, createHacker: Middleware.Util.asyncMiddleware(createHacker), findSelf: Middleware.Util.asyncMiddleware(findSelf), + getStats: Middleware.Util.asyncMiddleware(getStats) }; \ No newline at end of file diff --git a/models/account.model.js b/models/account.model.js index ff3c3401..412bc110 100644 --- a/models/account.model.js +++ b/models/account.model.js @@ -79,8 +79,17 @@ AccountSchema.methods.comparePassword = function (password) { /** * Returns if the accountType corresponds to a sponsor */ -AccountSchema.methods.isSponsor = function(){ +AccountSchema.methods.isSponsor = function () { return Constants.SPONSOR_TIERS.includes(this.accountType) || this.accountType == Constants.SPONSOR; -} +}; +/** + * Calculates the user's age + */ +AccountSchema.methods.getAge = function () { // birthday is a date + var ageDifMs = Date.now() - this.birthDate.getTime(); + var ageDate = new Date(ageDifMs); // miliseconds from epoch + return Math.abs(ageDate.getUTCFullYear() - 1970); +}; + //export the model module.exports = mongoose.model("Account", AccountSchema); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3e93231e..9ac919e8 100755 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "dependencies": { "@google-cloud/common": { "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.17.0.tgz", + "resolved": "http://registry.npmjs.org/@google-cloud/common/-/common-0.17.0.tgz", "integrity": "sha512-HRZLSU762E6HaKoGfJGa8W95yRjb9rY7LePhjaHK9ILAnFacMuUGVamDbTHu1csZomm1g3tZTtXfX/aAhtie/Q==", "requires": { "array-uniq": "^1.0.3", @@ -3429,7 +3429,7 @@ }, "gcp-metadata": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.6.3.tgz", + "resolved": "http://registry.npmjs.org/gcp-metadata/-/gcp-metadata-0.6.3.tgz", "integrity": "sha512-MSmczZctbz91AxCvqp9GHBoZOSbJKAICV7Ow/AIWSJZRrRchUd5NL1b2P4OfP+4m490BEUPhhARfpHdqCxuCvg==", "requires": { "axios": "^0.18.0", @@ -5237,6 +5237,11 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, + "memory-cache": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz", + "integrity": "sha1-eJCwHVLADI68nVM+H46xfjA0hxo=" + }, "meow": { "version": "3.7.0", "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", diff --git a/package.json b/package.json index 92aee488..6905b136 100755 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "express-winston": "^2.5.1", "handlebars": "^4.0.12", "jsonwebtoken": "^8.1.0", + "memory-cache": "^0.2.0", "mongoose": "^5.1.0", "multer": "^1.3.1", "passport": "^0.4.0", @@ -47,4 +48,4 @@ "mocha": "^5.2.0", "nodemon": "^1.17.3" } -} \ No newline at end of file +} diff --git a/routes/api/hacker.js b/routes/api/hacker.js index 2282aba8..b75d291f 100644 --- a/routes/api/hacker.js +++ b/routes/api/hacker.js @@ -166,6 +166,45 @@ module.exports = { Middleware.Hacker.sendAppliedStatusEmail, Controllers.Hacker.createdHacker ); + + /** + * @api {get} /hacker/stats + * Gets the stats of all of the hackers who have applied. + * @apiName getHackerStats + * @apiGroup Hacker + * @apiVersion 0.0.9 + * @apiSuccess {string} message Success message + * @apiSuccess {object} data Hacker object + * @apiSuccessExample {object} Success-Response: + * { + * "message": "Retrieved stats", + * "data": { + * "stats" : { + * "total": 10, + "status": { "Applied": 10 }, + "school": { "McGill University": 3, "Harvard University": 7 }, + degree: { "Undergraduate": 10 }, + gender: { "Male": 1, "Female": 9 }, + needsBus: { "true": 7, "false": 3 }, + ethnicity: { "White": 10, }, + jobInterest: { "Internship": 10 }, + major: { "Computer Science": 10 }, + graduationYear: { "2019": 10 }, + dietaryRestrictions: { "None": 10 }, + shirtSize: { "M": 3, "XL": 7 }, + age: { "22": 10 } + } + * } + * } + * + */ + hackerRouter.route("/stats").get( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized(), + Middleware.Hacker.getStats, + Controllers.Hacker.gotStats + ); + /** * @api {patch} /hacker/status/:id update a hacker's status * @apiName patchHackerStatus diff --git a/services/hacker.service.js b/services/hacker.service.js index f732b36a..0b2b3a46 100644 --- a/services/hacker.service.js +++ b/services/hacker.service.js @@ -2,6 +2,9 @@ const Hacker = require("../models/hacker.model"); const logger = require("./logger.service"); +const cache = require("memory-cache"); + +const Constants = require("../constants/general.constant"); /** * @function createHacker * @param {{_id: ObjectId, accountId: ObjectId, school: string, gender: string, needsBus: boolean, application: {Object}}} hackerDetails @@ -56,11 +59,10 @@ async function findIds(queries) { const TAG = `[Hacker Service # findIds ]:`; let ids = []; - queries.forEach(async (query) => { + for (const query of queries) { let currId = await Hacker.findOne(query, "_id", logger.queryCallbackFactory(TAG, "hacker", query)); ids.push(currId); - }); - + } return ids; } @@ -79,10 +81,69 @@ function findByAccountId(accountId) { return Hacker.findOne(query, logger.updateCallbackFactory(TAG, "hacker")); } +async function getStats() { + const TAG = `[ hacker Service # getHackerStats ]`; + const CACHE_KEY = "hackerStats"; + if (cache.get(CACHE_KEY) !== null) { + logger.info(`${TAG} Getting cached stats`); + return cache.get(CACHE_KEY); + } + const allHackers = await Hacker.find({}, logger.updateCallbackFactory(TAG, "hacker")).populate({ + path: "accountId", + }); + const stats = { + total: 0, + status: {}, + school: {}, + degree: {}, + gender: {}, + needsBus: {}, + ethnicity: {}, + jobInterest: {}, + major: {}, + graduationYear: {}, + dietaryRestrictions: {}, + shirtSize: {}, + age: {} + }; + + allHackers.forEach((hacker) => { + if (!hacker.accountId) { + // user is no longer with us for some reason :( + return; + } + stats.total += 1; + stats.status[hacker.status] = (stats.status[hacker.status]) ? stats.status[hacker.status] + 1 : 1; + stats.school[hacker.school] = (stats.school[hacker.school]) ? stats.school[hacker.school] + 1 : 1; + stats.degree[hacker.degree] = (stats.degree[hacker.degree]) ? stats.degree[hacker.degree] + 1 : 1; + stats.gender[hacker.gender] = (stats.gender[hacker.gender]) ? stats.gender[hacker.gender] + 1 : 1; + stats.needsBus[hacker.needsBus] = (stats.needsBus[hacker.needsBus]) ? stats.needsBus[hacker.needsBus] + 1 : 1; + + for (const ethnicity of hacker.ethnicity) { + stats.ethnicity[ethnicity] = (stats.ethnicity[ethnicity]) ? stats.ethnicity[ethnicity] + 1 : 1; + } + + stats.jobInterest[hacker.application.jobInterest] = (stats.jobInterest[hacker.application.jobInterest]) ? stats.jobInterest[hacker.application.jobInterest] + 1 : 1; + stats.major[hacker.major] = (stats.major[hacker.major]) ? stats.major[hacker.major] + 1 : 1; + stats.graduationYear[hacker.graduationYear] = (stats.graduationYear[hacker.graduationYear]) ? stats.graduationYear[hacker.graduationYear] + 1 : 1; + + for (const dietaryRestrictions of hacker.accountId.dietaryRestrictions) { + stats.dietaryRestrictions[dietaryRestrictions] = (stats.dietaryRestrictions[dietaryRestrictions]) ? stats.dietaryRestrictions[dietaryRestrictions] + 1 : 1; + } + stats.shirtSize[hacker.accountId.shirtSize] = (stats.shirtSize[hacker.accountId.shirtSize]) ? stats.shirtSize[hacker.accountId.shirtSize] + 1 : 1; + const age = hacker.accountId.getAge(); + stats.age[age] = (stats.age[age]) ? stats.age[age] + 1 : 1; + }); + cache.put(CACHE_KEY, stats, 5 * 60 * 1000); //set a time-out of 5 minutes + return stats; +} + + module.exports = { createHacker: createHacker, findById: findById, updateOne: updateOne, findIds: findIds, findByAccountId: findByAccountId, + getStats: getStats }; \ No newline at end of file From 7de707b241f39d2dbf9a63350b52cf1f838ffa0e Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sun, 16 Dec 2018 22:13:56 -0500 Subject: [PATCH 2/9] Update docs --- docs/api/api_data.js | 37 +++++++++++++++++++++++++++++++++++++ docs/api/api_data.json | 37 +++++++++++++++++++++++++++++++++++++ docs/api/api_project.js | 2 +- docs/api/api_project.json | 2 +- 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/docs/api/api_data.js b/docs/api/api_data.js index 4f5597f3..92dd875f 100644 --- a/docs/api/api_data.js +++ b/docs/api/api_data.js @@ -1355,6 +1355,43 @@ define({ "filename": "routes/api/hacker.js", "groupTitle": "Hacker" }, + { + "type": "get", + "url": "/hacker/stats", + "title": "Gets the stats of all of the hackers who have applied.", + "name": "getHackerStats", + "group": "Hacker", + "version": "0.0.9", + "success": { + "fields": { + "Success 200": [{ + "group": "Success 200", + "type": "string", + "optional": false, + "field": "message", + "description": "

Success message

" + }, + { + "group": "Success 200", + "type": "object", + "optional": false, + "field": "data", + "description": "

Hacker object

" + } + ] + }, + "examples": [{ + "title": "Success-Response: ", + "content": "{\n \"message\": \"Retrieved stats\",\n \"data\": {\n \"stats\" : {\n \"total\": 10,\n \"status\": { \"Applied\": 10 },\n \"school\": { \"McGill University\": 3, \"Harvard University\": 7 },\n degree: { \"Undergraduate\": 10 },\n gender: { \"Male\": 1, \"Female\": 9 },\n needsBus: { \"true\": 7, \"false\": 3 },\n ethnicity: { \"White\": 10, },\n jobInterest: { \"Internship\": 10 },\n major: { \"Computer Science\": 10 },\n graduationYear: { \"2019\": 10 },\n dietaryRestrictions: { \"None\": 10 },\n shirtSize: { \"M\": 3, \"XL\": 7 },\n age: { \"22\": 10 }\n }\n }\n}", + "type": "object" + }] + }, + "filename": "routes/api/hacker.js", + "groupTitle": "Hacker", + "sampleRequest": [{ + "url": "https://api.mchacks.ca/api/hacker/stats" + }] + }, { "type": "patch", "url": "/hacker/:id", diff --git a/docs/api/api_data.json b/docs/api/api_data.json index 933b1514..b42ad911 100644 --- a/docs/api/api_data.json +++ b/docs/api/api_data.json @@ -1354,6 +1354,43 @@ "filename": "routes/api/hacker.js", "groupTitle": "Hacker" }, + { + "type": "get", + "url": "/hacker/stats", + "title": "Gets the stats of all of the hackers who have applied.", + "name": "getHackerStats", + "group": "Hacker", + "version": "0.0.9", + "success": { + "fields": { + "Success 200": [{ + "group": "Success 200", + "type": "string", + "optional": false, + "field": "message", + "description": "

Success message

" + }, + { + "group": "Success 200", + "type": "object", + "optional": false, + "field": "data", + "description": "

Hacker object

" + } + ] + }, + "examples": [{ + "title": "Success-Response: ", + "content": "{\n \"message\": \"Retrieved stats\",\n \"data\": {\n \"stats\" : {\n \"total\": 10,\n \"status\": { \"Applied\": 10 },\n \"school\": { \"McGill University\": 3, \"Harvard University\": 7 },\n degree: { \"Undergraduate\": 10 },\n gender: { \"Male\": 1, \"Female\": 9 },\n needsBus: { \"true\": 7, \"false\": 3 },\n ethnicity: { \"White\": 10, },\n jobInterest: { \"Internship\": 10 },\n major: { \"Computer Science\": 10 },\n graduationYear: { \"2019\": 10 },\n dietaryRestrictions: { \"None\": 10 },\n shirtSize: { \"M\": 3, \"XL\": 7 },\n age: { \"22\": 10 }\n }\n }\n}", + "type": "object" + }] + }, + "filename": "routes/api/hacker.js", + "groupTitle": "Hacker", + "sampleRequest": [{ + "url": "https://api.mchacks.ca/api/hacker/stats" + }] + }, { "type": "patch", "url": "/hacker/:id", diff --git a/docs/api/api_project.js b/docs/api/api_project.js index 62fd88fb..6d90c460 100644 --- a/docs/api/api_project.js +++ b/docs/api/api_project.js @@ -9,7 +9,7 @@ define({ "apidoc": "0.3.0", "generator": { "name": "apidoc", - "time": "2018-12-11T06:22:15.249Z", + "time": "2018-12-17T03:13:26.391Z", "url": "http://apidocjs.com", "version": "0.17.6" } diff --git a/docs/api/api_project.json b/docs/api/api_project.json index 8ae30989..ff163a2c 100644 --- a/docs/api/api_project.json +++ b/docs/api/api_project.json @@ -9,7 +9,7 @@ "apidoc": "0.3.0", "generator": { "name": "apidoc", - "time": "2018-12-11T06:22:15.249Z", + "time": "2018-12-17T03:13:26.391Z", "url": "http://apidocjs.com", "version": "0.17.6" } From 6d92d06426974ec7ab6163ddca2a44ed2bf2a192 Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sun, 16 Dec 2018 22:30:23 -0500 Subject: [PATCH 3/9] Add tests --- tests/hacker.test.js | 65 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/tests/hacker.test.js b/tests/hacker.test.js index 8b18025d..6cea4d30 100644 --- a/tests/hacker.test.js +++ b/tests/hacker.test.js @@ -719,4 +719,69 @@ describe("POST add a hacker resume", function () { }); }); }); +}); + +describe("GET Hacker stats", function () { + it("It should SUCCEED and get hacker stats", function (done) { + //this takes a lot of time for some reason + util.auth.login(agent, Admin1, (error) => { + if (error) { + return done(error); + } + return agent + .get(`/api/hacker/stats`) + .end(function (err, res) { + res.should.have.status(200); + res.should.have.property("body"); + res.body.should.have.property("message"); + res.body.message.should.equal("Retrieved stats"); + res.body.should.have.property("data"); + res.body.data.should.have.property("stats"); + res.body.data.stats.should.have.property("total"); + res.body.data.stats.should.have.property("status"); + res.body.data.stats.should.have.property("school"); + res.body.data.stats.should.have.property("degree"); + res.body.data.stats.should.have.property("gender"); + res.body.data.stats.should.have.property("needsBus"); + res.body.data.stats.should.have.property("ethnicity"); + res.body.data.stats.should.have.property("jobInterest"); + res.body.data.stats.should.have.property("major"); + res.body.data.stats.should.have.property("graduationYear"); + res.body.data.stats.should.have.property("dietaryRestrictions"); + res.body.data.stats.should.have.property("shirtSize"); + res.body.data.stats.should.have.property("age"); + done(); + }); + }); + }); + it("It should FAIL and get hacker stats due to invalid Authorization", function (done) { + //this takes a lot of time for some reason + util.auth.login(agent, storedAccount1, (error) => { + if (error) { + return done(error); + } + return agent + .get(`/api/hacker/stats`) + .end(function (err, res) { + res.should.have.status(403); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal(Constants.Error.AUTH_403_MESSAGE); + res.body.should.have.property("data"); + done(); + }); + }); + }); + it("It should FAIL and get hacker stats due to invalid Authentication", function (done) { + //this takes a lot of time for some reason + chai.request(server.app) + .get(`/api/hacker/stats`) + .end(function (err, res) { + res.should.have.status(401); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal(Constants.Error.AUTH_401_MESSAGE); + done(); + }); + }); }); \ No newline at end of file From 01cf46c3862439552e1ad41d80eac462da1fb31a Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sun, 16 Dec 2018 23:48:33 -0500 Subject: [PATCH 4/9] Fix crucial bug, add additional search feature --- constants/role.constant.js | 2 +- constants/routes.constant.js | 9 + middlewares/search.middleware.js | 7 +- middlewares/validators/search.validator.js | 3 +- middlewares/validators/validator.helper.js | 4 +- routes/api/search.js | 7 +- services/search.service.js | 30 +- tests/search.service.spec.js | 358 ++++++++++++++------- tests/util/account.test.util.js | 6 +- 9 files changed, 294 insertions(+), 132 deletions(-) diff --git a/constants/role.constant.js b/constants/role.constant.js index 8b2f7870..d626a747 100644 --- a/constants/role.constant.js +++ b/constants/role.constant.js @@ -6,7 +6,7 @@ const Constants = { const mongoose = require("mongoose"); const accountRole = { - "_id": mongoose.Types.ObjectId(0), + "_id": mongoose.Types.ObjectId("00000000e285ec4f6ec7e5c2"), "name": "account", "routes": [ Constants.Routes.authRoutes.login, diff --git a/constants/routes.constant.js b/constants/routes.constant.js index 30504705..aa40bc5a 100644 --- a/constants/routes.constant.js +++ b/constants/routes.constant.js @@ -159,6 +159,13 @@ const volunteerRoutes = { }, }; +const searchRoutes = { + "get": { + requestType: Constants.REQUEST_TYPES.GET, + uri: "/api/search/" + } +}; + const allRoutes = { "Auth": authRoutes, "Account": accountRoutes, @@ -166,6 +173,7 @@ const allRoutes = { "Sponsor": sponsorRoutes, "Team": teamRoutes, "Volunteer": volunteerRoutes, + "Search": searchRoutes, }; /** @@ -201,6 +209,7 @@ module.exports = { sponsorRoutes: sponsorRoutes, teamRoutes: teamRoutes, volunteerRoutes: volunteerRoutes, + searchRoutes: searchRoutes, allRoutes: allRoutes, listAllRoutes: listAllRoutes, }; \ No newline at end of file diff --git a/middlewares/search.middleware.js b/middlewares/search.middleware.js index 2d398b0d..e33a966d 100644 --- a/middlewares/search.middleware.js +++ b/middlewares/search.middleware.js @@ -36,6 +36,10 @@ function parseQuery(req, res, next) { req.body.sort_by = ""; } + if (!req.body.hasOwnProperty("expand")) { + req.body.expand = false; + } + return next(); } @@ -52,7 +56,8 @@ async function executeQuery(req, res, next) { req.body.page, req.body.limit, req.body.sort, - req.body.sort_by + req.body.sort_by, + req.body.expand ); return next(); } diff --git a/middlewares/validators/search.validator.js b/middlewares/validators/search.validator.js index dfe009c2..837c42ed 100644 --- a/middlewares/validators/search.validator.js +++ b/middlewares/validators/search.validator.js @@ -3,11 +3,12 @@ const VALIDATOR = require("./validator.helper"); module.exports = { searchQueryValidator: [ - VALIDATOR.searchModelValidator("param", "model", false), + VALIDATOR.searchModelValidator("query", "model", false), VALIDATOR.alphaValidator("query", "sort", true), VALIDATOR.integerValidator("query", "page", true, 0), VALIDATOR.integerValidator("query", "limit", true, 0, 1000), VALIDATOR.searchSortValidator("query", "sort_by"), + VALIDATOR.booleanValidator("query", "expand", true), VALIDATOR.searchValidator("query", "q") ], }; \ No newline at end of file diff --git a/middlewares/validators/validator.helper.js b/middlewares/validators/validator.helper.js index 1d5dcbfa..54389ef6 100644 --- a/middlewares/validators/validator.helper.js +++ b/middlewares/validators/validator.helper.js @@ -460,7 +460,7 @@ function searchValidator(fieldLocation, fieldname) { }) => { //value is a serialized JSON value = JSON.parse(value); - let modelString = req.params.model + let modelString = req.query.model //Supported models for searching let model; if (modelString === Constants.HACKER.toLowerCase()) { @@ -508,7 +508,7 @@ function searchSortValidator(fieldLocation, fieldName) { .custom((value, { req }) => { - let modelString = req.params.model + let modelString = req.query.model if (modelString.equals("hacker")) { model = Models.Hacker; } else { diff --git a/routes/api/search.js b/routes/api/search.js index a506f84f..8918da65 100644 --- a/routes/api/search.js +++ b/routes/api/search.js @@ -11,7 +11,8 @@ const Middleware = { Search: require("../../middlewares/validators/search.validator") }, parseBody: require("../../middlewares/parse-body.middleware"), - Search: require('../../middlewares/search.middleware') + Search: require("../../middlewares/search.middleware"), + Auth: require("../../middlewares/auth.middleware") }; module.exports = { @@ -50,7 +51,9 @@ module.exports = { * @apiErrorExample {object} Error-Response: * {"message": "Validation failed", "data": {}} */ - searchRouter.route("/:model").get( + searchRouter.route("/").get( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized(), Middleware.Validator.Search.searchQueryValidator, Middleware.parseBody.middleware, Middleware.Search.parseQuery, diff --git a/services/search.service.js b/services/search.service.js index 20b0f242..4f1225f3 100644 --- a/services/search.service.js +++ b/services/search.service.js @@ -6,23 +6,30 @@ const logger = require("./logger.service"); * @function executeQuery * @param {string} model the model which is being searched * @param {Array} queryArray array of clauses for the query + * @param {number} page the page number you want + * @param {number} limit the limit to the number of responses you want + * @param {"asc"|"desc"} sort which direction you want to sort by + * @param {string} sort_by the attribute you want to sort by * @returns {Promise<[Array]>} * @description Builds and executes a search query based on a subset of mongodb */ -function executeQuery(model, queryArray, page, limit, sort, sort_by){ +function executeQuery(model, queryArray, page, limit, sort, sort_by, shouldExpand = false) { var query; - switch(model.toLowerCase()){ + switch (model.toLowerCase()) { case "hacker": - query = Hacker.find(); + query = (shouldExpand) ? Hacker.find().populate({ + path: "accountId", + select: " -password" + }) : Hacker.find(); break; default: - return []; + return []; } - for(var i in queryArray) { + for (var i in queryArray) { var clause = queryArray[i]; - var param = clause["param"]; - var val = clause["value"]; - switch (clause["operation"]) { + var param = clause.param; + var val = clause.value; + switch (clause.operation) { case "equals": query.where(param).equals(val); break; @@ -53,10 +60,9 @@ function executeQuery(model, queryArray, page, limit, sort, sort_by){ } } - if(sort == "desc"){ - query.sort("-"+sort_by); - } - else if(sort == "asc"){ + if (sort == "desc") { + query.sort("-" + sort_by); + } else if (sort == "asc") { query.sort(sort_by); } return query.lean() diff --git a/tests/search.service.spec.js b/tests/search.service.spec.js index 8429a8d2..e5ec9788 100644 --- a/tests/search.service.spec.js +++ b/tests/search.service.spec.js @@ -9,143 +9,281 @@ const agent = chai.request.agent(server.app); const assert = require("chai").assert; const should = chai.should(); const logger = require("../services/logger.service"); -const SearchService = require("../services/search.service"); + +const Constants = { + Error: require("../constants/error.constant"), +}; const util = { hacker: require("./util/hacker.test.util"), - account: require("./util/account.test.util") + account: require("./util/account.test.util"), + auth: require("./util/auth.test.util") }; const queryToExecute = [{ param: "gender", operation: "equals", value: "Female" -}] +}]; const query2 = [{ param: "school", operation: "ne", value: "McGill" -}] +}]; const badQuery = [{ param: "password", operation: "equals", value: "passowrd" -}] +}]; + +const Admin1 = util.account.Admin1; +const HackerA = util.account.Account2; describe("Searching for hackers", function () { + it("Should FAIL to search due to invalid authentication", function (done) { + util.auth.login(agent, { + email: "abc", + password: "def" + }, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get("/api/search") + .query({ + model: "hacker", + q: JSON.stringify(queryToExecute) + }) + .end(function (err, res) { + res.should.have.status(401); + res.body.message.should.equal(Constants.Error.AUTH_401_MESSAGE); + res.body.should.have.property("data"); + done(); + }); + }); + }); + + it("Should FAIL to search due to invalid authorization", function (done) { + util.auth.login(agent, HackerA, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get("/api/search") + .query({ + model: "hacker", + q: JSON.stringify(queryToExecute) + }) + .end(function (err, res) { + res.should.have.status(403); + res.body.message.should.equal(Constants.Error.AUTH_403_MESSAGE); + res.body.should.have.property("data"); + done(); + }); + }); + }); it("Should return all female hackers", function (done) { - chai.request(server.app) - .get("/api/search/hacker") - .query({ - q: JSON.stringify(queryToExecute) - }) - .end(function (err, res) { - res.should.have.status(200); - res.body.should.have.property('data'); - res.body.data.should.have.length(2); - done(); - }); - }) + util.auth.login(agent, Admin1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get("/api/search") + .query({ + model: "hacker", + q: JSON.stringify(queryToExecute) + }) + .end(function (err, res) { + res.should.have.status(200); + res.body.should.have.property('data'); + res.body.data.should.have.length(2); + done(); + }); + }); + }); it("Should return an error as hackers don't have password stored", function (done) { - chai.request(server.app) - .get("/api/search/hacker") - .query({ - q: JSON.stringify(badQuery) - }) - .end(function (err, res) { - res.should.have.status(422); - done(); - }); - }) + util.auth.login(agent, Admin1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get("/api/search") + .query({ + model: "hacker", + q: JSON.stringify(badQuery) + }) + .end(function (err, res) { + res.should.have.status(422); + done(); + }); + }); + }); + it("Should return an error as staff aren't searchable", function (done) { - chai.request(server.app) - .get("/api/search/staff") - .query({ - q: JSON.stringify(badQuery) - }) - .end(function (err, res) { - res.should.have.status(422); - res.body.data.model.msg.should.equal("Must be a valid searchable model"); - done(); - }); - }) + util.auth.login(agent, Admin1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get("/api/search") + .query({ + model: "staff", + q: JSON.stringify(badQuery) + }) + .end(function (err, res) { + res.should.have.status(422); + res.body.data.model.msg.should.equal("Must be a valid searchable model"); + done(); + }); + }); + }); it("Should throw an error because model is not lowercase", function (done) { - chai.request(server.app) - .get("/api/search/Hacker") - .query({ - q: JSON.stringify(query2) - }) - .end(function (err, res) { - res.should.have.status(422); - res.body.data.model.msg.should.equal("Model must be lower case"); - done(); - }) - }) + util.auth.login(agent, Admin1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get("/api/search") + .query({ + model: "Hacker", + q: JSON.stringify(query2) + }) + .end(function (err, res) { + res.should.have.status(422); + res.body.data.model.msg.should.equal("Model must be lower case"); + done(); + }); + }); + }); it("Should throw an error because out of a fake model", function (done) { - chai.request(server.app) - .get("/api/search/hackerz") - .query({ - q: JSON.stringify(query2) - }) - .end(function (err, res) { - res.should.have.status(422); - res.body.data.model.msg.should.equal("Must be a valid searchable model"); - done(); - }) - }) + util.auth.login(agent, Admin1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get("/api/search") + .query({ + model: "hackerz", + q: JSON.stringify(query2) + }) + .end(function (err, res) { + res.should.have.status(422); + res.body.data.model.msg.should.equal("Must be a valid searchable model"); + done(); + }); + }); + }); it("Should only return 1 hacker (page size)", function (done) { - chai.request(server.app) - .get("/api/search/hacker") - .query({ - q: JSON.stringify(query2), - limit: 1 - }) - .end(function (err, res) { - res.should.have.status(200); - res.body.data.should.have.length(1); - done(); - }) - }) + util.auth.login(agent, Admin1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get("/api/search") + .query({ + model: "hacker", + q: JSON.stringify(query2), + limit: 1 + }) + .end(function (err, res) { + res.should.have.status(200); + res.body.data.should.have.length(1); + done(); + }); + }); + }); it("Should only return 1 hacker (pagination)", function (done) { - chai.request(server.app) - .get("/api/search/hacker") - //There are two test samples so by making limit 1, there will be something on the second page - .query({ - q: JSON.stringify(query2), - limit: 1, - page: 1 - }) - .end(function (err, res) { - res.should.have.status(200); - res.body.data.should.have.length(1); - done(); - }) - }) + util.auth.login(agent, Admin1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get("/api/search") + //There are two test samples so by making limit 1, there will be something on the second page + .query({ + model: "hacker", + q: JSON.stringify(query2), + limit: 1, + page: 1 + }) + .end(function (err, res) { + res.should.have.status(200); + res.body.data.should.have.length(1); + done(); + }); + }); + }); it("Should throw an error because out of bounds (page size)", function (done) { - chai.request(server.app) - .get("/api/search/hacker") - .query({ - q: JSON.stringify(query2), - limit: 5000 - }) - .end(function (err, res) { - res.should.have.status(422); - done(); - }) - }) + util.auth.login(agent, Admin1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get("/api/search") + .query({ + model: "hacker", + q: JSON.stringify(query2), + limit: 5000 + }) + .end(function (err, res) { + res.should.have.status(422); + done(); + }); + }); + }); it("Should throw an error because out of bounds (pagination)", function (done) { - chai.request(server.app) - .get("/api/search/hacker") - .query({ - q: JSON.stringify(query2), - limit: 1, - page: -1 - }) - .end(function (err, res) { - res.should.have.status(422); - done(); - }) - }) -}) \ No newline at end of file + util.auth.login(agent, Admin1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get("/api/search") + .query({ + model: "hacker", + q: JSON.stringify(query2), + limit: 1, + page: -1 + }) + .end(function (err, res) { + res.should.have.status(422); + done(); + }); + }); + }); + + it("Should expand the accountId", function (done) { + util.auth.login(agent, Admin1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .get("/api/search") + .query({ + model: "hacker", + q: JSON.stringify(queryToExecute), + expand: true + }) + .end(function (err, res) { + res.should.have.status(200); + res.body.should.have.property("data"); + res.body.data.should.have.length(2); + res.body.data.should.have.length(2); + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/tests/util/account.test.util.js b/tests/util/account.test.util.js index e5910ebe..513fefd4 100644 --- a/tests/util/account.test.util.js +++ b/tests/util/account.test.util.js @@ -14,7 +14,7 @@ const newAccount1 = { "password": "1234567890", "dietaryRestrictions": ["none"], "shirtSize": "S", - "accountType": Constants.Hacker, + "accountType": Constants.HACKER, "birthDate": "1997-12-30", "phoneNumber": 1234567890, }; @@ -70,7 +70,7 @@ const Account2 = { "dietaryRestrictions": ["vegetarian"], "shirtSize": "M", "confirmed": true, - "accountType": Constants.Hacker, + "accountType": Constants.HACKER, "birthDate": "1990-01-04", "phoneNumber": 1000000004, }; @@ -159,7 +159,7 @@ const Hacker3 = { "dietaryRestrictions": ["vegetarian"], "shirtSize": "M", "confirmed": true, - "accountType": Constants.Hacker, + "accountType": Constants.HACKER, "birthDate": "1990-01-04", "phoneNumber": 1000000004, }; From deb8e59abb7ed6b617d54f1682b187eb13e446f6 Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Sun, 16 Dec 2018 23:52:09 -0500 Subject: [PATCH 5/9] Update documentation --- docs/api/api_data.js | 66 ++++++++++++++++++++++++++++++--------- docs/api/api_data.json | 66 ++++++++++++++++++++++++++++++--------- docs/api/api_project.js | 2 +- docs/api/api_project.json | 2 +- routes/api/search.js | 10 ++++-- 5 files changed, 112 insertions(+), 34 deletions(-) diff --git a/docs/api/api_data.js b/docs/api/api_data.js index 4f5597f3..6291d8a9 100644 --- a/docs/api/api_data.js +++ b/docs/api/api_data.js @@ -1699,27 +1699,63 @@ define({ }, { "type": "get", - "url": "/search/:model", + "url": "/search/", "title": "provide a specific query for any defined model", "name": "search", "group": "Search", "version": "0.0.8", "parameter": { "fields": { - "param": [{ - "group": "param", - "type": "String", - "optional": false, - "field": "model", - "description": "

the model to be searched

" - }], "query": [{ - "group": "query", - "type": "Array", - "optional": false, - "field": "q", - "description": "

the query to be executed. For more information on how to format this, please see https://docs.mchacks.ca/architecture/

" - }] + "group": "query", + "type": "String", + "optional": false, + "field": "model", + "description": "

the model to be searched

" + }, + { + "group": "query", + "type": "Array", + "optional": false, + "field": "q", + "description": "

the query to be executed. For more information on how to format this, please see https://docs.mchacks.ca/architecture/

" + }, + { + "group": "query", + "type": "String", + "optional": false, + "field": "sort", + "description": "

either "asc" or "desc"

" + }, + { + "group": "query", + "type": "number", + "optional": false, + "field": "page", + "description": "

the page number that you would like

" + }, + { + "group": "query", + "type": "number", + "optional": false, + "field": "limit", + "description": "

the maximum number of results that you would like returned

" + }, + { + "group": "query", + "type": "any", + "optional": false, + "field": "sort_by", + "description": "

any parameter you want to sort the results by

" + }, + { + "group": "query", + "type": "boolean", + "optional": false, + "field": "expand", + "description": "

whether you want to expand sub documents within the results

" + } + ] } }, "success": { @@ -1779,7 +1815,7 @@ define({ "filename": "routes/api/search.js", "groupTitle": "Search", "sampleRequest": [{ - "url": "https://api.mchacks.ca/api/search/:model" + "url": "https://api.mchacks.ca/api/search/" }] }, { diff --git a/docs/api/api_data.json b/docs/api/api_data.json index 933b1514..10665c2e 100644 --- a/docs/api/api_data.json +++ b/docs/api/api_data.json @@ -1698,27 +1698,63 @@ }, { "type": "get", - "url": "/search/:model", + "url": "/search/", "title": "provide a specific query for any defined model", "name": "search", "group": "Search", "version": "0.0.8", "parameter": { "fields": { - "param": [{ - "group": "param", - "type": "String", - "optional": false, - "field": "model", - "description": "

the model to be searched

" - }], "query": [{ - "group": "query", - "type": "Array", - "optional": false, - "field": "q", - "description": "

the query to be executed. For more information on how to format this, please see https://docs.mchacks.ca/architecture/

" - }] + "group": "query", + "type": "String", + "optional": false, + "field": "model", + "description": "

the model to be searched

" + }, + { + "group": "query", + "type": "Array", + "optional": false, + "field": "q", + "description": "

the query to be executed. For more information on how to format this, please see https://docs.mchacks.ca/architecture/

" + }, + { + "group": "query", + "type": "String", + "optional": false, + "field": "sort", + "description": "

either "asc" or "desc"

" + }, + { + "group": "query", + "type": "number", + "optional": false, + "field": "page", + "description": "

the page number that you would like

" + }, + { + "group": "query", + "type": "number", + "optional": false, + "field": "limit", + "description": "

the maximum number of results that you would like returned

" + }, + { + "group": "query", + "type": "any", + "optional": false, + "field": "sort_by", + "description": "

any parameter you want to sort the results by

" + }, + { + "group": "query", + "type": "boolean", + "optional": false, + "field": "expand", + "description": "

whether you want to expand sub documents within the results

" + } + ] } }, "success": { @@ -1778,7 +1814,7 @@ "filename": "routes/api/search.js", "groupTitle": "Search", "sampleRequest": [{ - "url": "https://api.mchacks.ca/api/search/:model" + "url": "https://api.mchacks.ca/api/search/" }] }, { diff --git a/docs/api/api_project.js b/docs/api/api_project.js index 62fd88fb..48b10347 100644 --- a/docs/api/api_project.js +++ b/docs/api/api_project.js @@ -9,7 +9,7 @@ define({ "apidoc": "0.3.0", "generator": { "name": "apidoc", - "time": "2018-12-11T06:22:15.249Z", + "time": "2018-12-17T04:51:43.722Z", "url": "http://apidocjs.com", "version": "0.17.6" } diff --git a/docs/api/api_project.json b/docs/api/api_project.json index 8ae30989..af7bf436 100644 --- a/docs/api/api_project.json +++ b/docs/api/api_project.json @@ -9,7 +9,7 @@ "apidoc": "0.3.0", "generator": { "name": "apidoc", - "time": "2018-12-11T06:22:15.249Z", + "time": "2018-12-17T04:51:43.722Z", "url": "http://apidocjs.com", "version": "0.17.6" } diff --git a/routes/api/search.js b/routes/api/search.js index 8918da65..9d1a4253 100644 --- a/routes/api/search.js +++ b/routes/api/search.js @@ -20,13 +20,19 @@ module.exports = { const searchRouter = new express.Router(); /** - * @api {get} /search/:model provide a specific query for any defined model + * @api {get} /search/ provide a specific query for any defined model * @apiName search * @apiGroup Search * @apiVersion 0.0.8 * - * @apiParam (param) {String} model the model to be searched + * @apiParam (query) {String} model the model to be searched * @apiParam (query) {Array} q the query to be executed. For more information on how to format this, please see https://docs.mchacks.ca/architecture/ + * @apiParam (query) {String} model the model to be searched + * @apiParam (query) {String} sort either "asc" or "desc" + * @apiParam (query) {number} page the page number that you would like + * @apiParam (query) {number} limit the maximum number of results that you would like returned + * @apiParam (query) {any} sort_by any parameter you want to sort the results by + * @apiParam (query) {boolean} expand whether you want to expand sub documents within the results * * @apiSuccess {String} message Success message * @apiSuccess {Object} data Results From cc375b23b5de58c3592ddc3bcd4f788267227d57 Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Mon, 17 Dec 2018 00:02:29 -0500 Subject: [PATCH 6/9] Make test more descriptive --- tests/search.service.spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/search.service.spec.js b/tests/search.service.spec.js index e5ec9788..fb7c6732 100644 --- a/tests/search.service.spec.js +++ b/tests/search.service.spec.js @@ -264,7 +264,7 @@ describe("Searching for hackers", function () { }); }); - it("Should expand the accountId", function (done) { + it("Should expand the accountId when expand is set to true", function (done) { util.auth.login(agent, Admin1, (error) => { if (error) { agent.close(); @@ -281,7 +281,8 @@ describe("Searching for hackers", function () { res.should.have.status(200); res.body.should.have.property("data"); res.body.data.should.have.length(2); - res.body.data.should.have.length(2); + res.body.data[0].should.have.property("accountId"); + res.body.data[0].accountId.should.have.property("email"); done(); }); }); From 19ead414b20b6ccf05f22d3cfc0f173de30601a1 Mon Sep 17 00:00:00 2001 From: Pierre Theo Klein Date: Tue, 18 Dec 2018 22:40:42 -0500 Subject: [PATCH 7/9] Move variable to constants file --- constants/general.constant.js | 7 ++++++- services/hacker.service.js | 7 +++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/constants/general.constant.js b/constants/general.constant.js index 919fde8b..d89e4392 100644 --- a/constants/general.constant.js +++ b/constants/general.constant.js @@ -80,6 +80,9 @@ CREATE_ACC_EMAIL_SUBJECTS[SPONSOR] = `You've been invited to create a sponsor ac CREATE_ACC_EMAIL_SUBJECTS[VOLUNTEER] = `You've been invited to create a volunteer account for ${HACKATHON_NAME}`; CREATE_ACC_EMAIL_SUBJECTS[STAFF] = `You've been invited to create a staff account for ${HACKATHON_NAME}`; +const CACHE_TIMEOUT_STATS = 5 * 60 * 1000; +const CACHE_KEY_STATS = "hackerStats"; + module.exports = { HACKATHON_NAME: HACKATHON_NAME, DEVPOST_REGEX: DEVPOST_REGEX, @@ -112,5 +115,7 @@ module.exports = { SPONSOR_T4: SPONSOR_T4, SPONSOR_T5: SPONSOR_T5, ROLE_CATEGORIES: ROLE_CATEGORIES, - POST_ROLES: POST_ROLES + POST_ROLES: POST_ROLES, + CACHE_TIMEOUT_STATS: CACHE_TIMEOUT_STATS, + CACHE_KEY_STATS: CACHE_KEY_STATS }; \ No newline at end of file diff --git a/services/hacker.service.js b/services/hacker.service.js index 0b2b3a46..385135a2 100644 --- a/services/hacker.service.js +++ b/services/hacker.service.js @@ -83,10 +83,9 @@ function findByAccountId(accountId) { async function getStats() { const TAG = `[ hacker Service # getHackerStats ]`; - const CACHE_KEY = "hackerStats"; - if (cache.get(CACHE_KEY) !== null) { + if (cache.get(Constants.CACHE_KEY_STATS) !== null) { logger.info(`${TAG} Getting cached stats`); - return cache.get(CACHE_KEY); + return cache.get(Constants.CACHE_KEY_STATS); } const allHackers = await Hacker.find({}, logger.updateCallbackFactory(TAG, "hacker")).populate({ path: "accountId", @@ -134,7 +133,7 @@ async function getStats() { const age = hacker.accountId.getAge(); stats.age[age] = (stats.age[age]) ? stats.age[age] + 1 : 1; }); - cache.put(CACHE_KEY, stats, 5 * 60 * 1000); //set a time-out of 5 minutes + cache.put(Constants.CACHE_KEY_STATS, stats, Constants.CACHE_TIMEOUT_STATS); //set a time-out of 5 minutes return stats; } From a492167a74360c3aeb23d88f01097ffe7360e1b5 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Thu, 20 Dec 2018 06:26:51 -0500 Subject: [PATCH 8/9] Constants/successes (#213) * Fix mention of docs to reflect new docs address * version 1.1.0 * version 1.1.1 * allow booleanValidator to take comparator value for codeOfConduct * Create tests for false codeOfConduct in hacker * Move success messages to constants file * add missing success message --- README.md | 2 +- VERSION | 2 +- app.js | 2 +- constants/success.constant.js | 72 ++++++++++++++++++++++ controllers/account.controller.js | 11 ++-- controllers/auth.controller.js | 18 +++--- controllers/hacker.controller.js | 13 ++-- controllers/search.controller.js | 14 ++--- controllers/sponsor.controller.js | 7 ++- controllers/team.controller.js | 5 +- controllers/volunteer.controller.js | 3 +- deployment.yaml | 2 +- middlewares/validators/hacker.validator.js | 2 +- middlewares/validators/validator.helper.js | 18 ++++-- tests/account.test.js | 27 ++++---- tests/auth.test.js | 6 +- tests/hacker.test.js | 52 ++++++++++++---- tests/sponsor.test.js | 9 +-- tests/team.test.js | 8 ++- tests/util/hacker.test.util.js | 27 ++++++++ tests/volunteer.test.js | 3 +- 21 files changed, 229 insertions(+), 74 deletions(-) create mode 100644 constants/success.constant.js diff --git a/README.md b/README.md index 1ed30901..7e873a2c 100755 --- a/README.md +++ b/README.md @@ -14,4 +14,4 @@ API for registration, live-site ## How to use and contribute -See documentation here: +See documentation here: diff --git a/VERSION b/VERSION index e4c0d46e..8cfbc905 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.3 \ No newline at end of file +1.1.1 \ No newline at end of file diff --git a/app.js b/app.js index 842e6e37..7167e691 100755 --- a/app.js +++ b/app.js @@ -42,7 +42,7 @@ if (!Services.env.isProduction()) { } else { // TODO: change this when necessary corsOptions = { - origin: [`https://${process.env.FRONTEND_ADDRESS_DEPLOY}`, `https://hackerapi.mchacks.ca`], + origin: [`https://${process.env.FRONTEND_ADDRESS_DEPLOY}`, `https://docs.mchacks.ca`], credentials: true }; } diff --git a/constants/success.constant.js b/constants/success.constant.js new file mode 100644 index 00000000..0e9a335b --- /dev/null +++ b/constants/success.constant.js @@ -0,0 +1,72 @@ +"use strict"; + +const ACCOUNT_GET_BY_EMAIL = "Account found by user email."; +const ACCOUNT_GET_BY_ID = "Account found by user id."; +const ACCOUNT_CREATE = "Account creation successful."; +const ACCOUNT_UPDATE = "Account update successful."; +const ACCOUNT_INVITE = "Account invitation successful."; + +const AUTH_LOGIN = "Login successful."; +const AUTH_LOGOUT = "Logout successful."; +const AUTH_SEND_RESET_EMAIL = "Send reset email successful."; +const AUTH_RESET_PASSWORD = "Reset password successful."; +const AUTH_CONFIRM_ACCOUNT = "Confirm account successful."; +const AUTH_GET_ROLE_BINDINGS = "Get role bindings successful."; +const AUTH_GET_ROLES = "Get roles successful."; +const AUTH_SEND_CONFIRMATION_EMAIL = "Send confirmation email successful."; + +const HACKER_GET_BY_ID = "Hacker found by id."; +const HACKER_READ = "Hacker retrieval successful."; +const HACKER_CREATE = "Hacker creation successful."; +const HACKER_UPDATE = "Hacker update successful."; + +const RESUME_UPLOAD = "Resume upload successful."; +const RESUME_DOWNLOAD = "Resume download successful."; + +const SEARCH_QUERY = "Query search successful. Returning results."; +const SEARCH_NO_RESULTS = "Query search successful. No results found."; + + +const SPONSOR_GET_BY_ID = "Sponsor found by id."; +const SPONSOR_CREATE = "Sponsor creation successful."; + +const TEAM_GET_BY_ID = "Team found by id."; +const TEAM_CREATE = "Team creation successful."; + +const VOLUNTEER_CREATE = "Volunteer creation successful."; + +module.exports = { + ACCOUNT_GET_BY_EMAIL: ACCOUNT_GET_BY_EMAIL, + ACCOUNT_GET_BY_ID: ACCOUNT_GET_BY_ID, + ACCOUNT_CREATE: ACCOUNT_CREATE, + ACCOUNT_UPDATE: ACCOUNT_UPDATE, + ACCOUNT_INVITE: ACCOUNT_INVITE, + + AUTH_LOGIN: AUTH_LOGIN, + AUTH_LOGOUT: AUTH_LOGOUT, + AUTH_SEND_RESET_EMAIL: AUTH_SEND_RESET_EMAIL, + AUTH_RESET_PASSWORD: AUTH_RESET_PASSWORD, + AUTH_CONFIRM_ACCOUNT: AUTH_CONFIRM_ACCOUNT, + AUTH_GET_ROLE_BINDINGS: AUTH_GET_ROLE_BINDINGS, + AUTH_SEND_CONFIRMATION_EMAIL: AUTH_SEND_CONFIRMATION_EMAIL, + AUTH_GET_ROLES: AUTH_GET_ROLES, + + HACKER_GET_BY_ID: HACKER_GET_BY_ID, + HACKER_READ: HACKER_READ, + HACKER_CREATE: HACKER_CREATE, + HACKER_UPDATE: HACKER_UPDATE, + + RESUME_UPLOAD: RESUME_UPLOAD, + RESUME_DOWNLOAD: RESUME_DOWNLOAD, + + SEARCH_QUERY: SEARCH_QUERY, + SEARCH_NO_RESULTS: SEARCH_NO_RESULTS, + + SPONSOR_GET_BY_ID: SPONSOR_GET_BY_ID, + SPONSOR_CREATE: SPONSOR_CREATE, + + TEAM_GET_BY_ID: TEAM_GET_BY_ID, + TEAM_CREATE: TEAM_CREATE, + + VOLUNTEER_CREATE: VOLUNTEER_CREATE, +}; \ No newline at end of file diff --git a/controllers/account.controller.js b/controllers/account.controller.js index ec82f020..5f6f8310 100644 --- a/controllers/account.controller.js +++ b/controllers/account.controller.js @@ -6,6 +6,7 @@ const Services = { const Util = require("../middlewares/util.middleware"); const Constants = { Error: require("../constants/error.constant"), + Success: require("../constants/success.constant"), }; @@ -22,7 +23,7 @@ async function getUserByEmail(req, res) { if (acc) { return res.status(200).json({ - message: "Account found by user email", + message: Constants.Success.ACCOUNT_GET_BY_EMAIL, data: acc.toStrippedJSON() }); } else { @@ -47,7 +48,7 @@ async function getUserById(req, res) { if (acc) { return res.status(200).json({ - message: "Account found by user id", + message: Constants.Success.ACCOUNT_GET_BY_ID, data: acc.toStrippedJSON() }); } else { @@ -69,7 +70,7 @@ async function getUserById(req, res) { async function addUser(req, res) { const acc = req.body.account; return res.status(200).json({ - message: "Account creation successful", + message: Constants.Success.ACCOUNT_CREATE, data: acc.toStrippedJSON() }); } @@ -87,14 +88,14 @@ async function addUser(req, res) { */ function updatedAccount(req, res) { return res.status(200).json({ - message: "Changed account information", + message: Constants.Success.ACCOUNT_UPDATE, data: req.body }); } function invitedAccount(req, res) { return res.status(200).json({ - message: "Successfully invited user", + message: Constants.Success.ACCOUNT_INVITE, data: {} }); } diff --git a/controllers/auth.controller.js b/controllers/auth.controller.js index c4687a88..bfefef30 100644 --- a/controllers/auth.controller.js +++ b/controllers/auth.controller.js @@ -1,52 +1,54 @@ "use strict"; +const Success = require("../constants/success.constant"); + module.exports = { onSuccessfulLogin: function (req, res) { return res.status(200).json({ - message: "Successfully logged in", + message: Success.LOGIN, data: {} }); }, logout: function (req, res) { req.logout(); return res.status(200).json({ - message: "Successfully logged out", + message: Success.LOGOUT, data: {} }); }, sentResetEmail: function (req, res) { return res.status(200).json({ - message: "Sent reset email", + message: Success.AUTH_SEND_RESET_EMAIL, data: {} }); }, resetPassword: function (req, res) { return res.status(200).json({ - message: "Successfully reset password", + message: Success.AUTH_RESET_PASSWORD, data: {} }); }, confirmAccount: function (req, res) { return res.status(200).json({ - message: "Successfully confirmed account", + message: Success.AUTH_CONFIRM_ACCOUNT, data: {} }); }, retrieveRoleBindings: function (req, res) { return res.status(200).json({ - message: "Successfully retrieved role bindings", + message: Success.AUTH_GET_ROLE_BINDINGS, data: req.roleBindings.toJSON() }); }, sentConfirmationEmail: function (req, res) { return res.status(200).json({ - message: "Successfully resent account email", + message: Success.AUTH_SEND_CONFIRMATION_EMAIL, data: {} }) }, retrievedRoles: function (req, res) { return res.status(200).json({ - message: "Successfully retrieved all roles", + message: Success.AUTH_GET_ROLES, data: req.roles }) } diff --git a/controllers/hacker.controller.js b/controllers/hacker.controller.js index dd5164a1..694ca6e8 100644 --- a/controllers/hacker.controller.js +++ b/controllers/hacker.controller.js @@ -5,6 +5,7 @@ const Services = { }; const Util = require("../middlewares/util.middleware"); const Constants = { + Success: require("../constants/success.constant"), Error: require("../constants/error.constant"), }; @@ -21,7 +22,7 @@ async function findById(req, res) { if (hacker) { return res.status(200).json({ - message: "Successfully retrieved hacker information", + message: Constants.Success.HACKER_GET_BY_ID, data: hacker.toJSON() }); } else { @@ -41,7 +42,7 @@ async function findById(req, res) { */ function showHacker(req, res) { return res.status(200).json({ - message: "Hacker retrieval successful", + message: Constants.Success.HACKER_READ, data: req.body.hacker.toJSON() }); } @@ -55,7 +56,7 @@ function showHacker(req, res) { */ function createdHacker(req, res) { return res.status(200).json({ - message: "Hacker creation successful", + message: Constants.Success.HACKER_CREATE, data: req.body.hacker.toJSON() }); } @@ -73,14 +74,14 @@ function createdHacker(req, res) { */ function updatedHacker(req, res) { return res.status(200).json({ - message: "Changed hacker information", + message: Constants.Success.HACKER_UPDATE, data: req.body }); } function uploadedResume(req, res) { return res.status(200).json({ - message: "Uploaded resume", + message: Constants.Success.RESUME_UPLOAD, data: { filename: req.body.gcfilename } @@ -89,7 +90,7 @@ function uploadedResume(req, res) { function downloadedResume(req, res) { return res.status(200).json({ - message: "Downloaded resume", + message: Constants.Success.RESUME_DOWNLOAD, data: { id: req.body.id, resume: req.body.resume diff --git a/controllers/search.controller.js b/controllers/search.controller.js index 4faacc06..52ca25de 100644 --- a/controllers/search.controller.js +++ b/controllers/search.controller.js @@ -4,16 +4,16 @@ const Services = { Logger: require("../services/logger.service") }; const Util = require("../middlewares/util.middleware"); +const Success = require("../constants/success.constant"); async function searchResults(req, res) { let results = req.body.results; let message; - if(results.length < 1){ - message = "No results found." - results = {} - } - else{ - message = "Successfully executed query, returning all results" + if (results.length < 1) { + message = Success.SEARCH_NO_RESULTS; + results = {}; + } else { + message = Success.SEARCH_QUERY; } return res.status(200).json({ message: message, @@ -23,4 +23,4 @@ async function searchResults(req, res) { module.exports = { searchResults: Util.asyncMiddleware(searchResults) -} \ No newline at end of file +}; \ No newline at end of file diff --git a/controllers/sponsor.controller.js b/controllers/sponsor.controller.js index 496e6421..8c571797 100644 --- a/controllers/sponsor.controller.js +++ b/controllers/sponsor.controller.js @@ -5,8 +5,9 @@ const Services = { }; const Util = require("../middlewares/util.middleware"); const Constants = { + Success: require("../constants/success.constant"), Error: require("../constants/error.constant"), -} +}; /** * @async @@ -22,7 +23,7 @@ async function findById(req, res) { if (sponsor) { return res.status(200).json({ - message: "Successfully retrieved sponsor information", + message: Constants.Success.SPONSOR_GET_BY_ID, data: sponsor.toJSON() }); } else { @@ -48,7 +49,7 @@ async function createSponsor(req, res) { if (success) { return res.status(200).json({ - message: "Sponsor creation successful", + message: Constants.Success.SPONSOR_CREATE, data: sponsorDetails }); } else { diff --git a/controllers/team.controller.js b/controllers/team.controller.js index 016dabbe..d39b5239 100644 --- a/controllers/team.controller.js +++ b/controllers/team.controller.js @@ -7,6 +7,7 @@ const Services = { }; const Util = require("../middlewares/util.middleware"); const Constants = { + Success: require("../constants/success.constant"), Error: require("../constants/error.constant"), }; @@ -23,7 +24,7 @@ async function findById(req, res) { if (team) { return res.status(200).json({ - message: "Successfully retrieved team information", + message: Constants.Success.TEAM_GET_BY_ID, data: team.toJSON() }); } else { @@ -49,7 +50,7 @@ async function createTeam(req, res) { if (success) { return res.status(200).json({ - message: "Team creation successful", + message: Constants.Success.TEAM_CREATE, data: teamDetails }); } else { diff --git a/controllers/volunteer.controller.js b/controllers/volunteer.controller.js index 6f9574a4..d3909e4e 100644 --- a/controllers/volunteer.controller.js +++ b/controllers/volunteer.controller.js @@ -5,6 +5,7 @@ const Services = { }; const Util = require("../middlewares/util.middleware"); const Constants = { + Success: require("../constants/success.constant"), Error: require("../constants/error.constant"), } @@ -23,7 +24,7 @@ async function createVolunteer(req, res) { if (success) { return res.status(200).json({ - message: "Volunteer creation successful", + message: Constants.Success.VOLUNTEER_CREATE, data: volunteerDetails }); } else { diff --git a/deployment.yaml b/deployment.yaml index dbb0bdb9..d9adb40b 100644 --- a/deployment.yaml +++ b/deployment.yaml @@ -18,7 +18,7 @@ spec: secretName: hackboard-secret containers: - name: hackboard - image: gcr.io/mchacks-api/hackboard:1.0.2 + image: gcr.io/mchacks-api/hackboard:latest ports: # - containerPort: 443 - containerPort: 8080 \ No newline at end of file diff --git a/middlewares/validators/hacker.validator.js b/middlewares/validators/hacker.validator.js index cbe2b984..bf90c54f 100644 --- a/middlewares/validators/hacker.validator.js +++ b/middlewares/validators/hacker.validator.js @@ -13,7 +13,7 @@ module.exports = { VALIDATOR.alphaArrayValidator("body", "ethnicity", false), VALIDATOR.nameValidator("body", "major", false), VALIDATOR.integerValidator("body", "graduationYear", false, 2019, 2030), - VALIDATOR.booleanValidator("body", "codeOfConduct", false), + VALIDATOR.booleanValidator("body", "codeOfConduct", false, true), ], updateConfirmationValidator: [ diff --git a/middlewares/validators/validator.helper.js b/middlewares/validators/validator.helper.js index 54389ef6..704332da 100644 --- a/middlewares/validators/validator.helper.js +++ b/middlewares/validators/validator.helper.js @@ -103,19 +103,29 @@ function mongoIdArrayValidator(fieldLocation, fieldname, optional = true) { } /** - * Validates that field must be boolean. + * Validates that field must be boolean. Optionally checks for desired boolean * @param {"query" | "body" | "header" | "param"} fieldLocation the location where the field should be found * @param {string} fieldname name of the field that needs to be validated. * @param {boolean} optional whether the field is optional or not. */ -function booleanValidator(fieldLocation, fieldname, optional = true) { +function booleanValidator(fieldLocation, fieldname, optional = true, desire = null) { const booleanField = setProperValidationChainBuilder(fieldLocation, fieldname, "invalid boolean"); if (optional) { // do not use check falsy option as a 'false' boolean will be skipped - return booleanField.optional().isBoolean().withMessage("must be boolean"); + return booleanField.optional().isBoolean().withMessage("must be boolean").custom((val) => { + if (desire !== null) { + return desire === val; + } + return true; + }).withMessage(`Must be equal to ${desire}`); } else { - return booleanField.exists().isBoolean().withMessage("must be boolean"); + return booleanField.exists().isBoolean().withMessage("must be boolean").custom((val) => { + if (desire !== null) { + return desire === val; + } + return true; + }).withMessage(`Must be equal to ${desire}`); } } diff --git a/tests/account.test.js b/tests/account.test.js index 72d3bafb..d36cdde2 100644 --- a/tests/account.test.js +++ b/tests/account.test.js @@ -8,7 +8,8 @@ const Account = require("../models/account.model"); const should = chai.should(); const Constants = { Error: require("../constants/error.constant"), - General: require("../constants/general.constant") + General: require("../constants/general.constant"), + Success: require("../constants/success.constant"), }; @@ -76,7 +77,7 @@ describe("GET user account", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Account found by user email"); + res.body.message.should.equal(Constants.Success.ACCOUNT_GET_BY_EMAIL); res.body.should.have.property("data"); res.body.data.should.be.a("object"); res.body.data.should.have.property("firstName"); @@ -106,7 +107,7 @@ describe("GET user account", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Account found by user id"); + res.body.message.should.equal(Constants.Success.ACCOUNT_GET_BY_ID); res.body.should.have.property("data"); // use acc.toStrippedJSON to deal with hidden passwords and convert _id to id @@ -133,7 +134,7 @@ describe("GET user account", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Account found by user id"); + res.body.message.should.equal(Constants.Success.ACCOUNT_GET_BY_ID); res.body.should.have.property("data"); // use acc.toStrippedJSON to deal with hidden passwords and convert _id to id @@ -180,7 +181,7 @@ describe("POST create account", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Account creation successful"); + res.body.message.should.equal(Constants.Success.ACCOUNT_CREATE); // use acc.toStrippedJSON to deal with hidden passwords and convert _id to id const acc = (new Account(newAccount1)).toStrippedJSON(); @@ -212,7 +213,7 @@ describe("POST confirm account", function () { .end(function (err, res) { res.should.have.status(200); res.body.should.have.property("message"); - res.body.message.should.equal("Successfully confirmed account"); + res.body.message.should.equal(Constants.Success.AUTH_CONFIRM_ACCOUNT); done(); }); }); @@ -281,7 +282,7 @@ describe("PATCH update account", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Changed account information"); + res.body.message.should.equal(Constants.Success.ACCOUNT_UPDATE); res.body.should.have.property("data"); // Is this correct matching of data? res.body.data.firstName.should.equal(updatedInfo.firstName); @@ -306,7 +307,7 @@ describe("PATCH update account", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Changed account information"); + res.body.message.should.equal(Constants.Success.ACCOUNT_UPDATE); res.body.should.have.property("data"); // Is this correct matching of data? res.body.data.firstName.should.equal(updatedInfo.firstName); @@ -353,7 +354,7 @@ describe("POST reset password", function () { .end(function (err, res) { res.should.have.status(200); res.body.should.have.property("message"); - res.body.message.should.equal("Successfully reset password"); + res.body.message.should.equal(Constants.Success.AUTH_RESET_PASSWORD); done(); }); }); @@ -397,7 +398,7 @@ describe("PATCH change password for logged in user", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Successfully reset password"); + res.body.message.should.equal(Constants.Success.AUTH_RESET_PASSWORD); done(); }); }); @@ -437,7 +438,7 @@ describe("GET retrieve permissions", function () { .end(function (err, res) { res.should.have.status(200); res.body.should.have.property("message"); - res.body.message.should.equal("Successfully retrieved role bindings"); + res.body.message.should.equal(Constants.Success.AUTH_GET_ROLE_BINDINGS); res.body.should.have.property("data"); res.body.data.should.be.a("object"); res.body.data.should.have.property("roles"); @@ -474,7 +475,7 @@ describe("GET resend confirmation email", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Successfully resent account email"); + res.body.message.should.equal(Constants.Success.AUTH_SEND_CONFIRMATION_EMAIL); done(); }); }); @@ -538,7 +539,7 @@ describe("POST invite account", function () { } res.should.have.status(200); res.body.should.have.property("message"); - res.body.message.should.equal("Successfully invited user"); + res.body.message.should.equal(Constants.Success.ACCOUNT_INVITE); done(); }); }); diff --git a/tests/auth.test.js b/tests/auth.test.js index 65452f31..d0a428bc 100644 --- a/tests/auth.test.js +++ b/tests/auth.test.js @@ -17,6 +17,10 @@ const util = { role: require("./util/role.test.util") }; +const constants = { + success: require("../constants/success.constant"), +} + const roles = require("../constants/role.constant"); // hacker role binding @@ -38,7 +42,7 @@ describe("GET roles", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Successfully retrieved all roles"); + res.body.message.should.equal(constants.success.AUTH_GET_ROLES); res.body.should.have.property("data"); res.body.data.should.be.a("Array"); diff --git a/tests/hacker.test.js b/tests/hacker.test.js index 6cea4d30..85b7af2a 100644 --- a/tests/hacker.test.js +++ b/tests/hacker.test.js @@ -9,6 +9,7 @@ const Hacker = require("../models/hacker.model"); const fs = require("fs"); const path = require("path"); const Constants = { + Success: require("../constants/success.constant"), General: require("../constants/general.constant"), Error: require("../constants/error.constant"), }; @@ -36,6 +37,9 @@ const storedAccount2 = util.account.Account2; const storedHacker2 = util.hacker.HackerB; const newHacker1 = util.hacker.newHacker1; +// badConductHacker1 is the same as newHacker1, even linking to the same account +// the difference is that badConductHacker1 does not accept the code of conducts +const badConductHacker1 = util.hacker.badCodeOfConductHacker1; const newHackerAccount1 = util.account.allAccounts[13]; const newHacker2 = util.hacker.newHacker2; @@ -71,7 +75,7 @@ describe("GET hacker", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Hacker retrieval successful"); + res.body.message.should.equal(Constants.Success.HACKER_READ); res.body.should.have.property("data"); let hacker = new Hacker(storedHacker1); @@ -117,7 +121,7 @@ describe("GET hacker", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Successfully retrieved hacker information"); + res.body.message.should.equal(Constants.Success.HACKER_GET_BY_ID); res.body.should.have.property("data"); let hacker = new Hacker(storedHacker1); @@ -145,7 +149,7 @@ describe("GET hacker", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Successfully retrieved hacker information"); + res.body.message.should.equal(Constants.Success.HACKER_GET_BY_ID); res.body.should.have.property("data"); let hacker = new Hacker(storedHacker1); @@ -240,7 +244,7 @@ describe("POST create hacker", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Hacker creation successful"); + res.body.message.should.equal(Constants.Success.HACKER_CREATE); res.body.should.have.property("data"); // create JSON version of model @@ -272,7 +276,7 @@ describe("POST create hacker", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Hacker creation successful"); + res.body.message.should.equal(Constants.Success.HACKER_CREATE); res.body.should.have.property("data"); // create JSON version of model @@ -288,6 +292,30 @@ describe("POST create hacker", function () { }); }); + // should fail due to 'false' on code of conduct + it("should FAIL if the new hacker does not accept code of conduct", function (done) { + util.auth.login(agent, newHackerAccount1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .post(`/api/hacker/`) + .type("application/json") + .send(badConductHacker1) + .end(function (err, res) { + res.should.have.status(422); + res.should.be.json; + res.body.should.have.property("message"); + res.body.message.should.equal("Validation failed"); + res.body.should.have.property("data"); + res.body.data.should.have.property("codeOfConduct"); + res.body.data.codeOfConduct.msg.should.equal("Must be equal to true"); + done(); + }); + }); + }); + // fail on unconfirmed account, using admin it("should FAIL to create a new hacker if the account hasn't been confirmed", function (done) { util.auth.login(agent, Admin1, (error) => { @@ -385,7 +413,7 @@ describe("PATCH update one hacker", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Changed hacker information"); + res.body.message.should.equal(Constants.Success.HACKER_UPDATE); res.body.should.have.property("data"); chai.assert.equal(JSON.stringify(res.body.data), JSON.stringify({ gender: "Other" @@ -411,7 +439,7 @@ describe("PATCH update one hacker", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Changed hacker information"); + res.body.message.should.equal(Constants.Success.HACKER_UPDATE); res.body.should.have.property("data"); chai.assert.equal(JSON.stringify(res.body.data), JSON.stringify({ status: "Accepted" @@ -461,7 +489,7 @@ describe("PATCH update one hacker", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Changed hacker information"); + res.body.message.should.equal(Constants.Success.HACKER_UPDATE); res.body.should.have.property("data"); chai.assert.equal(JSON.stringify(res.body.data), JSON.stringify({ status: "Checked-in" @@ -512,7 +540,7 @@ describe("PATCH update one hacker", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Changed hacker information"); + res.body.message.should.equal(Constants.Success.HACKER_UPDATE); res.body.should.have.property("data"); chai.assert.equal(JSON.stringify(res.body.data), JSON.stringify({ gender: "Other" @@ -591,7 +619,7 @@ describe("PATCH update one hacker", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Changed hacker information"); + res.body.message.should.equal(Constants.Success.HACKER_UPDATE); res.body.should.have.property("data"); chai.assert.equal(JSON.stringify(res.body.data), JSON.stringify({ status: Constants.General.HACKER_STATUS_CONFIRMED @@ -622,7 +650,7 @@ describe("PATCH update one hacker", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Changed hacker information"); + res.body.message.should.equal(Constants.Success.HACKER_UPDATE); res.body.should.have.property("data"); chai.assert.equal(JSON.stringify(res.body.data), JSON.stringify({ status: Constants.General.HACKER_STATUS_ACCEPTED @@ -706,7 +734,7 @@ describe("POST add a hacker resume", function () { res.should.have.status(200); res.should.have.property("body"); res.body.should.have.property("message"); - res.body.message.should.equal("Uploaded resume"); + res.body.message.should.equal(Constants.Success.RESUME_UPLOAD); res.body.should.have.property("data"); res.body.data.should.have.property("filename"); StorageService.download(res.body.data.filename).then((value) => { diff --git a/tests/sponsor.test.js b/tests/sponsor.test.js index f577a6fe..dd6b9f5a 100644 --- a/tests/sponsor.test.js +++ b/tests/sponsor.test.js @@ -8,6 +8,7 @@ const Sponsor = require("../models/sponsor.model"); const should = chai.should(); const mongoose = require("mongoose"); const Constants = { + Success: require("../constants/success.constant"), Error: require("../constants/error.constant"), }; @@ -67,7 +68,7 @@ describe("GET user sponsor", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Successfully retrieved sponsor information"); + res.body.message.should.equal(Constants.Success.SPONSOR_GET_BY_ID); res.body.should.have.property("data"); res.body.data.should.be.a("object"); @@ -93,7 +94,7 @@ describe("GET user sponsor", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Successfully retrieved sponsor information"); + res.body.message.should.equal(Constants.Success.SPONSOR_GET_BY_ID); res.body.should.have.property("data"); res.body.data.should.be.a("object"); @@ -180,10 +181,10 @@ describe("POST create sponsor", function () { .type("application/json") .send(newSponsor) .end(function (err, res) { - // res.should.have.status(200); + res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Sponsor creation successful"); + res.body.message.should.equal(Constants.Success.SPONSOR_CREATE); res.body.should.have.property("data"); // deleting _id because that was generated, and not part of original data diff --git a/tests/team.test.js b/tests/team.test.js index 723a3e9e..bbb4473f 100644 --- a/tests/team.test.js +++ b/tests/team.test.js @@ -9,6 +9,10 @@ const util = { team: require("./util/team.test.util"), }; +const Constants = { + Success: require("../constants/success.constant"), +} + describe("GET team", function () { it("should SUCCEED and list a team's information from /api/team/:id GET", function (done) { chai.request(server.app) @@ -17,7 +21,7 @@ describe("GET team", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Successfully retrieved team information"); + res.body.message.should.equal(Constants.Success.TEAM_GET_BY_ID); res.body.should.have.property("data"); let team = new Team(util.team.Team1); @@ -37,7 +41,7 @@ describe("POST create team", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Team creation successful"); + res.body.message.should.equal(Constants.Success.TEAM_CREATE); res.body.should.have.property("data"); // deleting _id because that was generated, and not part of original data diff --git a/tests/util/hacker.test.util.js b/tests/util/hacker.test.util.js index 8f32ff44..360a80e2 100644 --- a/tests/util/hacker.test.util.js +++ b/tests/util/hacker.test.util.js @@ -27,6 +27,32 @@ const invalidHacker1 = { "codeOfConduct": true, }; +// duplicate of newHack1, but with false for code of conduct +const badCodeOfConductHacker1 = { + "accountId": Util.Account.generatedAccounts[6]._id, + "school": "University of ASDF", + "degree": "Masters", + "gender": "Female", + "needsBus": true, + "application": { + "portfolioURL": { + //gcloud bucket link + "resume": "www.gcloud.com/myResume100", + "github": "www.github.com/Person1", + "dropler": undefined, + "personal": "www.person1.com", + "linkedIn": "www.linkedin.com/in/Person1", + "other": undefined + }, + "jobInterest": "Full-time", + "skills": ["CSS", "HTML", "JS"], + }, + "ethnicity": ["Caucasian"], + "major": "EE", + "graduationYear": 2019, + "codeOfConduct": false, +}; + const duplicateAccountLinkHacker1 = { "_id": mongoose.Types.ObjectId(), "accountId": Util.Account.Account1._id, @@ -195,6 +221,7 @@ module.exports = { invalidHacker1: invalidHacker1, newHacker1: newHacker1, newHacker2: newHacker2, + badCodeOfConductHacker1: badCodeOfConductHacker1, HackerA: HackerA, HackerB: HackerB, HackerC: HackerC, diff --git a/tests/volunteer.test.js b/tests/volunteer.test.js index 6e5d56ae..e527bd56 100644 --- a/tests/volunteer.test.js +++ b/tests/volunteer.test.js @@ -7,6 +7,7 @@ const agent = chai.request.agent(server.app); const should = chai.should(); const Volunteer = require("../models/volunteer.model"); const Constants = { + Success: require("../constants/success.constant"), Error: require("../constants/error.constant"), }; @@ -79,7 +80,7 @@ describe("POST create volunteer", function () { res.should.have.status(200); res.should.be.json; res.body.should.have.property("message"); - res.body.message.should.equal("Volunteer creation successful"); + res.body.message.should.equal(Constants.Success.VOLUNTEER_CREATE); res.body.should.have.property("data"); // deleting _id because that was generated, and not part of original data From 938cb2037c88b9ac7592f53d25c01ac17e3d0cb2 Mon Sep 17 00:00:00 2001 From: Richard Zhang Date: Thu, 20 Dec 2018 16:20:10 -0500 Subject: [PATCH 9/9] Fix develop (#223) * Fix mention of docs to reflect new docs address * version 1.1.0 * version 1.1.1 * allow booleanValidator to take comparator value for codeOfConduct * Create tests for false codeOfConduct in hacker