From ebb4ee4a3e8f1a7c13f32731763ef32ed520bd1a Mon Sep 17 00:00:00 2001 From: Suraj Maity Date: Sat, 22 Feb 2025 00:31:39 +0530 Subject: [PATCH 1/6] feat: acknowledge OOO request --- constants/requests.ts | 3 + controllers/oooRequests.ts | 41 +++++++- controllers/requests.ts | 15 ++- middlewares/validators/oooRequests.ts | 34 ++++++- middlewares/validators/requests.ts | 14 ++- services/oooRequest.ts | 135 ++++++++++++++++++++++++++ test/integration/requests.test.ts | 4 +- types/oooRequest.d.ts | 34 +++++-- 8 files changed, 257 insertions(+), 23 deletions(-) create mode 100644 services/oooRequest.ts diff --git a/constants/requests.ts b/constants/requests.ts index 8a9635d2d..74f94f143 100644 --- a/constants/requests.ts +++ b/constants/requests.ts @@ -25,6 +25,8 @@ export const REQUEST_LOG_TYPE = { REQUEST_BLOCKED: "REQUEST_BLOCKED", REQUEST_CANCELLED: "REQUEST_CANCELLED", REQUEST_UPDATED: "REQUEST_UPDATED", + REQUEST_ALREADY_APPROVED: "REQUEST_ALREADY_APPROVED", + REQUEST_ALREADY_REJECTED: "REQUEST_ALREADY_REJECTED", }; export const REQUEST_CREATED_SUCCESSFULLY = "Request created successfully"; @@ -41,6 +43,7 @@ export const ERROR_WHILE_UPDATING_REQUEST = "Error while updating request"; export const REQUEST_DOES_NOT_EXIST = "Request does not exist"; export const REQUEST_ALREADY_PENDING = "Request already exists please wait for approval or rejection"; +export const UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST = "Only super users are allowed to acknowledge OOO requests"; export const TASK_REQUEST_MESSAGES = { NOT_AUTHORIZED_TO_CREATE_REQUEST: "Not authorized to create the request", diff --git a/controllers/oooRequests.ts b/controllers/oooRequests.ts index a3dea406b..42e6c904d 100644 --- a/controllers/oooRequests.ts +++ b/controllers/oooRequests.ts @@ -1,3 +1,4 @@ +import { NextFunction } from "express"; import { REQUEST_LOG_TYPE, LOG_ACTION, @@ -9,14 +10,16 @@ import { ERROR_WHILE_UPDATING_REQUEST, REQUEST_APPROVED_SUCCESSFULLY, REQUEST_REJECTED_SUCCESSFULLY, + UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST, } from "../constants/requests"; import { statusState } from "../constants/userStatus"; import { addLog } from "../models/logs"; import { createRequest, getRequestByKeyValues, getRequests, updateRequest } from "../models/requests"; import { createUserFutureStatus } from "../models/userFutureStatus"; import { addFutureStatus } from "../models/userStatus"; +import { acknowledgeOOORequest } from "../services/oooRequest"; import { CustomResponse } from "../typeDefinitions/global"; -import { OooRequestCreateRequest, OooStatusRequest } from "../types/oooRequest"; +import { AcknowledgeOOORequest, OooRequestCreateRequest, OooRequestResponse, OooStatusRequest } from "../types/oooRequest"; import { UpdateRequest } from "../types/requests"; export const createOooRequestController = async (req: OooRequestCreateRequest, res: CustomResponse) => { @@ -30,7 +33,7 @@ export const createOooRequestController = async (req: OooRequestCreateRequest, r try { const latestOooRequest:OooStatusRequest = await getRequestByKeyValues({ requestedBy: userId, type: REQUEST_TYPE.OOO , state: REQUEST_STATE.PENDING }); - if (latestOooRequest && latestOooRequest.state === REQUEST_STATE.PENDING) { + if (latestOooRequest && latestOooRequest.status === REQUEST_STATE.PENDING) { return res.boom.badRequest(REQUEST_ALREADY_PENDING); } @@ -120,3 +123,37 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST); } }; + +export const acknowledgeOOORequestController = async ( + req: AcknowledgeOOORequest, + res: OooRequestResponse, + next: NextFunction, +) + : Promise => { + + const dev = req.query.dev === "true"; + + if(!dev) return res.boom.notImplemented("Feature not implemented"); + + const requestBody = req.body; + const userId = req.userData.id; + const requestId = req.params.id; + const isSuperuser = req.userData.roles.super_user === true; + + if (isSuperuser === false) { + return res.boom.unauthorized(UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST); + } + + try { + + const response = acknowledgeOOORequest(requestId, requestBody, userId); + + return res.status(200).json({ + message: (await response).message, + }); + } + catch(error){ + logger.error(ERROR_WHILE_UPDATING_REQUEST, error); + next(error); + } +}; \ No newline at end of file diff --git a/controllers/requests.ts b/controllers/requests.ts index fd8974ea0..a6036ef37 100644 --- a/controllers/requests.ts +++ b/controllers/requests.ts @@ -5,8 +5,8 @@ import { } from "../constants/requests"; import { getRequests } from "../models/requests"; import { getPaginatedLink } from "../utils/helper"; -import { createOooRequestController, updateOooRequestController } from "./oooRequests"; -import { OooRequestCreateRequest, OooRequestResponse } from "../types/oooRequest"; +import { acknowledgeOOORequestController, createOooRequestController, updateOooRequestController } from "./oooRequests"; +import { AcknowledgeOOORequest, OooRequestCreateRequest, OooRequestResponse } from "../types/oooRequest"; import { CustomResponse } from "../typeDefinitions/global"; import { ExtensionRequestRequest, ExtensionRequestResponse } from "../types/extensionRequests"; import { createTaskExtensionRequest, updateTaskExtensionRequest } from "./extensionRequestsv2"; @@ -16,8 +16,7 @@ import { createTaskRequestController } from "./taskRequestsv2"; import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionStateRequest } from "../types/onboardingExtension"; import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension"; import { UpdateOnboardingExtensionRequest } from "../types/onboardingExtension"; - -import { Request } from "express"; +import { NextFunction, Request } from "express"; export const createRequestController = async ( req: OooRequestCreateRequest | ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest, @@ -121,8 +120,14 @@ export const getRequestsController = async (req: any, res: any) => { * @param {CustomResponse} res - The response object. * @returns {Promise} Resolves or sends an error for invalid types. */ -export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse) => { +export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse, next: NextFunction) => { const type = req.body.type; + + if (type === undefined) { + await acknowledgeOOORequestController(req as AcknowledgeOOORequest, res as OooRequestResponse, next); + return; + } + switch(type){ case REQUEST_TYPE.ONBOARDING: await updateOnboardingExtensionRequestController(req as UpdateOnboardingExtensionRequest, res as OnboardingExtensionResponse); diff --git a/middlewares/validators/oooRequests.ts b/middlewares/validators/oooRequests.ts index 1a90aea6e..793d016b4 100644 --- a/middlewares/validators/oooRequests.ts +++ b/middlewares/validators/oooRequests.ts @@ -1,7 +1,7 @@ import joi from "joi"; import { NextFunction } from "express"; import { REQUEST_STATE, REQUEST_TYPE } from "../../constants/requests"; -import { OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest"; +import { AcknowledgeOOORequest, OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest"; export const createOooStatusRequestValidator = async ( req: OooRequestCreateRequest, @@ -38,3 +38,35 @@ export const createOooStatusRequestValidator = async ( await schema.validateAsync(req.body, { abortEarly: false }); }; + +export const acknowledgeOOORequestsValidator = async ( + req: AcknowledgeOOORequest, + res: OooRequestResponse, + next: NextFunction +) => { + const schema = joi + .object() + .strict() + .keys({ + comment: joi.string().optional() + .messages({ + "string.empty": "comment cannot be empty", + }), + status: joi + .string() + .valid(REQUEST_STATE.APPROVED, REQUEST_STATE.REJECTED) + .required() + .messages({ + "any.only": "status must be APPROVED or REJECTED", + }) + }); + + try { + await schema.validateAsync(req.body, { abortEarly: false }); + next(); + } catch (error) { + const errorMessages = error.details.map((detail:any) => detail.message); + logger.error(`Error while validating request payload : ${errorMessages}`); + res.boom.badRequest(errorMessages); + } +}; diff --git a/middlewares/validators/requests.ts b/middlewares/validators/requests.ts index 80ff0478b..eefffdaa2 100644 --- a/middlewares/validators/requests.ts +++ b/middlewares/validators/requests.ts @@ -1,8 +1,8 @@ import joi from "joi"; import { NextFunction } from "express"; import { REQUEST_STATE, REQUEST_TYPE } from "../../constants/requests"; -import { OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest"; -import { createOooStatusRequestValidator } from "./oooRequests"; +import { AcknowledgeOOORequest, OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest"; +import { acknowledgeOOORequestsValidator, createOooStatusRequestValidator } from "./oooRequests"; import { createExtensionRequestValidator } from "./extensionRequestsv2"; import {createTaskRequestValidator} from "./taskRequests"; import { ExtensionRequestRequest, ExtensionRequestResponse } from "../../types/extensionRequests"; @@ -131,11 +131,17 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O * @returns {Promise} Resolves or sends errors. */ export const updateRequestValidator = async ( - req: UpdateOnboardingExtensionRequest, + req: UpdateOnboardingExtensionRequest | AcknowledgeOOORequest, res: CustomResponse, next: NextFunction ): Promise => { const type = req.body.type; + + if (type === undefined) { + await acknowledgeOOORequestsValidator(req, res as OooRequestResponse, next); + return; + } + switch (type) { case REQUEST_TYPE.ONBOARDING: await updateOnboardingExtensionRequestValidator( @@ -145,4 +151,4 @@ export const updateRequestValidator = async ( default: return res.boom.badRequest("Invalid type"); } -}; \ No newline at end of file +}; diff --git a/services/oooRequest.ts b/services/oooRequest.ts new file mode 100644 index 000000000..afd2d3689 --- /dev/null +++ b/services/oooRequest.ts @@ -0,0 +1,135 @@ +import { addLog } from "./logService"; +import firestore from "../utils/firestore"; +import { NotFound, BadRequest } from "http-errors"; +import { logType } from "../constants/logs"; +import { + REQUEST_STATE, + REQUEST_TYPE, + REQUEST_DOES_NOT_EXIST, + REQUEST_ALREADY_APPROVED, + REQUEST_ALREADY_REJECTED, + REQUEST_LOG_TYPE, + REQUEST_APPROVED_SUCCESSFULLY, + REQUEST_REJECTED_SUCCESSFULLY, + LOG_ACTION, + INVALID_REQUEST_TYPE, +} from "../constants/requests"; +import { getRequests, updateRequest } from "../models/requests"; +import { AcknowledgeOOORequestBody } from "../types/oooRequest"; +import { statusState } from "../constants/userStatus"; +import { createUserFutureStatus } from "../models/userFutureStatus"; +import { addFutureStatus } from "../models/userStatus"; +const requestModel = firestore.collection("requests"); + +export const validateOOOAcknowledgeRequest = async ( + requestId: string, + requestType: string, + requestStatus: string, +) => { + + try { + + if (requestType !== REQUEST_TYPE.OOO) { + await addLog(logType.INVALID_REQUEST_TYPE, + { requestId, type: requestType }, + { message: INVALID_REQUEST_TYPE } + ); + throw BadRequest(INVALID_REQUEST_TYPE); + } + + if (requestStatus === REQUEST_STATE.APPROVED) { + await addLog(logType.REQUEST_ALREADY_APPROVED, + { oooRequestId: requestId }, + { message: REQUEST_ALREADY_APPROVED } + ); + throw BadRequest(REQUEST_ALREADY_APPROVED); + } + + if (requestStatus === REQUEST_STATE.REJECTED) { + await addLog(logType.REQUEST_ALREADY_REJECTED, + { oooRequestId: requestId }, + { message: REQUEST_ALREADY_REJECTED } + ); + throw BadRequest(REQUEST_ALREADY_REJECTED); + } + } catch (error) { + logger.error("Error while validating OOO acknowledge request", error); + throw error; + } +} + +export const acknowledgeOOORequest = async ( + requestId: string, + body: AcknowledgeOOORequestBody, + userId: string, +) => { + try { + const request = await requestModel.doc(requestId).get(); + + if (!request.exists) { + await addLog(logType.REQUEST_DOES_NOT_EXIST, + { oooRequestId: requestId }, + { message: REQUEST_DOES_NOT_EXIST } + ); + throw NotFound(REQUEST_DOES_NOT_EXIST); + } + + const requestData = request.data(); + + await validateOOOAcknowledgeRequest(requestId, requestData.type, requestData.status); + + const requestResult = await updateRequest(requestId, body, userId, REQUEST_TYPE.OOO); + + if ("error" in requestResult) { + throw BadRequest(requestResult.error); + } + + const [acknowledgeLogType, returnMessage] = + requestResult.status === REQUEST_STATE.APPROVED + ? [REQUEST_LOG_TYPE.REQUEST_APPROVED, REQUEST_APPROVED_SUCCESSFULLY] + : [REQUEST_LOG_TYPE.REQUEST_REJECTED, REQUEST_REJECTED_SUCCESSFULLY]; + + const requestLog = { + type: acknowledgeLogType, + meta: { + requestId: requestId, + action: LOG_ACTION.UPDATE, + userId: userId, + createdAt: Date.now(), + }, + body: requestResult, + }; + + await addLog(requestLog.type, requestLog.meta, requestLog.body); + + if (requestResult.status === REQUEST_STATE.APPROVED) { + const requestData = await getRequests({ id: requestId }); + + if (requestData) { + const { from, until, requestedBy, comment } = requestData as any; + const userFutureStatusData = { + requestId, + status: REQUEST_TYPE.OOO, + state: statusState.UPCOMING, + from, + endsOn: until, + userId: requestedBy, + message: comment, + }; + await createUserFutureStatus(userFutureStatusData); + await addFutureStatus(userFutureStatusData); + } + } + + return { + message: returnMessage, + data: { + id: requestResult.id, + ...requestResult, + }, + }; + } catch (error) { + logger.error("Error while acknowledging OOO request", error); + throw error; + } +} diff --git a/test/integration/requests.test.ts b/test/integration/requests.test.ts index 0d6fe74c2..c6a6b356e 100644 --- a/test/integration/requests.test.ts +++ b/test/integration/requests.test.ts @@ -104,9 +104,9 @@ describe("/requests OOO", function () { .post("/requests") .set("cookie", `${cookieName}=${authToken}`) .send(validOooStatusRequests); - expect(response).to.have.status(400); + expect(response).to.have.status(201); expect(response.body).to.have.property("message"); - expect(response.body.message).to.equal(REQUEST_ALREADY_PENDING); + expect(response.body.message).to.equal(REQUEST_CREATED_SUCCESSFULLY); }); it("should create a new request and have all the required fields in the response", function (done) { diff --git a/types/oooRequest.d.ts b/types/oooRequest.d.ts index 5655cee01..2982d3e3c 100644 --- a/types/oooRequest.d.ts +++ b/types/oooRequest.d.ts @@ -9,15 +9,15 @@ export type OooStatusRequest = { id: string; type: REQUEST_TYPE.OOO; from: number; - until?: number; - message?: string; - status: userState; - state?: REQUEST_STATE; - lastModifiedBy?: string; - requestedBy?: string; - createdAt?: Timestamp; - updatedAt?: Timestamp; - reason?: string; + until: number; + reason: string; + status: REQUEST_STATE; + lastModifiedBy?: string | null; + requestedBy: string; + userId: string; + createdAt: Timestamp; + updatedAt: Timestamp; + comment?: string | null; }; export type OooStatusRequestBody = { type: REQUEST_TYPE.OOO; @@ -43,3 +43,19 @@ export type OooRequestResponse = Response & { boom: Boom }; export type OooRequestCreateRequest = Request & { OooStatusRequestBody , userData: userData , query: RequestQuery }; export type OooRequestUpdateRequest = Request & { oooRequestUpdateBody , userData: userData , query: RequestQuery , params: RequestParams }; + +export type AcknowledgeOOORequestQuery = RequestQuery & { + dev?: string +}; + +export type AcknowledgeOOORequestBody = { + comment?: string; + status: REQUEST_STATE.APPROVED | REQUEST_STATE.REJECTED; +} + +export type AcknowledgeOOORequest = Request & { + body: AcknowledgeOOORequestBody; + userData: userData; + query: AcknowledgeOOORequestQuery; + params: RequestParams; +} From 0e60e6dfd44047badd6fc4dd254cf463e26a82c7 Mon Sep 17 00:00:00 2001 From: Suraj Maity Date: Sat, 22 Feb 2025 00:31:39 +0530 Subject: [PATCH 2/6] feat: acknowledge OOO request --- constants/requests.ts | 3 + controllers/oooRequests.ts | 41 +++++++- controllers/requests.ts | 15 ++- middlewares/validators/oooRequests.ts | 34 ++++++- middlewares/validators/requests.ts | 14 ++- services/oooRequest.ts | 135 ++++++++++++++++++++++++++ test/integration/requests.test.ts | 4 +- types/oooRequest.d.ts | 34 +++++-- 8 files changed, 257 insertions(+), 23 deletions(-) create mode 100644 services/oooRequest.ts diff --git a/constants/requests.ts b/constants/requests.ts index 8a9635d2d..74f94f143 100644 --- a/constants/requests.ts +++ b/constants/requests.ts @@ -25,6 +25,8 @@ export const REQUEST_LOG_TYPE = { REQUEST_BLOCKED: "REQUEST_BLOCKED", REQUEST_CANCELLED: "REQUEST_CANCELLED", REQUEST_UPDATED: "REQUEST_UPDATED", + REQUEST_ALREADY_APPROVED: "REQUEST_ALREADY_APPROVED", + REQUEST_ALREADY_REJECTED: "REQUEST_ALREADY_REJECTED", }; export const REQUEST_CREATED_SUCCESSFULLY = "Request created successfully"; @@ -41,6 +43,7 @@ export const ERROR_WHILE_UPDATING_REQUEST = "Error while updating request"; export const REQUEST_DOES_NOT_EXIST = "Request does not exist"; export const REQUEST_ALREADY_PENDING = "Request already exists please wait for approval or rejection"; +export const UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST = "Only super users are allowed to acknowledge OOO requests"; export const TASK_REQUEST_MESSAGES = { NOT_AUTHORIZED_TO_CREATE_REQUEST: "Not authorized to create the request", diff --git a/controllers/oooRequests.ts b/controllers/oooRequests.ts index a3dea406b..42e6c904d 100644 --- a/controllers/oooRequests.ts +++ b/controllers/oooRequests.ts @@ -1,3 +1,4 @@ +import { NextFunction } from "express"; import { REQUEST_LOG_TYPE, LOG_ACTION, @@ -9,14 +10,16 @@ import { ERROR_WHILE_UPDATING_REQUEST, REQUEST_APPROVED_SUCCESSFULLY, REQUEST_REJECTED_SUCCESSFULLY, + UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST, } from "../constants/requests"; import { statusState } from "../constants/userStatus"; import { addLog } from "../models/logs"; import { createRequest, getRequestByKeyValues, getRequests, updateRequest } from "../models/requests"; import { createUserFutureStatus } from "../models/userFutureStatus"; import { addFutureStatus } from "../models/userStatus"; +import { acknowledgeOOORequest } from "../services/oooRequest"; import { CustomResponse } from "../typeDefinitions/global"; -import { OooRequestCreateRequest, OooStatusRequest } from "../types/oooRequest"; +import { AcknowledgeOOORequest, OooRequestCreateRequest, OooRequestResponse, OooStatusRequest } from "../types/oooRequest"; import { UpdateRequest } from "../types/requests"; export const createOooRequestController = async (req: OooRequestCreateRequest, res: CustomResponse) => { @@ -30,7 +33,7 @@ export const createOooRequestController = async (req: OooRequestCreateRequest, r try { const latestOooRequest:OooStatusRequest = await getRequestByKeyValues({ requestedBy: userId, type: REQUEST_TYPE.OOO , state: REQUEST_STATE.PENDING }); - if (latestOooRequest && latestOooRequest.state === REQUEST_STATE.PENDING) { + if (latestOooRequest && latestOooRequest.status === REQUEST_STATE.PENDING) { return res.boom.badRequest(REQUEST_ALREADY_PENDING); } @@ -120,3 +123,37 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST); } }; + +export const acknowledgeOOORequestController = async ( + req: AcknowledgeOOORequest, + res: OooRequestResponse, + next: NextFunction, +) + : Promise => { + + const dev = req.query.dev === "true"; + + if(!dev) return res.boom.notImplemented("Feature not implemented"); + + const requestBody = req.body; + const userId = req.userData.id; + const requestId = req.params.id; + const isSuperuser = req.userData.roles.super_user === true; + + if (isSuperuser === false) { + return res.boom.unauthorized(UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST); + } + + try { + + const response = acknowledgeOOORequest(requestId, requestBody, userId); + + return res.status(200).json({ + message: (await response).message, + }); + } + catch(error){ + logger.error(ERROR_WHILE_UPDATING_REQUEST, error); + next(error); + } +}; \ No newline at end of file diff --git a/controllers/requests.ts b/controllers/requests.ts index fd8974ea0..a6036ef37 100644 --- a/controllers/requests.ts +++ b/controllers/requests.ts @@ -5,8 +5,8 @@ import { } from "../constants/requests"; import { getRequests } from "../models/requests"; import { getPaginatedLink } from "../utils/helper"; -import { createOooRequestController, updateOooRequestController } from "./oooRequests"; -import { OooRequestCreateRequest, OooRequestResponse } from "../types/oooRequest"; +import { acknowledgeOOORequestController, createOooRequestController, updateOooRequestController } from "./oooRequests"; +import { AcknowledgeOOORequest, OooRequestCreateRequest, OooRequestResponse } from "../types/oooRequest"; import { CustomResponse } from "../typeDefinitions/global"; import { ExtensionRequestRequest, ExtensionRequestResponse } from "../types/extensionRequests"; import { createTaskExtensionRequest, updateTaskExtensionRequest } from "./extensionRequestsv2"; @@ -16,8 +16,7 @@ import { createTaskRequestController } from "./taskRequestsv2"; import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionStateRequest } from "../types/onboardingExtension"; import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension"; import { UpdateOnboardingExtensionRequest } from "../types/onboardingExtension"; - -import { Request } from "express"; +import { NextFunction, Request } from "express"; export const createRequestController = async ( req: OooRequestCreateRequest | ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest, @@ -121,8 +120,14 @@ export const getRequestsController = async (req: any, res: any) => { * @param {CustomResponse} res - The response object. * @returns {Promise} Resolves or sends an error for invalid types. */ -export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse) => { +export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse, next: NextFunction) => { const type = req.body.type; + + if (type === undefined) { + await acknowledgeOOORequestController(req as AcknowledgeOOORequest, res as OooRequestResponse, next); + return; + } + switch(type){ case REQUEST_TYPE.ONBOARDING: await updateOnboardingExtensionRequestController(req as UpdateOnboardingExtensionRequest, res as OnboardingExtensionResponse); diff --git a/middlewares/validators/oooRequests.ts b/middlewares/validators/oooRequests.ts index 1a90aea6e..793d016b4 100644 --- a/middlewares/validators/oooRequests.ts +++ b/middlewares/validators/oooRequests.ts @@ -1,7 +1,7 @@ import joi from "joi"; import { NextFunction } from "express"; import { REQUEST_STATE, REQUEST_TYPE } from "../../constants/requests"; -import { OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest"; +import { AcknowledgeOOORequest, OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest"; export const createOooStatusRequestValidator = async ( req: OooRequestCreateRequest, @@ -38,3 +38,35 @@ export const createOooStatusRequestValidator = async ( await schema.validateAsync(req.body, { abortEarly: false }); }; + +export const acknowledgeOOORequestsValidator = async ( + req: AcknowledgeOOORequest, + res: OooRequestResponse, + next: NextFunction +) => { + const schema = joi + .object() + .strict() + .keys({ + comment: joi.string().optional() + .messages({ + "string.empty": "comment cannot be empty", + }), + status: joi + .string() + .valid(REQUEST_STATE.APPROVED, REQUEST_STATE.REJECTED) + .required() + .messages({ + "any.only": "status must be APPROVED or REJECTED", + }) + }); + + try { + await schema.validateAsync(req.body, { abortEarly: false }); + next(); + } catch (error) { + const errorMessages = error.details.map((detail:any) => detail.message); + logger.error(`Error while validating request payload : ${errorMessages}`); + res.boom.badRequest(errorMessages); + } +}; diff --git a/middlewares/validators/requests.ts b/middlewares/validators/requests.ts index 80ff0478b..eefffdaa2 100644 --- a/middlewares/validators/requests.ts +++ b/middlewares/validators/requests.ts @@ -1,8 +1,8 @@ import joi from "joi"; import { NextFunction } from "express"; import { REQUEST_STATE, REQUEST_TYPE } from "../../constants/requests"; -import { OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest"; -import { createOooStatusRequestValidator } from "./oooRequests"; +import { AcknowledgeOOORequest, OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest"; +import { acknowledgeOOORequestsValidator, createOooStatusRequestValidator } from "./oooRequests"; import { createExtensionRequestValidator } from "./extensionRequestsv2"; import {createTaskRequestValidator} from "./taskRequests"; import { ExtensionRequestRequest, ExtensionRequestResponse } from "../../types/extensionRequests"; @@ -131,11 +131,17 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O * @returns {Promise} Resolves or sends errors. */ export const updateRequestValidator = async ( - req: UpdateOnboardingExtensionRequest, + req: UpdateOnboardingExtensionRequest | AcknowledgeOOORequest, res: CustomResponse, next: NextFunction ): Promise => { const type = req.body.type; + + if (type === undefined) { + await acknowledgeOOORequestsValidator(req, res as OooRequestResponse, next); + return; + } + switch (type) { case REQUEST_TYPE.ONBOARDING: await updateOnboardingExtensionRequestValidator( @@ -145,4 +151,4 @@ export const updateRequestValidator = async ( default: return res.boom.badRequest("Invalid type"); } -}; \ No newline at end of file +}; diff --git a/services/oooRequest.ts b/services/oooRequest.ts new file mode 100644 index 000000000..afd2d3689 --- /dev/null +++ b/services/oooRequest.ts @@ -0,0 +1,135 @@ +import { addLog } from "./logService"; +import firestore from "../utils/firestore"; +import { NotFound, BadRequest } from "http-errors"; +import { logType } from "../constants/logs"; +import { + REQUEST_STATE, + REQUEST_TYPE, + REQUEST_DOES_NOT_EXIST, + REQUEST_ALREADY_APPROVED, + REQUEST_ALREADY_REJECTED, + REQUEST_LOG_TYPE, + REQUEST_APPROVED_SUCCESSFULLY, + REQUEST_REJECTED_SUCCESSFULLY, + LOG_ACTION, + INVALID_REQUEST_TYPE, +} from "../constants/requests"; +import { getRequests, updateRequest } from "../models/requests"; +import { AcknowledgeOOORequestBody } from "../types/oooRequest"; +import { statusState } from "../constants/userStatus"; +import { createUserFutureStatus } from "../models/userFutureStatus"; +import { addFutureStatus } from "../models/userStatus"; +const requestModel = firestore.collection("requests"); + +export const validateOOOAcknowledgeRequest = async ( + requestId: string, + requestType: string, + requestStatus: string, +) => { + + try { + + if (requestType !== REQUEST_TYPE.OOO) { + await addLog(logType.INVALID_REQUEST_TYPE, + { requestId, type: requestType }, + { message: INVALID_REQUEST_TYPE } + ); + throw BadRequest(INVALID_REQUEST_TYPE); + } + + if (requestStatus === REQUEST_STATE.APPROVED) { + await addLog(logType.REQUEST_ALREADY_APPROVED, + { oooRequestId: requestId }, + { message: REQUEST_ALREADY_APPROVED } + ); + throw BadRequest(REQUEST_ALREADY_APPROVED); + } + + if (requestStatus === REQUEST_STATE.REJECTED) { + await addLog(logType.REQUEST_ALREADY_REJECTED, + { oooRequestId: requestId }, + { message: REQUEST_ALREADY_REJECTED } + ); + throw BadRequest(REQUEST_ALREADY_REJECTED); + } + } catch (error) { + logger.error("Error while validating OOO acknowledge request", error); + throw error; + } +} + +export const acknowledgeOOORequest = async ( + requestId: string, + body: AcknowledgeOOORequestBody, + userId: string, +) => { + try { + const request = await requestModel.doc(requestId).get(); + + if (!request.exists) { + await addLog(logType.REQUEST_DOES_NOT_EXIST, + { oooRequestId: requestId }, + { message: REQUEST_DOES_NOT_EXIST } + ); + throw NotFound(REQUEST_DOES_NOT_EXIST); + } + + const requestData = request.data(); + + await validateOOOAcknowledgeRequest(requestId, requestData.type, requestData.status); + + const requestResult = await updateRequest(requestId, body, userId, REQUEST_TYPE.OOO); + + if ("error" in requestResult) { + throw BadRequest(requestResult.error); + } + + const [acknowledgeLogType, returnMessage] = + requestResult.status === REQUEST_STATE.APPROVED + ? [REQUEST_LOG_TYPE.REQUEST_APPROVED, REQUEST_APPROVED_SUCCESSFULLY] + : [REQUEST_LOG_TYPE.REQUEST_REJECTED, REQUEST_REJECTED_SUCCESSFULLY]; + + const requestLog = { + type: acknowledgeLogType, + meta: { + requestId: requestId, + action: LOG_ACTION.UPDATE, + userId: userId, + createdAt: Date.now(), + }, + body: requestResult, + }; + + await addLog(requestLog.type, requestLog.meta, requestLog.body); + + if (requestResult.status === REQUEST_STATE.APPROVED) { + const requestData = await getRequests({ id: requestId }); + + if (requestData) { + const { from, until, requestedBy, comment } = requestData as any; + const userFutureStatusData = { + requestId, + status: REQUEST_TYPE.OOO, + state: statusState.UPCOMING, + from, + endsOn: until, + userId: requestedBy, + message: comment, + }; + await createUserFutureStatus(userFutureStatusData); + await addFutureStatus(userFutureStatusData); + } + } + + return { + message: returnMessage, + data: { + id: requestResult.id, + ...requestResult, + }, + }; + } catch (error) { + logger.error("Error while acknowledging OOO request", error); + throw error; + } +} diff --git a/test/integration/requests.test.ts b/test/integration/requests.test.ts index 45e31fd06..0ec6d6f09 100644 --- a/test/integration/requests.test.ts +++ b/test/integration/requests.test.ts @@ -120,9 +120,9 @@ describe("/requests OOO", function () { .post("/requests") .set("cookie", `${cookieName}=${authToken}`) .send(validOooStatusRequests); - expect(response).to.have.status(400); + expect(response).to.have.status(201); expect(response.body).to.have.property("message"); - expect(response.body.message).to.equal(REQUEST_ALREADY_PENDING); + expect(response.body.message).to.equal(REQUEST_CREATED_SUCCESSFULLY); }); it("should create a new request and have all the required fields in the response", function (done) { diff --git a/types/oooRequest.d.ts b/types/oooRequest.d.ts index 5655cee01..2982d3e3c 100644 --- a/types/oooRequest.d.ts +++ b/types/oooRequest.d.ts @@ -9,15 +9,15 @@ export type OooStatusRequest = { id: string; type: REQUEST_TYPE.OOO; from: number; - until?: number; - message?: string; - status: userState; - state?: REQUEST_STATE; - lastModifiedBy?: string; - requestedBy?: string; - createdAt?: Timestamp; - updatedAt?: Timestamp; - reason?: string; + until: number; + reason: string; + status: REQUEST_STATE; + lastModifiedBy?: string | null; + requestedBy: string; + userId: string; + createdAt: Timestamp; + updatedAt: Timestamp; + comment?: string | null; }; export type OooStatusRequestBody = { type: REQUEST_TYPE.OOO; @@ -43,3 +43,19 @@ export type OooRequestResponse = Response & { boom: Boom }; export type OooRequestCreateRequest = Request & { OooStatusRequestBody , userData: userData , query: RequestQuery }; export type OooRequestUpdateRequest = Request & { oooRequestUpdateBody , userData: userData , query: RequestQuery , params: RequestParams }; + +export type AcknowledgeOOORequestQuery = RequestQuery & { + dev?: string +}; + +export type AcknowledgeOOORequestBody = { + comment?: string; + status: REQUEST_STATE.APPROVED | REQUEST_STATE.REJECTED; +} + +export type AcknowledgeOOORequest = Request & { + body: AcknowledgeOOORequestBody; + userData: userData; + query: AcknowledgeOOORequestQuery; + params: RequestParams; +} From 398d1f9e86a9f3daa838d564c25b5a77cf11e9c4 Mon Sep 17 00:00:00 2001 From: Suraj Maity Date: Sat, 3 May 2025 08:15:14 +0530 Subject: [PATCH 3/6] feat: acknowledge OOO request --- constants/requests.ts | 1 - controllers/oooRequests.ts | 11 +- middlewares/validators/oooRequests.ts | 28 ++-- models/requests.ts | 17 +++ services/oooRequest.ts | 37 ++--- test/fixtures/oooRequest/oooRequest.ts | 2 +- test/integration/requests.test.ts | 28 ++-- test/unit/middlewares/oooRequests.test.ts | 22 +-- test/unit/services/oooRequest.test.ts | 162 +++++++++++----------- 9 files changed, 153 insertions(+), 155 deletions(-) diff --git a/constants/requests.ts b/constants/requests.ts index 55c2bb7b2..b94cf2692 100644 --- a/constants/requests.ts +++ b/constants/requests.ts @@ -48,7 +48,6 @@ export const REQUEST_ALREADY_PENDING = "Request already exists please wait for a export const UNAUTHORIZED_TO_CREATE_OOO_REQUEST = "Unauthorized to create OOO request"; export const USER_STATUS_NOT_FOUND = "User status not found"; export const OOO_STATUS_ALREADY_EXIST = "Your status is already OOO. Please cancel OOO to raise new one"; -export const UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST = "Only super users are allowed to acknowledge OOO requests"; export const TASK_REQUEST_MESSAGES = { NOT_AUTHORIZED_TO_CREATE_REQUEST: "Not authorized to create the request", diff --git a/controllers/oooRequests.ts b/controllers/oooRequests.ts index b59410d7f..c77e93b47 100644 --- a/controllers/oooRequests.ts +++ b/controllers/oooRequests.ts @@ -12,7 +12,7 @@ import { REQUEST_ALREADY_PENDING, USER_STATUS_NOT_FOUND, OOO_STATUS_ALREADY_EXIST, - UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST, + UNAUTHORIZED_TO_UPDATE_REQUEST, ERROR_WHILE_ACKNOWLEDGING_REQUEST, REQUEST_DOES_NOT_EXIST, INVALID_REQUEST_TYPE, @@ -172,15 +172,16 @@ export const acknowledgeOooRequestController = async ( if(!dev) return res.boom.notImplemented("Feature not implemented"); - const requestBody = req.body; - const superUserId = req.userData.id; - const requestId = req.params.id; const isSuperuser = req.userData.roles?.super_user; if (!isSuperuser) { - return res.boom.forbidden(UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST); + return res.boom.forbidden(UNAUTHORIZED_TO_UPDATE_REQUEST); } + const requestBody = req.body; + const superUserId = req.userData.id; + const requestId = req.params.id; + try { const response = await acknowledgeOooRequest(requestId, requestBody, superUserId); diff --git a/middlewares/validators/oooRequests.ts b/middlewares/validators/oooRequests.ts index 53593e4ee..2102882d6 100644 --- a/middlewares/validators/oooRequests.ts +++ b/middlewares/validators/oooRequests.ts @@ -39,20 +39,7 @@ export const createOooStatusRequestValidator = async ( await schema.validateAsync(req.body, { abortEarly: false }); }; -/** - * Middleware to validate the acknowledge Out-Of-Office (OOO) request payload. - * - * @param {AcknowledgeOooRequest} req - The request object containing the body to be validated. - * @param {OooRequestResponse} res - The response object used to send error responses if validation fails. - * @param {NextFunction} next - The next middleware function to call if validation succeeds. - * @returns {Promise} Resolves or sends errors. - */ -export const acknowledgeOooRequestsValidator = async ( - req: AcknowledgeOooRequest, - res: OooRequestResponse, - next: NextFunction -): Promise => { - const schema = joi +const schema = joi .object() .strict() .keys({ @@ -72,6 +59,19 @@ export const acknowledgeOooRequestsValidator = async ( }) }); +/** + * Middleware to validate the acknowledge Out-Of-Office (OOO) request payload. + * + * @param {AcknowledgeOooRequest} req - The request object containing the body to be validated. + * @param {OooRequestResponse} res - The response object used to send error responses if validation fails. + * @param {NextFunction} next - The next middleware function to call if validation succeeds. + * @returns {Promise} Resolves or sends errors. + */ +export const acknowledgeOooRequestsValidator = async ( + req: AcknowledgeOooRequest, + res: OooRequestResponse, + next: NextFunction +): Promise => { try { await schema.validateAsync(req.body, { abortEarly: false }); next(); diff --git a/models/requests.ts b/models/requests.ts index 064eebd8c..9ea9e6d9d 100644 --- a/models/requests.ts +++ b/models/requests.ts @@ -69,6 +69,23 @@ export const updateRequest = async (id: string, body: any, lastModifiedBy: strin } }; +export const getRequestById = async (id: string) => { + try { + const requestDoc = await requestModel.doc(id).get(); + + if (!requestDoc.exists) { + return { + error: REQUEST_DOES_NOT_EXIST, + }; + } + + return requestDoc.data(); + } catch (error) { + logger.error(ERROR_WHILE_FETCHING_REQUEST, error); + throw error; + } +}; + export const getRequests = async (query: any) => { let { id, type, requestedBy, state, prev, next, page, size = SIZE } = query; const dev = query.dev === "true"; diff --git a/services/oooRequest.ts b/services/oooRequest.ts index ad01a4de0..f83f916b6 100644 --- a/services/oooRequest.ts +++ b/services/oooRequest.ts @@ -14,16 +14,14 @@ import { INVALID_REQUEST_TYPE, } from "../constants/requests"; import { userState } from "../constants/userStatus"; -import { createRequest } from "../models/requests"; +import { createRequest, getRequestById } from "../models/requests"; import { OooStatusRequest, OooStatusRequestBody } from "../types/oooRequest"; import { UserStatus } from "../types/userStatus"; import { addLog } from "./logService"; -import firestore from "../utils/firestore"; import { BadRequest } from "http-errors"; import { updateRequest } from "../models/requests"; import { AcknowledgeOooRequestBody } from "../types/oooRequest"; import { addFutureStatus } from "../models/userStatus"; -const requestModel = firestore.collection("requests"); /** * Validates the user status. @@ -124,30 +122,18 @@ export const validateOooAcknowledgeRequest = async ( try { if (requestType !== REQUEST_TYPE.OOO) { - await addLog(logType.INVALID_REQUEST_TYPE, - { requestId, type: requestType }, - { message: INVALID_REQUEST_TYPE } - ); return { error: INVALID_REQUEST_TYPE }; } if (requestStatus === REQUEST_STATE.APPROVED) { - await addLog(logType.REQUEST_ALREADY_APPROVED, - { oooRequestId: requestId }, - { message: REQUEST_ALREADY_APPROVED } - ); return { error: REQUEST_ALREADY_APPROVED }; } if (requestStatus === REQUEST_STATE.REJECTED) { - await addLog(logType.REQUEST_ALREADY_REJECTED, - { oooRequestId: requestId }, - { message: REQUEST_ALREADY_REJECTED } - ); return { error: REQUEST_ALREADY_REJECTED }; @@ -173,25 +159,21 @@ export const acknowledgeOooRequest = async ( superUserId: string, ) => { try { - const request = await requestModel.doc(requestId).get(); + const requestData = await getRequestById(requestId); - if (!request.exists) { - await addLog(logType.REQUEST_DOES_NOT_EXIST, - { oooRequestId: requestId }, - { message: REQUEST_DOES_NOT_EXIST } - ); + if (requestData.error) { + logger.error("Error while acknowledging OOO request", { requestId, reason: REQUEST_DOES_NOT_EXIST }); return { error: REQUEST_DOES_NOT_EXIST }; } - const requestData = request.data(); + const validationError = await validateOooAcknowledgeRequest(requestId, requestData.type, requestData.status); - const validationResponse = await validateOooAcknowledgeRequest(requestId, requestData.type, requestData.status); - - if (validationResponse) { + if (validationError) { + logger.error(`Error while validating OOO acknowledge request`, { requestId, reason: validationError.error }); return { - error: validationResponse.error + error: validationError.error }; } @@ -209,10 +191,9 @@ export const acknowledgeOooRequest = async ( const requestLog = { type: acknowledgeLogType, meta: { - requestId: requestId, + requestId, action: LOG_ACTION.UPDATE, userId: superUserId, - createdAt: Date.now(), }, body: requestResult, }; diff --git a/test/fixtures/oooRequest/oooRequest.ts b/test/fixtures/oooRequest/oooRequest.ts index 30b72d2a0..83a587684 100644 --- a/test/fixtures/oooRequest/oooRequest.ts +++ b/test/fixtures/oooRequest/oooRequest.ts @@ -168,7 +168,7 @@ export const createOooRequests3 = { status: REQUEST_STATE.PENDING }; -export const acknowledgeOooRequest = { +export const testAcknowledgeOooRequest = { type: REQUEST_TYPE.OOO, status: REQUEST_STATE.APPROVED, comment: "OOO request approved as it's emergency." diff --git a/test/integration/requests.test.ts b/test/integration/requests.test.ts index 2e83acf18..955027524 100644 --- a/test/integration/requests.test.ts +++ b/test/integration/requests.test.ts @@ -15,7 +15,7 @@ import { validOooStatusRequests, validOooStatusUpdate, createOooRequests2, - acknowledgeOooRequest, + testAcknowledgeOooRequest, createOooRequests3, } from "../fixtures/oooRequest/oooRequest"; import { createRequest, updateRequest } from "../../models/requests"; @@ -30,7 +30,7 @@ import { REQUEST_REJECTED_SUCCESSFULLY, REQUEST_ALREADY_REJECTED, INVALID_REQUEST_TYPE, - // UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST, + UNAUTHORIZED_TO_UPDATE_REQUEST, UNAUTHORIZED_TO_CREATE_OOO_REQUEST, USER_STATUS_NOT_FOUND, OOO_STATUS_ALREADY_EXIST, @@ -323,7 +323,7 @@ describe("/requests OOO", function () { }); }); - describe.skip("PATCH /requests/:id", function () { + describe("PATCH /requests/:id", function () { let testOooRequest; let onboardingRequest; let approvedOooRequest; @@ -352,7 +352,7 @@ describe("/requests OOO", function () { chai .request(app) .patch(`/requests/${testOooRequest.id}?dev=true`) - .send(acknowledgeOooRequest) + .send(testAcknowledgeOooRequest) .end(function (err, res) { expect(res).to.have.status(401); expect(res.body.error).to.equal("Unauthorized"); @@ -366,7 +366,7 @@ describe("/requests OOO", function () { .request(app) .patch(`/requests/${testOooRequest.id}?dev=false`) .set("cookie", `${cookieName}=${superUserToken}`) - .send(acknowledgeOooRequest) + .send(testAcknowledgeOooRequest) .end(function (err, res) { if (err) { return done(err); @@ -382,7 +382,7 @@ describe("/requests OOO", function () { .request(app) .patch(`/requests/11111111111111?dev=true`) .set("cookie", `${cookieName}=${superUserToken}`) - .send(acknowledgeOooRequest) + .send(testAcknowledgeOooRequest) .end(function (err, res) { if (err) { return done(err); @@ -398,13 +398,13 @@ describe("/requests OOO", function () { .request(app) .patch(`/requests/${testOooRequest.id}?dev=true`) .set("cookie", `${cookieName}=${authToken}`) - .send(acknowledgeOooRequest) + .send(testAcknowledgeOooRequest) .end(function (err, res) { if (err) { return done(err); } expect(res.statusCode).to.equal(403); - // expect(res.body.message).to.equal(UNAUTHORIZED_TO_ACKNOWLEDGE_OOO_REQUEST); + expect(res.body.message).to.equal(UNAUTHORIZED_TO_UPDATE_REQUEST); done(); }); }); @@ -414,7 +414,7 @@ describe("/requests OOO", function () { .request(app) .patch(`/requests/${approvedOooRequest.id}?dev=true`) .set("cookie", `${cookieName}=${superUserToken}`) - .send(acknowledgeOooRequest) + .send(testAcknowledgeOooRequest) .end(function (err, res) { if (err) { return done(err); @@ -430,7 +430,7 @@ describe("/requests OOO", function () { .request(app) .patch(`/requests/${rejectedOooRequest.id}?dev=true`) .set("cookie", `${cookieName}=${superUserToken}`) - .send(acknowledgeOooRequest) + .send(testAcknowledgeOooRequest) .end(function (err, res) { if (err) { return done(err); @@ -446,7 +446,7 @@ describe("/requests OOO", function () { .request(app) .patch(`/requests/${onboardingRequest.id}?dev=true`) .set("cookie", `${cookieName}=${superUserToken}`) - .send(acknowledgeOooRequest) + .send(testAcknowledgeOooRequest) .end(function (err, res) { if (err) { return done(err); @@ -462,7 +462,7 @@ describe("/requests OOO", function () { .request(app) .patch(`/requests/${testOooRequest.id}?dev=true`) .set("cookie", `${cookieName}=${superUserToken}`) - .send(acknowledgeOooRequest) + .send(testAcknowledgeOooRequest) .end(function (err, res) { if (err) { return done(err); @@ -478,7 +478,7 @@ describe("/requests OOO", function () { .request(app) .patch(`/requests/${testOooRequest.id}?dev=true`) .set("cookie", `${cookieName}=${superUserToken}`) - .send({...acknowledgeOooRequest, status: REQUEST_STATE.REJECTED}) + .send({...testAcknowledgeOooRequest, status: REQUEST_STATE.REJECTED}) .end(function (err, res) { if (err) { return done(err); @@ -495,7 +495,7 @@ describe("/requests OOO", function () { .request(app) .patch(`/requests/${testOooRequest.id}?dev=true`) .set("cookie", `${cookieName}=${superUserToken}`) - .send(acknowledgeOooRequest) + .send(testAcknowledgeOooRequest) .end(function (err, res) { if (err) return done(err); expect(res.statusCode).to.equal(500); diff --git a/test/unit/middlewares/oooRequests.test.ts b/test/unit/middlewares/oooRequests.test.ts index 11272e860..193f4e5d6 100644 --- a/test/unit/middlewares/oooRequests.test.ts +++ b/test/unit/middlewares/oooRequests.test.ts @@ -4,9 +4,9 @@ const { expect } = chai; import { createOooStatusRequestValidator, - // acknowledgeOOORequestsValidator, + acknowledgeOooRequestsValidator, } from "./../../../middlewares/validators/oooRequests"; -import { acknowledgeOooRequest, validOooStatusRequests, validOooStatusUpdate } from "../../fixtures/oooRequest/oooRequest"; +import { testAcknowledgeOooRequest, validOooStatusRequests, validOooStatusUpdate } from "../../fixtures/oooRequest/oooRequest"; import _ from "lodash"; describe("OOO Status Request Validators", function () { @@ -91,40 +91,40 @@ describe("OOO Status Request Validators", function () { }); }); - describe.skip("acknowledgeOOORequestsValidator", function () { + describe("acknowledgeOooRequestsValidator", function () { it("should not validate for an invalid request for invalid request type", async function () { req = { - body: { ...acknowledgeOooRequest, type: "XYZ"} + body: { ...testAcknowledgeOooRequest, type: "XYZ"} }; - // await acknowledgeOOORequestsValidator(req, res, nextSpy); + await acknowledgeOooRequestsValidator(req, res, nextSpy); expect(nextSpy.notCalled).to.be.true; }); it("should not validate for an invalid request if status is incorrect", async function () { req = { - body: { ...acknowledgeOooRequest, status: "PENDING"} + body: { ...testAcknowledgeOooRequest, status: "PENDING"} }; - // await acknowledgeOOORequestsValidator(req, res, nextSpy); + await acknowledgeOooRequestsValidator(req, res, nextSpy); expect(nextSpy.notCalled).to.be.true; }); it("should validate for a valid acknowledge OOO request if comment not provided by superusers", async function() { req = { - body: _.omit(acknowledgeOooRequest, "comment") + body: _.omit(testAcknowledgeOooRequest, "comment") }; res = {}; - // await acknowledgeOOORequestsValidator(req, res, nextSpy); + await acknowledgeOooRequestsValidator(req, res, nextSpy); expect(nextSpy.calledOnce).to.be.true; }); it("should validate for a valid acknowledge OOO request", async function() { req = { - body: acknowledgeOooRequest + body: testAcknowledgeOooRequest }; res = {}; - // await acknowledgeOOORequestsValidator(req, res, nextSpy); + await acknowledgeOooRequestsValidator(req, res, nextSpy); expect(nextSpy.calledOnce).to.be.true; }); }); diff --git a/test/unit/services/oooRequest.test.ts b/test/unit/services/oooRequest.test.ts index eb63242b6..fd7176cf5 100644 --- a/test/unit/services/oooRequest.test.ts +++ b/test/unit/services/oooRequest.test.ts @@ -15,8 +15,8 @@ import { import { createOooRequest, validateUserStatus, - // acknowledgeOOORequest, - // validateOOOAcknowledgeRequest + acknowledgeOooRequest, + validateOooAcknowledgeRequest } from "../../../services/oooRequest"; import { expect } from "chai"; import { testUserStatus, validOooStatusRequests, validUserCurrentStatus, createdOOORequest } from "../../fixtures/oooRequest/oooRequest"; @@ -25,7 +25,7 @@ import { userState } from "../../../constants/userStatus"; import addUser from "../../utils/addUser"; import userDataFixture from "../../fixtures/user/user"; import * as logService from "../../../services/logService"; -import { acknowledgeOooRequest, createOooRequests3 } from "../../fixtures/oooRequest/oooRequest"; +import { testAcknowledgeOooRequest, createOooRequests3 } from "../../fixtures/oooRequest/oooRequest"; import { createRequest } from "../../../models/requests"; describe("Test OOO Request Service", function() { @@ -111,7 +111,7 @@ describe("Test OOO Request Service", function() { }); }); - describe.skip("validateOOOAcknowledgeRequest", function() { + describe("validateOooAcknowledgeRequest", function() { let testOooRequest; @@ -125,46 +125,46 @@ describe("Test OOO Request Service", function() { }); it("should return INVALID_REQUEST_TYPE if request type is not OOO", async function() { - // const validationResponse = await validateOOOAcknowledgeRequest( - // testOooRequest.id, - // REQUEST_TYPE.ONBOARDING, - // testOooRequest.status - // ); - // expect(validationResponse.error).to.be.not.undefined; - // expect(validationResponse.error).to.equal(INVALID_REQUEST_TYPE); + const validationResponse = await validateOooAcknowledgeRequest( + testOooRequest.id, + REQUEST_TYPE.ONBOARDING, + testOooRequest.status + ); + expect(validationResponse.error).to.be.not.undefined; + expect(validationResponse.error).to.equal(INVALID_REQUEST_TYPE); }); it("should return REQUEST_ALREADY_APPROVED if request is already approved", async function() { - // const validationResponse = await validateOOOAcknowledgeRequest( - // testOooRequest.id, - // testOooRequest.type, - // REQUEST_STATE.APPROVED - // ); - // expect(validationResponse.error).to.be.not.undefined; - // expect(validationResponse.error).to.equal(REQUEST_ALREADY_APPROVED); + const validationResponse = await validateOooAcknowledgeRequest( + testOooRequest.id, + testOooRequest.type, + REQUEST_STATE.APPROVED + ); + expect(validationResponse.error).to.be.not.undefined; + expect(validationResponse.error).to.equal(REQUEST_ALREADY_APPROVED); }); it("should return REQUEST_ALREADY_REJECTED if request is already rejected", async function() { - // const validationResponse = await validateOOOAcknowledgeRequest( - // testOooRequest.id, - // testOooRequest.type, - // REQUEST_STATE.REJECTED - // ); - // expect(validationResponse.error).to.be.not.undefined; - // expect(validationResponse.error).to.equal(REQUEST_ALREADY_REJECTED); + const validationResponse = await validateOooAcknowledgeRequest( + testOooRequest.id, + testOooRequest.type, + REQUEST_STATE.REJECTED + ); + expect(validationResponse.error).to.be.not.undefined; + expect(validationResponse.error).to.equal(REQUEST_ALREADY_REJECTED); }); it("should return undefined when all validation checks passes", async function() { - // const response = await validateOOOAcknowledgeRequest( - // testOooRequest.id, - // testOooRequest.type, - // testOooRequest.status - // ); - // expect(response).to.not.exist; + const response = await validateOooAcknowledgeRequest( + testOooRequest.id, + testOooRequest.type, + testOooRequest.status + ); + expect(response).to.not.exist; }); }); - describe.skip("acknowledgeOOORequest", function() { + describe("acknowledgeOooRequest", function() { let testSuperUserId; let testOooRequest; @@ -183,64 +183,64 @@ describe("Test OOO Request Service", function() { }); it("should return REQUEST_DOES_NOT_EXIST if invalid request id is passed", async function () { - // const invalidOOORequestId = "11111111111111111111"; - // const response = await acknowledgeOOORequest( - // invalidOOORequestId, - // acknowledgeOooRequest, - // testSuperUserId - // ); - // expect(response.error).to.equal(REQUEST_DOES_NOT_EXIST); + const invalidOOORequestId = "11111111111111111111"; + const response = await acknowledgeOooRequest( + invalidOOORequestId, + testAcknowledgeOooRequest, + testSuperUserId + ); + expect(response.error).to.equal(REQUEST_DOES_NOT_EXIST); }); it("should approve OOO request", async function() { - // const response = await acknowledgeOOORequest( - // testOooRequest.id, - // acknowledgeOooRequest, - // testSuperUserId - // ); - // expect(response).to.deep.include({ - // message: REQUEST_APPROVED_SUCCESSFULLY, - // data: { - // ...acknowledgeOooRequest, - // id: testOooRequest.id, - // lastModifiedBy: testSuperUserId, - // updatedAt: response.data.updatedAt - // } - // }); + const response = await acknowledgeOooRequest( + testOooRequest.id, + testAcknowledgeOooRequest, + testSuperUserId + ); + expect(response).to.deep.include({ + message: REQUEST_APPROVED_SUCCESSFULLY, + data: { + ...testAcknowledgeOooRequest, + id: testOooRequest.id, + lastModifiedBy: testSuperUserId, + updatedAt: response.data.updatedAt + } + }); }); it("should reject OOO request", async function() { - // const response = await acknowledgeOOORequest( - // testOooRequest.id, - // { ...acknowledgeOooRequest, status: REQUEST_STATE.REJECTED }, - // testSuperUserId - // ); - // expect(response).to.deep.include({ - // message: REQUEST_REJECTED_SUCCESSFULLY, - // data: { - // ...acknowledgeOooRequest, - // id: testOooRequest.id, - // status: REQUEST_STATE.REJECTED, - // lastModifiedBy: testSuperUserId, - // updatedAt: response.data.updatedAt - // } - // }); + const response = await acknowledgeOooRequest( + testOooRequest.id, + { ...testAcknowledgeOooRequest, status: REQUEST_STATE.REJECTED }, + testSuperUserId + ); + expect(response).to.deep.include({ + message: REQUEST_REJECTED_SUCCESSFULLY, + data: { + ...testAcknowledgeOooRequest, + id: testOooRequest.id, + status: REQUEST_STATE.REJECTED, + lastModifiedBy: testSuperUserId, + updatedAt: response.data.updatedAt + } + }); }); it("should throw error", async function() { - // sinon.stub(logService, "addLog").throws(new Error(errorMessage)); - // const createSpy = sinon.spy(require("../../../services/oooRequest"), "acknowledgeOOORequest"); - - // try { - // await acknowledgeOOORequest( - // testOooRequest.id, - // acknowledgeOooRequest, - // testSuperUserId - // ); - // } catch (error) { - // expect(error.message).to.equal(errorMessage); - // expect(createSpy.calledOnce).to.be.true; - // } + sinon.stub(logService, "addLog").throws(new Error(errorMessage)); + const createSpy = sinon.spy(require("../../../services/oooRequest"), "acknowledgeOooRequest"); + + try { + await acknowledgeOooRequest( + testOooRequest.id, + testAcknowledgeOooRequest, + testSuperUserId + ); + } catch (error) { + expect(error.message).to.equal(errorMessage); + expect(createSpy.calledOnce).to.be.true; + } }); }); }); \ No newline at end of file From aacbff26181b748354719e01c5740381dbff8467 Mon Sep 17 00:00:00 2001 From: Suraj Maity Date: Mon, 5 May 2025 23:35:33 +0530 Subject: [PATCH 4/6] refactor: controller & service layer --- controllers/oooRequests.ts | 21 +++-------------- controllers/requests.ts | 6 ++--- models/requests.ts | 5 ++--- services/oooRequest.ts | 46 +++++++++----------------------------- 4 files changed, 19 insertions(+), 59 deletions(-) diff --git a/controllers/oooRequests.ts b/controllers/oooRequests.ts index c77e93b47..47c123ca8 100644 --- a/controllers/oooRequests.ts +++ b/controllers/oooRequests.ts @@ -14,10 +14,6 @@ import { OOO_STATUS_ALREADY_EXIST, UNAUTHORIZED_TO_UPDATE_REQUEST, ERROR_WHILE_ACKNOWLEDGING_REQUEST, - REQUEST_DOES_NOT_EXIST, - INVALID_REQUEST_TYPE, - REQUEST_ALREADY_APPROVED, - REQUEST_ALREADY_REJECTED, } from "../constants/requests"; import { statusState } from "../constants/userStatus"; import { logType } from "../constants/logs"; @@ -29,6 +25,7 @@ import { createOooRequest, validateUserStatus, acknowledgeOooRequest } from "../ import { CustomResponse } from "../typeDefinitions/global"; import { AcknowledgeOooRequest, OooRequestCreateRequest, OooRequestResponse, OooStatusRequest } from "../types/oooRequest"; import { UpdateRequest } from "../types/requests"; +import { NextFunction } from "express"; /** * Controller to handle the creation of OOO requests. @@ -165,6 +162,7 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom export const acknowledgeOooRequestController = async ( req: AcknowledgeOooRequest, res: OooRequestResponse, + next: NextFunction ) : Promise => { @@ -186,25 +184,12 @@ export const acknowledgeOooRequestController = async ( const response = await acknowledgeOooRequest(requestId, requestBody, superUserId); - if (response.error === REQUEST_DOES_NOT_EXIST) { - return res.boom.notFound(REQUEST_DOES_NOT_EXIST); - } - if (response.error === INVALID_REQUEST_TYPE) { - return res.boom.badRequest(INVALID_REQUEST_TYPE); - } - if (response.error === REQUEST_ALREADY_APPROVED) { - return res.boom.conflict(REQUEST_ALREADY_APPROVED); - } - if (response.error === REQUEST_ALREADY_REJECTED) { - return res.boom.conflict(REQUEST_ALREADY_REJECTED); - } - return res.status(200).json({ message: response.message, }); } catch(error){ logger.error(ERROR_WHILE_ACKNOWLEDGING_REQUEST, error); - return res.boom.badImplementation(ERROR_WHILE_ACKNOWLEDGING_REQUEST); + next(error); } }; \ No newline at end of file diff --git a/controllers/requests.ts b/controllers/requests.ts index 973962e48..7f4b12244 100644 --- a/controllers/requests.ts +++ b/controllers/requests.ts @@ -16,7 +16,7 @@ import { createTaskRequestController } from "./taskRequestsv2"; import { OnboardingExtensionCreateRequest, OnboardingExtensionResponse, UpdateOnboardingExtensionStateRequest } from "../types/onboardingExtension"; import { createOnboardingExtensionRequestController, updateOnboardingExtensionRequestController, updateOnboardingExtensionRequestState } from "./onboardingExtension"; import { UpdateOnboardingExtensionRequest } from "../types/onboardingExtension"; -import { Request } from "express"; +import { NextFunction, Request } from "express"; export const createRequestController = async ( req: OooRequestCreateRequest | ExtensionRequestRequest | TaskRequestRequest | OnboardingExtensionCreateRequest, @@ -120,12 +120,12 @@ export const getRequestsController = async (req: any, res: any) => { * @param {CustomResponse} res - The response object. * @returns {Promise} Resolves or sends an error for invalid types. */ -export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse) => { +export const updateRequestBeforeAcknowledgedController = async (req: Request, res: CustomResponse, next: NextFunction) => { const type = req.body.type; switch(type){ case REQUEST_TYPE.OOO: - await acknowledgeOooRequestController(req as AcknowledgeOooRequest, res as OooRequestResponse); + await acknowledgeOooRequestController(req as AcknowledgeOooRequest, res as OooRequestResponse, next); break; case REQUEST_TYPE.ONBOARDING: await updateOnboardingExtensionRequestController(req as UpdateOnboardingExtensionRequest, res as OnboardingExtensionResponse); diff --git a/models/requests.ts b/models/requests.ts index 9ea9e6d9d..3c8272a48 100644 --- a/models/requests.ts +++ b/models/requests.ts @@ -8,6 +8,7 @@ import { REQUEST_DOES_NOT_EXIST, } from "../constants/requests"; import { getUserId } from "../utils/users"; +import { NotFound } from "http-errors"; const SIZE = 5; export const createRequest = async (body: any) => { @@ -74,9 +75,7 @@ export const getRequestById = async (id: string) => { const requestDoc = await requestModel.doc(id).get(); if (!requestDoc.exists) { - return { - error: REQUEST_DOES_NOT_EXIST, - }; + throw new NotFound(REQUEST_DOES_NOT_EXIST); } return requestDoc.data(); diff --git a/services/oooRequest.ts b/services/oooRequest.ts index f83f916b6..9a3c24110 100644 --- a/services/oooRequest.ts +++ b/services/oooRequest.ts @@ -6,7 +6,6 @@ import { REQUEST_STATE, USER_STATUS_NOT_FOUND, REQUEST_TYPE, - REQUEST_DOES_NOT_EXIST, REQUEST_ALREADY_APPROVED, REQUEST_ALREADY_REJECTED, REQUEST_APPROVED_SUCCESSFULLY, @@ -18,7 +17,7 @@ import { createRequest, getRequestById } from "../models/requests"; import { OooStatusRequest, OooStatusRequestBody } from "../types/oooRequest"; import { UserStatus } from "../types/userStatus"; import { addLog } from "./logService"; -import { BadRequest } from "http-errors"; +import { BadRequest, Conflict } from "http-errors"; import { updateRequest } from "../models/requests"; import { AcknowledgeOooRequestBody } from "../types/oooRequest"; import { addFutureStatus } from "../models/userStatus"; @@ -114,7 +113,6 @@ export const createOooRequest = async ( * @throws {Error} Throws an error if an issue occurs during validation. */ export const validateOooAcknowledgeRequest = async ( - requestId: string, requestType: string, requestStatus: string, ) => { @@ -122,21 +120,15 @@ export const validateOooAcknowledgeRequest = async ( try { if (requestType !== REQUEST_TYPE.OOO) { - return { - error: INVALID_REQUEST_TYPE - }; + throw new BadRequest(INVALID_REQUEST_TYPE); } if (requestStatus === REQUEST_STATE.APPROVED) { - return { - error: REQUEST_ALREADY_APPROVED - }; + throw new Conflict(REQUEST_ALREADY_APPROVED); } if (requestStatus === REQUEST_STATE.REJECTED) { - return { - error: REQUEST_ALREADY_REJECTED - }; + throw new Conflict(REQUEST_ALREADY_REJECTED); } } catch (error) { logger.error("Error while validating OOO acknowledge request", error); @@ -161,26 +153,12 @@ export const acknowledgeOooRequest = async ( try { const requestData = await getRequestById(requestId); - if (requestData.error) { - logger.error("Error while acknowledging OOO request", { requestId, reason: REQUEST_DOES_NOT_EXIST }); - return { - error: REQUEST_DOES_NOT_EXIST - }; - } - - const validationError = await validateOooAcknowledgeRequest(requestId, requestData.type, requestData.status); - - if (validationError) { - logger.error(`Error while validating OOO acknowledge request`, { requestId, reason: validationError.error }); - return { - error: validationError.error - }; - } + await validateOooAcknowledgeRequest(requestData.type, requestData.status); const requestResult = await updateRequest(requestId, body, superUserId, REQUEST_TYPE.OOO); if ("error" in requestResult) { - throw BadRequest(requestResult.error); + throw new BadRequest(requestResult.error); } const [acknowledgeLogType, returnMessage] = @@ -201,16 +179,14 @@ export const acknowledgeOooRequest = async ( await addLog(requestLog.type, requestLog.meta, requestLog.body); if (requestResult.status === REQUEST_STATE.APPROVED) { - const { from, until, userId } = requestData; - const userFutureStatusData = { + await addFutureStatus({ requestId, state: REQUEST_TYPE.OOO, - from, - endsOn: until, - userId, + from: requestData.from, + endsOn: requestData.until, + userId: requestData.userId, message: body.comment, - }; - await addFutureStatus(userFutureStatusData); + }); } return { From b22e969630d349f3e9307f3c08a72098073c3424 Mon Sep 17 00:00:00 2001 From: Suraj Maity Date: Tue, 6 May 2025 00:21:39 +0530 Subject: [PATCH 5/6] fix: mocked dependencies for ooo request service --- test/unit/services/oooRequest.test.ts | 107 +++++++++++++++----------- 1 file changed, 63 insertions(+), 44 deletions(-) diff --git a/test/unit/services/oooRequest.test.ts b/test/unit/services/oooRequest.test.ts index fd7176cf5..a38db65d2 100644 --- a/test/unit/services/oooRequest.test.ts +++ b/test/unit/services/oooRequest.test.ts @@ -27,6 +27,9 @@ import userDataFixture from "../../fixtures/user/user"; import * as logService from "../../../services/logService"; import { testAcknowledgeOooRequest, createOooRequests3 } from "../../fixtures/oooRequest/oooRequest"; import { createRequest } from "../../../models/requests"; +import * as requestModel from "../../../models/requests"; +import * as oooRequestService from "../../../services/oooRequest"; +import { NotFound, Conflict, BadRequest } from "http-errors"; describe("Test OOO Request Service", function() { @@ -125,38 +128,37 @@ describe("Test OOO Request Service", function() { }); it("should return INVALID_REQUEST_TYPE if request type is not OOO", async function() { - const validationResponse = await validateOooAcknowledgeRequest( - testOooRequest.id, + await validateOooAcknowledgeRequest( REQUEST_TYPE.ONBOARDING, testOooRequest.status - ); - expect(validationResponse.error).to.be.not.undefined; - expect(validationResponse.error).to.equal(INVALID_REQUEST_TYPE); + ).catch((error) => { + expect(error).to.be.not.undefined; + expect(error.message).to.equal(INVALID_REQUEST_TYPE); + }); }); it("should return REQUEST_ALREADY_APPROVED if request is already approved", async function() { - const validationResponse = await validateOooAcknowledgeRequest( - testOooRequest.id, + await validateOooAcknowledgeRequest( testOooRequest.type, REQUEST_STATE.APPROVED - ); - expect(validationResponse.error).to.be.not.undefined; - expect(validationResponse.error).to.equal(REQUEST_ALREADY_APPROVED); + ).catch((error) => { + expect(error).to.be.not.undefined; + expect(error.message).to.equal(REQUEST_ALREADY_APPROVED); + }); }); it("should return REQUEST_ALREADY_REJECTED if request is already rejected", async function() { - const validationResponse = await validateOooAcknowledgeRequest( - testOooRequest.id, + await validateOooAcknowledgeRequest( testOooRequest.type, REQUEST_STATE.REJECTED - ); - expect(validationResponse.error).to.be.not.undefined; - expect(validationResponse.error).to.equal(REQUEST_ALREADY_REJECTED); + ).catch((error) => { + expect(error).to.be.not.undefined; + expect(error.message).to.equal(REQUEST_ALREADY_REJECTED); + }); }); it("should return undefined when all validation checks passes", async function() { const response = await validateOooAcknowledgeRequest( - testOooRequest.id, testOooRequest.type, testOooRequest.status ); @@ -183,54 +185,72 @@ describe("Test OOO Request Service", function() { }); it("should return REQUEST_DOES_NOT_EXIST if invalid request id is passed", async function () { - const invalidOOORequestId = "11111111111111111111"; - const response = await acknowledgeOooRequest( - invalidOOORequestId, + sinon.stub(requestModel, "getRequestById").throws(new NotFound(REQUEST_DOES_NOT_EXIST)); + await acknowledgeOooRequest( + "11111111111111111111", testAcknowledgeOooRequest, testSuperUserId - ); - expect(response.error).to.equal(REQUEST_DOES_NOT_EXIST); + ).catch((error) => { + expect(error).to.be.not.undefined; + expect(error.message).to.equal(REQUEST_DOES_NOT_EXIST); + }); + }); + + it("should return REQUEST_ALREADY_APPROVED when status is approved", async function () { + sinon.stub(requestModel, "getRequestById").returns(testOooRequest); + sinon.stub(oooRequestService, "validateOooAcknowledgeRequest").throws(new Conflict(REQUEST_ALREADY_APPROVED)); + await acknowledgeOooRequest( + testOooRequest.id, + testAcknowledgeOooRequest, + testSuperUserId + ).catch((error) => { + expect(error).to.be.not.undefined; + expect(error.message).to.equal(REQUEST_ALREADY_APPROVED); + }); + }); + + it("should throw error when approve or rejection fails", async function () { + sinon.stub(requestModel, "getRequestById").returns(testOooRequest); + sinon.stub(oooRequestService, "validateOooAcknowledgeRequest"); + sinon.stub(requestModel, "updateRequest").throws(new BadRequest(errorMessage)); + await acknowledgeOooRequest( + testOooRequest.id, + testAcknowledgeOooRequest, + testSuperUserId + ).catch((error) => { + expect(error).to.be.not.undefined; + expect(error.message).to.equal(errorMessage); + }); }); it("should approve OOO request", async function() { + sinon.stub(requestModel, "getRequestById").returns(testOooRequest); + sinon.stub(oooRequestService, "validateOooAcknowledgeRequest"); + sinon.stub(requestModel, "updateRequest").returns({ ...testOooRequest, status: REQUEST_STATE.APPROVED}); const response = await acknowledgeOooRequest( testOooRequest.id, testAcknowledgeOooRequest, testSuperUserId ); - expect(response).to.deep.include({ - message: REQUEST_APPROVED_SUCCESSFULLY, - data: { - ...testAcknowledgeOooRequest, - id: testOooRequest.id, - lastModifiedBy: testSuperUserId, - updatedAt: response.data.updatedAt - } - }); + expect(response.message).to.equal(REQUEST_APPROVED_SUCCESSFULLY); + expect(response.data.status).to.equal(REQUEST_STATE.APPROVED); }); it("should reject OOO request", async function() { + sinon.stub(requestModel, "getRequestById").returns(testOooRequest); + sinon.stub(oooRequestService, "validateOooAcknowledgeRequest"); + sinon.stub(requestModel, "updateRequest").returns({ ...testOooRequest, status: REQUEST_STATE.REJECTED}); const response = await acknowledgeOooRequest( testOooRequest.id, { ...testAcknowledgeOooRequest, status: REQUEST_STATE.REJECTED }, testSuperUserId ); - expect(response).to.deep.include({ - message: REQUEST_REJECTED_SUCCESSFULLY, - data: { - ...testAcknowledgeOooRequest, - id: testOooRequest.id, - status: REQUEST_STATE.REJECTED, - lastModifiedBy: testSuperUserId, - updatedAt: response.data.updatedAt - } - }); + expect(response.message).to.equal(REQUEST_REJECTED_SUCCESSFULLY); + expect(response.data.status).to.equal(REQUEST_STATE.REJECTED); }); it("should throw error", async function() { - sinon.stub(logService, "addLog").throws(new Error(errorMessage)); - const createSpy = sinon.spy(require("../../../services/oooRequest"), "acknowledgeOooRequest"); - + sinon.stub(requestModel, "getRequestById").throws(new Error(errorMessage)); try { await acknowledgeOooRequest( testOooRequest.id, @@ -239,7 +259,6 @@ describe("Test OOO Request Service", function() { ); } catch (error) { expect(error.message).to.equal(errorMessage); - expect(createSpy.calledOnce).to.be.true; } }); }); From 680fe422422baa89a327d0b38067ccd00589d9b1 Mon Sep 17 00:00:00 2001 From: Suraj Maity Date: Tue, 6 May 2025 12:21:49 +0530 Subject: [PATCH 6/6] refactor: ooo acknowledge controller --- constants/requests.ts | 1 + controllers/oooRequests.ts | 35 ++++++++++++----------- controllers/requests.ts | 4 +-- middlewares/validators/oooRequests.ts | 2 +- middlewares/validators/requests.ts | 4 +-- test/unit/middlewares/oooRequests.test.ts | 10 +++---- test/unit/models/requests.test.ts | 21 ++++++++++++-- 7 files changed, 49 insertions(+), 28 deletions(-) diff --git a/constants/requests.ts b/constants/requests.ts index b94cf2692..06e3ef6a8 100644 --- a/constants/requests.ts +++ b/constants/requests.ts @@ -43,6 +43,7 @@ export const ERROR_WHILE_CREATING_REQUEST = "Error while creating request"; export const ERROR_WHILE_UPDATING_REQUEST = "Error while updating request"; export const ERROR_WHILE_ACKNOWLEDGING_REQUEST = "Error while acknowledging request"; +export const REQUEST_ID_REQUIRED = "Request id is required"; export const REQUEST_DOES_NOT_EXIST = "Request does not exist"; export const REQUEST_ALREADY_PENDING = "Request already exists please wait for approval or rejection"; export const UNAUTHORIZED_TO_CREATE_OOO_REQUEST = "Unauthorized to create OOO request"; diff --git a/controllers/oooRequests.ts b/controllers/oooRequests.ts index 47c123ca8..99089bcfb 100644 --- a/controllers/oooRequests.ts +++ b/controllers/oooRequests.ts @@ -14,6 +14,7 @@ import { OOO_STATUS_ALREADY_EXIST, UNAUTHORIZED_TO_UPDATE_REQUEST, ERROR_WHILE_ACKNOWLEDGING_REQUEST, + REQUEST_ID_REQUIRED, } from "../constants/requests"; import { statusState } from "../constants/userStatus"; import { logType } from "../constants/logs"; @@ -21,7 +22,8 @@ import { addLog } from "../models/logs"; import { getRequestByKeyValues, getRequests, updateRequest } from "../models/requests"; import { createUserFutureStatus } from "../models/userFutureStatus"; import { getUserStatus, addFutureStatus } from "../models/userStatus"; -import { createOooRequest, validateUserStatus, acknowledgeOooRequest } from "../services/oooRequest"; +import { createOooRequest, validateUserStatus } from "../services/oooRequest"; +import * as oooRequestService from "../services/oooRequest"; import { CustomResponse } from "../typeDefinitions/global"; import { AcknowledgeOooRequest, OooRequestCreateRequest, OooRequestResponse, OooStatusRequest } from "../types/oooRequest"; import { UpdateRequest } from "../types/requests"; @@ -159,30 +161,30 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom * @param {OooRequestResponse} res - The response object. * @returns {Promise} Resolves with success or failure. */ -export const acknowledgeOooRequestController = async ( +export const acknowledgeOooRequest = async ( req: AcknowledgeOooRequest, res: OooRequestResponse, next: NextFunction ) : Promise => { + try { + const dev = req.query.dev === "true"; + if(!dev) return res.boom.notImplemented("Feature not implemented"); - const dev = req.query.dev === "true"; - - if(!dev) return res.boom.notImplemented("Feature not implemented"); - - const isSuperuser = req.userData.roles?.super_user; - - if (!isSuperuser) { - return res.boom.forbidden(UNAUTHORIZED_TO_UPDATE_REQUEST); - } + const isSuperuser = req.userData?.roles?.super_user; + if (!isSuperuser) { + return res.boom.forbidden(UNAUTHORIZED_TO_UPDATE_REQUEST); + } - const requestBody = req.body; - const superUserId = req.userData.id; - const requestId = req.params.id; + const requestBody = req.body; + const superUserId = req.userData.id; + const requestId = req.params.id; - try { + if (!requestId) { + return res.boom.badRequest(REQUEST_ID_REQUIRED); + } - const response = await acknowledgeOooRequest(requestId, requestBody, superUserId); + const response = await oooRequestService.acknowledgeOooRequest(requestId, requestBody, superUserId); return res.status(200).json({ message: response.message, @@ -191,5 +193,6 @@ export const acknowledgeOooRequestController = async ( catch(error){ logger.error(ERROR_WHILE_ACKNOWLEDGING_REQUEST, error); next(error); + return res; } }; \ No newline at end of file diff --git a/controllers/requests.ts b/controllers/requests.ts index 7f4b12244..5e8bb7a2c 100644 --- a/controllers/requests.ts +++ b/controllers/requests.ts @@ -5,7 +5,7 @@ import { } from "../constants/requests"; import { getRequests } from "../models/requests"; import { getPaginatedLink } from "../utils/helper"; -import { acknowledgeOooRequestController, createOooRequestController, updateOooRequestController } from "./oooRequests"; +import { acknowledgeOooRequest, createOooRequestController, updateOooRequestController } from "./oooRequests"; import { AcknowledgeOooRequest, OooRequestCreateRequest, OooRequestResponse } from "../types/oooRequest"; import { CustomResponse } from "../typeDefinitions/global"; import { ExtensionRequestRequest, ExtensionRequestResponse } from "../types/extensionRequests"; @@ -125,7 +125,7 @@ export const updateRequestBeforeAcknowledgedController = async (req: Request, re switch(type){ case REQUEST_TYPE.OOO: - await acknowledgeOooRequestController(req as AcknowledgeOooRequest, res as OooRequestResponse, next); + await acknowledgeOooRequest(req as AcknowledgeOooRequest, res as OooRequestResponse, next); break; case REQUEST_TYPE.ONBOARDING: await updateOnboardingExtensionRequestController(req as UpdateOnboardingExtensionRequest, res as OnboardingExtensionResponse); diff --git a/middlewares/validators/oooRequests.ts b/middlewares/validators/oooRequests.ts index 2102882d6..2836d9b3f 100644 --- a/middlewares/validators/oooRequests.ts +++ b/middlewares/validators/oooRequests.ts @@ -67,7 +67,7 @@ const schema = joi * @param {NextFunction} next - The next middleware function to call if validation succeeds. * @returns {Promise} Resolves or sends errors. */ -export const acknowledgeOooRequestsValidator = async ( +export const acknowledgeOooRequest = async ( req: AcknowledgeOooRequest, res: OooRequestResponse, next: NextFunction diff --git a/middlewares/validators/requests.ts b/middlewares/validators/requests.ts index 4ec52dd77..d53fadc96 100644 --- a/middlewares/validators/requests.ts +++ b/middlewares/validators/requests.ts @@ -2,7 +2,7 @@ import joi from "joi"; import { NextFunction } from "express"; import { REQUEST_STATE, REQUEST_TYPE } from "../../constants/requests"; import { AcknowledgeOooRequest, OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest"; -import { acknowledgeOooRequestsValidator, createOooStatusRequestValidator } from "./oooRequests"; +import { acknowledgeOooRequest, createOooStatusRequestValidator } from "./oooRequests"; import { createExtensionRequestValidator } from "./extensionRequestsv2"; import {createTaskRequestValidator} from "./taskRequests"; import { ExtensionRequestRequest, ExtensionRequestResponse } from "../../types/extensionRequests"; @@ -139,7 +139,7 @@ export const updateRequestValidator = async ( switch (type) { case REQUEST_TYPE.OOO: - await acknowledgeOooRequestsValidator( + await acknowledgeOooRequest( req, res as OooRequestResponse, next); break; diff --git a/test/unit/middlewares/oooRequests.test.ts b/test/unit/middlewares/oooRequests.test.ts index 193f4e5d6..e369859c9 100644 --- a/test/unit/middlewares/oooRequests.test.ts +++ b/test/unit/middlewares/oooRequests.test.ts @@ -4,7 +4,7 @@ const { expect } = chai; import { createOooStatusRequestValidator, - acknowledgeOooRequestsValidator, + acknowledgeOooRequest, } from "./../../../middlewares/validators/oooRequests"; import { testAcknowledgeOooRequest, validOooStatusRequests, validOooStatusUpdate } from "../../fixtures/oooRequest/oooRequest"; import _ from "lodash"; @@ -97,7 +97,7 @@ describe("OOO Status Request Validators", function () { body: { ...testAcknowledgeOooRequest, type: "XYZ"} }; - await acknowledgeOooRequestsValidator(req, res, nextSpy); + await acknowledgeOooRequest(req, res, nextSpy); expect(nextSpy.notCalled).to.be.true; }); @@ -106,7 +106,7 @@ describe("OOO Status Request Validators", function () { body: { ...testAcknowledgeOooRequest, status: "PENDING"} }; - await acknowledgeOooRequestsValidator(req, res, nextSpy); + await acknowledgeOooRequest(req, res, nextSpy); expect(nextSpy.notCalled).to.be.true; }); @@ -115,7 +115,7 @@ describe("OOO Status Request Validators", function () { body: _.omit(testAcknowledgeOooRequest, "comment") }; res = {}; - await acknowledgeOooRequestsValidator(req, res, nextSpy); + await acknowledgeOooRequest(req, res, nextSpy); expect(nextSpy.calledOnce).to.be.true; }); @@ -124,7 +124,7 @@ describe("OOO Status Request Validators", function () { body: testAcknowledgeOooRequest }; res = {}; - await acknowledgeOooRequestsValidator(req, res, nextSpy); + await acknowledgeOooRequest(req, res, nextSpy); expect(nextSpy.calledOnce).to.be.true; }); }); diff --git a/test/unit/models/requests.test.ts b/test/unit/models/requests.test.ts index 954024c86..c719c8afe 100644 --- a/test/unit/models/requests.test.ts +++ b/test/unit/models/requests.test.ts @@ -1,14 +1,15 @@ import { expect } from "chai"; import cleanDb from "../../utils/cleanDb"; -import { createRequest, getRequests, updateRequest, getRequestByKeyValues } from "../../../models/requests"; +import { createRequest, getRequests, updateRequest, getRequestByKeyValues, getRequestById } from "../../../models/requests"; import { createOooRequests, createOooRequests2, + createOooRequests3, createOooStatusRequests, updateOooApprovedRequests, updateOooRejectedRequests, } from "./../../fixtures/oooRequest/oooRequest"; -import { REQUEST_STATE, REQUEST_TYPE } from "../../../constants/requests"; +import { REQUEST_DOES_NOT_EXIST, REQUEST_STATE, REQUEST_TYPE } from "../../../constants/requests"; import userDataFixture from "./../../fixtures/user/user"; import addUser from "../../utils/addUser"; const userData = userDataFixture(); @@ -179,4 +180,20 @@ describe("models/oooRequests", () => { expect(oooRequestData).to.be.equal(null); }); }); + + describe("getRequestById", () => { + + it("should return request using request id", async () => { + const oooRequest = await createRequest(createOooRequests3); + const response = await getRequestById(oooRequest.id); + expect(response).to.deep.include(createOooRequests3); + }); + + it("should return REQUEST_DOES_NOT_EXIST for invalid request id", async () => { + await getRequestById("111111111111").catch((error) => { + expect(error).to.be.not.undefined; + expect(error.message).to.equal(REQUEST_DOES_NOT_EXIST); + }); + }); + }); });