diff --git a/config/custom-environment-variables.js b/config/custom-environment-variables.js index 17063ed51..a17419a73 100644 --- a/config/custom-environment-variables.js +++ b/config/custom-environment-variables.js @@ -88,10 +88,6 @@ module.exports = { __name: "USER_TOKEN_REFRESH_TTL", __format: "number", }, - impersonationTtl: { - __name: "USER_TOKEN_IMPERSONATION_TTL", - __format: "number", - }, publicKey: "PUBLIC_KEY", privateKey: "PRIVATE_KEY", }, @@ -100,10 +96,6 @@ module.exports = { botPublicKey: "BOT_PUBLIC_KEY", }, - discordService: { - publicKey: "DISCORD_SERVICE_PUBLIC_KEY", - }, - cronJobHandler: { publicKey: "CRON_JOB_PUBLIC_KEY", }, diff --git a/config/default.js b/config/default.js index c53633c24..4b7dc2b83 100644 --- a/config/default.js +++ b/config/default.js @@ -86,7 +86,6 @@ module.exports = { cookieV2Name: `rds-session-v2-${NODE_ENV}`, ttl: 30 * 24 * 60 * 60, // in seconds refreshTtl: 180 * 24 * 60 * 60, // in seconds - impersonationTtl: 15 * 60, // in seconds publicKey: "", privateKey: "", }, @@ -95,10 +94,6 @@ module.exports = { botPublicKey: "", }, - discordService: { - publicKey: "DISCORD_SERVICE_PUBLIC_KEY", - }, - // Cloudinary keys cloudinary: { cloud_name: "Cloud_name", diff --git a/constants/bot.ts b/constants/bot.ts index eabf7c58d..bc8a01534 100644 --- a/constants/bot.ts +++ b/constants/bot.ts @@ -1,15 +1,10 @@ const CLOUDFLARE_WORKER = "Cloudflare Worker"; const BAD_TOKEN = "BAD.JWT.TOKEN"; const CRON_JOB_HANDLER = "Cron Job Handler"; -const DISCORD_SERVICE = "Discord Service"; const Services = { CLOUDFLARE_WORKER: CLOUDFLARE_WORKER, CRON_JOB_HANDLER: CRON_JOB_HANDLER, }; -const DiscordServiceHeader = { - name: "x-service-name" -} - -module.exports = { CLOUDFLARE_WORKER, BAD_TOKEN, CRON_JOB_HANDLER, Services, DISCORD_SERVICE, DiscordServiceHeader }; +module.exports = { CLOUDFLARE_WORKER, BAD_TOKEN, CRON_JOB_HANDLER, Services }; diff --git a/constants/requests.ts b/constants/requests.ts index e9d510012..06e3ef6a8 100644 --- a/constants/requests.ts +++ b/constants/requests.ts @@ -26,6 +26,8 @@ export const REQUEST_LOG_TYPE = { REQUEST_CANCELLED: "REQUEST_CANCELLED", REQUEST_UPDATED: "REQUEST_UPDATED", PENDING_REQUEST_FOUND: "PENDING_REQUEST_FOUND", + REQUEST_ALREADY_APPROVED: "REQUEST_ALREADY_APPROVED", + REQUEST_ALREADY_REJECTED: "REQUEST_ALREADY_REJECTED", }; export const REQUEST_CREATED_SUCCESSFULLY = "Request created successfully"; @@ -39,7 +41,9 @@ export const REQUEST_ALREADY_REJECTED = "Request already rejected"; export const ERROR_WHILE_FETCHING_REQUEST = "Error while fetching request"; 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"; @@ -68,14 +72,3 @@ export const INVALID_REQUEST_TYPE = "Invalid request type"; export const INVALID_REQUEST_DEADLINE = "New deadline of the request must be greater than old deadline"; export const REQUEST_UPDATED_SUCCESSFULLY = "Request updated successfully"; export const UNAUTHORIZED_TO_UPDATE_REQUEST = "Unauthorized to update request"; - -export const FEATURE_NOT_IMPLEMENTED = "Feature not implemented"; - -export const INVALID_ACTION_PARAM = "Invalid 'action' parameter: must be either 'START' or 'STOP'"; - -export const OPERATION_NOT_ALLOWED = "You are not allowed for this operation at the moment"; - -export const IMPERSONATION_LOG_TYPE = { - SESSION_STARTED:"SESSION_STARTED", - SESSION_STOPPED:"SESSION_STOPPED" -} \ No newline at end of file diff --git a/controllers/oooRequests.ts b/controllers/oooRequests.ts index 36d2baeab..99089bcfb 100644 --- a/controllers/oooRequests.ts +++ b/controllers/oooRequests.ts @@ -12,6 +12,9 @@ import { REQUEST_ALREADY_PENDING, USER_STATUS_NOT_FOUND, 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"; @@ -20,9 +23,11 @@ import { getRequestByKeyValues, getRequests, updateRequest } from "../models/req import { createUserFutureStatus } from "../models/userFutureStatus"; import { getUserStatus, addFutureStatus } from "../models/userStatus"; import { createOooRequest, validateUserStatus } from "../services/oooRequest"; +import * as oooRequestService from "../services/oooRequest"; import { CustomResponse } from "../typeDefinitions/global"; -import { OooRequestCreateRequest, OooRequestResponse, OooStatusRequest } from "../types/oooRequest"; +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. @@ -148,3 +153,46 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom return res.boom.badImplementation(ERROR_WHILE_UPDATING_REQUEST); } }; + +/** + * Acknowledges an Out-of-Office (OOO) request + * + * @param {AcknowledgeOooRequest} req - The request object. + * @param {OooRequestResponse} res - The response object. + * @returns {Promise} Resolves with success or failure. + */ +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 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; + + if (!requestId) { + return res.boom.badRequest(REQUEST_ID_REQUIRED); + } + + const response = await oooRequestService.acknowledgeOooRequest(requestId, requestBody, superUserId); + + return res.status(200).json({ + message: response.message, + }); + } + 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 fd8974ea0..5e8bb7a2c 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 { acknowledgeOooRequest, 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,9 +120,13 @@ 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 acknowledgeOooRequest(req as AcknowledgeOooRequest, res as OooRequestResponse, next); + break; case REQUEST_TYPE.ONBOARDING: await updateOnboardingExtensionRequestController(req as UpdateOnboardingExtensionRequest, res as OnboardingExtensionResponse); break; diff --git a/middlewares/authenticate.js b/middlewares/authenticate.js index 552fb5181..667d41b32 100644 --- a/middlewares/authenticate.js +++ b/middlewares/authenticate.js @@ -1,82 +1,62 @@ const authService = require("../services/authService"); const dataAccess = require("../services/dataAccessLayer"); -const logger = require("../utils/logger"); /** - * Middleware to check if the user is restricted or in an impersonation session. - * - * - If the user is impersonating, only GET requests and the STOP impersonation route are allowed. - * - If the user is restricted (based on roles), only GET requests are permitted. + * Middleware to check if the user has been restricted. If user is restricted, + * then only allow read requests and do not allow to any edit/create requests. * * Note: This requires that user is authenticated hence must be called after * the user authentication middleware. We are calling it from within the * `authenticate` middleware itself to avoid explicitly adding this middleware * while defining routes. * - * @async - * @function checkRestricted - * @param {import('express').Request} req - Express request object - * @param {import('express').Response} res - Express response object - * @param {Function} next - Express next middleware function - * @returns {void} + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express middleware function + * @returns {Object} - Returns unauthorized object if user has been restricted. */ const checkRestricted = async (req, res, next) => { const { roles } = req.userData; - - if (req.isImpersonating) { - const isStopImpersonationRoute = - req.method === "PATCH" && - req.baseUrl === "/impersonation" && - /^\/[a-zA-Z0-9_-]+$/.test(req.path) && - req.query.action === "STOP"; - - if (req.method !== "GET" && !isStopImpersonationRoute) { - return res.boom.forbidden("Only viewing is permitted during impersonation"); - } - } - if (roles && roles.restricted && req.method !== "GET") { return res.boom.forbidden("You are restricted from performing this action"); } - return next(); }; /** - * Authentication middleware that: - * 1. Verifies JWT token from cookies (or headers in non-production). - * 2. Handles impersonation if applicable. - * 3. Refreshes token if it's expired but still within the refresh TTL window. - * 4. Attaches user data to `req.userData` for downstream use. + * Middleware to validate the authenticated routes + * 1] Verifies the token and adds user info to `req.userData` for further use + * 2] In case of JWT expiry, adds a new JWT to the response if `currTime - tokenInitialisationTime <= refreshTtl` * - * @async - * @function - * @param {import('express').Request} req - Express request object - * @param {import('express').Response} res - Express response object - * @param {Function} next - Express next middleware function - * @returns {Promise} - Calls `next()` on successful authentication or returns an error response. + * The currently implemented mechanism satisfies the current use case. + * Authentication with JWT and a refreshToken to be added once we have user permissions and authorizations to be handled + * + * @todo: Add tests to assert on refreshed JWT generation by modifying the TTL values for the specific test. Currently not possible in the absence of a test-suite. + * + * + * @param req {Object} - Express request object + * @param res {Object} - Express response object + * @param next {Function} - Express middleware function + * @return {Object} - Returns unauthenticated object if token is invalid */ module.exports = async (req, res, next) => { try { let token = req.cookies[config.get("userToken.cookieName")]; /** - * Enable Bearer Token authentication for NON-PRODUCTION environments. - * Useful for Swagger or manual testing where cookies are not easily managed. + * Enable Bearer Token authentication for NON-PRODUCTION environments + * This is enabled as Swagger UI does not support cookie authe */ if (process.env.NODE_ENV !== "production" && !token) { - token = req.headers.authorization?.split(" ")[1]; + token = req.headers.authorization.split(" ")[1]; } - const { userId, impersonatedUserId } = authService.verifyAuthToken(token); - // `req.isImpersonating` keeps track of the impersonation session - req.isImpersonating = Boolean(impersonatedUserId); - - const userData = impersonatedUserId - ? await dataAccess.retrieveUsers({ id: impersonatedUserId }) - : await dataAccess.retrieveUsers({ id: userId }); + const { userId } = authService.verifyAuthToken(token); + // add user data to `req.userData` for further use + const userData = await dataAccess.retrieveUsers({ id: userId }); req.userData = userData.user; + return checkRestricted(req, res, next); } catch (err) { logger.error(err); @@ -98,13 +78,14 @@ module.exports = async (req, res, next) => { sameSite: "lax", }); - const userData = await dataAccess.retrieveUsers({ id: userId }); - req.userData = userData.user; - + // add user data to `req.userData` for further use + req.userData = await dataAccess.retrieveUsers({ id: userId }); return checkRestricted(req, res, next); + } else { + return res.boom.unauthorized("Unauthenticated User"); } + } else { return res.boom.unauthorized("Unauthenticated User"); } - return res.boom.unauthorized("Unauthenticated User"); } }; diff --git a/middlewares/authorizeBot.js b/middlewares/authorizeBot.js index e6a9611a3..f03ab5491 100644 --- a/middlewares/authorizeBot.js +++ b/middlewares/authorizeBot.js @@ -1,5 +1,5 @@ const botVerifcation = require("../services/botVerificationService"); -const { CLOUDFLARE_WORKER, CRON_JOB_HANDLER, DISCORD_SERVICE, DiscordServiceHeader } = require("../constants/bot"); +const { CLOUDFLARE_WORKER, CRON_JOB_HANDLER } = require("../constants/bot"); const verifyCronJob = async (req, res, next) => { try { @@ -18,18 +18,13 @@ const verifyCronJob = async (req, res, next) => { const verifyDiscordBot = async (req, res, next) => { try { const token = req.headers.authorization.split(" ")[1]; - const serviceName = req.headers[DiscordServiceHeader.name] || ""; - - if (serviceName === DISCORD_SERVICE && botVerifcation.verifyDiscordService(token).name === DISCORD_SERVICE) { - return next(); - } - const data = botVerifcation.verifyToken(token); - if (data.name === CLOUDFLARE_WORKER) { - return next(); + + if (data.name !== CLOUDFLARE_WORKER) { + return res.boom.unauthorized("Unauthorized Bot"); } - return res.boom.unauthorized("Unauthorized Bot"); + return next(); } catch (error) { if (error.message === "invalid token") { return res.boom.unauthorized("Unauthorized Bot"); @@ -37,4 +32,5 @@ const verifyDiscordBot = async (req, res, next) => { return res.boom.badRequest("Invalid Request"); } }; + module.exports = { verifyDiscordBot, verifyCronJob }; diff --git a/middlewares/validators/oooRequests.ts b/middlewares/validators/oooRequests.ts index ab73929f1..2836d9b3f 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,46 @@ export const createOooStatusRequestValidator = async ( await schema.validateAsync(req.body, { abortEarly: false }); }; + +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", + }), + type: joi.string().equal(REQUEST_TYPE.OOO).required().messages({ + "type.any": "type is required", + }) + }); + +/** + * 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 acknowledgeOooRequest = async ( + req: AcknowledgeOooRequest, + res: OooRequestResponse, + next: NextFunction +): Promise => { + try { + await schema.validateAsync(req.body, { abortEarly: false }); + next(); + } catch (error) { + const errorMessages = error.details.map((detail:{message: string}) => detail.message); + logger.error(`Error while validating request payload : ${errorMessages}`); + return res.boom.badRequest(errorMessages); + } +}; diff --git a/middlewares/validators/requests.ts b/middlewares/validators/requests.ts index 80ff0478b..d53fadc96 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 { acknowledgeOooRequest, createOooStatusRequestValidator } from "./oooRequests"; import { createExtensionRequestValidator } from "./extensionRequestsv2"; import {createTaskRequestValidator} from "./taskRequests"; import { ExtensionRequestRequest, ExtensionRequestResponse } from "../../types/extensionRequests"; @@ -125,18 +125,24 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O /** * Validates update requests based on their type. * - * @param {UpdateOnboardingExtensionRequest} req - Request object. + * @param {UpdateOnboardingExtensionRequest | AcknowledgeOooRequest} req - Request object. * @param {CustomResponse} res - Response object. * @param {NextFunction} next - Next middleware if valid. * @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; + switch (type) { + case REQUEST_TYPE.OOO: + await acknowledgeOooRequest( + req, + res as OooRequestResponse, next); + break; case REQUEST_TYPE.ONBOARDING: await updateOnboardingExtensionRequestValidator( req, @@ -145,4 +151,4 @@ export const updateRequestValidator = async ( default: return res.boom.badRequest("Invalid type"); } -}; \ No newline at end of file +}; diff --git a/models/requests.ts b/models/requests.ts index 064eebd8c..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) => { @@ -69,6 +70,21 @@ 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) { + throw new NotFound(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/models/tasks.js b/models/tasks.js index 467903d97..b7a2f40da 100644 --- a/models/tasks.js +++ b/models/tasks.js @@ -7,7 +7,7 @@ const userUtils = require("../utils/users"); const { chunks } = require("../utils/array"); const { DOCUMENT_WRITE_SIZE } = require("../constants/constants"); const { fromFirestoreData, toFirestoreData, buildTasks } = require("../utils/tasks"); -const { TASK_TYPE, TASK_STATUS, TASK_STATUS_OLD, TASK_SIZE, COMPLETED_TASK_STATUS } = require("../constants/tasks"); +const { TASK_TYPE, TASK_STATUS, TASK_STATUS_OLD, TASK_SIZE } = require("../constants/tasks"); const { IN_PROGRESS, NEEDS_REVIEW, @@ -19,7 +19,6 @@ const { SANITY_CHECK, BACKLOG, DONE, - AVAILABLE, } = TASK_STATUS; const { OLD_ACTIVE, OLD_BLOCKED, OLD_PENDING, OLD_COMPLETED } = TASK_STATUS_OLD; const { INTERNAL_SERVER_ERROR } = require("../constants/errorMessages"); @@ -598,11 +597,17 @@ const getOverdueTasks = async (days = 0) => { const currentTime = Math.floor(Date.now() / 1000); const targetTime = days > 0 ? currentTime + days * 24 * 60 * 60 : currentTime; - const completeTaskStatuses = Object.values(COMPLETED_TASK_STATUS); - - const query = tasksModel - .where("endsOn", "<", targetTime) - .where("status", "not-in", [...completeTaskStatuses, BACKLOG, AVAILABLE]); + const OVERDUE_TASK_STATUSES = [ + IN_PROGRESS, + ASSIGNED, + NEEDS_REVIEW, + IN_REVIEW, + SMOKE_TESTING, + BLOCKED, + SANITY_CHECK, + ]; + + const query = tasksModel.where("endsOn", "<", targetTime).where("status", "in", OVERDUE_TASK_STATUSES); const snapshot = await query.get(); if (snapshot.empty) { diff --git a/routes/discordactions.js b/routes/discordactions.js index b0f197b86..5475e0344 100644 --- a/routes/discordactions.js +++ b/routes/discordactions.js @@ -31,24 +31,14 @@ const ROLES = require("../constants/roles"); const { Services } = require("../constants/bot"); const { verifyCronJob } = require("../middlewares/authorizeBot"); const { authorizeAndAuthenticate } = require("../middlewares/authorizeUsersAndService"); -const { disableRoute } = require("../middlewares/shortCircuit"); const router = express.Router(); router.post("/groups", authenticate, checkIsVerifiedDiscord, validateGroupRoleBody, createGroupRole); router.get("/groups", authenticate, checkIsVerifiedDiscord, validateLazyLoadingParams, getPaginatedAllGroupRoles); router.delete("/groups/:groupId", authenticate, checkIsVerifiedDiscord, authorizeRoles([SUPERUSER]), deleteGroupRole); router.post("/roles", authenticate, checkIsVerifiedDiscord, validateMemberRoleBody, addGroupRoleToMember); -/** - * Short-circuit the GET method for this endpoint - * Refer https://github.com/Real-Dev-Squad/todo-action-items/issues/269 for more details. - */ -router.get("/invite", disableRoute, authenticate, getUserDiscordInvite); -/** - * Short-circuit this POST method for this endpoint - * Refer https://github.com/Real-Dev-Squad/todo-action-items/issues/269 for more details. - */ -router.post("/invite", disableRoute, authenticate, checkCanGenerateDiscordLink, generateInviteForUser); - +router.get("/invite", authenticate, getUserDiscordInvite); +router.post("/invite", authenticate, checkCanGenerateDiscordLink, generateInviteForUser); router.delete("/roles", authenticate, checkIsVerifiedDiscord, deleteRole); router.get("/roles", authenticate, checkIsVerifiedDiscord, getGroupsRoleId); router.patch( diff --git a/routes/index.ts b/routes/index.ts index 0abe183bc..985d1f424 100644 --- a/routes/index.ts +++ b/routes/index.ts @@ -41,6 +41,5 @@ app.use("/v1/notifications", require("./notify")); app.use("/goals", require("./goals")); app.use("/invites", require("./invites")); app.use("/requests", require("./requests")); -app.use("/impersonation", devFlagMiddleware, require("./impersonation")); app.use("/subscription", devFlagMiddleware, require("./subscription")); module.exports = app; diff --git a/services/authService.js b/services/authService.js index e66ef105c..4e698b44f 100644 --- a/services/authService.js +++ b/services/authService.js @@ -12,21 +12,6 @@ const generateAuthToken = (payload) => { }); }; -/** - * Generates a short-lived JWT token for impersonation sessions. - * - * @param {Object} payload - The payload to include in the JWT (e.g., userId, impersonatedUserId). - * @param {string} payload.userId - The ID of the super-user initiating the impersonation. - * @param {string} payload.impersonatedUserId - The ID of the user being impersonated. - * @returns {string} - The generated JWT for impersonation, signed using RS256 algorithm. - */ -const generateImpersonationAuthToken = (payload) => { - return jwt.sign(payload, config.get("userToken.privateKey"), { - algorithm: "RS256", - expiresIn: config.get("userToken.impersonationTtl"), - }); -}; - /** * Verifies if the JWT is valid. Throws error in case of signature error or expiry * @@ -51,5 +36,4 @@ module.exports = { generateAuthToken, verifyAuthToken, decodeAuthToken, - generateImpersonationAuthToken, }; diff --git a/services/botVerificationService.js b/services/botVerificationService.js index fdf8ca338..2f8f96cd9 100644 --- a/services/botVerificationService.js +++ b/services/botVerificationService.js @@ -10,16 +10,6 @@ const verifyToken = (token) => { return jwt.verify(token, config.get("botToken.botPublicKey"), { algorithms: ["RS256"] }); }; -/** - * Verifies if the JWT for Discord Service is valid. Throws error in case of signature error or expiry - * - * @param token {String} - JWT to be verified - * @return {Object} - Decode value of JWT - */ -const verifyDiscordService = (token) => { - return jwt.verify(token, config.get("discordService.publicKey"), { algorithms: ["RS256"] }); -}; - /** * Verifies if the JWT is valid. Throws error in case of signature error or expiry * @@ -30,4 +20,4 @@ const verifyCronJob = (token) => { return jwt.verify(token, config.get("cronJobHandler.publicKey"), { algorithms: ["RS256"] }); }; -module.exports = { verifyToken, verifyCronJob, verifyDiscordService }; +module.exports = { verifyToken, verifyCronJob }; diff --git a/services/oooRequest.ts b/services/oooRequest.ts index 2e2f35ab7..9a3c24110 100644 --- a/services/oooRequest.ts +++ b/services/oooRequest.ts @@ -5,12 +5,22 @@ import { REQUEST_LOG_TYPE, REQUEST_STATE, USER_STATUS_NOT_FOUND, + REQUEST_TYPE, + REQUEST_ALREADY_APPROVED, + REQUEST_ALREADY_REJECTED, + REQUEST_APPROVED_SUCCESSFULLY, + REQUEST_REJECTED_SUCCESSFULLY, + 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 { BadRequest, Conflict } from "http-errors"; +import { updateRequest } from "../models/requests"; +import { AcknowledgeOooRequestBody } from "../types/oooRequest"; +import { addFutureStatus } from "../models/userStatus"; /** * Validates the user status. @@ -93,3 +103,101 @@ export const createOooRequest = async ( throw error; } } + +/** + * Validates an Out-Of-Office (OOO) acknowledge request + * + * @param {string} requestId - The unique identifier of the request. + * @param {string} requestType - The type of the request (expected to be 'OOO'). + * @param {string} requestStatus - The current status of the request. + * @throws {Error} Throws an error if an issue occurs during validation. + */ +export const validateOooAcknowledgeRequest = async ( + requestType: string, + requestStatus: string, +) => { + + try { + + if (requestType !== REQUEST_TYPE.OOO) { + throw new BadRequest(INVALID_REQUEST_TYPE); + } + + if (requestStatus === REQUEST_STATE.APPROVED) { + throw new Conflict(REQUEST_ALREADY_APPROVED); + } + + if (requestStatus === REQUEST_STATE.REJECTED) { + throw new Conflict(REQUEST_ALREADY_REJECTED); + } + } catch (error) { + logger.error("Error while validating OOO acknowledge request", error); + throw error; + } +} + +/** + * Acknowledges an Out-of-Office (OOO) request + * + * @param {string} requestId - The ID of the OOO request to acknowledge. + * @param {AcknowledgeOooRequestBody} body - The acknowledgement body containing acknowledging details. + * @param {string} superUserId - The unique identifier of the superuser user. + * @returns {Promise} The acknowledged OOO request. + * @throws {Error} Throws an error if an issue occurs during acknowledgment process. + */ +export const acknowledgeOooRequest = async ( + requestId: string, + body: AcknowledgeOooRequestBody, + superUserId: string, +) => { + try { + const requestData = await getRequestById(requestId); + + await validateOooAcknowledgeRequest(requestData.type, requestData.status); + + const requestResult = await updateRequest(requestId, body, superUserId, REQUEST_TYPE.OOO); + + if ("error" in requestResult) { + throw new 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, + action: LOG_ACTION.UPDATE, + userId: superUserId, + }, + body: requestResult, + }; + + await addLog(requestLog.type, requestLog.meta, requestLog.body); + + if (requestResult.status === REQUEST_STATE.APPROVED) { + await addFutureStatus({ + requestId, + state: REQUEST_TYPE.OOO, + from: requestData.from, + endsOn: requestData.until, + userId: requestData.userId, + message: body.comment, + }); + } + + return { + message: returnMessage, + data: { + id: requestResult.id, + ...requestResult, + }, + }; + } catch (error) { + logger.error("Error while acknowledging OOO request", error); + throw error; + } +} \ No newline at end of file diff --git a/test/config/test.js b/test/config/test.js index d28f93dc0..824800679 100644 --- a/test/config/test.js +++ b/test/config/test.js @@ -87,7 +87,6 @@ module.exports = { cookieV2Name: `rds-session-v2-${NODE_ENV}`, ttl: 30 * 24 * 60 * 60, // in seconds refreshTtl: 180 * 24 * 60 * 60, // in seconds - impersonationTtl: 15 * 60, // in seconds publicKey: "-----BEGIN PUBLIC KEY-----\n" + "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHo6sGbw8qk+XU9sBVa4w2aEq01i\n" + @@ -154,47 +153,6 @@ module.exports = { "-----END RSA PRIVATE KEY-----", }, - discordService: { - publicKey: - "-----BEGIN PUBLIC KEY-----\n" + - "MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBK3CkprcpAYxme7vtdjpWO\n" + - "gFFjoYsqU3OmhMEty/s1gnW5tgbK4ief4xk+cU+mu3YvjzWudT/SV17tAWxL4Y+G\n" + - "incJwL5gpQwlnw9qOAdRGkpBriQLec7kNVIydZXbUitziy+iSimxNzdDmjvlK9ZG\n" + - "miVLZm+MePbUtgaIpfgd+4bRWzudlITiNmWY7HppLzyBw+037iEICM4kwPPFI+SO\n" + - "GJhpAAmD6vk0MeZk1NeQmyQp/uOPpWmVRzgyK+XVc6AwZHV+/n6xAIT91/DjJlD1\n" + - "N+nS7Sqo3RJ04+KlNRUclzINOC7JBYkKtG7YQ0U9nNLkRrRlON+O6tY4OT86T1O1\n" + - "AgMBAAE=\n" + - "-----END PUBLIC KEY-----", - privateKey: - "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIIEoQIBAAKCAQBK3CkprcpAYxme7vtdjpWOgFFjoYsqU3OmhMEty/s1gnW5tgbK\n" + - "4ief4xk+cU+mu3YvjzWudT/SV17tAWxL4Y+GincJwL5gpQwlnw9qOAdRGkpBriQL\n" + - "ec7kNVIydZXbUitziy+iSimxNzdDmjvlK9ZGmiVLZm+MePbUtgaIpfgd+4bRWzud\n" + - "lITiNmWY7HppLzyBw+037iEICM4kwPPFI+SOGJhpAAmD6vk0MeZk1NeQmyQp/uOP\n" + - "pWmVRzgyK+XVc6AwZHV+/n6xAIT91/DjJlD1N+nS7Sqo3RJ04+KlNRUclzINOC7J\n" + - "BYkKtG7YQ0U9nNLkRrRlON+O6tY4OT86T1O1AgMBAAECggEAAhInHV0ObEuRiOEJ\n" + - "mSP5pTCNj9kHNYuLdn7TrUWoVGmgghu0AmbRO84Xg6+0yWMEOPqYPJRHyLTcDmhs\n" + - "q4i45Lrt4hov6hKGzH+i+IhGQ4sbpMeBfcPH4m5LMNQp6iBSzWZ7Ud0FXD6vy7H3\n" + - "mDZnPhrDj1ttGJC8G1RRx/P3cjTccU3lsae6wNjkXaSveWGgPS3m0x95eOPPwa2C\n" + - "KvVLx+kYr2r0uLF5vHN6H9uWqUTWo1GVX3nO+obapYbtcIqCbGQI4eTkvgq0qG7J\n" + - "Nh5IwYJz0bzYFfSQSRwRz9JaCzFRiP55aZnJgk2um5JdbxYCHpw5E1NV/7OsPXlE\n" + - "e4vGHQKBgQCSD/ZQu/1TeyqBF8RRdl9YtOhVAFJDiHTPFNNz9V8eak+x6hFOOGOf\n" + - "QHnbg0X4meYuilaBwXiEsSswPuVAW87VnRHrR2yyyC8knCMcvii3g9q+ed0+ri2+\n" + - "cslDPaDkcvl98qoZEfv/lk7BA7jPFToLMNfNdoHrZXVezZxetVbsuwKBgQCDNJFB\n" + - "XDxXlkIVkT8ozD/qvyQsDXz/wlOob6AkY0J7IdND5jPCi799Q1O1H7pJu50cAi+O\n" + - "ar5EuFxA8TfTKJnIVJBZFrN0O1un86WhCvB8PjgguxqtmJlEPVveiZXnTTfvXVeq\n" + - "G6+3eU/yRw9VDX61iidbWNc+SbMJ9sFQPKNyTwKBgFoaFqx/CyqwU+wGqUhHaVHj\n" + - "Z17oL9cRGl2UT0y9FMxCcJ8j8UD7cBkRQRq0xDkzVtdm5y5sFthkImxEoE8vU0xa\n" + - "9G7bRKaU7t/6oX5dn+h1Ij9WFbFQ6U8OqDEel13Vvyp+w4drnLRyGGrgzOSSB5hX\n" + - "rQhGDqcTk2/EDq4t1015AoGAWDnv9vhz5x22AFS0GNYHoO25ABpt1Hmy0Y+GKxHH\n" + - "8Y6URpM0ePyJ3kx4rFHSbaRICD58BhNHMGScPFs4A7jIeApNKmr2bxE/F9fhp0H4\n" + - "5kLccT3/uX3kihuMfD8eWvP0yfOFcHC/nutnU+5uo+24J5Dn2CgMTOk4CFoyMack\n" + - "7UcCgYBHdbFcXWGHfEqLJZChRrKhWLxn9jkJ0apvnO1j6c5yiAo3yJkSV5Z9IdAc\n" + - "lgOC/dJBTZLcBtixdERqcJ+o4P7oFRS6hz/9n4s+kkzxXVqEmtJmBQvHUo3I/Qgc\n" + - "Ba+XMCP64pXPC3r1llhKRwIl+6UFn+QlpbxtgQjhbULnSbc7fw==\n" + - "-----END RSA PRIVATE KEY-----", - }, - rdsServerlessBot: { rdsServerLessPublicKey: "-----BEGIN PUBLIC KEY-----\n" + 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/fixtures/tasks/tasks.js b/test/fixtures/tasks/tasks.js index a6389d73c..ed8532c81 100644 --- a/test/fixtures/tasks/tasks.js +++ b/test/fixtures/tasks/tasks.js @@ -158,83 +158,5 @@ module.exports = () => { createdAt: 1644753600, updatedAt: 1644753600, }, - { - title: "Overdue task", - purpose: "testing for tasks tests", - type: "feature", - assignee: "anuj-chhikara", - createdBy: "ankush", - createdAt: 1644753600, - updatedAt: 1644753600, - status: "COMPLETED", - percentCompleted: 100, - endsOn: 1647172800, - startedOn: 1644753600, - }, - { - title: "Overdue task", - purpose: "testing for tasks tests", - type: "feature", - assignee: "anuj-chhikara", - createdBy: "ankush", - createdAt: 1644753600, - updatedAt: 1644753600, - status: "DONE", - percentCompleted: 100, - endsOn: 1647172800, - startedOn: 1644753600, - }, - { - title: "Overdue task", - purpose: "testing for tasks tests", - type: "feature", - assignee: "anuj-chhikara", - createdBy: "ankush", - createdAt: 1644753600, - updatedAt: 1644753600, - status: "VERIFIED", - percentCompleted: 100, - endsOn: 1647172800, - startedOn: 1644753600, - }, - { - title: "Overdue task", - purpose: "testing for tasks tests", - type: "feature", - assignee: "anuj-chhikara", - createdBy: "ankush", - createdAt: 1644753600, - updatedAt: 1644753600, - status: "BACKLOG", - percentCompleted: 20, - endsOn: 1647172800, - startedOn: 1644753600, - }, - { - title: "Overdue task", - purpose: "testing for tasks tests", - type: "feature", - assignee: "anuj-chhikara", - createdBy: "ankush", - createdAt: 1644753600, - updatedAt: 1644753600, - status: "AVAILABLE", - percentCompleted: 0, - endsOn: 1647172800, - startedOn: 1644753600, - }, - { - title: "Overdue task", - purpose: "testing for tasks tests", - type: "feature", - assignee: "anuj-chhikara", - createdBy: "ankush", - createdAt: 1644753600, - updatedAt: 1644753600, - status: "APPROVED", - percentCompleted: 80, - endsOn: 1647172800, - startedOn: 1644753600, - }, ]; }; diff --git a/test/integration/discordactions.test.js b/test/integration/discordactions.test.js index ac5c7af1a..d5604897f 100644 --- a/test/integration/discordactions.test.js +++ b/test/integration/discordactions.test.js @@ -952,8 +952,7 @@ describe("Discord actions", function () { }); }); - // eslint-disable-next-line mocha/no-skipped-tests - describe.skip("GET /discord-actions/invite", function () { + describe("GET /discord-actions/invite", function () { it("should return the invite for the user if no userId is provided in the params and the invite exists", async function () { await addInviteToInviteModel({ userId: superUserId, inviteLink: "discord.gg/apQYT7HB" }); @@ -995,8 +994,7 @@ describe("Discord actions", function () { }); // <------ Will revisit this later https://github.com/Real-Dev-Squad/website-backend/issues/2078 ---> - // eslint-disable-next-line mocha/no-skipped-tests - describe.skip("POST /discord-actions/invite", function () { + describe("POST /discord-actions/invite", function () { afterEach(function () { sinon.restore(); }); 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/integration/users.test.js b/test/integration/users.test.js index 28749bad0..8ba547d3f 100644 --- a/test/integration/users.test.js +++ b/test/integration/users.test.js @@ -10,7 +10,6 @@ const profileDiffs = require("../../models/profileDiffs"); const cleanDb = require("../utils/cleanDb"); // Import fixtures const userData = require("../fixtures/user/user")(); -const tasksData = require("../fixtures/tasks/tasks")(); const profileDiffData = require("../fixtures/profileDiffs/profileDiffs")(); const superUser = userData[4]; const searchParamValues = require("../fixtures/user/search")(); @@ -27,7 +26,7 @@ const { const { addJoinData, addOrUpdate } = require("../../models/users"); const userStatusModel = require("../../models/userStatus"); const { MAX_USERNAME_LENGTH } = require("../../constants/users.ts"); -const { TASK_STATUS } = require("../../constants/tasks"); + const userRoleUpdate = userData[4]; const userRoleUnArchived = userData[13]; const userAlreadyMember = userData[0]; @@ -36,6 +35,7 @@ const userAlreadyArchived = userData[5]; const userAlreadyUnArchived = userData[4]; const nonSuperUser = userData[0]; const newUser = userData[18]; + const cookieName = config.get("userToken.cookieName"); const { userPhotoVerificationData } = require("../fixtures/user/photo-verification"); const Sinon = require("sinon"); @@ -497,18 +497,12 @@ describe("Users", function () { }); describe("GET /users", function () { - let userWithOverdueApprovedTask; - beforeEach(async function () { const { userId } = await addOrUpdate(userData[0]); await userStatusModel.updateUserStatus(userId, userStatusDataForNewUser); await addOrUpdate(userData[1]); await addOrUpdate(userData[2]); await addOrUpdate(userData[3]); - - const assigneeData = { ...userData[6], discordId: getDiscordMembers[0].user.id }; - userWithOverdueApprovedTask = await addUser(assigneeData); - await taskModel.add({ ...tasksData[0], assignee: userWithOverdueApprovedTask, status: TASK_STATUS.APPROVED }); }); afterEach(async function () { @@ -884,24 +878,6 @@ describe("Users", function () { expect(res.body.message).to.equal("User not found"); }); - it("should return users who have overdue tasks with APPROVED status", function (done) { - chai - .request(app) - .get("/users?query=filterBy:overdue_tasks") - .end((err, res) => { - if (err) { - return done(err); - } - expect(res).to.have.status(200); - expect(res.body).to.be.an("object"); - expect(res.body.users).to.be.an("array"); - expect(res.body.users.length).to.equal(1); - expect(res.body.users[0].id).to.equal(userWithOverdueApprovedTask); - - return done(); - }); - }); - it("Should return user ID(s) with overdue tasks within the last 1 day", function (done) { chai .request(app) diff --git a/test/unit/middlewares/authenticate.test.js b/test/unit/middlewares/authenticate.test.js index 6e6f37a97..acb011a07 100644 --- a/test/unit/middlewares/authenticate.test.js +++ b/test/unit/middlewares/authenticate.test.js @@ -113,88 +113,4 @@ describe("Authentication Middleware", function () { expect(nextSpy.notCalled).to.equal(true); }); }); - - describe("Impersonation and Refresh Logic", function () { - it("should allow impersonation and set userData of impersonated user", async function () { - const user = { id: "user123", roles: { restricted: false } }; - - const verifyAuthTokenStub = Sinon.stub(authService, "verifyAuthToken").returns({ - userId: "admin", - impersonatedUserId: user.id, - }); - - const retrieveUsersStub = Sinon.stub(dataAccess, "retrieveUsers").resolves({ user }); - - req.cookies = { - [config.get("userToken.cookieName")]: "validToken", - }; - - req.method = "GET"; - req.baseUrl = "/impersonation"; - req.path = "/abc123"; - req.query = {}; - - await authMiddleware(req, res, nextSpy); - - expect(verifyAuthTokenStub.calledOnce).to.equal(true); - expect(retrieveUsersStub.calledOnce).to.equal(true); - - expect(req.userData.id).to.equal(user.id); - expect(req.isImpersonating).to.equal(true); - expect(nextSpy.calledOnce).to.equal(true); - expect(res.boom.unauthorized.notCalled).to.equal(true); - expect(res.boom.forbidden.notCalled).to.equal(true); - }); - - it("should restrict all write/update type requests during impersonation", async function () { - req.method = "POST"; - req.baseUrl = "/impersonation"; - req.path = "/abc123"; - req.query = {}; - - Sinon.stub(authService, "verifyAuthToken").returns({ userId: "admin", impersonatedUserId: "impUser" }); - Sinon.stub(dataAccess, "retrieveUsers").resolves({ user: { id: "impUser", roles: {} } }); - - await authMiddleware(req, res, nextSpy); - - expect(req.isImpersonating).to.equal(true); - expect(res.boom.forbidden.calledOnce).to.equal(true); - expect(res.boom.forbidden.firstCall.args[0]).to.include("Only viewing is permitted"); - }); - - it("should allow PATCH STOP request during impersonation", async function () { - req.method = "PATCH"; - req.baseUrl = "/impersonation"; - req.path = "/randomId"; - req.query = { action: "STOP" }; - - Sinon.stub(authService, "verifyAuthToken").returns({ userId: "admin", impersonatedUserId: "impUser" }); - Sinon.stub(dataAccess, "retrieveUsers").resolves({ user: { id: "impUser", roles: {} } }); - - await authMiddleware(req, res, nextSpy); - - expect(req.isImpersonating).to.equal(true); - expect(nextSpy.calledOnce).to.equal(true); - }); - - it("should refresh token if expired and within TTL", async function () { - const now = Math.floor(Date.now() / 1000); - req.cookies[config.get("userToken.cookieName")] = "expiredToken"; - - Sinon.stub(authService, "verifyAuthToken").throws({ name: "TokenExpiredError" }); - Sinon.stub(authService, "decodeAuthToken").returns({ - userId: "user123", - impersonatedUserId: "impUserId", - iat: now - 10, - }); - Sinon.stub(authService, "generateAuthToken").returns("newToken"); - Sinon.stub(dataAccess, "retrieveUsers").resolves({ user: { id: "user123", roles: {} } }); - - await authMiddleware(req, res, nextSpy); - - expect(res.cookie.calledOnce).to.equal(true); - expect(res.cookie.firstCall.args[1]).to.equal("newToken"); - expect(nextSpy.calledOnce).to.equal(true); - }); - }); }); diff --git a/test/unit/middlewares/authorizeBot.test.js b/test/unit/middlewares/authorizeBot.test.js index 27d3be684..f986d5d4b 100644 --- a/test/unit/middlewares/authorizeBot.test.js +++ b/test/unit/middlewares/authorizeBot.test.js @@ -2,14 +2,7 @@ const authorizeBot = require("../../../middlewares/authorizeBot"); const sinon = require("sinon"); const expect = require("chai").expect; const bot = require("../../utils/generateBotToken"); -const jwt = require("jsonwebtoken"); -const { - BAD_TOKEN, - CLOUDFLARE_WORKER, - CRON_JOB_HANDLER, - DISCORD_SERVICE, - DiscordServiceHeader, -} = require("../../../constants/bot"); +const { BAD_TOKEN, CLOUDFLARE_WORKER, CRON_JOB_HANDLER } = require("../../../constants/bot"); describe("Middleware | Authorize Bot", function () { describe("Check authorization of bot", function (done) { @@ -122,124 +115,4 @@ describe("Middleware | Authorize Bot", function () { expect(nextSpy.calledOnce).to.be.equal(true); }); }); - - describe("Check authorization for discord service", function () { - let nextSpy, boomBadRequestSpy, boomUnauthorizedSpy; - - beforeEach(function () { - nextSpy = sinon.spy(); - boomBadRequestSpy = sinon.spy(); - boomUnauthorizedSpy = sinon.spy(); - }); - - afterEach(function () { - sinon.restore(); - }); - - it("should return unauthorized when token is invalid for discord service", function () { - const jwtStub = sinon.stub(jwt, "verify").throws(new Error("invalid token")); - - const request = { - headers: { - authorization: `Bearer ${BAD_TOKEN}`, - [DiscordServiceHeader.name]: DISCORD_SERVICE, - }, - }; - - const response = { - boom: { - unauthorized: boomUnauthorizedSpy, - }, - }; - - authorizeBot.verifyDiscordBot(request, response, nextSpy); - - expect(nextSpy.calledOnce).to.be.equal(false); - expect(boomUnauthorizedSpy.calledOnce).to.be.equal(true); - - jwtStub.restore(); - }); - - it("should return bad request when passing bad token in header for discord service", function () { - const request = { - headers: { - authorization: `Bearer BAD_TOKEN`, - [DiscordServiceHeader.name]: DISCORD_SERVICE, - }, - }; - - const response = { - boom: { - badRequest: boomBadRequestSpy, - }, - }; - - authorizeBot.verifyDiscordBot(request, response, nextSpy); - expect(nextSpy.calledOnce).to.be.equal(false); - expect(boomBadRequestSpy.calledOnce).to.be.equal(true); - }); - - it("should allow request propagation when token is valid for discord service", function () { - const jwtToken = bot.generateDiscordServiceToken({ name: DISCORD_SERVICE }); - const request = { - headers: { - authorization: `Bearer ${jwtToken}`, - [DiscordServiceHeader.name]: DISCORD_SERVICE, - }, - }; - - authorizeBot.verifyDiscordBot(request, {}, nextSpy); - expect(nextSpy.calledOnce).to.be.equal(true); - }); - - it("should allow request propagation when token is valid for cloudflare worker", function () { - const jwtToken = bot.generateToken({ name: CLOUDFLARE_WORKER }); - const request = { - headers: { - authorization: `Bearer ${jwtToken}`, - }, - }; - - authorizeBot.verifyDiscordBot(request, {}, nextSpy); - expect(nextSpy.calledOnce).to.be.equal(true); - }); - - it("should return unauthorized when token is valid but not for discord service", function () { - const jwtToken = bot.generateDiscordServiceToken({ name: "Invalid" }); - const request = { - headers: { - authorization: `Bearer ${jwtToken}`, - [DiscordServiceHeader.name]: DISCORD_SERVICE, - }, - }; - - const response = { - boom: { - unauthorized: boomUnauthorizedSpy, - }, - }; - - authorizeBot.verifyDiscordBot(request, response, nextSpy); - expect(nextSpy.calledOnce).to.be.equal(false); - expect(boomUnauthorizedSpy.calledOnce).to.be.equal(true); - }); - - it("should return unauthorized when token is invalid for cloudflare worker", function () { - const jwtToken = bot.generateToken({ name: "Invalid" }); - const request = { - headers: { - authorization: `Bearer ${jwtToken}`, - }, - }; - - const response = { - boom: { - unauthorized: boomUnauthorizedSpy, - }, - }; - authorizeBot.verifyDiscordBot(request, response, nextSpy); - expect(nextSpy.calledOnce).to.be.equal(false); - expect(boomUnauthorizedSpy.calledOnce).to.be.equal(true); - }); - }); }); diff --git a/test/unit/middlewares/oooRequests.test.ts b/test/unit/middlewares/oooRequests.test.ts index 11272e860..e369859c9 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, + acknowledgeOooRequest, } 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 acknowledgeOooRequest(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 acknowledgeOooRequest(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 acknowledgeOooRequest(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 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); + }); + }); + }); }); diff --git a/test/unit/models/tasks.test.js b/test/unit/models/tasks.test.js index 0041c45e7..dc66a228b 100644 --- a/test/unit/models/tasks.test.js +++ b/test/unit/models/tasks.test.js @@ -301,39 +301,18 @@ describe("tasks", function () { await cleanDb(); }); - it("should exclude overdue tasks with COMPLETED, DONE, VERIFIED, BACKLOG, and AVAILABLE statuses", async function () { - await tasks.updateTask(tasksData[8]); - await tasks.updateTask(tasksData[11]); - await tasks.updateTask(tasksData[12]); - await tasks.updateTask(tasksData[9]); - await tasks.updateTask(tasksData[10]); - - const result = await tasks.getOverdueTasks(); - - const statuses = result.map((task) => task.status); - expect(statuses).to.not.include.members([ - TASK_STATUS.COMPLETED, - TASK_STATUS.BACKLOG, - TASK_STATUS.AVAILABLE, - TASK_STATUS.DONE, - TASK_STATUS.VERIFIED, - ]); - }); - it("should return the overdue tasks for the given days", async function () { const days = 10; const overdueTask = { ...tasksData[0] }; overdueTask.endsOn = Date.now() / 1000 + 24 * 60 * 60 * 7; await tasks.updateTask(overdueTask); const usersWithOverdueTasks = await tasks.getOverdueTasks(days); - expect(usersWithOverdueTasks.length).to.be.equal(6); + expect(usersWithOverdueTasks.length).to.be.equal(5); }); it("should return all users which have overdue tasks if days is not passed", async function () { - await tasks.updateTask(tasksData[12]); - const usersWithOverdueTasks = await tasks.getOverdueTasks(); - expect(usersWithOverdueTasks.length).to.be.equal(5); + expect(usersWithOverdueTasks.length).to.be.equal(4); }); }); @@ -357,8 +336,8 @@ describe("tasks", function () { it("Should update task status COMPLETED to DONE", async function () { const res = await tasks.updateTaskStatus(); - expect(res.totalTasks).to.be.equal(15); - expect(res.totalUpdatedStatus).to.be.equal(15); + expect(res.totalTasks).to.be.equal(9); + expect(res.totalUpdatedStatus).to.be.equal(9); }); it("should throw an error if firebase batch operation fails", async function () { diff --git a/test/unit/services/authService.test.js b/test/unit/services/authService.test.js index 4ddac22ad..fbadbabcb 100644 --- a/test/unit/services/authService.test.js +++ b/test/unit/services/authService.test.js @@ -28,30 +28,4 @@ describe("authService", function () { return done(); }); - - describe("generateImpersonationAuthToken", function () { - const payload = { userId: "devUser123", impersonatedUserId: "testUser456" }; - - it("should generate a valid JWT with correct payload", function (done) { - const jwtToken = authService.generateImpersonationAuthToken(payload); - const decoded = authService.verifyAuthToken(jwtToken); // Assuming verifyAuthToken uses the same public key - - expect(decoded).to.have.all.keys("userId", "impersonatedUserId", "iat", "exp"); - expect(decoded.userId).to.equal(payload.userId); - expect(decoded.impersonatedUserId).to.equal(payload.impersonatedUserId); - - return done(); - }); - - it("should decode the impersonation JWT without verifying", function (done) { - const jwtToken = authService.generateImpersonationAuthToken(payload); - const decoded = authService.decodeAuthToken(jwtToken); // No signature verification - - expect(decoded).to.have.all.keys("userId", "impersonatedUserId", "iat", "exp"); - expect(decoded.userId).to.equal(payload.userId); - expect(decoded.impersonatedUserId).to.equal(payload.impersonatedUserId); - - return done(); - }); - }); }); diff --git a/test/unit/services/oooRequest.test.ts b/test/unit/services/oooRequest.test.ts index eb63242b6..a38db65d2 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,8 +25,11 @@ 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"; +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() { @@ -111,7 +114,7 @@ describe("Test OOO Request Service", function() { }); }); - describe.skip("validateOOOAcknowledgeRequest", function() { + describe("validateOooAcknowledgeRequest", function() { let testOooRequest; @@ -125,46 +128,45 @@ 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); + await validateOooAcknowledgeRequest( + REQUEST_TYPE.ONBOARDING, + testOooRequest.status + ).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, - // testOooRequest.type, - // REQUEST_STATE.APPROVED - // ); - // expect(validationResponse.error).to.be.not.undefined; - // expect(validationResponse.error).to.equal(REQUEST_ALREADY_APPROVED); + await validateOooAcknowledgeRequest( + testOooRequest.type, + REQUEST_STATE.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, - // testOooRequest.type, - // REQUEST_STATE.REJECTED - // ); - // expect(validationResponse.error).to.be.not.undefined; - // expect(validationResponse.error).to.equal(REQUEST_ALREADY_REJECTED); + await validateOooAcknowledgeRequest( + testOooRequest.type, + REQUEST_STATE.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 - // ); - // expect(response).to.not.exist; + const response = await validateOooAcknowledgeRequest( + testOooRequest.type, + testOooRequest.status + ); + expect(response).to.not.exist; }); }); - describe.skip("acknowledgeOOORequest", function() { + describe("acknowledgeOooRequest", function() { let testSuperUserId; let testOooRequest; @@ -183,64 +185,81 @@ 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); + sinon.stub(requestModel, "getRequestById").throws(new NotFound(REQUEST_DOES_NOT_EXIST)); + await acknowledgeOooRequest( + "11111111111111111111", + testAcknowledgeOooRequest, + testSuperUserId + ).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() { - // 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 - // } - // }); + 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.message).to.equal(REQUEST_APPROVED_SUCCESSFULLY); + expect(response.data.status).to.equal(REQUEST_STATE.APPROVED); }); 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 - // } - // }); + 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.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"); - - // try { - // await acknowledgeOOORequest( - // testOooRequest.id, - // acknowledgeOooRequest, - // testSuperUserId - // ); - // } catch (error) { - // expect(error.message).to.equal(errorMessage); - // expect(createSpy.calledOnce).to.be.true; - // } + sinon.stub(requestModel, "getRequestById").throws(new Error(errorMessage)); + try { + await acknowledgeOooRequest( + testOooRequest.id, + testAcknowledgeOooRequest, + testSuperUserId + ); + } catch (error) { + expect(error.message).to.equal(errorMessage); + } }); }); }); \ No newline at end of file diff --git a/test/unit/services/tasks.test.js b/test/unit/services/tasks.test.js index 7fb6d0a3e..39cd70651 100644 --- a/test/unit/services/tasks.test.js +++ b/test/unit/services/tasks.test.js @@ -53,7 +53,7 @@ describe("Tasks services", function () { const res = await updateTaskStatusToDone(tasks); expect(res).to.deep.equal({ - totalUpdatedStatus: 15, + totalUpdatedStatus: 9, totalOperationsFailed: 0, updatedTaskDetails: taskDetails, failedTaskDetails: [], @@ -73,7 +73,7 @@ describe("Tasks services", function () { expect(res).to.deep.equal({ totalUpdatedStatus: 0, - totalOperationsFailed: 15, + totalOperationsFailed: 9, updatedTaskDetails: [], failedTaskDetails: taskDetails, }); diff --git a/test/utils/generateBotToken.js b/test/utils/generateBotToken.js index f112c6161..11259058a 100644 --- a/test/utils/generateBotToken.js +++ b/test/utils/generateBotToken.js @@ -13,19 +13,6 @@ const generateToken = (data) => { }); }; -/** - * Generates the JWT - * - * @param payload {Object} - Payload to be added in the JWT - * @return {String} - Generated JWT - */ -const generateDiscordServiceToken = (data) => { - return jwt.sign(data, config.get("discordService.privateKey"), { - algorithm: "RS256", - expiresIn: "1m", - }); -}; - const generateCronJobToken = (data) => { const token = jwt.sign(data, config.get("cronJobHandler.privateKey"), { algorithm: "RS256", @@ -34,4 +21,4 @@ const generateCronJobToken = (data) => { return token; }; -module.exports = { generateToken, generateCronJobToken, generateDiscordServiceToken }; +module.exports = { generateToken, generateCronJobToken }; diff --git a/types/oooRequest.d.ts b/types/oooRequest.d.ts index 6b1c282a8..865daeb94 100644 --- a/types/oooRequest.d.ts +++ b/types/oooRequest.d.ts @@ -42,3 +42,20 @@ export type OooRequestCreateRequest = Request & { }; export type OooRequestUpdateRequest = Request & { oooRequestUpdateBody , userData: userData , query: RequestQuery , params: RequestParams }; + +export type AcknowledgeOooRequestQuery = RequestQuery & { + dev?: string +}; + +export type AcknowledgeOooRequestBody = { + type: REQUEST_TYPE.OOO; + comment?: string; + status: REQUEST_STATE.APPROVED | REQUEST_STATE.REJECTED; +} + +export type AcknowledgeOooRequest = Request & { + body: AcknowledgeOooRequestBody; + userData: userData; + query: AcknowledgeOooRequestQuery; + params: RequestParams; +}