Skip to content

Commit 3d82b14

Browse files
authored
Merge pull request #213 from Real-Dev-Squad/dc-update
2 parents ac2507c + d8c8414 commit 3d82b14

File tree

6 files changed

+260
-1
lines changed

6 files changed

+260
-1
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { env } from "../typeDefinitions/default.types";
2+
import JSONResponse from "../utils/JsonResponse";
3+
import * as response from "../constants/responses";
4+
import { verifyNodejsBackendAuthToken } from "../utils/verifyAuthToken";
5+
import { sendTaskUpdate } from "../utils/sendTaskUpdates";
6+
import { TaskUpdates } from "../typeDefinitions/taskUpdate";
7+
import { IRequest } from "itty-router";
8+
9+
export const sendTaskUpdatesHandler = async (request: IRequest, env: env) => {
10+
const authHeader = request.headers.get("Authorization");
11+
if (!authHeader) {
12+
return new JSONResponse(response.UNAUTHORIZED, { status: 401 });
13+
}
14+
try {
15+
await verifyNodejsBackendAuthToken(authHeader, env);
16+
const updates: TaskUpdates = await request.json();
17+
const { completed, planned, blockers } = updates.content;
18+
await sendTaskUpdate(completed, planned, blockers, env);
19+
return new JSONResponse(
20+
"Task update sent on Discord's tracking-updates channel."
21+
);
22+
} catch (error: any) {
23+
return new JSONResponse({
24+
res: response.INTERNAL_SERVER_ERROR,
25+
message: error.message,
26+
status: 500,
27+
});
28+
}
29+
};

src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { getGuildMemberDetailsHandler } from "./controllers/getGuildMemberDetail
2020
import { send } from "./handlers/scheduledEventHandler";
2121
import { generateInviteLink } from "./controllers/generateDiscordInvite";
2222
import { sendProfileBlockedMessage } from "./controllers/profileHandler";
23+
import { sendTaskUpdatesHandler } from "./controllers/taskUpdatesHandler";
2324

2425
const router = Router();
2526

@@ -57,6 +58,8 @@ router.delete("/roles", removeGuildRoleHandler);
5758

5859
router.post("/profile/blocked", sendProfileBlockedMessage);
5960

