Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions controllers/oooRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,9 @@ export const createOooRequestController = async (
if (!isUserPartOfDiscord) {
return res.boom.forbidden(UNAUTHORIZED_TO_CREATE_OOO_REQUEST);
}

try {
const userStatus = await getUserStatus(userId);
const validationResponse = await validateUserStatus(userId, userStatus);

if (validationResponse) {
if (validationResponse.error === USER_STATUS_NOT_FOUND) {
return res.boom.notFound(validationResponse.error);
Expand All @@ -70,7 +68,7 @@ export const createOooRequestController = async (
status: REQUEST_STATE.PENDING,
});

if (latestOooRequest) {
if (latestOooRequest && latestOooRequest.status === REQUEST_STATE.PENDING ) {
await addLog(logType.PENDING_REQUEST_FOUND,
{ userId, oooRequestId: latestOooRequest.id },
{ message: REQUEST_ALREADY_PENDING }
Expand Down Expand Up @@ -103,7 +101,7 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom
return res.boom.badRequest(requestResult.error);
}
const [logType, returnMessage] =
requestResult.state === REQUEST_STATE.APPROVED
requestResult.status === REQUEST_STATE.APPROVED
? [REQUEST_LOG_TYPE.REQUEST_APPROVED, REQUEST_APPROVED_SUCCESSFULLY]
: [REQUEST_LOG_TYPE.REQUEST_REJECTED, REQUEST_REJECTED_SUCCESSFULLY];

Expand All @@ -118,19 +116,19 @@ export const updateOooRequestController = async (req: UpdateRequest, res: Custom
body: requestResult,
};
await addLog(requestLog.type, requestLog.meta, requestLog.body);
if (requestResult.state === REQUEST_STATE.APPROVED) {
const requestData = await getRequests({ id: requestId });
if (requestResult.status === REQUEST_STATE.APPROVED) {
const requestData = await getRequests({ id: requestId, type: REQUEST_TYPE.OOO });

if (requestData) {
const { from, until, requestedBy, message } = requestData as any;
const { from, until, userId, reason } = requestData as any;
const userFutureStatusData = {
requestId,
status: REQUEST_TYPE.OOO,
state: statusState.UPCOMING,
from,
endsOn: until,
userId: requestedBy,
message,
userId,
message: reason,
};
await createUserFutureStatus(userFutureStatusData);
await addFutureStatus(userFutureStatusData);
Expand Down
4 changes: 4 additions & 0 deletions middlewares/validators/oooRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export const createOooStatusRequestValidator = async (
"string.empty": "type cannot be empty",
"any.required": "type is required",
}),
status: joi.string().valid(REQUEST_STATE.PENDING).required().messages({
"string.empty": "status cannot be empty",
"any.required": "status is required",
}),
});

await schema.validateAsync(req.body, { abortEarly: false });
Expand Down
26 changes: 14 additions & 12 deletions middlewares/validators/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,25 @@ export const updateRequestsMiddleware = async (
res: CustomResponse,
next: NextFunction
) => {
const schema = joi
.object()
.strict()
.keys({
const { type } = req.body;
const baseSchema = {
reason: joi.string().optional()
.messages({
"string.empty": "reason cannot be empty",
}),
state: joi
.string()
.valid(REQUEST_STATE.APPROVED, REQUEST_STATE.REJECTED)
.required()
.messages({
"any.only": "state must be APPROVED or REJECTED",
}),
type: joi.string().valid(REQUEST_TYPE.OOO, REQUEST_TYPE.EXTENSION, REQUEST_TYPE.ONBOARDING).required(),
message: joi.string().optional()
};

const schema = joi.object().strict().keys({
...baseSchema,
status: type === REQUEST_TYPE.OOO ? joi.string().valid(REQUEST_STATE.PENDING, REQUEST_STATE.APPROVED, REQUEST_STATE.REJECTED).required() : joi.string().valid(REQUEST_STATE.APPROVED, REQUEST_STATE.REJECTED).optional(),
comment: type === REQUEST_TYPE.OOO
? joi.string().required().messages({
"any.required": "comment is required for OOO requests",
"string.empty": "comment cannot be empty for OOO requests"
})
: joi.string().optional(),
state: type !== REQUEST_TYPE.OOO ? joi.string().valid(REQUEST_STATE.APPROVED, REQUEST_STATE.REJECTED).required() : joi.string().valid(REQUEST_STATE.APPROVED, REQUEST_STATE.REJECTED).optional(),
});

try {
Expand Down
15 changes: 11 additions & 4 deletions models/requests.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import firestore from "../utils/firestore";
const requestModel = firestore.collection("requests");
import { REQUEST_ALREADY_APPROVED, REQUEST_ALREADY_REJECTED, REQUEST_STATE } from "../constants/requests";
import { REQUEST_ALREADY_APPROVED, REQUEST_ALREADY_REJECTED, REQUEST_STATE, REQUEST_TYPE } from "../constants/requests";
import {
ERROR_WHILE_FETCHING_REQUEST,
ERROR_WHILE_CREATING_REQUEST,
ERROR_WHILE_UPDATING_REQUEST,
REQUEST_DOES_NOT_EXIST,
} from "../constants/requests";
import { getUserId } from "../utils/users";
import { mapOOOResponseData } from "../utils/mapOOOResponseData";
const logger = require("../utils/logger");
const SIZE = 5;

export const createRequest = async (body: any) => {
Expand Down Expand Up @@ -37,12 +39,12 @@ export const updateRequest = async (id: string, body: any, lastModifiedBy: strin
error: REQUEST_DOES_NOT_EXIST,
};
}
if (existingRequestDoc.data().state === REQUEST_STATE.APPROVED) {
if (existingRequestDoc.data().status === REQUEST_STATE.APPROVED) {
return {
error: REQUEST_ALREADY_APPROVED,
};
}
if (existingRequestDoc.data().state === REQUEST_STATE.REJECTED) {
if (existingRequestDoc.data().status === REQUEST_STATE.REJECTED) {
return {
error: REQUEST_ALREADY_REJECTED,
};
Expand Down Expand Up @@ -101,7 +103,8 @@ export const getRequests = async (query: any) => {
requestQuery = requestQuery.where("type", "==", type);
}
if (state) {
requestQuery = requestQuery.where("state", "==", state);
const fieldName = (type === REQUEST_TYPE.OOO && !dev) ? "status" : "state";
requestQuery = requestQuery.where(fieldName, "==", state);
}

requestQuery = requestQuery.orderBy("createdAt", "desc");
Expand Down Expand Up @@ -149,6 +152,10 @@ export const getRequests = async (query: any) => {
return null;
}

// because the Request document is changed and the dashboard site still expects the old data format when dev flag is off
if(type === REQUEST_TYPE.OOO && !dev){
allRequests = mapOOOResponseData(allRequests);
}
return {
allRequests,
prev: prevDoc.empty ? null : prevDoc.docs[0].id,
Expand Down
7 changes: 4 additions & 3 deletions test/fixtures/oooRequest/oooRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export const validOooStatusRequests = {
type: "OOO",
from: Date.now() + 1 * 24 * 60 * 60 * 1000,
until: Date.now() + 5 * 24 * 60 * 60 * 1000,
reason: "Out of office for personal reasons."
reason: "Out of office for personal reasons.",
status: REQUEST_STATE.PENDING,
};

export const createdOOORequest = {
Expand Down Expand Up @@ -70,8 +71,8 @@ export const updateOooRejectedRequests = {
};

export const validOooStatusUpdate ={
state: REQUEST_STATE.APPROVED,
reason: "Welcome back! Enjoy the conference.",
status: REQUEST_STATE.APPROVED,
comment: "Welcome back! Enjoy the conference.",
type:REQUEST_TYPE.OOO
}

Expand Down
2 changes: 1 addition & 1 deletion test/integration/onboardingExtension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ describe("/requests Onboarding Extension", () => {
if(err) return done(err);
expect(res.statusCode).to.equal(400);
expect(res.body.error).to.equal("Bad Request");
expect(res.body.message).to.equal('"type" must be one of [OOO, EXTENSION, ONBOARDING]');
expect(res.body.message).to.equal('Invalid request type');
done();
})
})
Expand Down
99 changes: 98 additions & 1 deletion test/integration/requests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import {
UNAUTHORIZED_TO_CREATE_OOO_REQUEST,
USER_STATUS_NOT_FOUND,
OOO_STATUS_ALREADY_EXIST,
REQUEST_FETCHED_SUCCESSFULLY,
ERROR_WHILE_FETCHING_REQUEST,
} from "../../constants/requests";
import { updateTask } from "../../models/tasks";
import { validTaskAssignmentRequest, validTaskCreqtionRequest } from "../fixtures/taskRequests/taskRequests";
Expand Down Expand Up @@ -560,7 +562,6 @@ describe("/requests OOO", function () {
done();
});
});

it("should return 400 if request is already approved", function (done) {
chai
.request(app)
Expand Down Expand Up @@ -691,7 +692,71 @@ describe("/requests OOO", function () {
done();
});
});
it("should return requests with page link when page query is passed", function (done) {
chai
.request(app)
.get("/requests?page=1")
.end(function (err, res) {
expect(res).to.have.status(200);
expect(res.body).to.have.property("message");
expect(res.body.message).to.equal(REQUEST_FETCHED_SUCCESSFULLY);
expect(res.body).to.have.property("page");
expect(res.body.page).to.equal("/requests?page=2");
done();
});
});

it("should return requests with next and prev links when cursor based pagination is used", function (done) {
chai
.request(app)
.get("/requests?next=someDocId")
.end(function (err, res) {
expect(res).to.have.status(200);
expect(res.body).to.have.property("message");
expect(res.body.message).to.equal(REQUEST_FETCHED_SUCCESSFULLY);
expect(res.body).to.have.property("next");
expect(res.body).to.have.property("prev");
done();
});
});
});



it("should return 204 if requests array is empty", function (done) {
chai
.request(app)
.get("/requests")
.end(function (err, res) {
expect(res).to.have.status(204);
done();
});
});

it("should return request when queried by id", function (done) {
chai
.request(app)
.get("/requests?id=someId")
.end(function (err, res) {
expect(res).to.have.status(200);
expect(res.body).to.have.property("message");
expect(res.body.message).to.equal(REQUEST_FETCHED_SUCCESSFULLY);
expect(res.body).to.have.property("data");
done();
});
});

it("should return 500 if error occurs while fetching requests", function (done) {
chai
.request(app)
.get("/requests")
.end(function (err, res) {
expect(res).to.have.status(500);
expect(res.body).to.have.property("message");
expect(res.body.message).to.equal(ERROR_WHILE_FETCHING_REQUEST);
done();
});
});
});

describe("/requests Extension", function () {
Expand Down Expand Up @@ -965,6 +1030,37 @@ describe("/requests Extension", function () {
});
});

it("should return 400(Bad Request) if request type is invalid", function (done) {
chai
.request(app)
.put(`/requests/${pendingExtensionRequestId}`)
.set("cookie", `${cookieName}=${superUserJwtToken}`)
.send({
type: "INVALID_TYPE",
state: REQUEST_STATE.APPROVED
})
.end(function (err, res) {
expect(res).to.have.status(400);
expect(res.body).to.have.property("message");
expect(res.body.message).to.equal("Invalid request type");
done();
});
});

it("should return 400(Bad Request) if request state is invalid", function (done) {
chai
.request(app)
.put(`/requests/${pendingExtensionRequestId}`)
.set("cookie", `${cookieName}=${superUserJwtToken}`)
.send(invalidExtensionRequest)
.end(function (err, res) {
expect(res).to.have.status(400);
expect(res.body).to.have.property("message");
expect(res.body.message).to.equal("state must be APPROVED or REJECTED");
done();
});
});

it("should return 401 if user is not super user", function (done) {
chai
.request(app)
Expand Down Expand Up @@ -1070,6 +1166,7 @@ describe("/requests Extension", function () {
done();
});
});

});
});

Expand Down
7 changes: 5 additions & 2 deletions test/unit/middlewares/oooRequests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,20 @@ describe("OOO Status Request Validators", function () {
from: null,
until: null,
reason: "",
status: "INVALID"
},
};
try {
await createOooStatusRequestValidator(req as any, res as any, nextSpy);
} catch (error) {
expect(error).to.be.an.instanceOf(Error);
expect(error.details.length).to.equal(4);
expect(error.details.length).to.equal(5);
expect(error.details[0].message).to.equal(`"from" must be a number`);
expect(error.details[1].message).to.equal(`"until" must be a number`);
expect(error.details[1].message).to.equal(`"until" must be a number`);
expect(error.details[2].message).to.equal('reason cannot be empty');
expect(error.details[3].message).to.equal('"type" must be [OOO]');
expect(error.details[4].message).to.equal('"status" must be [PENDING]');

}
});

Expand Down
22 changes: 22 additions & 0 deletions utils/mapOOOResponseData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { OooStatusRequest } from "../types/oooRequest";

/**
* @description Map the OOO response data to the desired format
* @param data - The OOO response data in the new format
* @returns The mapped OOO response data in the old format
*/
export const mapOOOResponseData = (data: OooStatusRequest[]) => {
return data.map((item) => ({
id: item.id,
from: item.from,
until: item.until,
type: item.type,
message: item.reason,
state: item.status,
reason: item.comment,
createdAt: item.createdAt,
updatedAt: item.updatedAt,
requestedBy: item.userId,
lastModifiedBy: item.lastModifiedBy,
}));
};
Loading