diff --git a/constants/error.constant.js b/constants/error.constant.js index 60ac33ca..c86160e8 100644 --- a/constants/error.constant.js +++ b/constants/error.constant.js @@ -25,12 +25,15 @@ const TEAM_MEMBER_422_MESSAGE = "Duplicate team member in input"; const VALIDATION_422_MESSAGE = "Validation failed"; const ACCOUNT_DUPLICATE_422_MESSAGE = "Account already exists"; const ROLE_DUPLICATE_422_MESSAGE = "Role already exists"; +const SETTINGS_422_MESSAGE = + "openTime must be before closeTime, and closeTime must be before confirmTime"; const ACCOUNT_TOKEN_401_MESSAGE = "Invalid token for account"; const AUTH_401_MESSAGE = "Invalid Authentication"; const AUTH_403_MESSAGE = "Invalid Authorization"; const ACCOUNT_403_MESSAGE = "Account not verified"; +const SETTINGS_403_MESSAGE = "Applications are not open right now"; const TEAM_READ_500_MESSAGE = "Error while retrieving team"; const TEAM_UPDATE_500_MESSAGE = "Error while updating team"; @@ -79,6 +82,7 @@ module.exports = { HACKER_STATUS_409_MESSAGE: HACKER_STATUS_409_MESSAGE, TEAM_SIZE_409_MESSAGE: TEAM_SIZE_409_MESSAGE, ROLE_DUPLICATE_422_MESSAGE: ROLE_DUPLICATE_422_MESSAGE, + SETTINGS_422_MESSAGE: SETTINGS_422_MESSAGE, ROLE_CREATE_500_MESSAGE: ROLE_CREATE_500_MESSAGE, TEAM_NAME_409_MESSAGE: TEAM_NAME_409_MESSAGE, TEAM_JOIN_SAME_409_MESSAGE: TEAM_JOIN_SAME_409_MESSAGE, @@ -86,6 +90,7 @@ module.exports = { VOLUNTEER_404_MESSAGE: VOLUNTEER_404_MESSAGE, SPONSOR_UPDATE_500_MESSAGE: SPONSOR_UPDATE_500_MESSAGE, SETTINGS_404_MESSAGE: SETTINGS_404_MESSAGE, + SETTINGS_403_MESSAGE: SETTINGS_403_MESSAGE, TRAVEL_404_MESSAGE: TRAVEL_404_MESSAGE, TRAVEL_CREATE_500_MESSAGE: TRAVEL_CREATE_500_MESSAGE }; diff --git a/middlewares/settings.middleware.js b/middlewares/settings.middleware.js index 6d91acae..828268aa 100644 --- a/middlewares/settings.middleware.js +++ b/middlewares/settings.middleware.js @@ -55,6 +55,28 @@ async function updateSettings(req, res, next) { } } +/** + * @function confirmValidPatch + * @param {{body:{settingsDetails:{openTime:Date, closeTime:Date, confirmTime:Date}}}} req + * @param {*} res + * @param {*} next + * @return {void} + * @description Confirms that openTime < closeTime < confirmTime + */ +function confirmValidPatch(req, res, next) { + const openTime = new Date(req.body.settingsDetails.openTime); + const closeTime = new Date(req.body.settingsDetails.closeTime); + const confirmTime = new Date(req.body.settingsDetails.confirmTime); + if (openTime < closeTime && closeTime < confirmTime) { + return next(); + } + return next({ + status: 422, + message: Constants.Error.SETTINGS_422_MESSAGE, + error: req.body.settingsDetails + }); +} + /** * @function updateSettings * @param {*} req @@ -76,8 +98,38 @@ async function getSettings(req, res, next) { } } +/** + * @function confirmAppsOpen + * @param {*} req + * @param {*} res + * @param {*} next + * @description Only succeeds if the currentTime > openTime, and currentTime < closeTime + */ +async function confirmAppsOpen(req, res, next) { + const settings = await Services.Settings.getSettings(); + if (!settings) { + return next({ + status: 500, + message: Constants.Error.GENERIC_500_MESSAGE + }); + } else { + const now = Date.now(); + const openTime = new Date(settings.openTime); + const closeTime = new Date(settings.closeTime); + if (openTime < now && closeTime > now) { + return next(); + } + return next({ + status: 403, + message: Constants.Error.SETTINGS_403_MESSAGE + }); + } +} + module.exports = { parsePatch: parsePatch, + confirmValidPatch: confirmValidPatch, + confirmAppsOpen: Middleware.Util.asyncMiddleware(confirmAppsOpen), updateSettings: Middleware.Util.asyncMiddleware(updateSettings), getSettings: Middleware.Util.asyncMiddleware(getSettings) }; diff --git a/routes/api/hacker.js b/routes/api/hacker.js index 038ed869..1d4b108d 100644 --- a/routes/api/hacker.js +++ b/routes/api/hacker.js @@ -14,7 +14,8 @@ const Middleware = { Util: require("../../middlewares/util.middleware"), Hacker: require("../../middlewares/hacker.middleware"), Auth: require("../../middlewares/auth.middleware"), - Search: require("../../middlewares/search.middleware") + Search: require("../../middlewares/search.middleware"), + Settings: require("../../middlewares/settings.middleware") }; const Services = { Hacker: require("../../services/hacker.service"), @@ -180,8 +181,9 @@ module.exports = { Middleware.Auth.ensureAuthenticated(), Middleware.Auth.ensureAuthorized(), Middleware.Validator.Hacker.newHackerValidator, - Middleware.parseBody.middleware, + Middleware.Settings.confirmAppsOpen, + // validate type Middleware.Hacker.validateConfirmedStatusFromAccountId, // validate that the accountId is not being used for any other thing diff --git a/routes/api/settings.js b/routes/api/settings.js index 74c6d289..3a8839f9 100644 --- a/routes/api/settings.js +++ b/routes/api/settings.js @@ -78,6 +78,7 @@ module.exports = { Middleware.Validator.Settings.createSettingsValidator, Middleware.parseBody.middleware, Middleware.Settings.parsePatch, + Middleware.Settings.confirmValidPatch, Middleware.Settings.updateSettings, Controllers.Settings.patchedSettings ); diff --git a/tests/hacker.test.js b/tests/hacker.test.js index 89b82f08..027dfa34 100644 --- a/tests/hacker.test.js +++ b/tests/hacker.test.js @@ -18,6 +18,7 @@ const util = { auth: require("./util/auth.test.util"), hacker: require("./util/hacker.test.util"), account: require("./util/account.test.util"), + settings: require("./util/settings.test.util"), accountConfirmation: require("./util/accountConfirmation.test.util") }; const StorageService = require("../services/storage.service"); @@ -459,6 +460,56 @@ describe("POST create hacker", function() { }); }); + it("should FAIL to create a new hacker when applications have not yet opened.", function(done) { + // Upload application not yet open. + util.settings.setApplicationNotYetOpen().then( + util.auth.login(agent, newHackerAccount0, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .post(`/api/hacker/`) + .type("application/json") + .send(newHacker0) + .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.SETTINGS_403_MESSAGE + ); + done(); + }); + }) + ); + }); + + it("should FAIL to create a new hacker when applications have closed.", function(done) { + // Upload application closed. + util.settings.setApplicationClosed().then( + util.auth.login(agent, newHackerAccount0, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .post(`/api/hacker/`) + .type("application/json") + .send(newHacker0) + .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.SETTINGS_403_MESSAGE + ); + done(); + }); + }) + ); + }); + // should fail due to travel request larger than 100 it("should FAIL if the new hacker inputs a value larger than 100 for travel reimbursement", function(done) { util.auth.login(agent, newHackerAccount0, (error) => { diff --git a/tests/settings.test.js b/tests/settings.test.js index f456a4a4..0ba5d2f4 100644 --- a/tests/settings.test.js +++ b/tests/settings.test.js @@ -7,7 +7,8 @@ const agent = chai.request.agent(server.app); chai.should(); const util = { account: require("./util/account.test.util"), - auth: require("./util/auth.test.util") + auth: require("./util/auth.test.util"), + settings: require("./util/settings.test.util") }; const Constants = { @@ -84,11 +85,7 @@ describe("PATCH settings", function() { agent .patch(`/api/settings/`) .type("application/json") - .send({ - openTime: new Date().toString(), - closeTime: new Date().toString(), - confirmTime: new Date().toString() - }) + .send(util.settings.settingConfirmClosed) // does not have password because of to stripped json .end(function(err, res) { res.should.have.status(200); diff --git a/tests/util/settings.test.util.js b/tests/util/settings.test.util.js index 20277adc..bf7ff150 100644 --- a/tests/util/settings.test.util.js +++ b/tests/util/settings.test.util.js @@ -1,19 +1,19 @@ const Settings = require("../../models/settings.model"); const logger = require("../../services/logger.service"); -const settingRegistrationNotYetOpen = { +const settingApplicationNotYetOpen = { openTime: new Date(Date.now() + 100000000000), closeTime: new Date(Date.now() + 10000000000000000), confirmTime: new Date(Date.now() + 100000000000000000) }; -const settingRegistrationOpen = { +const settingApplicationOpen = { openTime: new Date(Date.now() - 100), closeTime: new Date(Date.now() + 10000000000), confirmTime: new Date(Date.now() + 100000000000000) }; -const settingRegistrationClosed = { +const settingApplicationClosed = { openTime: new Date(Date.now() - 100), closeTime: new Date(Date.now() - 1000), confirmTime: new Date(Date.now() + 100000000000000) @@ -26,9 +26,22 @@ const settingConfirmClosed = { }; async function storeAll() { - const toStore = new Settings(settingRegistrationClosed); + const toStore = new Settings(settingApplicationOpen); Settings.collection.insertOne(toStore); } + +async function setApplicationClosed() { + await dropAll(); + const toStore = new Settings(settingApplicationClosed); + Settings.collection.insertOne(toStore); +} + +async function setApplicationNotYetOpen() { + await dropAll(); + const toStore = new Settings(settingApplicationNotYetOpen); + Settings.collection.insertOne(toStore); +} + async function dropAll() { try { await Settings.collection.drop(); @@ -43,8 +56,10 @@ async function dropAll() { module.exports = { storeAll: storeAll, dropAll: dropAll, - settingRegistrationNotYetOpen: settingRegistrationNotYetOpen, - settingRegistrationOpen: settingRegistrationOpen, - settingRegistrationClosed: settingRegistrationClosed, + setApplicationClosed: setApplicationClosed, + setApplicationNotYetOpen: setApplicationNotYetOpen, + settingApplicationNotYetOpen: settingApplicationNotYetOpen, + settingApplicationOpen: settingApplicationOpen, + settingApplicationClosed: settingApplicationClosed, settingConfirmClosed: settingConfirmClosed };