61+
router.post("/task/update", sendTaskUpdatesHandler);
62+
6063
router.post("/", async (request, env, ctx: ExecutionContext) => {
6164
const message: discordMessageRequest = await request.json();
6265

@@ -83,7 +86,7 @@ export default {
8386
env: env,
8487
ctx: ExecutionContext
8588
): Promise<Response> {
86-
const apiUrls = ["/invite", "/roles", "/profile/blocked"];
89+
const apiUrls = ["/invite", "/roles", "/profile/blocked", "/task/update"];
8790
const url = new URL(request.url);
8891
if (request.method === "POST" && !apiUrls.includes(url.pathname)) {
8992
const isVerifiedRequest = await verifyBot(request, env);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export interface TaskUpdates {
2+
content: {
3+
completed: string;
4+
planned: string;
5+
blockers: string;
6+
};
7+
}

src/utils/sendTaskUpdates.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { env } from "../typeDefinitions/default.types";
2+
import config from "../../config/config";
3+
4+
export async function sendTaskUpdate(
5+
completed: string,
6+
planned: string,
7+
blockers: string,
8+
env: env
9+
): Promise<void> {
10+
const formattedString = `**Completed**: ${completed}\n\n**Planned**: ${planned}\n\n**Blockers**: ${blockers}`;
11+
const bodyObj = {
12+
content: formattedString,
13+
};
14+
const url = config(env).TRACKING_CHANNEL_URL;
15+
try {
16+
const response = await fetch(url, {
17+
method: "POST",
18+
body: JSON.stringify(bodyObj),
19+
headers: {
20+
"Content-Type": "application/json",
21+
Authorization: `Bot ${env.DISCORD_TOKEN}`,
22+
},
23+
});
24+
25+
if (!response.ok) {
26+
throw new Error(
27+
`Failed to send task update: ${response.status} - ${response.statusText}`
28+
);
29+
}
30+
} catch (error) {
31+
console.error("Error occurred while sending task update:", error);
32+
throw error;
33+
}
34+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { sendTaskUpdatesHandler } from "../../../src/controllers/taskUpdatesHandler";
2+
import JSONResponse from "../../../src/utils/JsonResponse";
3+
import * as response from "../../../src/constants/responses";
4+
import { sendTaskUpdate } from "../../../src/utils/sendTaskUpdates";
5+
6+
import { generateDummyRequestObject } from "../../fixtures/fixture";
7+
8+
jest.mock("../../../src/utils/verifyAuthToken", () => ({
9+
verifyNodejsBackendAuthToken: jest.fn().mockResolvedValue(true),
10+
}));
11+
jest.mock("../../../src/utils/sendTaskUpdates", () => ({
12+
sendTaskUpdate: jest.fn().mockResolvedValue(undefined),
13+
}));
14+
15+
describe("sendTaskUpdatesHandler", () => {
16+
const mockEnv = { DISCORD_TOKEN: "mockToken" };
17+
const mockData = {
18+
content: {
19+
completed: "Wrote test cases",
20+
planned: "Will test the feature at staging.",
21+
blockers: "NA",
22+
},
23+
};
24+
afterEach(() => {
25+
jest.clearAllMocks();
26+
});
27+
28+
it("sendTaskUpdate function should return undefined after successfully sending the message", async () => {
29+
const { completed, planned, blockers } = mockData.content;
30+
const response = await sendTaskUpdate(
31+
completed,
32+
planned,
33+
blockers,
34+
mockEnv
35+
);
36+
expect(response).toBe(undefined);
37+
});
38+
39+
it("should return Bad Signature object if no auth headers provided", async () => {
40+
const mockRequest = generateDummyRequestObject({ url: "/task/update" });
41+
const result: JSONResponse = await sendTaskUpdatesHandler(
42+
mockRequest,
43+
mockEnv
44+
);
45+
const jsonResponse: { error: string } = await result.json();
46+
expect(result.status).toBe(401);
47+
expect(jsonResponse).toEqual(response.UNAUTHORIZED);
48+
});
49+
});
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import config from "../../../config/config";
2+
import JSONResponse from "../../../src/utils/JsonResponse";
3+
import { sendTaskUpdate } from "../../../src/utils/sendTaskUpdates";
4+
5+
describe("sendTaskUpdate function", () => {
6+
const mockEnv = { DISCORD_TOKEN: "mockToken" };
7+
const completed = "Task completed successfully";
8+
const planned = "Plan for the next phase";
9+
const blockers = "No blockers";
10+
11+
const assertFetchCall = (url: string, bodyObj: any, mockEnv: any) => {
12+
expect(global.fetch).toHaveBeenCalledWith(url, {
13+
method: "POST",
14+
headers: {
15+
"Content-Type": "application/json",
16+
Authorization: `Bot ${mockEnv.DISCORD_TOKEN}`,
17+
},
18+
body: JSON.stringify(bodyObj),
19+
});
20+
};
21+
22+
afterEach(() => {
23+
jest.clearAllMocks();
24+
});
25+
26+
test("should send the task update to discord tracking channel when all fields are present", async () => {
27+
const url = config(mockEnv).TRACKING_CHANNEL_URL;
28+
const formattedString = `**Completed**: ${completed}\n\n**Planned**: ${planned}\n\n**Blockers**: ${blockers}`;
29+
const bodyObj = {
30+
content: formattedString,
31+
};
32+
33+
jest
34+
.spyOn(global, "fetch")
35+
.mockImplementation(() => Promise.resolve(new JSONResponse("")));
36+
37+
await sendTaskUpdate(completed, planned, blockers, mockEnv);
38+
39+
assertFetchCall(url, bodyObj, mockEnv);
40+
});
41+
42+
test("should send the task update to discord tracking channel when only completed is present", async () => {
43+
const url = config(mockEnv).TRACKING_CHANNEL_URL;
44+
const formattedString = `**Completed**: ${completed}\n\n**Planned**: \n\n**Blockers**: `;
45+
const bodyObj = {
46+
content: formattedString,
47+
};
48+
49+
jest
50+
.spyOn(global, "fetch")
51+
.mockImplementation(() => Promise.resolve(new JSONResponse("")));
52+
53+
await sendTaskUpdate(completed, "", "", mockEnv);
54+
55+
assertFetchCall(url, bodyObj, mockEnv);
56+
});
57+
58+
test("should send the task update to discord tracking channel when only planned is present", async () => {
59+
const url = config(mockEnv).TRACKING_CHANNEL_URL;
60+
const formattedString = `**Completed**: \n\n**Planned**: ${planned}\n\n**Blockers**: `;
61+
const bodyObj = {
62+
content: formattedString,
63+
};
64+
65+
jest
66+
.spyOn(global, "fetch")
67+
.mockImplementation(() => Promise.resolve(new JSONResponse("")));
68+
69+
await sendTaskUpdate("", planned, "", mockEnv);
70+
71+
assertFetchCall(url, bodyObj, mockEnv);
72+
});
73+
74+
test("should send the task update to discord tracking channel when only blockers is present", async () => {
75+
const url = config(mockEnv).TRACKING_CHANNEL_URL;
76+
const formattedString = `**Completed**: \n\n**Planned**: \n\n**Blockers**: ${blockers}`;
77+
const bodyObj = {
78+
content: formattedString,
79+
};
80+
81+
jest
82+
.spyOn(global, "fetch")
83+
.mockImplementation(() => Promise.resolve(new JSONResponse("")));
84+
85+
await sendTaskUpdate("", "", blockers, mockEnv);
86+
87+
assertFetchCall(url, bodyObj, mockEnv);
88+
});
89+
90+
test("should send the task update to discord tracking channel when only completed and planned are present", async () => {
91+
const url = config(mockEnv).TRACKING_CHANNEL_URL;
92+
const formattedString = `**Completed**: ${completed}\n\n**Planned**: ${planned}\n\n**Blockers**: `;
93+
const bodyObj = {
94+
content: formattedString,
95+
};
96+
97+
jest
98+
.spyOn(global, "fetch")
99+
.mockImplementation(() => Promise.resolve(new JSONResponse("")));
100+
101+
await sendTaskUpdate(completed, planned, "", mockEnv);
102+
103+
assertFetchCall(url, bodyObj, mockEnv);
104+
});
105+
106+
test("should send the task update to discord tracking channel when only completed and blockers are present", async () => {
107+
const url = config(mockEnv).TRACKING_CHANNEL_URL;
108+
const formattedString = `**Completed**: ${completed}\n\n**Planned**: \n\n**Blockers**: ${blockers}`;
109+
const bodyObj = {
110+
content: formattedString,
111+
};
112+
113+
jest
114+
.spyOn(global, "fetch")
115+
.mockImplementation(() => Promise.resolve(new JSONResponse("")));
116+
117+
await sendTaskUpdate(completed, "", blockers, mockEnv);
118+
119+
assertFetchCall(url, bodyObj, mockEnv);
120+
});
121+
122+
test("should send the task update to discord tracking channel when only planned and blockers are present", async () => {
123+
const url = config(mockEnv).TRACKING_CHANNEL_URL;
124+
const formattedString = `**Completed**: \n\n**Planned**: ${planned}\n\n**Blockers**: ${blockers}`;
125+
const bodyObj = {
126+
content: formattedString,
127+
};
128+
129+
jest
130+
.spyOn(global, "fetch")
131+
.mockImplementation(() => Promise.resolve(new JSONResponse("")));
132+
133+
await sendTaskUpdate("", planned, blockers, mockEnv);
134+
135+
assertFetchCall(url, bodyObj, mockEnv);
136+
});
137+
});

0 commit comments

Comments
 (0)