generated from Real-Dev-Squad/website-template
-
Notifications
You must be signed in to change notification settings - Fork 264
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Middleware for Migrating Task Creation Requests to /request API E…
…ndpoint (#2053) * feat: added middleware for TCR with test * test: fix failing tests * refactor: removed console log from middleware
- Loading branch information
1 parent
2f141ff
commit 529d73d
Showing
9 changed files
with
253 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import joi from "joi"; | ||
import { TaskRequestResponse, TaskRequestRequest } from "../../types/taskRequests"; | ||
import { NextFunction } from "express"; | ||
import { REQUEST_TYPE, REQUEST_STATE } from "../../constants/requests"; | ||
import { GITHUB_URL } from "../../constants/urls"; | ||
|
||
import config from "config"; | ||
import { TASK_REQUEST_TYPE } from "../../constants/taskRequests"; | ||
const githubOrg = config.get("githubApi.org"); | ||
const githubBaseUrl = config.get("githubApi.baseUrl"); | ||
const githubIssuerUrlPattern = new RegExp(`^${githubBaseUrl}/repos/${githubOrg}/.+/issues/\\d+$`); | ||
const githubIssueHtmlUrlPattern = new RegExp(`^${GITHUB_URL}/${githubOrg}/.+/issues/\\d+$`); // Example: https://github.com/Real-Dev-Squad/website-status/issues/1050 | ||
|
||
export const createTaskRequestValidator = async ( | ||
req: TaskRequestRequest, | ||
res: TaskRequestResponse, | ||
next: NextFunction | ||
) => { | ||
const schema = joi | ||
.object() | ||
.strict() | ||
.keys({ | ||
requestType: joi.string().valid(TASK_REQUEST_TYPE.CREATION, TASK_REQUEST_TYPE.ASSIGNMENT).required().messages({ | ||
"string.empty": "requestType cannot be empty", | ||
"any.required": "requestType is required", | ||
}), | ||
externalIssueUrl: joi.string().required().regex(githubIssuerUrlPattern).required().messages({ | ||
"string.empty": "externalIssueUrl cannot be empty", | ||
"any.required": "externalIssueUrl is required", | ||
}), | ||
externalIssueHtmlUrl: joi.string().required().regex(githubIssueHtmlUrlPattern).messages({ | ||
"string.empty": "externalIssueHtmlUrl cannot be empty", | ||
"any.required": "externalIssueHtmlUrl is required", | ||
}), | ||
type: joi.string().valid(REQUEST_TYPE.TASK).required().messages({ | ||
"string.empty": "type cannot be empty", | ||
"any.required": "type is required", | ||
}), | ||
state: joi.string().valid(REQUEST_STATE.PENDING).required().messages({ | ||
"string.empty": "state cannot be empty", | ||
"any.required": "state is required", | ||
}), | ||
proposedStartDate: joi.number().required().messages({ | ||
"number.base": "proposedStartDate must be a number", | ||
"any.required": "proposedStartDate is required", | ||
}), | ||
proposedDeadline: joi.number().required().greater(joi.ref("proposedStartDate")). | ||
messages({ | ||
"number.base": "proposedDeadline must be a number", | ||
"any.required": "proposedDeadline is required", | ||
}), | ||
description: joi.string().optional().messages({ | ||
"string.empty": "description cannot be empty", | ||
}), | ||
markdownEnabled: joi.boolean().optional().messages({ | ||
"boolean.base": "markdownEnabled must be a boolean", | ||
}), | ||
taskId: joi.when('requestType', { | ||
is: TASK_REQUEST_TYPE.ASSIGNMENT, | ||
then: joi.string().required().messages({ | ||
"string.empty": "taskId cannot be empty", | ||
"any.required": "taskId is required when requestType is ASSIGNMENT", | ||
}), | ||
otherwise: joi.forbidden() | ||
}), | ||
userId: joi.when('requestType', { | ||
is: TASK_REQUEST_TYPE.CREATION, | ||
then: joi.string().required().messages({ | ||
"string.empty": "userId cannot be empty", | ||
"any.required": "userId is required when requestType is CREATION", | ||
}), | ||
otherwise: joi.forbidden() | ||
}), | ||
}); | ||
await schema.validateAsync(req.body, { abortEarly: false }); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { REQUEST_STATE, REQUEST_TYPE } from "../../../constants/requests"; | ||
import { TASK_REQUEST_TYPE } from "../../../constants/taskRequests"; | ||
|
||
export const validTaskCreqtionRequest = { | ||
externalIssueUrl: "https://api.github.com/repos/Real-Dev-Squad/website-my/issues/599", | ||
externalIssueHtmlUrl: "https://github.com/Real-Dev-Squad/website-my/issues/599", | ||
userId: "iODXB6ns8jaZB9p0XlBw", | ||
requestType: TASK_REQUEST_TYPE.CREATION, | ||
proposedStartDate: 1718845551203, | ||
proposedDeadline: 1719450351203, | ||
description: "Task Create Description", | ||
markdownEnabled: true, | ||
state: REQUEST_STATE.PENDING, | ||
type: REQUEST_TYPE.TASK, | ||
}; | ||
|
||
export const validTaskAssignmentRequest = { | ||
externalIssueUrl: "https://api.github.com/repos/Real-Dev-Squad/website-my/issues/599", | ||
externalIssueHtmlUrl: "https://github.com/Real-Dev-Squad/website-my/issues/599", | ||
taskId: "iODXB6ns8jaZB9p0XlBw", | ||
requestType: TASK_REQUEST_TYPE.ASSIGNMENT, | ||
proposedStartDate: 1718845551203, | ||
proposedDeadline: 1719450351203, | ||
description: "Task Create Description", | ||
markdownEnabled: true, | ||
state: REQUEST_STATE.PENDING, | ||
type: REQUEST_TYPE.TASK, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import chai from "chai"; | ||
import sinon from "sinon"; | ||
const { expect } = chai; | ||
|
||
import { createTaskRequestValidator } from "./../../../middlewares/validators/taskRequests"; | ||
|
||
import { validTaskCreqtionRequest, validTaskAssignmentRequest } from "../../fixtures/taskRequests/taskRequests"; | ||
|
||
describe("Task Request Validators", function () { | ||
let req: any; | ||
let res: any; | ||
let nextSpy; | ||
beforeEach(function () { | ||
res = { | ||
boom: { | ||
badRequest: sinon.spy(), | ||
}, | ||
}; | ||
nextSpy = sinon.spy(); | ||
}); | ||
describe("createTaskRequestValidator", function () { | ||
it("should validate for a valid create request", async function () { | ||
req = { | ||
body: validTaskCreqtionRequest, | ||
}; | ||
res = {}; | ||
|
||
await createTaskRequestValidator(req as any, res as any, nextSpy); | ||
expect(nextSpy.calledOnce); | ||
}); | ||
|
||
it("should not validate for an invalid request on wrong type", async function () { | ||
req = { | ||
body: { type: "ACTIVE" }, | ||
res: {}, | ||
}; | ||
try { | ||
await createTaskRequestValidator(req as any, res as any, nextSpy); | ||
} catch (error) { | ||
expect(error).to.be.an.instanceOf(Error); | ||
expect(error.details[0].message).to.equal("requestType is required"); | ||
} | ||
}); | ||
|
||
it("should validate for varid task assignment request", async function () { | ||
req = { | ||
body: validTaskAssignmentRequest, | ||
}; | ||
res = {}; | ||
|
||
await createTaskRequestValidator(req as any, res as any, nextSpy); | ||
expect(nextSpy.calledOnce); | ||
}); | ||
|
||
it("should not validate if taskID is missing in task assignment request", async function () { | ||
req = { | ||
body: { | ||
...validTaskAssignmentRequest, | ||
taskId: undefined, | ||
}, | ||
}; | ||
try { | ||
await createTaskRequestValidator(req as any, res as any, nextSpy); | ||
} catch (error) { | ||
expect(error).to.be.an.instanceOf(Error); | ||
expect(error.details[0].message).to.equal("taskId is required when requestType is ASSIGNMENT"); | ||
} | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { REQUEST_STATE } from "./../constants/requests"; | ||
import { Request, Response } from "express"; | ||
import { Boom } from "express-boom"; | ||
import { REQUEST_STATE, REQUEST_TYPE } from "../constants/requests"; | ||
import { TASK_REQUEST_STATUS, TASK_REQUEST_TYPE } from "../constants/taskRequests"; | ||
|
||
import { userData } from "./global"; | ||
export type TaskCreationRequest = { | ||
id: string; | ||
type: REQUEST_TYPE.TASK; | ||
externalIssueUrl: string; | ||
externalIssueHtmlUrl: string; | ||
requestType: TASK_REQUEST_TYPE.CREATION | TASK_REQUEST_TYPE.ASSIGNMENT; | ||
userId?: string; | ||
taskId?: string; | ||
state: REQUEST_STATE; | ||
requestedBy?: string; | ||
proposedStartDate: number; | ||
proposedDeadline: number; | ||
description?: string; | ||
markdownEnabled?: boolean; | ||
createdAt?: Timestamp; | ||
updatedAt?: Timestamp; | ||
requesters?: string[]; | ||
lastModifiedBy?: string; | ||
approvedTo?: string; | ||
}; | ||
|
||
export type TaskCreationRequestBody = { | ||
type: REQUEST_TYPE.TASK; | ||
state: REQUEST_STATE.PENDING; | ||
externalIssueUrl: string; | ||
externalIssueHtmlUrl: string; | ||
requestType: TASK_REQUEST_TYPE.CREATION; | ||
requestedBy?: string; | ||
proposedStartDate: number; | ||
proposedDeadline: number; | ||
description?: string; | ||
markdownEnabled?: boolean; | ||
}; | ||
|
||
export type TaskCreationRequestUpdateBody = { | ||
lastModifiedBy?: string; | ||
type?: REQUEST_TYPE.TASK; | ||
id?: string; | ||
state: REQUEST_STATE.APPROVED | REQUEST_STATE.REJECTED; | ||
approvedTo?: string; | ||
}; | ||
|
||
export type RequestQuery = { | ||
dev?: string; | ||
type?: string; | ||
requestedBy?: string; | ||
state?: REQUEST_STATE.APPROVED | REQUEST_STATE.PENDING | REQUEST_STATE.REJECTED; | ||
id?: string; | ||
prev?: string; | ||
next?: string; | ||
page?: number; | ||
size?: number; | ||
}; | ||
|
||
export type TaskRequestResponse = Response & { Boom: Boom }; | ||
export type TaskRequestRequest = Request & { | ||
TaskCreationRequestBody: TaskCreationRequestBody; | ||
userData: userData; | ||
query: RequestQuery; | ||
Boom: Boom; | ||
}; |