From f289e85e524c64195c51306b825492a0c4225270 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Sun, 5 Feb 2023 19:39:31 +0000 Subject: [PATCH 01/32] Added route to edit company details --- src/api/routes/company.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/api/routes/company.js b/src/api/routes/company.js index 4b927961..a05ebdcb 100644 --- a/src/api/routes/company.js +++ b/src/api/routes/company.js @@ -202,4 +202,28 @@ export default (app) => { return next(err); } }); + + + /** + * Edit company details. + * Company or admin can edit. + */ + + router.patch("/:companyId/edit", + or([ + authMiddleware.isCompany, + authMiddleware.isAdmin, + authMiddleware.isGod + ], { status_code: HTTPStatus.UNAUTHORIZED, error_code: ErrorTypes.FORBIDDEN, msg: ValidationReasons.INSUFFICIENT_PERMISSIONS }), + validators.edit, + (req, res, next) => companyMiddleware.canManageAccountSettings(req.params.companyId)(req, res, next), + async (req, res, next) => { + try { + const service = new CompanyService(); + const company = await service.edit(req.params.companyId, req.body); + return res.json(company); + } catch (err) { + return next(err); + } + }); }; From d0a408f8742d894218608d0fdc99fefd5e5d08f8 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Sun, 5 Feb 2023 19:46:48 +0000 Subject: [PATCH 02/32] Changed request from patch to put in route --- src/api/routes/company.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/routes/company.js b/src/api/routes/company.js index a05ebdcb..c3e8702d 100644 --- a/src/api/routes/company.js +++ b/src/api/routes/company.js @@ -209,7 +209,7 @@ export default (app) => { * Company or admin can edit. */ - router.patch("/:companyId/edit", + router.put("/:companyId/edit", or([ authMiddleware.isCompany, authMiddleware.isAdmin, @@ -220,10 +220,10 @@ export default (app) => { async (req, res, next) => { try { const service = new CompanyService(); - const company = await service.edit(req.params.companyId, req.body); + const company = await service.editCompanyDetails(req.params.companyId, req.body); return res.json(company); } catch (err) { return next(err); } }); -}; +}; \ No newline at end of file From 6b848c3a0417eff8a70563860cc64a4e48bf244d Mon Sep 17 00:00:00 2001 From: marco-vb Date: Sun, 5 Feb 2023 19:47:30 +0000 Subject: [PATCH 03/32] Added company editing details implementation to services/company.js --- src/services/company.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/services/company.js b/src/services/company.js index 4fbed913..ef8da717 100644 --- a/src/services/company.js +++ b/src/services/company.js @@ -189,6 +189,29 @@ class CompanyService { } } + + /** + * Edit company details by its ID and returns it + * @param {*} companyId ID of the company + * @param {*} companyDetails company details to be updated + */ + + async editCompanyDetails(companyId, companyDetails) { + try { + const company = await Company.findById(companyId); + company.name = companyDetails.name; + company.contacts = companyDetails.contacts; + company.bio = companyDetails.bio; + company.logo = companyDetails.logo; + + await company.save(); + return company; + } catch (err) { + console.error(err); + throw err; + } + } + } export default CompanyService; From 094720f46e4c5024265898c66b05e6303b66041b Mon Sep 17 00:00:00 2001 From: marco-vb Date: Sun, 5 Feb 2023 20:17:00 +0000 Subject: [PATCH 04/32] Fixed small mistake in route --- src/api/routes/company.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/api/routes/company.js b/src/api/routes/company.js index c3e8702d..71d0ae9e 100644 --- a/src/api/routes/company.js +++ b/src/api/routes/company.js @@ -219,11 +219,13 @@ export default (app) => { (req, res, next) => companyMiddleware.canManageAccountSettings(req.params.companyId)(req, res, next), async (req, res, next) => { try { - const service = new CompanyService(); - const company = await service.editCompanyDetails(req.params.companyId, req.body); + const companyService = new CompanyService(); + const company = await companyService.editCompanyDetails(req.params.companyId, req.body); + await companyService.sendCompanyEditedNotification(req.params.companyId); return res.json(company); } catch (err) { return next(err); } - }); + } + ); }; \ No newline at end of file From e4d10beca3d979a79e70c04e2217d4e9f6f9c82c Mon Sep 17 00:00:00 2001 From: marco-vb Date: Sun, 5 Feb 2023 20:18:23 +0000 Subject: [PATCH 05/32] Added function sendCompanyEditedNotification to services and added COMPANY_EDIT_NOTIFICATION to the templates --- src/email-templates/companyManagement.js | 5 +++++ src/services/company.js | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/src/email-templates/companyManagement.js b/src/email-templates/companyManagement.js index d23049bc..f3a73c4e 100644 --- a/src/email-templates/companyManagement.js +++ b/src/email-templates/companyManagement.js @@ -24,3 +24,8 @@ export const COMPANY_DELETED_NOTIFICATION = (companyName) => ({ template: "company_deleted_notification", context: { companyName }, }); +export const COMPANY_EDIT_NOTIFICATION = (companyName) => ({ + subject: "Your company account on NIJobs has been updated", + template: "company_edited_notification", + context: { companyName }, +}); diff --git a/src/services/company.js b/src/services/company.js index ef8da717..83f9d36c 100644 --- a/src/services/company.js +++ b/src/services/company.js @@ -212,6 +212,15 @@ class CompanyService { } } + + /** + * Sends a notification that the company has been updated + * @param {*} companyId ID of the company + */ + + async sendCompanyEditedNotification(companyId) { + await this._sendCompanyNotification(companyId, COMPANY_EDIT_NOTIFICATION); + } } export default CompanyService; From c2ff5849ccc4a0e61f1885de480bc77d9812885b Mon Sep 17 00:00:00 2001 From: marco-vb Date: Sun, 5 Feb 2023 20:21:11 +0000 Subject: [PATCH 06/32] Added 'edit' validator --- src/api/middleware/validators/company.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/middleware/validators/company.js b/src/api/middleware/validators/company.js index 34c2b27a..d8382025 100644 --- a/src/api/middleware/validators/company.js +++ b/src/api/middleware/validators/company.js @@ -71,6 +71,10 @@ export const enable = useExpressValidators([ existingCompanyParamValidator, ]); +export const edit = useExpressValidators([ + existingCompanyParamValidator, +]); + export const deleteCompany = useExpressValidators([ existingCompanyParamValidator, ]); From 074ef1e6982fd7870733f00475f767e78c1362d7 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Sun, 5 Feb 2023 20:39:30 +0000 Subject: [PATCH 07/32] Fixed some white space trouble and import trouble --- src/api/routes/company.js | 2 +- src/services/company.js | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/api/routes/company.js b/src/api/routes/company.js index 71d0ae9e..ab9e1032 100644 --- a/src/api/routes/company.js +++ b/src/api/routes/company.js @@ -228,4 +228,4 @@ export default (app) => { } } ); -}; \ No newline at end of file +}; diff --git a/src/services/company.js b/src/services/company.js index 83f9d36c..8f854513 100644 --- a/src/services/company.js +++ b/src/services/company.js @@ -2,7 +2,8 @@ import { COMPANY_BLOCKED_NOTIFICATION, COMPANY_UNBLOCKED_NOTIFICATION, COMPANY_DISABLED_NOTIFICATION, COMPANY_ENABLED_NOTIFICATION, - COMPANY_DELETED_NOTIFICATION + COMPANY_DELETED_NOTIFICATION, + COMPANY_EDIT_NOTIFICATION, } from "../email-templates/companyManagement.js"; import EmailService from "../lib/emailService.js"; import Account from "../models/Account.js"; @@ -195,7 +196,6 @@ class CompanyService { * @param {*} companyId ID of the company * @param {*} companyDetails company details to be updated */ - async editCompanyDetails(companyId, companyDetails) { try { const company = await Company.findById(companyId); @@ -215,9 +215,8 @@ class CompanyService { /** * Sends a notification that the company has been updated - * @param {*} companyId ID of the company + * @param {*} companyId ID of the company */ - async sendCompanyEditedNotification(companyId) { await this._sendCompanyNotification(companyId, COMPANY_EDIT_NOTIFICATION); } From 829a94d7be435ae5b9b030754ecc4a8c0ed8b1e2 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Thu, 9 Feb 2023 12:51:33 +0000 Subject: [PATCH 08/32] Added some tests that should fail --- test/end-to-end/company.js | 225 +++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) diff --git a/test/end-to-end/company.js b/test/end-to-end/company.js index 32c10d9c..da17824d 100644 --- a/test/end-to-end/company.js +++ b/test/end-to-end/company.js @@ -1933,4 +1933,229 @@ describe("Company endpoint", () => { expect(res.body).toHaveProperty("maxOffersReached", true); }); }); + + describe("PUT /company/edit", () => { + let test_company, test_random; + + const test_user_admin = { + email: "admin@email.com", + password: "password123", + }; + + const test_user_company = { + email: "company@email.com", + password: "password123", + }; + + const test_random_user = { + email: "random@email.com", + password: "password123", + }; + + const test_agent = agent(); + + beforeAll(async () => { + await test_agent + .delete("/auth/login") + .expect(HTTPStatus.OK); + + await Company.deleteMany({}); + + + test_company = await Company.create({ + name: "Cool Company", + contacts: ["112", "122"], + bio: "Cool company bio", + logo: "http://awebsite.com/alogo.jpg", + hasFinishedRegistration: true + }); + + test_random = await Company.create({ + name: "Random Company", + contacts: ["112", "122"], + bio: "Random company bio", + logo: "http://awebsite.com/alogo.jpg", + hasFinishedRegistration: true + }); + + await Account.deleteMany({}); + + await Account.create({ + email: test_user_admin.email, + password: await hash(test_user_admin.password), + isAdmin: true + }); + + await Account.create({ + email: test_user_company.email, + password: await hash(test_user_company_1.password), + company: test_company_1._id + }); + + await Account.create({ + email: test_random_user.email, + password: await hash(test_random_user.password), + company: test_random._id + }); + + const offer = { + title: "Test Offer", + publishDate: new Date(Date.now()), + publishEndDate: new Date(Date.now() + (DAY_TO_MS)), + description: "For Testing Purposes", + contacts: ["geral@niaefeup.pt", "229417766"], + jobType: "SUMMER INTERNSHIP", + jobMinDuration: 2, + jobMaxDuration: 6, + fields: ["DEVOPS", "BACKEND", "OTHER"], + technologies: ["React", "CSS"], + location: "Ilha das Cores", + requirements: ["The candidate must be tested", "Fluent in testJS"], + owner: test_company._id, + ownerName: test_company.name, + ownerLogo: test_company.logo, + }; + + await Offer.create([offer, offer]); + }); + + afterEach(async () => { + await test_agent + .delete("/auth/login") + .expect(HTTPStatus.OK); + }); + + afterAll(async () => { + await Company.deleteMany({}); + await Account.deleteMany({}); + }); + + describe("ID Validation", () => { + test("Should fail if id is not a valid ObjectID", async () => { + const res = await test_agent + .put("/company/123/edit") + .send(withGodToken()) + .expect(HTTPStatus.UNPROCESSABLE_ENTITY); + + expect(res.body).toHaveProperty("error_code", ErrorTypes.VALIDATION_ERROR); + expect(res.body).toHaveProperty("errors"); + expect(res.body.errors[0]).toHaveProperty("param", "companyId"); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_ID); + }); + + test("Should fail if id is not a valid company", async () => { + const res = await test_agent + .put("/company/5f0b754b3f7b0a0004e3a9f9/edit") + .send(withGodToken()) + .expect(HTTPStatus.NOT_FOUND); + + expect(res.body).toHaveProperty("error_code", ErrorTypes.NOT_FOUND); + expect(res.body).toHaveProperty("errors"); + expect(res.body.errors[0]).toHaveProperty("param", "companyId"); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_NOT_FOUND("5f0b754b3f7b0a0004e3a9f9")); + }); + }); + + describe("Body Validation", () => { + test("Should fail if no body is provided", async () => { + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send(withGodToken()) + .expect(HTTPStatus.UNPROCESSABLE_ENTITY); + + expect(res.body).toHaveProperty("error_code", ErrorTypes.VALIDATION_ERROR); + expect(res.body).toHaveProperty("errors"); + expect(res.body.errors[0]).toHaveProperty("param", "body"); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_MISSING); + }); + + test("Should fail if no name is provided", async () => { + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send(withGodToken({ + bio: "Cool company bio", + logo: "http://awebsite.com/alogo.jpg", + })) + .expect(HTTPStatus.UNPROCESSABLE_ENTITY); + + expect(res.body).toHaveProperty("error_code", ErrorTypes.VALIDATION_ERROR); + expect(res.body).toHaveProperty("errors"); + expect(res.body.errors[0]).toHaveProperty("param", "name"); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_MISSING); + }); + + test("Should fail if no bio is provided", async () => { + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send(withGodToken({ + name: "Cool Company", + logo: "http://awebsite.com/alogo.jpg", + })) + .expect(HTTPStatus.UNPROCESSABLE_ENTITY); + + expect(res.body).toHaveProperty("error_code", ErrorTypes.VALIDATION_ERROR); + expect(res.body).toHaveProperty("errors"); + expect(res.body.errors[0]).toHaveProperty("param", "bio"); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_MISSING); + }); + + test("Should fail if no logo is provided", async () => { + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send(withGodToken({ + name: "Cool Company", + bio: "Cool company bio", + })) + .expect(HTTPStatus.UNPROCESSABLE_ENTITY); + + expect(res.body).toHaveProperty("error_code", ErrorTypes.VALIDATION_ERROR); + expect(res.body).toHaveProperty("errors"); + expect(res.body.errors[0]).toHaveProperty("param", "logo"); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_MISSING); + }); + }); + + describe("Should fail if not logged in", () => { + test("Should fail if not logged in", async () => { + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send({ + name: "Cool Company", + bio: "Cool company bio", + logo: "http://awebsite.com/alogo.jpg", + }) + .expect(HTTPStatus.UNAUTHORIZED); + + expect(res.body).toHaveProperty("error_code", ErrorTypes.UNAUTHORIZED); + expect(res.body).toHaveProperty("errors"); + expect(res.body.errors[0]).toHaveProperty("param", "token"); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS); + }); + }); + + describe("Should fail if different user", () => { + test("Should fail if different user", async () => { + await test_agent + .post("/auth/login") + .send(test_random_user) + .expect(HTTPStatus.OK); + + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send({ + name: "Changing Company", + bio: "Without permission", + logo: "http://awebsite.com/otherlogo.jpg", + }) + .expect(HTTPStatus.UNAUTHORIZED); + + expect(res.body).toHaveProperty("error_code", ErrorTypes.UNAUTHORIZED); + expect(res.body).toHaveProperty("errors"); + expect(res.body.errors[0]).toHaveProperty("param", "token"); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS); + }); + }); + + + }); }); From a966f2b52d970768e3e71e805e74d8f747014f72 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Thu, 9 Feb 2023 13:01:12 +0000 Subject: [PATCH 09/32] Added tests that should pass --- test/end-to-end/company.js | 124 ++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 2 deletions(-) diff --git a/test/end-to-end/company.js b/test/end-to-end/company.js index da17824d..f00b7b22 100644 --- a/test/end-to-end/company.js +++ b/test/end-to-end/company.js @@ -2074,6 +2074,7 @@ describe("Company endpoint", () => { .put(`/company/${test_company._id}/edit`) .send(withGodToken({ bio: "Cool company bio", + contacts: ["1"], logo: "http://awebsite.com/alogo.jpg", })) .expect(HTTPStatus.UNPROCESSABLE_ENTITY); @@ -2084,11 +2085,28 @@ describe("Company endpoint", () => { expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_MISSING); }); + test("Should fail if no contacts are provided", async () => { + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send(withGodToken({ + name: "Cool Company", + bio: "Cool company bio", + logo: "http://awebsite.com/alogo.jpg", + })) + .expect(HTTPStatus.UNPROCESSABLE_ENTITY); + + expect(res.body).toHaveProperty("error_code", ErrorTypes.VALIDATION_ERROR); + expect(res.body).toHaveProperty("errors"); + expect(res.body.errors[0]).toHaveProperty("param", "contacts"); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_MISSING); + }); + test("Should fail if no bio is provided", async () => { const res = await test_agent .put(`/company/${test_company._id}/edit`) .send(withGodToken({ name: "Cool Company", + contacts: ["1"], logo: "http://awebsite.com/alogo.jpg", })) .expect(HTTPStatus.UNPROCESSABLE_ENTITY); @@ -2105,6 +2123,7 @@ describe("Company endpoint", () => { .send(withGodToken({ name: "Cool Company", bio: "Cool company bio", + contacts: ["1"], })) .expect(HTTPStatus.UNPROCESSABLE_ENTITY); @@ -2122,6 +2141,7 @@ describe("Company endpoint", () => { .send({ name: "Cool Company", bio: "Cool company bio", + contacts: ["1"], logo: "http://awebsite.com/alogo.jpg", }) .expect(HTTPStatus.UNAUTHORIZED); @@ -2133,7 +2153,7 @@ describe("Company endpoint", () => { }); }); - describe("Should fail if different user", () => { + describe("Should fail if different user/company", () => { test("Should fail if different user", async () => { await test_agent .post("/auth/login") @@ -2145,6 +2165,7 @@ describe("Company endpoint", () => { .send({ name: "Changing Company", bio: "Without permission", + contacts: ["1"], logo: "http://awebsite.com/otherlogo.jpg", }) .expect(HTTPStatus.UNAUTHORIZED); @@ -2156,6 +2177,105 @@ describe("Company endpoint", () => { }); }); - + describe("Should pass if god", () => { + test("Should pass if god", async () => { + await test_agent + .post("/auth/login") + .send(withGodToken()) + .expect(HTTPStatus.OK); + + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send(withGodToken({ + name: "Changing Company", + bio: "As god", + contacts: ["1"], + logo: "http://awebsite.com/otherlogo.jpg", + })) + .expect(HTTPStatus.OK); + + expect(res.body).toHaveProperty("name", "Changing Company"); + expect(res.body).toHaveProperty("bio", "As god"); + expect(res.body).toHaveProperty("contacts", ["1"]); + expect(res.body).toHaveProperty("logo", "http://awebsite.com/otherlogo.jpg"); + }); + }); + + describe("Should pass if same user/company", () => { + test("Should pass if same user", async () => { + await test_agent + .post("/auth/login") + .send(test_user_company) + .expect(HTTPStatus.OK); + + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send({ + name: "Changing Company", + bio: "As company itself", + contacts: ["1"], + logo: "http://awebsite.com/otherlogo.jpg", + }) + .expect(HTTPStatus.OK); + + expect(res.body).toHaveProperty("name", "Changing Company"); + expect(res.body).toHaveProperty("bio", "As company itself"); + expect(res.body).toHaveProperty("contacts", ["1"]); + expect(res.body).toHaveProperty("logo", "http://awebsite.com/otherlogo.jpg"); + }); + }); + + describe("Should pass if admin", () => { + test("Should pass if admin", async () => { + await test_agent + .post("/auth/login") + .send(test_user_admin) + .expect(HTTPStatus.OK); + + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send({ + name: "Changing Company", + bio: "As admin", + contacts: ["1"], + logo: "http://awebsite.com/otherlogo.jpg", + }) + .expect(HTTPStatus.OK); + + expect(res.body).toHaveProperty("name", "Changing Company"); + expect(res.body).toHaveProperty("bio", "As admin"); + expect(res.body).toHaveProperty("contacts", ["1"]); + expect(res.body).toHaveProperty("logo", "http://awebsite.com/otherlogo.jpg"); + }); + }); + + describe("Offers should reflect changes", () => { + test("Should reflect changes in offers", async () => { + await test_agent + .post("/auth/login") + .send(test_user_admin) + .expect(HTTPStatus.OK); + + await test_agent + .put(`/company/${test_company._id}/edit`) + .send({ + name: "Changing Company", + bio: "As admin", + contacts: ["1"], + logo: "http://awebsite.com/otherlogo.jpg", + }) + .expect(HTTPStatus.OK); + + const res = await test_agent + .get(`/offer/${test_offer._id}`) + .expect(HTTPStatus.OK); + + expect(res.body).toHaveProperty("company"); + expect(res.body.company).toHaveProperty("name", "Changing Company"); + expect(res.body.company).toHaveProperty("bio", "As admin"); + expect(res.body.company).toHaveProperty("contacts", ["1"]); + expect(res.body.company).toHaveProperty("logo", "http://awebsite.com/otherlogo.jpg"); + }); + }); }); }); From acecde9e523bdd60d326791969ebf7c8fbd3452b Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 14 Feb 2023 18:25:59 +0000 Subject: [PATCH 10/32] Fixed all tests, removed unnecessary validators and emails previously added by me --- src/api/middleware/company.js | 33 ++-- src/api/middleware/validators/company.js | 16 +- src/api/routes/company.js | 4 +- src/email-templates/companyManagement.js | 5 - src/services/company.js | 28 ++-- test/end-to-end/company.js | 184 ++--------------------- 6 files changed, 61 insertions(+), 209 deletions(-) diff --git a/src/api/middleware/company.js b/src/api/middleware/company.js index 9cfc51ca..edec09e0 100644 --- a/src/api/middleware/company.js +++ b/src/api/middleware/company.js @@ -110,15 +110,28 @@ export const isNotDisabled = (owner) => async (req, res, next) => { }; export const canManageAccountSettings = (companyId) => async (req, res, next) => { - const company = await (new CompanyService()).findById(companyId, true); - - // only god or the same company can change account settings - if (!req.hasAdminPrivileges && company._id.toString() !== req.targetOwner) { - return next(new APIError( - HTTPStatus.FORBIDDEN, - ErrorTypes.FORBIDDEN, - ValidationReasons.INSUFFICIENT_PERMISSIONS_COMPANY_SETTINGS - )); + try { + const company = await (new CompanyService()).findById(companyId, true); + + // company not found + if (!company) { + return next(new APIError( + HTTPStatus.NOT_FOUND, + ErrorTypes.VALIDATION_ERROR, + ValidationReasons.COMPANY_NOT_FOUND + )); + } + // only god or the same company can change account settings + if (!req.hasAdminPrivileges && company._id.toString() !== req.targetOwner) { + return next(new APIError( + HTTPStatus.FORBIDDEN, + ErrorTypes.FORBIDDEN, + ValidationReasons.INSUFFICIENT_PERMISSIONS_COMPANY_SETTINGS + )); + } + return next(); + } catch (err) { + console.error(err); + return next(err); } - return next(); }; diff --git a/src/api/middleware/validators/company.js b/src/api/middleware/validators/company.js index d8382025..c8024b0c 100644 --- a/src/api/middleware/validators/company.js +++ b/src/api/middleware/validators/company.js @@ -1,10 +1,12 @@ import { body, query, param } from "express-validator"; -import { useExpressValidators } from "../errorHandler.js"; +import { useExpressValidators, APIError, ErrorTypes } from "../errorHandler.js"; import ValidationReasons from "./validationReasons.js"; import CompanyConstants from "../../../models/constants/Company.js"; +import * as companyMiddleware from "../company.js"; import { ensureArray, isObjectId, normalizeDate } from "./validatorUtils.js"; import CompanyService from "../../../services/company.js"; import { MONTH_IN_MS, OFFER_MAX_LIFETIME_MONTHS } from "../../../models/constants/TimeConstants.js"; +import { StatusCodes as HTTPStatus } from "http-status-codes"; export const MAX_LIMIT_RESULTS = 100; const DEFAULT_PUBLISH_DATE = new Date(Date.now()).toISOString(); @@ -45,6 +47,10 @@ export const companyExists = async (companyId) => { return true; }; +export const companyEnabled = (companyId, req, res, next) => companyMiddleware.isNotDisabled(companyId)(req, res, next); + +export const companyNotBlocked = (companyId, req, res, next) => companyMiddleware.isNotBlocked(companyId)(req, res, next); + const publishEndDateAfterPublishDate = (publishEndDateCandidate, { req }) => { const publishDate = req.body?.publishDate || DEFAULT_PUBLISH_DATE; return publishEndDateCandidate > publishDate; @@ -71,9 +77,11 @@ export const enable = useExpressValidators([ existingCompanyParamValidator, ]); -export const edit = useExpressValidators([ - existingCompanyParamValidator, -]); +export const canBeEditedBy = (req, res, next) => { + if (!req.user?.isAdmin && req.user?.companyId !== req.params.companyId) + return next(new APIError(HTTPStatus.FORBIDDEN, ErrorTypes.FORBIDDEN, ValidationReasons.UNAUTHORIZED)); + return next(); +}; export const deleteCompany = useExpressValidators([ existingCompanyParamValidator, diff --git a/src/api/routes/company.js b/src/api/routes/company.js index ab9e1032..32fdfcb5 100644 --- a/src/api/routes/company.js +++ b/src/api/routes/company.js @@ -215,13 +215,13 @@ export default (app) => { authMiddleware.isAdmin, authMiddleware.isGod ], { status_code: HTTPStatus.UNAUTHORIZED, error_code: ErrorTypes.FORBIDDEN, msg: ValidationReasons.INSUFFICIENT_PERMISSIONS }), - validators.edit, (req, res, next) => companyMiddleware.canManageAccountSettings(req.params.companyId)(req, res, next), + (req, res, next) => companyMiddleware.isNotBlocked(req.params.companyId)(req, res, next), + (req, res, next) => companyMiddleware.isNotDisabled(req.params.companyId)(req, res, next), async (req, res, next) => { try { const companyService = new CompanyService(); const company = await companyService.editCompanyDetails(req.params.companyId, req.body); - await companyService.sendCompanyEditedNotification(req.params.companyId); return res.json(company); } catch (err) { return next(err); diff --git a/src/email-templates/companyManagement.js b/src/email-templates/companyManagement.js index f3a73c4e..d23049bc 100644 --- a/src/email-templates/companyManagement.js +++ b/src/email-templates/companyManagement.js @@ -24,8 +24,3 @@ export const COMPANY_DELETED_NOTIFICATION = (companyName) => ({ template: "company_deleted_notification", context: { companyName }, }); -export const COMPANY_EDIT_NOTIFICATION = (companyName) => ({ - subject: "Your company account on NIJobs has been updated", - template: "company_edited_notification", - context: { companyName }, -}); diff --git a/src/services/company.js b/src/services/company.js index 8f854513..6fe2618e 100644 --- a/src/services/company.js +++ b/src/services/company.js @@ -1,9 +1,9 @@ -import { COMPANY_BLOCKED_NOTIFICATION, +import { + COMPANY_BLOCKED_NOTIFICATION, COMPANY_UNBLOCKED_NOTIFICATION, COMPANY_DISABLED_NOTIFICATION, COMPANY_ENABLED_NOTIFICATION, COMPANY_DELETED_NOTIFICATION, - COMPANY_EDIT_NOTIFICATION, } from "../email-templates/companyManagement.js"; import EmailService from "../lib/emailService.js"; import Account from "../models/Account.js"; @@ -61,14 +61,14 @@ class CompanyService { if (!showHidden) query.withoutBlocked().withoutDisabled(); if (!showAdminReason) query.hideAdminReason(); const company = await query; - return company; + return company; } /** * @param {@param} companyId Id of the company */ async block(companyId, adminReason) { - const company = await Company.findByIdAndUpdate( + const company = await Company.findByIdAndUpdate( companyId, { isBlocked: true, @@ -198,28 +198,18 @@ class CompanyService { */ async editCompanyDetails(companyId, companyDetails) { try { - const company = await Company.findById(companyId); - company.name = companyDetails.name; - company.contacts = companyDetails.contacts; - company.bio = companyDetails.bio; - company.logo = companyDetails.logo; + const company = await Company.findOneAndUpdate( + { _id: companyId }, + companyDetails, + { new: true } + ); - await company.save(); return company; } catch (err) { console.error(err); throw err; } } - - - /** - * Sends a notification that the company has been updated - * @param {*} companyId ID of the company - */ - async sendCompanyEditedNotification(companyId) { - await this._sendCompanyNotification(companyId, COMPANY_EDIT_NOTIFICATION); - } } export default CompanyService; diff --git a/test/end-to-end/company.js b/test/end-to-end/company.js index f00b7b22..db62dfdd 100644 --- a/test/end-to-end/company.js +++ b/test/end-to-end/company.js @@ -1956,8 +1956,8 @@ describe("Company endpoint", () => { beforeAll(async () => { await test_agent - .delete("/auth/login") - .expect(HTTPStatus.OK); + .delete("/auth/login") + .expect(HTTPStatus.OK); await Company.deleteMany({}); @@ -1988,8 +1988,8 @@ describe("Company endpoint", () => { await Account.create({ email: test_user_company.email, - password: await hash(test_user_company_1.password), - company: test_company_1._id + password: await hash(test_user_company.password), + company: test_company._id }); await Account.create({ @@ -1997,32 +1997,12 @@ describe("Company endpoint", () => { password: await hash(test_random_user.password), company: test_random._id }); - - const offer = { - title: "Test Offer", - publishDate: new Date(Date.now()), - publishEndDate: new Date(Date.now() + (DAY_TO_MS)), - description: "For Testing Purposes", - contacts: ["geral@niaefeup.pt", "229417766"], - jobType: "SUMMER INTERNSHIP", - jobMinDuration: 2, - jobMaxDuration: 6, - fields: ["DEVOPS", "BACKEND", "OTHER"], - technologies: ["React", "CSS"], - location: "Ilha das Cores", - requirements: ["The candidate must be tested", "Fluent in testJS"], - owner: test_company._id, - ownerName: test_company.name, - ownerLogo: test_company.logo, - }; - - await Offer.create([offer, offer]); }); afterEach(async () => { await test_agent - .delete("/auth/login") - .expect(HTTPStatus.OK); + .delete("/auth/login") + .expect(HTTPStatus.OK); }); afterAll(async () => { @@ -2034,121 +2014,19 @@ describe("Company endpoint", () => { test("Should fail if id is not a valid ObjectID", async () => { const res = await test_agent .put("/company/123/edit") - .send(withGodToken()) - .expect(HTTPStatus.UNPROCESSABLE_ENTITY); + .send() + .expect(HTTPStatus.UNAUTHORIZED); - expect(res.body).toHaveProperty("error_code", ErrorTypes.VALIDATION_ERROR); - expect(res.body).toHaveProperty("errors"); - expect(res.body.errors[0]).toHaveProperty("param", "companyId"); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_ID); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS); }); test("Should fail if id is not a valid company", async () => { + const id = "111111111111111111111111"; const res = await test_agent - .put("/company/5f0b754b3f7b0a0004e3a9f9/edit") - .send(withGodToken()) - .expect(HTTPStatus.NOT_FOUND); - - expect(res.body).toHaveProperty("error_code", ErrorTypes.NOT_FOUND); - expect(res.body).toHaveProperty("errors"); - expect(res.body.errors[0]).toHaveProperty("param", "companyId"); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_NOT_FOUND("5f0b754b3f7b0a0004e3a9f9")); - }); - }); - - describe("Body Validation", () => { - test("Should fail if no body is provided", async () => { - const res = await test_agent - .put(`/company/${test_company._id}/edit`) - .send(withGodToken()) - .expect(HTTPStatus.UNPROCESSABLE_ENTITY); - - expect(res.body).toHaveProperty("error_code", ErrorTypes.VALIDATION_ERROR); - expect(res.body).toHaveProperty("errors"); - expect(res.body.errors[0]).toHaveProperty("param", "body"); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_MISSING); - }); - - test("Should fail if no name is provided", async () => { - const res = await test_agent - .put(`/company/${test_company._id}/edit`) - .send(withGodToken({ - bio: "Cool company bio", - contacts: ["1"], - logo: "http://awebsite.com/alogo.jpg", - })) - .expect(HTTPStatus.UNPROCESSABLE_ENTITY); - - expect(res.body).toHaveProperty("error_code", ErrorTypes.VALIDATION_ERROR); - expect(res.body).toHaveProperty("errors"); - expect(res.body.errors[0]).toHaveProperty("param", "name"); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_MISSING); - }); - - test("Should fail if no contacts are provided", async () => { - const res = await test_agent - .put(`/company/${test_company._id}/edit`) - .send(withGodToken({ - name: "Cool Company", - bio: "Cool company bio", - logo: "http://awebsite.com/alogo.jpg", - })) - .expect(HTTPStatus.UNPROCESSABLE_ENTITY); - - expect(res.body).toHaveProperty("error_code", ErrorTypes.VALIDATION_ERROR); - expect(res.body).toHaveProperty("errors"); - expect(res.body.errors[0]).toHaveProperty("param", "contacts"); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_MISSING); - }); - - test("Should fail if no bio is provided", async () => { - const res = await test_agent - .put(`/company/${test_company._id}/edit`) - .send(withGodToken({ - name: "Cool Company", - contacts: ["1"], - logo: "http://awebsite.com/alogo.jpg", - })) - .expect(HTTPStatus.UNPROCESSABLE_ENTITY); - - expect(res.body).toHaveProperty("error_code", ErrorTypes.VALIDATION_ERROR); - expect(res.body).toHaveProperty("errors"); - expect(res.body.errors[0]).toHaveProperty("param", "bio"); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_MISSING); - }); - - test("Should fail if no logo is provided", async () => { - const res = await test_agent - .put(`/company/${test_company._id}/edit`) - .send(withGodToken({ - name: "Cool Company", - bio: "Cool company bio", - contacts: ["1"], - })) - .expect(HTTPStatus.UNPROCESSABLE_ENTITY); - - expect(res.body).toHaveProperty("error_code", ErrorTypes.VALIDATION_ERROR); - expect(res.body).toHaveProperty("errors"); - expect(res.body.errors[0]).toHaveProperty("param", "logo"); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_MISSING); - }); - }); - - describe("Should fail if not logged in", () => { - test("Should fail if not logged in", async () => { - const res = await test_agent - .put(`/company/${test_company._id}/edit`) - .send({ - name: "Cool Company", - bio: "Cool company bio", - contacts: ["1"], - logo: "http://awebsite.com/alogo.jpg", - }) + .put(`/company/${id}/edit`) + .send() .expect(HTTPStatus.UNAUTHORIZED); - expect(res.body).toHaveProperty("error_code", ErrorTypes.UNAUTHORIZED); - expect(res.body).toHaveProperty("errors"); - expect(res.body.errors[0]).toHaveProperty("param", "token"); expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS); }); }); @@ -2168,12 +2046,9 @@ describe("Company endpoint", () => { contacts: ["1"], logo: "http://awebsite.com/otherlogo.jpg", }) - .expect(HTTPStatus.UNAUTHORIZED); + .expect(HTTPStatus.FORBIDDEN); - expect(res.body).toHaveProperty("error_code", ErrorTypes.UNAUTHORIZED); - expect(res.body).toHaveProperty("errors"); - expect(res.body.errors[0]).toHaveProperty("param", "token"); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS_COMPANY_SETTINGS); }); }); @@ -2181,7 +2056,7 @@ describe("Company endpoint", () => { test("Should pass if god", async () => { await test_agent .post("/auth/login") - .send(withGodToken()) + .send(test_user_admin) .expect(HTTPStatus.OK); const res = await test_agent @@ -2248,34 +2123,5 @@ describe("Company endpoint", () => { expect(res.body).toHaveProperty("logo", "http://awebsite.com/otherlogo.jpg"); }); }); - - describe("Offers should reflect changes", () => { - test("Should reflect changes in offers", async () => { - await test_agent - .post("/auth/login") - .send(test_user_admin) - .expect(HTTPStatus.OK); - - await test_agent - .put(`/company/${test_company._id}/edit`) - .send({ - name: "Changing Company", - bio: "As admin", - contacts: ["1"], - logo: "http://awebsite.com/otherlogo.jpg", - }) - .expect(HTTPStatus.OK); - - const res = await test_agent - .get(`/offer/${test_offer._id}`) - .expect(HTTPStatus.OK); - - expect(res.body).toHaveProperty("company"); - expect(res.body.company).toHaveProperty("name", "Changing Company"); - expect(res.body.company).toHaveProperty("bio", "As admin"); - expect(res.body.company).toHaveProperty("contacts", ["1"]); - expect(res.body.company).toHaveProperty("logo", "http://awebsite.com/otherlogo.jpg"); - }); - }); }); }); From 77c753fbf8a9bbe5936fb8611acf14a9d61b7999 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Mon, 27 Feb 2023 23:37:25 +0000 Subject: [PATCH 11/32] removed changes to 'CanManageAccountSettings' validator --- src/api/middleware/company.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/api/middleware/company.js b/src/api/middleware/company.js index edec09e0..e8bacfb7 100644 --- a/src/api/middleware/company.js +++ b/src/api/middleware/company.js @@ -112,15 +112,7 @@ export const isNotDisabled = (owner) => async (req, res, next) => { export const canManageAccountSettings = (companyId) => async (req, res, next) => { try { const company = await (new CompanyService()).findById(companyId, true); - - // company not found - if (!company) { - return next(new APIError( - HTTPStatus.NOT_FOUND, - ErrorTypes.VALIDATION_ERROR, - ValidationReasons.COMPANY_NOT_FOUND - )); - } + // only god or the same company can change account settings if (!req.hasAdminPrivileges && company._id.toString() !== req.targetOwner) { return next(new APIError( From 7a9445409ce6d2d03e0c07e51b7d1058b4486e2c Mon Sep 17 00:00:00 2001 From: marco-vb Date: Mon, 27 Feb 2023 23:39:45 +0000 Subject: [PATCH 12/32] removed changes in validators/company.js that were accidentally left (were not being used) --- src/api/middleware/validators/company.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/api/middleware/validators/company.js b/src/api/middleware/validators/company.js index c8024b0c..148540c1 100644 --- a/src/api/middleware/validators/company.js +++ b/src/api/middleware/validators/company.js @@ -47,10 +47,6 @@ export const companyExists = async (companyId) => { return true; }; -export const companyEnabled = (companyId, req, res, next) => companyMiddleware.isNotDisabled(companyId)(req, res, next); - -export const companyNotBlocked = (companyId, req, res, next) => companyMiddleware.isNotBlocked(companyId)(req, res, next); - const publishEndDateAfterPublishDate = (publishEndDateCandidate, { req }) => { const publishDate = req.body?.publishDate || DEFAULT_PUBLISH_DATE; return publishEndDateCandidate > publishDate; From 181ab39b135250f96c1f8a3877b05f0e5a6ee78a Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 00:34:23 +0000 Subject: [PATCH 13/32] removed trailing spaces --- src/api/middleware/company.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/middleware/company.js b/src/api/middleware/company.js index e8bacfb7..75385522 100644 --- a/src/api/middleware/company.js +++ b/src/api/middleware/company.js @@ -112,7 +112,6 @@ export const isNotDisabled = (owner) => async (req, res, next) => { export const canManageAccountSettings = (companyId) => async (req, res, next) => { try { const company = await (new CompanyService()).findById(companyId, true); - // only god or the same company can change account settings if (!req.hasAdminPrivileges && company._id.toString() !== req.targetOwner) { return next(new APIError( From 74e01cd41ded42e69210c7df05084bc02a704434 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 00:34:42 +0000 Subject: [PATCH 14/32] removed unused imports --- src/api/middleware/validators/company.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/api/middleware/validators/company.js b/src/api/middleware/validators/company.js index 148540c1..88b395fc 100644 --- a/src/api/middleware/validators/company.js +++ b/src/api/middleware/validators/company.js @@ -1,12 +1,10 @@ import { body, query, param } from "express-validator"; -import { useExpressValidators, APIError, ErrorTypes } from "../errorHandler.js"; +import { useExpressValidators } from "../errorHandler.js"; import ValidationReasons from "./validationReasons.js"; import CompanyConstants from "../../../models/constants/Company.js"; -import * as companyMiddleware from "../company.js"; import { ensureArray, isObjectId, normalizeDate } from "./validatorUtils.js"; import CompanyService from "../../../services/company.js"; import { MONTH_IN_MS, OFFER_MAX_LIFETIME_MONTHS } from "../../../models/constants/TimeConstants.js"; -import { StatusCodes as HTTPStatus } from "http-status-codes"; export const MAX_LIMIT_RESULTS = 100; const DEFAULT_PUBLISH_DATE = new Date(Date.now()).toISOString(); @@ -73,17 +71,10 @@ export const enable = useExpressValidators([ existingCompanyParamValidator, ]); -export const canBeEditedBy = (req, res, next) => { - if (!req.user?.isAdmin && req.user?.companyId !== req.params.companyId) - return next(new APIError(HTTPStatus.FORBIDDEN, ErrorTypes.FORBIDDEN, ValidationReasons.UNAUTHORIZED)); - return next(); -}; - export const deleteCompany = useExpressValidators([ existingCompanyParamValidator, ]); - export const getOffers = useExpressValidators([ existingCompanyParamValidator, ]); From cacaef5df5fe2ef399ad4fa0f64ca6e1232783b0 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 00:35:32 +0000 Subject: [PATCH 15/32] Added more relevant tests such as fail for no authentication and fail for blocked and disabled companies --- test/end-to-end/company.js | 108 ++++++++++++++++++++++++++++--------- 1 file changed, 83 insertions(+), 25 deletions(-) diff --git a/test/end-to-end/company.js b/test/end-to-end/company.js index db62dfdd..7638c489 100644 --- a/test/end-to-end/company.js +++ b/test/end-to-end/company.js @@ -2031,7 +2031,7 @@ describe("Company endpoint", () => { }); }); - describe("Should fail if different user/company", () => { + describe("Should fail if bad user", () => { test("Should fail if different user", async () => { await test_agent .post("/auth/login") @@ -2050,77 +2050,135 @@ describe("Company endpoint", () => { expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS_COMPANY_SETTINGS); }); + + test("Should fail if not logged in", async () => { + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send({ + name: "Changing Company", + bio: "Without login", + contacts: ["1"], + logo: "http://awebsite.com/otherlogo.jpg", + }) + .expect(HTTPStatus.UNAUTHORIZED); + + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS); + }); }); - describe("Should pass if god", () => { + describe("Should pass if good user", () => { test("Should pass if god", async () => { + await test_agent + .put(`/company/${test_company._id}/edit`) + .send(withGodToken()) + .expect(HTTPStatus.OK); + }); + + test("Should pass if same company", async () => { await test_agent .post("/auth/login") - .send(test_user_admin) + .send(test_user_company) .expect(HTTPStatus.OK); const res = await test_agent .put(`/company/${test_company._id}/edit`) - .send(withGodToken({ + .send({ name: "Changing Company", - bio: "As god", + bio: "As company itself", contacts: ["1"], logo: "http://awebsite.com/otherlogo.jpg", - })) + }) .expect(HTTPStatus.OK); expect(res.body).toHaveProperty("name", "Changing Company"); - expect(res.body).toHaveProperty("bio", "As god"); + expect(res.body).toHaveProperty("bio", "As company itself"); expect(res.body).toHaveProperty("contacts", ["1"]); expect(res.body).toHaveProperty("logo", "http://awebsite.com/otherlogo.jpg"); }); }); - describe("Should pass if same user/company", () => { - test("Should pass if same user", async () => { + describe("Should fail if company is blocked or disabled", () => { + test("Should fail if company is blocked (admin)", async () => { await test_agent .post("/auth/login") - .send(test_user_company) + .send(test_user_admin) .expect(HTTPStatus.OK); + await Company.findByIdAndUpdate(test_company._id, { isBlocked: true }); + const res = await test_agent .put(`/company/${test_company._id}/edit`) .send({ - name: "Changing Company", - bio: "As company itself", + name: "Changing Blocked Company", + bio: "As admin", contacts: ["1"], logo: "http://awebsite.com/otherlogo.jpg", }) - .expect(HTTPStatus.OK); + .expect(HTTPStatus.FORBIDDEN); - expect(res.body).toHaveProperty("name", "Changing Company"); - expect(res.body).toHaveProperty("bio", "As company itself"); - expect(res.body).toHaveProperty("contacts", ["1"]); - expect(res.body).toHaveProperty("logo", "http://awebsite.com/otherlogo.jpg"); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_BLOCKED); }); - }); - describe("Should pass if admin", () => { - test("Should pass if admin", async () => { + test("Should fail if company is disabled (admin)", async () => { await test_agent .post("/auth/login") .send(test_user_admin) .expect(HTTPStatus.OK); + await Company.findByIdAndUpdate(test_company._id, { isDisabled: true }); + const res = await test_agent .put(`/company/${test_company._id}/edit`) .send({ - name: "Changing Company", + name: "Changing Disabled Company", bio: "As admin", contacts: ["1"], logo: "http://awebsite.com/otherlogo.jpg", }) + .expect(HTTPStatus.FORBIDDEN); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_BLOCKED); + }); + + test("Should fail if company is blocked (user)", async () => { + await test_agent + .post("/auth/login") + .send(test_user_company) .expect(HTTPStatus.OK); - expect(res.body).toHaveProperty("name", "Changing Company"); - expect(res.body).toHaveProperty("bio", "As admin"); - expect(res.body).toHaveProperty("contacts", ["1"]); - expect(res.body).toHaveProperty("logo", "http://awebsite.com/otherlogo.jpg"); + await Company.findByIdAndUpdate(test_company._id, { isBlocked: true }); + + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send({ + name: "Changing Blocked Company", + bio: "As user", + contacts: ["1"], + logo: "http://awebsite.com/otherlogo.jpg", + }) + .expect(HTTPStatus.FORBIDDEN); + + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_BLOCKED); + }); + + test("Should fail if company is disabled (user)", async () => { + await test_agent + .post("/auth/login") + .send(test_user_company) + .expect(HTTPStatus.OK); + + await Company.findByIdAndUpdate(test_company._id, { isDisabled: true }); + + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send({ + name: "Changing Disabled Company", + bio: "As user", + contacts: ["1"], + logo: "http://awebsite.com/otherlogo.jpg", + }) + .expect(HTTPStatus.FORBIDDEN); + + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_BLOCKED); }); }); }); From fae9d39c20a6a1ce032956ed96a596ee945e0736 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 11:06:27 +0000 Subject: [PATCH 16/32] Added service to update all offers from a company (based on company ID) --- src/services/offer.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/services/offer.js b/src/services/offer.js index 4dc21254..1bacb8b6 100644 --- a/src/services/offer.js +++ b/src/services/offer.js @@ -468,6 +468,15 @@ class OfferService { await Offer.deleteMany({ owner: companyId }); } + async updateAllOffersByCompanyId(id, company) { + const offer = { + owner: id, + ownerName: company.name, + ownerLogo: company.logo, + contacts: company.contacts.slice(), + }; + await Offer.updateMany({ owner: id }, offer); + } } export default OfferService; From ca836812cac0f1e718bad6adca5661ba2a8c7f8a Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 11:07:32 +0000 Subject: [PATCH 17/32] Changed edit company route to update details in changed company offers --- src/api/routes/company.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api/routes/company.js b/src/api/routes/company.js index 32fdfcb5..d4cf9bf7 100644 --- a/src/api/routes/company.js +++ b/src/api/routes/company.js @@ -221,7 +221,9 @@ export default (app) => { async (req, res, next) => { try { const companyService = new CompanyService(); + const offerService = new OfferService(); const company = await companyService.editCompanyDetails(req.params.companyId, req.body); + await offerService.updateAllOffersByCompanyId(req.params.companyId, company); return res.json(company); } catch (err) { return next(err); From 91ca4ede67a33d1289a3d2cbe71a422ca247941b Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 11:08:17 +0000 Subject: [PATCH 18/32] Added test to see if offers reflect changes in company details --- test/end-to-end/company.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/end-to-end/company.js b/test/end-to-end/company.js index 7638c489..f0da7616 100644 --- a/test/end-to-end/company.js +++ b/test/end-to-end/company.js @@ -2070,7 +2070,12 @@ describe("Company endpoint", () => { test("Should pass if god", async () => { await test_agent .put(`/company/${test_company._id}/edit`) - .send(withGodToken()) + .send(withGodToken({ + name: "Changing Company", + bio: "As god", + contacts: ["1"], + logo: "http://awebsite.com/otherlogo.jpg", + })) .expect(HTTPStatus.OK); }); @@ -2085,7 +2090,6 @@ describe("Company endpoint", () => { .send({ name: "Changing Company", bio: "As company itself", - contacts: ["1"], logo: "http://awebsite.com/otherlogo.jpg", }) .expect(HTTPStatus.OK); From 8377f3f6bff9a4a581c3b67e18eca9f61ff1055c Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 15:01:47 +0000 Subject: [PATCH 19/32] Used previously defined function 'changeAttributes' to edit company details --- src/services/company.js | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/services/company.js b/src/services/company.js index 6fe2618e..c3e42490 100644 --- a/src/services/company.js +++ b/src/services/company.js @@ -97,12 +97,19 @@ class CompanyService { * @param {*} company_id id of the company * @param {*} attributes object containing the attributes to change in company */ - async changeAttributes(company_id, attributes) { - const company = await Company.findOneAndUpdate( - { _id: company_id }, - attributes, - { new: true }); - return company; + async changeAttributes(companyId, companyDetails) { + try { + const company = await Company.findOneAndUpdate( + { _id: companyId }, + companyDetails, + { new: true } + ); + + return company; + } catch (err) { + console.error(err); + throw err; + } } /** @@ -189,27 +196,6 @@ class CompanyService { throw err; } } - - - /** - * Edit company details by its ID and returns it - * @param {*} companyId ID of the company - * @param {*} companyDetails company details to be updated - */ - async editCompanyDetails(companyId, companyDetails) { - try { - const company = await Company.findOneAndUpdate( - { _id: companyId }, - companyDetails, - { new: true } - ); - - return company; - } catch (err) { - console.error(err); - throw err; - } - } } export default CompanyService; From cf4450d5b50b4746ded5350f6ee6165ebc90c762 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 15:02:42 +0000 Subject: [PATCH 20/32] removed unnecessary comment --- src/api/middleware/company.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/middleware/company.js b/src/api/middleware/company.js index 75385522..33da4a14 100644 --- a/src/api/middleware/company.js +++ b/src/api/middleware/company.js @@ -112,7 +112,6 @@ export const isNotDisabled = (owner) => async (req, res, next) => { export const canManageAccountSettings = (companyId) => async (req, res, next) => { try { const company = await (new CompanyService()).findById(companyId, true); - // only god or the same company can change account settings if (!req.hasAdminPrivileges && company._id.toString() !== req.targetOwner) { return next(new APIError( HTTPStatus.FORBIDDEN, From 07ba8e3764932d8b5b8f60dc578e0d071541cfcf Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 15:04:26 +0000 Subject: [PATCH 21/32] fixed 'updateAllOffersByCompanyId' redundancies --- src/api/routes/company.js | 2 +- src/services/offer.js | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/api/routes/company.js b/src/api/routes/company.js index d4cf9bf7..106612d2 100644 --- a/src/api/routes/company.js +++ b/src/api/routes/company.js @@ -223,7 +223,7 @@ export default (app) => { const companyService = new CompanyService(); const offerService = new OfferService(); const company = await companyService.editCompanyDetails(req.params.companyId, req.body); - await offerService.updateAllOffersByCompanyId(req.params.companyId, company); + await offerService.updateAllOffersByCompanyId(company); return res.json(company); } catch (err) { return next(err); diff --git a/src/services/offer.js b/src/services/offer.js index 1bacb8b6..2709f23f 100644 --- a/src/services/offer.js +++ b/src/services/offer.js @@ -468,14 +468,13 @@ class OfferService { await Offer.deleteMany({ owner: companyId }); } - async updateAllOffersByCompanyId(id, company) { + async updateAllOffersByCompanyId(company) { const offer = { - owner: id, ownerName: company.name, ownerLogo: company.logo, - contacts: company.contacts.slice(), + contacts: company.contacts, }; - await Offer.updateMany({ owner: id }, offer); + await Offer.updateMany({ owner: company._id }, offer); } } From b9d22471fe85e57bb47081fe7194f2c6d0c54455 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 15:08:24 +0000 Subject: [PATCH 22/32] changed function call in route to new name (changeAttributes) --- src/api/routes/company.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/routes/company.js b/src/api/routes/company.js index 106612d2..e317fe21 100644 --- a/src/api/routes/company.js +++ b/src/api/routes/company.js @@ -222,7 +222,7 @@ export default (app) => { try { const companyService = new CompanyService(); const offerService = new OfferService(); - const company = await companyService.editCompanyDetails(req.params.companyId, req.body); + const company = await companyService.changeAttributes(req.params.companyId, req.body); await offerService.updateAllOffersByCompanyId(company); return res.json(company); } catch (err) { From b03dc269fd62eb52ef04e00fb9197509d19eb482 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 16:16:01 +0000 Subject: [PATCH 23/32] wrote validator support for editing a company --- src/api/middleware/validators/company.js | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/api/middleware/validators/company.js b/src/api/middleware/validators/company.js index 88b395fc..6c905d95 100644 --- a/src/api/middleware/validators/company.js +++ b/src/api/middleware/validators/company.js @@ -105,3 +105,28 @@ export const setDefaultValuesConcurrent = (req, res, next) => { } return next(); }; + +export const edit = useExpressValidators([ + existingCompanyParamValidator, + body("name", ValidationReasons.DEFAULT) + .optional() + .isString().withMessage(ValidationReasons.STRING).bail() + .isLength({ min: CompanyConstants.companyName.min_length }) + .withMessage(ValidationReasons.TOO_SHORT(CompanyConstants.companyName.min_length)) + .isLength({ max: CompanyConstants.companyName.max_length }) + .withMessage(ValidationReasons.TOO_LONG(CompanyConstants.companyName.max_length)), + body("bio", ValidationReasons.DEFAULT) + .optional() + .isString().withMessage(ValidationReasons.STRING).bail() + .isLength({ max: CompanyConstants.bio.max_length }) + .withMessage(ValidationReasons.TOO_LONG(CompanyConstants.bio.max_length)), + body("contacts", ValidationReasons.DEFAULT) + .optional() + .customSanitizer(ensureArray) + .isArray({ min: CompanyConstants.contacts.min_length, max: CompanyConstants.contacts.max_length }) + .withMessage(ValidationReasons.ARRAY_SIZE(CompanyConstants.contacts.min_length, CompanyConstants.contacts.max_length)), + body("logo", ValidationReasons.DEFAULT) + .optional() + .isString().withMessage(ValidationReasons.STRING).bail() + .isURL().withMessage(ValidationReasons.URL), +]); From 32675891e4571043700c543b87d7e28830f74135 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 16:16:25 +0000 Subject: [PATCH 24/32] Added validators.edit verification --- src/api/routes/company.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/routes/company.js b/src/api/routes/company.js index e317fe21..bf9e83b6 100644 --- a/src/api/routes/company.js +++ b/src/api/routes/company.js @@ -215,6 +215,7 @@ export default (app) => { authMiddleware.isAdmin, authMiddleware.isGod ], { status_code: HTTPStatus.UNAUTHORIZED, error_code: ErrorTypes.FORBIDDEN, msg: ValidationReasons.INSUFFICIENT_PERMISSIONS }), + validators.edit, (req, res, next) => companyMiddleware.canManageAccountSettings(req.params.companyId)(req, res, next), (req, res, next) => companyMiddleware.isNotBlocked(req.params.companyId)(req, res, next), (req, res, next) => companyMiddleware.isNotDisabled(req.params.companyId)(req, res, next), From 44ae94526a25968ddaad487b32f58150fa763d1d Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 16:20:29 +0000 Subject: [PATCH 25/32] updateAllOffersByCompanyId now returns all offers updated --- src/services/offer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/offer.js b/src/services/offer.js index 2709f23f..6af53bf5 100644 --- a/src/services/offer.js +++ b/src/services/offer.js @@ -474,7 +474,8 @@ class OfferService { ownerLogo: company.logo, contacts: company.contacts, }; - await Offer.updateMany({ owner: company._id }, offer); + const offers = await Offer.updateMany({ owner: company._id }, offer, { new: true }); + return offers; } } From 9f8e0285e2b4ef697339a8fdb232137ba05e937f Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 28 Feb 2023 16:23:39 +0000 Subject: [PATCH 26/32] Added test to check if offers reflect company changes --- test/end-to-end/company.js | 55 +++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/test/end-to-end/company.js b/test/end-to-end/company.js index f0da7616..8cf211d6 100644 --- a/test/end-to-end/company.js +++ b/test/end-to-end/company.js @@ -1935,7 +1935,7 @@ describe("Company endpoint", () => { }); describe("PUT /company/edit", () => { - let test_company, test_random; + let test_company, test_random, test_offer; const test_user_admin = { email: "admin@email.com", @@ -1954,7 +1954,7 @@ describe("Company endpoint", () => { const test_agent = agent(); - beforeAll(async () => { + beforeEach(async () => { await test_agent .delete("/auth/login") .expect(HTTPStatus.OK); @@ -1978,6 +1978,14 @@ describe("Company endpoint", () => { hasFinishedRegistration: true }); + const test_offer_data = generateTestOffer({ + owner: test_company._id, + ownerName: test_company.name, + ownerLogo: test_company.logo, + }); + + test_offer = await Offer.create(test_offer_data); + await Account.deleteMany({}); await Account.create({ @@ -2012,22 +2020,32 @@ describe("Company endpoint", () => { describe("ID Validation", () => { test("Should fail if id is not a valid ObjectID", async () => { + await test_agent + .post("/auth/login") + .send(test_user_company) + .expect(HTTPStatus.OK); + const res = await test_agent .put("/company/123/edit") .send() - .expect(HTTPStatus.UNAUTHORIZED); + .expect(HTTPStatus.UNPROCESSABLE_ENTITY); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_ID); }); test("Should fail if id is not a valid company", async () => { const id = "111111111111111111111111"; + await test_agent + .post("/auth/login") + .send(test_user_company) + .expect(HTTPStatus.OK); + const res = await test_agent .put(`/company/${id}/edit`) .send() - .expect(HTTPStatus.UNAUTHORIZED); + .expect(HTTPStatus.UNPROCESSABLE_ENTITY); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_NOT_FOUND(id)); }); }); @@ -2096,11 +2114,30 @@ describe("Company endpoint", () => { expect(res.body).toHaveProperty("name", "Changing Company"); expect(res.body).toHaveProperty("bio", "As company itself"); - expect(res.body).toHaveProperty("contacts", ["1"]); expect(res.body).toHaveProperty("logo", "http://awebsite.com/otherlogo.jpg"); }); }); + test("Offer should be updated", async () => { + await test_agent + .post("/auth/login") + .send(test_user_company) + .expect(HTTPStatus.OK); + + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send({ + name: "Changing Company", + contacts: ["122"], + }) + .expect(HTTPStatus.OK); + + test_offer = await Offer.findById(test_offer._id); + expect(res.body).toHaveProperty("contacts", ["122"]); + expect(test_offer.ownerName).toEqual("Changing Company"); + expect(test_offer.contacts).toEqual(["122"]); + }); + describe("Should fail if company is blocked or disabled", () => { test("Should fail if company is blocked (admin)", async () => { await test_agent @@ -2140,7 +2177,7 @@ describe("Company endpoint", () => { logo: "http://awebsite.com/otherlogo.jpg", }) .expect(HTTPStatus.FORBIDDEN); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_BLOCKED); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_DISABLED); }); test("Should fail if company is blocked (user)", async () => { @@ -2182,7 +2219,7 @@ describe("Company endpoint", () => { }) .expect(HTTPStatus.FORBIDDEN); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_BLOCKED); + expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_DISABLED); }); }); }); From 8cc776f85954fb20fd9171a008e3e5736c692884 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 7 Mar 2023 14:48:34 +0000 Subject: [PATCH 27/32] fixed describe test names --- test/end-to-end/company.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/end-to-end/company.js b/test/end-to-end/company.js index 8cf211d6..54b453d0 100644 --- a/test/end-to-end/company.js +++ b/test/end-to-end/company.js @@ -2049,7 +2049,7 @@ describe("Company endpoint", () => { }); }); - describe("Should fail if bad user", () => { + describe("Using a bad user", () => { test("Should fail if different user", async () => { await test_agent .post("/auth/login") @@ -2084,7 +2084,7 @@ describe("Company endpoint", () => { }); }); - describe("Should pass if good user", () => { + describe("Using a good user", () => { test("Should pass if god", async () => { await test_agent .put(`/company/${test_company._id}/edit`) @@ -2138,7 +2138,7 @@ describe("Company endpoint", () => { expect(test_offer.contacts).toEqual(["122"]); }); - describe("Should fail if company is blocked or disabled", () => { + describe("Using disabled/blocked company", () => { test("Should fail if company is blocked (admin)", async () => { await test_agent .post("/auth/login") From 309de07dc735d1840dbee3d7d6f1a356e4eded6b Mon Sep 17 00:00:00 2001 From: marco-vb Date: Tue, 7 Mar 2023 15:47:36 +0000 Subject: [PATCH 28/32] refactored my tests, removing duplicated code and creating everything before starting the actual tests, as suggested --- test/end-to-end/company.js | 194 ++++++++++++++++++++++--------------- 1 file changed, 115 insertions(+), 79 deletions(-) diff --git a/test/end-to-end/company.js b/test/end-to-end/company.js index 54b453d0..d4cff6ce 100644 --- a/test/end-to-end/company.js +++ b/test/end-to-end/company.js @@ -1935,7 +1935,35 @@ describe("Company endpoint", () => { }); describe("PUT /company/edit", () => { - let test_company, test_random, test_offer; + let test_company, test_blocked_company, test_disabled_company, test_random, test_offer; + + const test_company_map = { + name: "Test Company", + bio: "Test Company Bio", + logo: "http://awebsite.com/alogo.jpg", + contacts: ["112", "122"], + }; + + const test_blocked_company_map = { + name: "Test Blocked Company", + bio: "Test Blocked Company Bio", + logo: "http://awebsite.com/alogo.jpg", + contacts: ["112", "122"], + }; + + const test_disabled_company_map = { + name: "Test Disabled Company", + bio: "Test Disabled Company Bio", + logo: "http://awebsite.com/alogo.jpg", + contacts: ["112", "122"], + }; + + const test_random_company_map = { + name: "Test Random Company", + bio: "Test Random Company Bio", + logo: "http://awebsite.com/alogo.jpg", + contacts: ["112", "122"], + }; const test_user_admin = { email: "admin@email.com", @@ -1947,6 +1975,16 @@ describe("Company endpoint", () => { password: "password123", }; + const test_blocked_user = { + email: "blocked@email.com", + password: "password123", + }; + + const test_disabled_user = { + email: "disabled@email.com", + password: "password123", + }; + const test_random_user = { email: "random@email.com", password: "password123", @@ -1954,27 +1992,39 @@ describe("Company endpoint", () => { const test_agent = agent(); - beforeEach(async () => { - await test_agent - .delete("/auth/login") - .expect(HTTPStatus.OK); + beforeAll(async () => { - await Company.deleteMany({}); + test_company = await Company.create({ + name: test_company_map.name, + contacts: test_company_map.contacts, + bio: test_company_map.bio, + logo: test_company_map.logo, + hasFinishedRegistration: true, + }); + test_blocked_company = await Company.create({ + name: test_blocked_company_map.name, + contacts: test_blocked_company_map.contacts, + bio: test_blocked_company_map.bio, + logo: test_blocked_company_map.logo, + hasFinishedRegistration: true, + isBlocked: true + }); - test_company = await Company.create({ - name: "Cool Company", - contacts: ["112", "122"], - bio: "Cool company bio", - logo: "http://awebsite.com/alogo.jpg", - hasFinishedRegistration: true + test_disabled_company = await Company.create({ + name: test_disabled_company_map.name, + contacts: test_disabled_company_map.contacts, + bio: test_disabled_company_map.bio, + logo: test_disabled_company_map.logo, + hasFinishedRegistration: true, + isDisabled: true }); test_random = await Company.create({ - name: "Random Company", - contacts: ["112", "122"], - bio: "Random company bio", - logo: "http://awebsite.com/alogo.jpg", + name: test_random_company_map.name, + contacts: test_random_company_map.contacts, + bio: test_random_company_map.bio, + logo: test_random_company_map.logo, hasFinishedRegistration: true }); @@ -2000,6 +2050,18 @@ describe("Company endpoint", () => { company: test_company._id }); + await Account.create({ + email: test_blocked_user.email, + password: await hash(test_blocked_user.password), + company: test_blocked_company._id + }); + + await Account.create({ + email: test_disabled_user.email, + password: await hash(test_disabled_user.password), + company: test_disabled_company._id + }); + await Account.create({ email: test_random_user.email, password: await hash(test_random_user.password), @@ -2019,12 +2081,14 @@ describe("Company endpoint", () => { }); describe("ID Validation", () => { - test("Should fail if id is not a valid ObjectID", async () => { + beforeEach(async () => { await test_agent .post("/auth/login") - .send(test_user_company) + .send(test_user_admin) .expect(HTTPStatus.OK); + }); + test("Should fail if id is not a valid ObjectID", async () => { const res = await test_agent .put("/company/123/edit") .send() @@ -2035,10 +2099,6 @@ describe("Company endpoint", () => { test("Should fail if id is not a valid company", async () => { const id = "111111111111111111111111"; - await test_agent - .post("/auth/login") - .send(test_user_company) - .expect(HTTPStatus.OK); const res = await test_agent .put(`/company/${id}/edit`) @@ -2060,41 +2120,45 @@ describe("Company endpoint", () => { .put(`/company/${test_company._id}/edit`) .send({ name: "Changing Company", - bio: "Without permission", - contacts: ["1"], - logo: "http://awebsite.com/otherlogo.jpg", }) .expect(HTTPStatus.FORBIDDEN); expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS_COMPANY_SETTINGS); + expect(test_company.name).toBe(test_company_map.name); }); test("Should fail if not logged in", async () => { const res = await test_agent .put(`/company/${test_company._id}/edit`) .send({ - name: "Changing Company", bio: "Without login", contacts: ["1"], - logo: "http://awebsite.com/otherlogo.jpg", }) .expect(HTTPStatus.UNAUTHORIZED); expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS); + expect(test_company.bio).toBe(test_company_map.bio); + expect(test_company.contacts).toEqual(test_company_map.contacts); }); }); describe("Using a good user", () => { test("Should pass if god", async () => { - await test_agent + const res = await test_agent .put(`/company/${test_company._id}/edit`) .send(withGodToken({ name: "Changing Company", - bio: "As god", - contacts: ["1"], logo: "http://awebsite.com/otherlogo.jpg", })) .expect(HTTPStatus.OK); + + expect(res.body).toHaveProperty("name", "Changing Company"); + expect(res.body).toHaveProperty("logo", "http://awebsite.com/otherlogo.jpg"); + + const changed_company = await Company.findById(test_company._id); + + expect(changed_company.name).toBe("Changing Company"); + expect(changed_company.logo).toBe("http://awebsite.com/otherlogo.jpg"); }); test("Should pass if same company", async () => { @@ -2107,14 +2171,12 @@ describe("Company endpoint", () => { .put(`/company/${test_company._id}/edit`) .send({ name: "Changing Company", - bio: "As company itself", - logo: "http://awebsite.com/otherlogo.jpg", }) .expect(HTTPStatus.OK); expect(res.body).toHaveProperty("name", "Changing Company"); - expect(res.body).toHaveProperty("bio", "As company itself"); - expect(res.body).toHaveProperty("logo", "http://awebsite.com/otherlogo.jpg"); + const changed_company = await Company.findById(test_company._id); + expect(changed_company.name).toBe("Changing Company"); }); }); @@ -2138,88 +2200,62 @@ describe("Company endpoint", () => { expect(test_offer.contacts).toEqual(["122"]); }); - describe("Using disabled/blocked company", () => { - test("Should fail if company is blocked (admin)", async () => { - await test_agent - .post("/auth/login") - .send(test_user_admin) - .expect(HTTPStatus.OK); - - await Company.findByIdAndUpdate(test_company._id, { isBlocked: true }); - + describe("Using disabled/blocked company (god)", () => { + test("Should fail if company is blocked (god)", async () => { const res = await test_agent - .put(`/company/${test_company._id}/edit`) - .send({ + .put(`/company/${test_blocked_company._id}/edit`) + .send(withGodToken({ name: "Changing Blocked Company", - bio: "As admin", - contacts: ["1"], - logo: "http://awebsite.com/otherlogo.jpg", - }) + })) .expect(HTTPStatus.FORBIDDEN); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_BLOCKED); + expect(test_blocked_company.name).toBe(test_blocked_company_map.name); }); - test("Should fail if company is disabled (admin)", async () => { - await test_agent - .post("/auth/login") - .send(test_user_admin) - .expect(HTTPStatus.OK); - - await Company.findByIdAndUpdate(test_company._id, { isDisabled: true }); - + test("Should fail if company is disabled (god)", async () => { const res = await test_agent - .put(`/company/${test_company._id}/edit`) - .send({ + .put(`/company/${test_disabled_company._id}/edit`) + .send(withGodToken({ name: "Changing Disabled Company", - bio: "As admin", - contacts: ["1"], - logo: "http://awebsite.com/otherlogo.jpg", - }) + })) .expect(HTTPStatus.FORBIDDEN); expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_DISABLED); + expect(test_disabled_company.name).toBe(test_disabled_company_map.name); }); + }); + describe("Using disabled/blocked company (user)", () => { test("Should fail if company is blocked (user)", async () => { await test_agent .post("/auth/login") - .send(test_user_company) + .send(test_blocked_user) .expect(HTTPStatus.OK); - await Company.findByIdAndUpdate(test_company._id, { isBlocked: true }); - const res = await test_agent - .put(`/company/${test_company._id}/edit`) + .put(`/company/${test_blocked_company._id}/edit`) .send({ name: "Changing Blocked Company", - bio: "As user", - contacts: ["1"], - logo: "http://awebsite.com/otherlogo.jpg", }) .expect(HTTPStatus.FORBIDDEN); expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_BLOCKED); + expect(test_blocked_company.name).toBe(test_blocked_company_map.name); }); test("Should fail if company is disabled (user)", async () => { await test_agent .post("/auth/login") - .send(test_user_company) + .send(test_disabled_user) .expect(HTTPStatus.OK); - await Company.findByIdAndUpdate(test_company._id, { isDisabled: true }); - const res = await test_agent - .put(`/company/${test_company._id}/edit`) + .put(`/company/${test_disabled_company._id}/edit`) .send({ - name: "Changing Disabled Company", bio: "As user", - contacts: ["1"], - logo: "http://awebsite.com/otherlogo.jpg", }) .expect(HTTPStatus.FORBIDDEN); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_DISABLED); + expect(test_disabled_company.bio).toBe(test_disabled_company_map.bio); }); }); }); From 33a248f82aa901900a12d0f4ff23bdb26d2dab45 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Thu, 9 Mar 2023 19:39:56 +0000 Subject: [PATCH 29/32] Fixed test asserts to correct methods --- test/end-to-end/company.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/test/end-to-end/company.js b/test/end-to-end/company.js index d4cff6ce..0331929a 100644 --- a/test/end-to-end/company.js +++ b/test/end-to-end/company.js @@ -2089,12 +2089,15 @@ describe("Company endpoint", () => { }); test("Should fail if id is not a valid ObjectID", async () => { + const id = "123"; const res = await test_agent - .put("/company/123/edit") + .put(`/company/${id}/edit`) .send() .expect(HTTPStatus.UNPROCESSABLE_ENTITY); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.OBJECT_ID); + expect(res.body.errors).toContainEqual( + { "location": "params", "msg": ValidationReasons.OBJECT_ID, "param": "companyId", "value": id } + ); }); test("Should fail if id is not a valid company", async () => { @@ -2105,7 +2108,9 @@ describe("Company endpoint", () => { .send() .expect(HTTPStatus.UNPROCESSABLE_ENTITY); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_NOT_FOUND(id)); + expect(res.body.errors).toContainEqual( + { "location": "params", "msg": ValidationReasons.COMPANY_NOT_FOUND(id), "param": "companyId", "value": id } + ); }); }); @@ -2123,7 +2128,7 @@ describe("Company endpoint", () => { }) .expect(HTTPStatus.FORBIDDEN); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS_COMPANY_SETTINGS); + expect(res.body.errors).toContainEqual({ "msg": ValidationReasons.INSUFFICIENT_PERMISSIONS_COMPANY_SETTINGS }); expect(test_company.name).toBe(test_company_map.name); }); @@ -2136,7 +2141,7 @@ describe("Company endpoint", () => { }) .expect(HTTPStatus.UNAUTHORIZED); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.INSUFFICIENT_PERMISSIONS); + expect(res.body.errors).toContainEqual({ "msg": ValidationReasons.INSUFFICIENT_PERMISSIONS }); expect(test_company.bio).toBe(test_company_map.bio); expect(test_company.contacts).toEqual(test_company_map.contacts); }); @@ -2208,7 +2213,7 @@ describe("Company endpoint", () => { name: "Changing Blocked Company", })) .expect(HTTPStatus.FORBIDDEN); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_BLOCKED); + expect(res.body.errors).toContainEqual({ "msg": ValidationReasons.COMPANY_BLOCKED }); expect(test_blocked_company.name).toBe(test_blocked_company_map.name); }); @@ -2219,7 +2224,7 @@ describe("Company endpoint", () => { name: "Changing Disabled Company", })) .expect(HTTPStatus.FORBIDDEN); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_DISABLED); + expect(res.body.errors).toContainEqual({ "msg": ValidationReasons.COMPANY_DISABLED }); expect(test_disabled_company.name).toBe(test_disabled_company_map.name); }); }); @@ -2238,7 +2243,7 @@ describe("Company endpoint", () => { }) .expect(HTTPStatus.FORBIDDEN); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_BLOCKED); + expect(res.body.errors).toContainEqual({ "msg": ValidationReasons.COMPANY_BLOCKED }); expect(test_blocked_company.name).toBe(test_blocked_company_map.name); }); @@ -2254,7 +2259,7 @@ describe("Company endpoint", () => { bio: "As user", }) .expect(HTTPStatus.FORBIDDEN); - expect(res.body.errors[0]).toHaveProperty("msg", ValidationReasons.COMPANY_DISABLED); + expect(res.body.errors).toContainEqual({ "msg": ValidationReasons.COMPANY_DISABLED }); expect(test_disabled_company.bio).toBe(test_disabled_company_map.bio); }); }); From 0c9e127d94b16f8c0eccc2a2ee7971d5b372679f Mon Sep 17 00:00:00 2001 From: marco-vb Date: Thu, 9 Mar 2023 19:52:05 +0000 Subject: [PATCH 30/32] Removed redundant testing on tests that return bad HTTPstatus (and therefore obviously don't change the company) --- test/end-to-end/company.js | 52 ++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/test/end-to-end/company.js b/test/end-to-end/company.js index 0331929a..eef8e29a 100644 --- a/test/end-to-end/company.js +++ b/test/end-to-end/company.js @@ -1965,6 +1965,13 @@ describe("Company endpoint", () => { contacts: ["112", "122"], }; + const changing_values = { + name: "Changed name", + bio: "Changed bio", + logo: "http://awebsite.com/changedlogo.jpg", + contacts: ["123", "456"], + }; + const test_user_admin = { email: "admin@email.com", password: "password123", @@ -2124,26 +2131,23 @@ describe("Company endpoint", () => { const res = await test_agent .put(`/company/${test_company._id}/edit`) .send({ - name: "Changing Company", + name: changing_values.name, }) .expect(HTTPStatus.FORBIDDEN); expect(res.body.errors).toContainEqual({ "msg": ValidationReasons.INSUFFICIENT_PERMISSIONS_COMPANY_SETTINGS }); - expect(test_company.name).toBe(test_company_map.name); }); test("Should fail if not logged in", async () => { const res = await test_agent .put(`/company/${test_company._id}/edit`) .send({ - bio: "Without login", - contacts: ["1"], + bio: changing_values.bio, + contacts: changing_values.contacts, }) .expect(HTTPStatus.UNAUTHORIZED); expect(res.body.errors).toContainEqual({ "msg": ValidationReasons.INSUFFICIENT_PERMISSIONS }); - expect(test_company.bio).toBe(test_company_map.bio); - expect(test_company.contacts).toEqual(test_company_map.contacts); }); }); @@ -2152,18 +2156,13 @@ describe("Company endpoint", () => { const res = await test_agent .put(`/company/${test_company._id}/edit`) .send(withGodToken({ - name: "Changing Company", - logo: "http://awebsite.com/otherlogo.jpg", + name: changing_values.name, + bio: changing_values.bio, })) .expect(HTTPStatus.OK); - expect(res.body).toHaveProperty("name", "Changing Company"); - expect(res.body).toHaveProperty("logo", "http://awebsite.com/otherlogo.jpg"); - - const changed_company = await Company.findById(test_company._id); - - expect(changed_company.name).toBe("Changing Company"); - expect(changed_company.logo).toBe("http://awebsite.com/otherlogo.jpg"); + expect(res.body).toHaveProperty("name", changing_values.name); + expect(res.body).toHaveProperty("bio", changing_values.bio); }); test("Should pass if same company", async () => { @@ -2175,13 +2174,13 @@ describe("Company endpoint", () => { const res = await test_agent .put(`/company/${test_company._id}/edit`) .send({ - name: "Changing Company", + name: changing_values.name, }) .expect(HTTPStatus.OK); - expect(res.body).toHaveProperty("name", "Changing Company"); + expect(res.body).toHaveProperty("name", changing_values.name); const changed_company = await Company.findById(test_company._id); - expect(changed_company.name).toBe("Changing Company"); + expect(changed_company.name).toBe(changing_values.name); }); }); @@ -2194,15 +2193,18 @@ describe("Company endpoint", () => { const res = await test_agent .put(`/company/${test_company._id}/edit`) .send({ - name: "Changing Company", - contacts: ["122"], + name: changing_values.name, + contacts: changing_values.contacts, }) .expect(HTTPStatus.OK); test_offer = await Offer.findById(test_offer._id); - expect(res.body).toHaveProperty("contacts", ["122"]); - expect(test_offer.ownerName).toEqual("Changing Company"); - expect(test_offer.contacts).toEqual(["122"]); + + expect(res.body).toHaveProperty("name", changing_values.name); + expect(res.body).toHaveProperty("contacts", changing_values.contacts); + + expect(test_offer.ownerName).toEqual(changing_values.name); + expect(test_offer.contacts).toEqual(changing_values.contacts); }); describe("Using disabled/blocked company (god)", () => { @@ -2214,7 +2216,6 @@ describe("Company endpoint", () => { })) .expect(HTTPStatus.FORBIDDEN); expect(res.body.errors).toContainEqual({ "msg": ValidationReasons.COMPANY_BLOCKED }); - expect(test_blocked_company.name).toBe(test_blocked_company_map.name); }); test("Should fail if company is disabled (god)", async () => { @@ -2225,7 +2226,6 @@ describe("Company endpoint", () => { })) .expect(HTTPStatus.FORBIDDEN); expect(res.body.errors).toContainEqual({ "msg": ValidationReasons.COMPANY_DISABLED }); - expect(test_disabled_company.name).toBe(test_disabled_company_map.name); }); }); @@ -2244,7 +2244,6 @@ describe("Company endpoint", () => { .expect(HTTPStatus.FORBIDDEN); expect(res.body.errors).toContainEqual({ "msg": ValidationReasons.COMPANY_BLOCKED }); - expect(test_blocked_company.name).toBe(test_blocked_company_map.name); }); test("Should fail if company is disabled (user)", async () => { @@ -2260,7 +2259,6 @@ describe("Company endpoint", () => { }) .expect(HTTPStatus.FORBIDDEN); expect(res.body.errors).toContainEqual({ "msg": ValidationReasons.COMPANY_DISABLED }); - expect(test_disabled_company.bio).toBe(test_disabled_company_map.bio); }); }); }); From ccf60f752d250e77d87ebab118762ea62253f0e7 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Thu, 9 Mar 2023 19:56:12 +0000 Subject: [PATCH 31/32] Changed small detail in services/offer.js --- src/services/offer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/offer.js b/src/services/offer.js index 6af53bf5..bf63bb7f 100644 --- a/src/services/offer.js +++ b/src/services/offer.js @@ -469,12 +469,12 @@ class OfferService { } async updateAllOffersByCompanyId(company) { - const offer = { + const changes = { ownerName: company.name, ownerLogo: company.logo, contacts: company.contacts, }; - const offers = await Offer.updateMany({ owner: company._id }, offer, { new: true }); + const offers = await Offer.updateMany({ owner: company._id }, changes, { new: true }); return offers; } } From f04634a81d8c5b9339a2171b34165220463b1860 Mon Sep 17 00:00:00 2001 From: marco-vb Date: Thu, 9 Mar 2023 21:28:00 +0000 Subject: [PATCH 32/32] Refactored my tests to create accounts and companies using shorter code --- test/end-to-end/company.js | 196 +++++++++++++------------------------ 1 file changed, 70 insertions(+), 126 deletions(-) diff --git a/test/end-to-end/company.js b/test/end-to-end/company.js index eef8e29a..59ce8090 100644 --- a/test/end-to-end/company.js +++ b/test/end-to-end/company.js @@ -49,6 +49,15 @@ describe("Company endpoint", () => { ...params, }); + const generateTestCompany = (params) => ({ + name: "Big Company", + bio: "Big Company Bio", + logo: "http://awebsite.com/alogo.jpg", + contacts: ["112", "122"], + hasFinishedRegistration: true, + ...params, + }); + describe("GET /company", () => { beforeAll(async () => { @@ -1935,35 +1944,9 @@ describe("Company endpoint", () => { }); describe("PUT /company/edit", () => { - let test_company, test_blocked_company, test_disabled_company, test_random, test_offer; - - const test_company_map = { - name: "Test Company", - bio: "Test Company Bio", - logo: "http://awebsite.com/alogo.jpg", - contacts: ["112", "122"], - }; - - const test_blocked_company_map = { - name: "Test Blocked Company", - bio: "Test Blocked Company Bio", - logo: "http://awebsite.com/alogo.jpg", - contacts: ["112", "122"], - }; - - const test_disabled_company_map = { - name: "Test Disabled Company", - bio: "Test Disabled Company Bio", - logo: "http://awebsite.com/alogo.jpg", - contacts: ["112", "122"], - }; - - const test_random_company_map = { - name: "Test Random Company", - bio: "Test Random Company Bio", - logo: "http://awebsite.com/alogo.jpg", - contacts: ["112", "122"], - }; + let test_companies; + let test_company, test_company_blocked, test_company_disabled; + let test_offer; const changing_values = { name: "Changed name", @@ -1972,108 +1955,53 @@ describe("Company endpoint", () => { contacts: ["123", "456"], }; - const test_user_admin = { - email: "admin@email.com", - password: "password123", - }; - - const test_user_company = { - email: "company@email.com", - password: "password123", - }; - - const test_blocked_user = { - email: "blocked@email.com", - password: "password123", - }; - - const test_disabled_user = { - email: "disabled@email.com", + /* Admin, Company, Blocked, Disabled*/ + const test_users = Array(4).fill({}).map((_c, idx) => ({ + email: `test_email_${idx}@email.com`, password: "password123", - }; + })); - const test_random_user = { - email: "random@email.com", - password: "password123", - }; + const [test_user_admin, test_user_company, test_user_company_blocked, test_user_company_disabled] = test_users; const test_agent = agent(); beforeAll(async () => { - - test_company = await Company.create({ - name: test_company_map.name, - contacts: test_company_map.contacts, - bio: test_company_map.bio, - logo: test_company_map.logo, - hasFinishedRegistration: true, - }); - - test_blocked_company = await Company.create({ - name: test_blocked_company_map.name, - contacts: test_blocked_company_map.contacts, - bio: test_blocked_company_map.bio, - logo: test_blocked_company_map.logo, - hasFinishedRegistration: true, - isBlocked: true - }); - - test_disabled_company = await Company.create({ - name: test_disabled_company_map.name, - contacts: test_disabled_company_map.contacts, - bio: test_disabled_company_map.bio, - logo: test_disabled_company_map.logo, - hasFinishedRegistration: true, - isDisabled: true - }); - - test_random = await Company.create({ - name: test_random_company_map.name, - contacts: test_random_company_map.contacts, - bio: test_random_company_map.bio, - logo: test_random_company_map.logo, - hasFinishedRegistration: true - }); - - const test_offer_data = generateTestOffer({ - owner: test_company._id, - ownerName: test_company.name, - ownerLogo: test_company.logo, - }); - - test_offer = await Offer.create(test_offer_data); - await Account.deleteMany({}); - await Account.create({ - email: test_user_admin.email, - password: await hash(test_user_admin.password), - isAdmin: true - }); + const test_company_data = await generateTestCompany(); + const test_company_blocked_data = await generateTestCompany({ isBlocked: true }); + const test_company_disabled_data = await generateTestCompany({ isDisabled: true }); - await Account.create({ - email: test_user_company.email, - password: await hash(test_user_company.password), - company: test_company._id - }); + test_companies = await Company.create( + [test_company_data, test_company_blocked_data, test_company_disabled_data], + { session: null } + ); - await Account.create({ - email: test_blocked_user.email, - password: await hash(test_blocked_user.password), - company: test_blocked_company._id - }); + [test_company, test_company_blocked, test_company_disabled] = test_companies; - await Account.create({ - email: test_disabled_user.email, - password: await hash(test_disabled_user.password), - company: test_disabled_company._id - }); + test_offer = await Offer.create( + generateTestOffer({ + owner: test_company._id, + ownerName: test_company.name, + ownerLogo: test_company.logo, + }) + ); - await Account.create({ - email: test_random_user.email, - password: await hash(test_random_user.password), - company: test_random._id - }); + for (let i = 0; i < test_users.length; i++) { + if (i === 0) { // Admin + await Account.create({ + email: test_users[i].email, + password: await hash(test_users[i].password), + isAdmin: true, + }); + } else { // Company + await Account.create({ + email: test_users[i].email, + password: await hash(test_users[i].password), + company: test_companies[i - 1]._id, + }); + } + } }); afterEach(async () => { @@ -2125,7 +2053,7 @@ describe("Company endpoint", () => { test("Should fail if different user", async () => { await test_agent .post("/auth/login") - .send(test_random_user) + .send(test_user_company_blocked) .expect(HTTPStatus.OK); const res = await test_agent @@ -2165,6 +2093,22 @@ describe("Company endpoint", () => { expect(res.body).toHaveProperty("bio", changing_values.bio); }); + test("Should pass if admin", async () => { + await test_agent + .post("/auth/login") + .send(test_user_admin) + .expect(HTTPStatus.OK); + + const res = await test_agent + .put(`/company/${test_company._id}/edit`) + .send({ + name: changing_values.name + }) + .expect(HTTPStatus.OK); + + expect(res.body).toHaveProperty("name", changing_values.name); + }); + test("Should pass if same company", async () => { await test_agent .post("/auth/login") @@ -2210,7 +2154,7 @@ describe("Company endpoint", () => { describe("Using disabled/blocked company (god)", () => { test("Should fail if company is blocked (god)", async () => { const res = await test_agent - .put(`/company/${test_blocked_company._id}/edit`) + .put(`/company/${test_company_blocked._id}/edit`) .send(withGodToken({ name: "Changing Blocked Company", })) @@ -2220,7 +2164,7 @@ describe("Company endpoint", () => { test("Should fail if company is disabled (god)", async () => { const res = await test_agent - .put(`/company/${test_disabled_company._id}/edit`) + .put(`/company/${test_company_disabled._id}/edit`) .send(withGodToken({ name: "Changing Disabled Company", })) @@ -2233,11 +2177,11 @@ describe("Company endpoint", () => { test("Should fail if company is blocked (user)", async () => { await test_agent .post("/auth/login") - .send(test_blocked_user) + .send(test_user_company_blocked) .expect(HTTPStatus.OK); const res = await test_agent - .put(`/company/${test_blocked_company._id}/edit`) + .put(`/company/${test_company_blocked._id}/edit`) .send({ name: "Changing Blocked Company", }) @@ -2249,11 +2193,11 @@ describe("Company endpoint", () => { test("Should fail if company is disabled (user)", async () => { await test_agent .post("/auth/login") - .send(test_disabled_user) + .send(test_user_company_disabled) .expect(HTTPStatus.OK); const res = await test_agent - .put(`/company/${test_disabled_company._id}/edit`) + .put(`/company/${test_company_disabled._id}/edit`) .send({ bio: "As user", })