diff --git a/controllers/applications.ts b/controllers/applications.ts index ba344d643..5f7a25f2f 100644 --- a/controllers/applications.ts +++ b/controllers/applications.ts @@ -117,6 +117,8 @@ const updateApplication = async (req: CustomRequest, res: CustomResponse) => { return res.boom.notFound(APPLICATION_ERROR_MESSAGES.APPLICATION_NOT_FOUND); case APPLICATION_STATUS.unauthorized: return res.boom.unauthorized(APPLICATION_ERROR_MESSAGES.APPLICATION_EDIT_UNAUTHORIZED); + case APPLICATION_STATUS.notPending: + return res.boom.conflict(APPLICATION_ERROR_MESSAGES.APPLICATION_ALREADY_REVIEWED); case APPLICATION_STATUS.tooSoon: return res.boom.conflict(APPLICATION_ERROR_MESSAGES.EDIT_TOO_SOON); case APPLICATION_STATUS.success: diff --git a/models/applications.ts b/models/applications.ts index 08e1edc62..795020501 100644 --- a/models/applications.ts +++ b/models/applications.ts @@ -154,6 +154,15 @@ const updateApplication = async ( return { status: APPLICATION_STATUS.unauthorized }; } + const isEditableApplicationStatus = + application.status === APPLICATION_STATUS_TYPES.PENDING || + application.status === APPLICATION_STATUS_TYPES.CHANGES_REQUESTED; + + if (!isEditableApplicationStatus) { + return { status: APPLICATION_STATUS.notPending }; + } + + const lastEditAt = application.lastEditAt; if (lastEditAt) { const lastEditTimestamp = new Date(lastEditAt).getTime(); @@ -167,6 +176,7 @@ const updateApplication = async ( const requestBody = { ...dataToUpdate, lastEditAt: new Date(currentTime).toISOString(), + status: APPLICATION_STATUS_TYPES.PENDING, }; transaction.update(applicationRef, requestBody); diff --git a/test/integration/application.test.ts b/test/integration/application.test.ts index 9bb9d38bb..cb8c163da 100644 --- a/test/integration/application.test.ts +++ b/test/integration/application.test.ts @@ -12,7 +12,12 @@ const applicationModel = require("../../models/applications"); const applicationsData = require("../fixtures/applications/applications")(); const cookieName = config.get("userToken.cookieName"); -const { APPLICATION_ERROR_MESSAGES, API_RESPONSE_MESSAGES, APPLICATION_SCORE } = require("../../constants/application"); +const { + APPLICATION_ERROR_MESSAGES, + API_RESPONSE_MESSAGES, + APPLICATION_SCORE, + APPLICATION_STATUS_TYPES, +} = require("../../constants/application"); const imageService = require("../../services/imageService"); const { Buffer } = require("node:buffer"); @@ -378,6 +383,34 @@ describe("Application", function () { }); }); + it("should set application status to pending after a successful edit from changes_requested", async function () { + const applicationData = { + ...applicationsData[1], + userId, + status: APPLICATION_STATUS_TYPES.CHANGES_REQUESTED, + }; + const editableApplicationId = await applicationModel.addApplication(applicationData); + + expect(applicationData.status).to.equal(APPLICATION_STATUS_TYPES.CHANGES_REQUESTED); + + const patchRes = await chai + .request(app) + .patch(`/applications/${editableApplicationId}`) + .set("cookie", `${cookieName}=${jwt}`) + .send({ introduction: "Updated introduction text" }); + + expect(patchRes).to.have.status(200); + expect(patchRes.body.message).to.be.equal("Application updated successfully"); + + const getRes = await chai + .request(app) + .get(`/applications/${editableApplicationId}`) + .set("cookie", `${cookieName}=${superUserJwt}`); + + expect(getRes).to.have.status(200); + expect(getRes.body.application.status).to.be.equal("pending"); + }); + it("should return 400 when request body is empty", function (done) { chai .request(app) @@ -506,6 +539,25 @@ describe("Application", function () { expect(secondRes.body.message).to.be.equal(APPLICATION_ERROR_MESSAGES.EDIT_TOO_SOON); }); + it("should return 409 when trying to edit an application that has already been reviewed", async function () { + const reviewedApplicationData = { + ...applicationsData[1], + userId, + status: APPLICATION_STATUS_TYPES.ACCEPTED, + }; + const reviewedApplicationId = await applicationModel.addApplication(reviewedApplicationData); + + const res = await chai + .request(app) + .patch(`/applications/${reviewedApplicationId}`) + .set("cookie", `${cookieName}=${jwt}`) + .send({ introduction: "Attempt edit after review" }); + + expect(res).to.have.status(409); + expect(res.body.error).to.be.equal("Conflict"); + expect(res.body.message).to.be.equal(APPLICATION_ERROR_MESSAGES.APPLICATION_ALREADY_REVIEWED); + }); + it("should return 200 when updating city, state, and country", async function () { const applicationData = { ...applicationsData[0], userId }; const testApplicationId = await applicationModel.addApplication(applicationData); diff --git a/test/unit/controllers/applications.test.ts b/test/unit/controllers/applications.test.ts index f13748a39..0e869f3b8 100644 --- a/test/unit/controllers/applications.test.ts +++ b/test/unit/controllers/applications.test.ts @@ -123,6 +123,16 @@ describe("updateApplication", () => { expect(jsonSpy.called).to.be.false; }); + it("should return 409 when application status is not editable", async () => { + updateApplicationStub.resolves({ status: APPLICATION_STATUS.notPending }); + + await applicationsController.updateApplication(req as CustomRequest, res as CustomResponse); + + expect(boomConflict.calledOnce).to.be.true; + expect(boomConflict.firstCall.args[0]).to.equal(APPLICATION_ERROR_MESSAGES.APPLICATION_ALREADY_REVIEWED); + expect(jsonSpy.called).to.be.false; + }); + it("should return 500 when model returns unexpected status", async () => { updateApplicationStub.resolves({ status: "unknown" });