From a531932f20cf5f9b8af04d208e425cd3cf9a077a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Martins?= Date: Wed, 7 Feb 2024 15:48:39 +0000 Subject: [PATCH 1/5] Made application status be an Enum attribute instead of a virtual property. --- src/models/CompanyApplication.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/models/CompanyApplication.js b/src/models/CompanyApplication.js index 766f8962..35fa4555 100644 --- a/src/models/CompanyApplication.js +++ b/src/models/CompanyApplication.js @@ -80,18 +80,18 @@ export const CompanyApplicationProps = { return !!this.rejectedAt; }, }, + state: { + type: String, + enum: ApplicationStatus, + default: ApplicationStatus.PENDING, + required: true, + }, }; const CompanyApplicationSchema = new Schema(CompanyApplicationProps); CompanyApplicationSchema.index({ companyName: "text" }); -CompanyApplicationSchema.virtual("state").get(function() { - if (!this.approvedAt && !this.rejectedAt) return ApplicationStatus.PENDING; - else if (this.approvedAt) return ApplicationStatus.APPROVED; - else return ApplicationStatus.REJECTED; -}); - async function validateEmailUniqueAccount(value) { try { await checkDuplicatedEmail(value); @@ -107,7 +107,6 @@ function validateDecisionDate(value) { } function validateMutuallyExclusiveEvents(field) { - return function(value) { return !value || !this[field]; }; @@ -153,6 +152,7 @@ export const isRejectable = (application) => { CompanyApplicationSchema.methods.approve = function() { isApprovable(this); this.approvedAt = Date.now(); + this.state = ApplicationStatus.APPROVED; // Need to prevent validation, otherwise it will fail the email uniqueness, // Since there is already an application with same email: itself :) return this.save({ validateModifiedOnly: true }); @@ -162,6 +162,7 @@ CompanyApplicationSchema.methods.reject = function(reason) { isRejectable(this); this.rejectedAt = Date.now(); this.rejectReason = reason; + this.state = ApplicationStatus.REJECTED; // Need to prevent validation, otherwise it will fail the email uniqueness, // Since there is already an application with same email: itself :) return this.save({ validateModifiedOnly: true }); @@ -170,6 +171,7 @@ CompanyApplicationSchema.methods.reject = function(reason) { CompanyApplicationSchema.methods.undoApproval = function() { if (!this.approvedAt) throw new Error("Cannot undo approval of yet-to-be approved Company Application"); this.approvedAt = undefined; + this.state = ApplicationStatus.PENDING; // Need to prevent validation, otherwise it will fail the email uniqueness, // Since there is already an application with same email: itself :) return this.save({ validateModifiedOnly: true }); From 16a746ec21cb97dcb4d656aa8c07a0c7228d1f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Martins?= Date: Mon, 26 Feb 2024 13:18:13 +0000 Subject: [PATCH 2/5] Fixed test for filtering application results --- test/end-to-end/review.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/end-to-end/review.js b/test/end-to-end/review.js index aab25cdc..96992054 100644 --- a/test/end-to-end/review.js +++ b/test/end-to-end/review.js @@ -94,12 +94,15 @@ describe("Company application review endpoint test", () => { ...pendingApplication, submittedAt: new Date("2019-11-24"), approvedAt: pendingApplication.submittedAt.getTime() + 1, + state: ApplicationStatus.APPROVED, companyName: "approved Testing company", email: `approved${pendingApplication.email}`, }; + const rejectedApplication = { ...pendingApplication, submittedAt: new Date("2019-11-23"), rejectedAt: pendingApplication.submittedAt.getTime() + 1, + state: ApplicationStatus.REJECTED, companyName: "rejected Testing company", email: `rejected${pendingApplication.email}`, rejectReason: "2bad4nij0bs", From da3f818f43d2a5b9ef4915bd9a0c4e69107feb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Martins?= Date: Wed, 28 Feb 2024 16:41:49 +0000 Subject: [PATCH 3/5] Adapted default application state to depend on approvedAt and rejectedAt --- src/models/CompanyApplication.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/models/CompanyApplication.js b/src/models/CompanyApplication.js index 35fa4555..7dfb041e 100644 --- a/src/models/CompanyApplication.js +++ b/src/models/CompanyApplication.js @@ -83,7 +83,11 @@ export const CompanyApplicationProps = { state: { type: String, enum: ApplicationStatus, - default: ApplicationStatus.PENDING, + default: function() { + if (this.rejectedAt) return ApplicationStatus.REJECTED; + if (this.approvedAt) return ApplicationStatus.APPROVED; + return ApplicationStatus.PENDING; + }, required: true, }, }; From 37f418fb4bb7f7523b4a11f43756c099dd240d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Martins?= Date: Sun, 3 Mar 2024 12:23:03 +0000 Subject: [PATCH 4/5] Modified and added other company application schema tests related with Application Status Enum - Tested "migration" of database (using the default value for ApplicationStatus) - Replaced the virtual ApplicationStatus field tests --- test/company_application_schema.js | 116 +++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 14 deletions(-) diff --git a/test/company_application_schema.js b/test/company_application_schema.js index 2331eed7..522a1daa 100644 --- a/test/company_application_schema.js +++ b/test/company_application_schema.js @@ -30,34 +30,122 @@ describe("# CompanyApplication schema tests", () => { companyApplicationTester.maxLength("rejectReason", CompanyApplicationConstants.rejectReason.max_length); }); - describe("Virtual field tests", () => { - describe("Test `status` virtual field", () => { - test("should return PENDING", () => { - const application = new CompanyApplication({ - submittedAt: new Date("01/01/2020"), + describe("Application Status tests", () => { + const rejectReason = "Rejected for a good reason"; + const submittedAt = new Date("01/01/2020"); + const baseApplication = { + submittedAt: submittedAt, + companyName: "Testing company", + password: "password123", + motivation: "This company has a very valid motivation because otherwise, the tests would not exist.", + }; + beforeAll(async () => { + await CompanyApplication.deleteMany({}); + }); + afterAll(async () => { + await CompanyApplication.deleteMany({}); + }); + + describe("Approve and reject functions", () => { + test("should return PENDING", async () => { + const application = await CompanyApplication.create({ + ...baseApplication, + email: "pendingApplication@test.com" }); expect(application.state).toBe(ApplicationStatus.PENDING); + + expect(application.rejectedAt).toBeUndefined(); + expect(application.approvedAt).toBeUndefined(); + expect(application.rejectReason).toBeUndefined(); }); - test("should return REJECTED", () => { - const application = new CompanyApplication({ - submittedAt: new Date("01/01/2020"), - rejectedAt: new Date("02/01/2020"), + test("should return REJECTED", async () => { + const application = await CompanyApplication.create({ + ...baseApplication, + email: "rejectApplication@test.com" }); + await application.reject(rejectReason); + expect(application.state).toBe(ApplicationStatus.REJECTED); + expect(application.rejectReason).toBe(rejectReason); + expect(application.rejectedAt).toBeDefined(); + + expect(application.approvedAt).toBeUndefined(); + }); + + test("should return APPROVED", async () => { + const application = await CompanyApplication.create({ + ...baseApplication, + email: "approveApplication@test.com" + }); + await application.approve(); + + expect(application.state).toBe(ApplicationStatus.APPROVED); + expect(application.approvedAt).toBeDefined(); + + expect(application.rejectedAt).toBeUndefined(); + expect(application.rejectReason).toBeUndefined(); }); - test("should return APPROVED", () => { - const application = new CompanyApplication({ - submittedAt: new Date("01/01/2020"), - approvedAt: new Date("02/01/2020"), + test("should return PENDING after undoApproval", async () => { + const application = await CompanyApplication.create({ + ...baseApplication, + email: "undoApprovalApplication@test.com" }); + + await application.approve(); expect(application.state).toBe(ApplicationStatus.APPROVED); + + await application.undoApproval(); + expect(application.state).toBe(ApplicationStatus.PENDING); + + expect(application.rejectedAt).toBeUndefined(); + expect(application.approvedAt).toBeUndefined(); + expect(application.rejectReason).toBeUndefined(); }); + test("should throw error when trying to approve a rejected application", async () => { + const application = await CompanyApplication.create({ + ...baseApplication, + email: "invalidApprove@test.com", + }); + + await application.reject(rejectReason); + expect(() => application.approve()).toThrow(); + }); + + test("should throw error when trying to reject an approved application", async () => { + const application = await CompanyApplication.create({ + ...baseApplication, + email: "invalidReject@test.com", + }); + await application.approve(); + expect(() => application.reject(rejectReason)).toThrow(); + }); }); - }); + describe("Migration of application state with correct default value", () => { + const assessedAt = new Date(submittedAt.getTime() + 1); + test("should return APPROVED if approvedAt is defined", async () => { + const application = await CompanyApplication.create({ + ...baseApplication, + email: "approvedAtStatus@test.com", + approvedAt: assessedAt, + }); + expect(application.state).toBe(ApplicationStatus.APPROVED); + }); + + test("should return REJECTED if rejectedAt is defined", async () => { + const application = await CompanyApplication.create({ + ...baseApplication, + email: "rejectedAtStatus@test.com", + rejectedAt: assessedAt, + rejectReason: rejectReason, + }); + expect(application.state).toBe(ApplicationStatus.REJECTED); + }); + }); + }); }); From fcd1f9a5d902c09ebeaf04f5a0fc5ff08c5cbd6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Martins?= Date: Sun, 17 Mar 2024 11:54:45 +0000 Subject: [PATCH 5/5] Ran npm audit fix --- package-lock.json | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5d461e75..01a66ad4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5882,9 +5882,9 @@ "license": "ISC" }, "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" }, "node_modules/ipaddr.js": { "version": "1.9.1", @@ -7258,8 +7258,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "6.8.0", - "license": "MIT", + "version": "6.9.12", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.12.tgz", + "integrity": "sha512-pnLo7g37Br3jXbF0bl5DekBJihm2q+3bB3l2o/B060sWmb5l+VqeScAQCBqaQ+5ezRZFzW5SciZNGdRDEbq89w==", "engines": { "node": ">=6.0.0" } @@ -12850,9 +12851,9 @@ "version": "2.0.4" }, "ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" }, "ipaddr.js": { "version": "1.9.1" @@ -13760,7 +13761,9 @@ "version": "2.0.7" }, "nodemailer": { - "version": "6.8.0" + "version": "6.9.12", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.12.tgz", + "integrity": "sha512-pnLo7g37Br3jXbF0bl5DekBJihm2q+3bB3l2o/B060sWmb5l+VqeScAQCBqaQ+5ezRZFzW5SciZNGdRDEbq89w==" }, "nodemailer-express-handlebars": { "version": "5.0.0",