Skip to content

Commit b70a6b6

Browse files
authored
Merge branch 'develop' into grant-aws-access
2 parents cb6770b + 79dd9ba commit b70a6b6

File tree

6 files changed

+263
-0
lines changed

6 files changed

+263
-0
lines changed

src/constants/responses.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,4 @@ export const INVALID_TOKEN_FORMAT =
8282
export const AUTHENTICATION_ERROR = "Invalid Authentication token";
8383
export const TASK_UPDATE_SENT_MESSAGE =
8484
"Task update sent on Discord's tracking-updates channel.";
85+
export const NOT_IMPLEMENTED = "Feature not implemented";
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { IRequest } from "itty-router";
2+
import JSONResponse from "../utils/JsonResponse";
3+
import { env } from "../typeDefinitions/default.types";
4+
import * as response from "../constants/responses";
5+
import { verifyNodejsBackendAuthToken } from "../utils/verifyAuthToken";
6+
import { deleteGuildRole } from "../utils/deleteGuildRole";
7+
8+
export async function deleteGuildRoleHandler(request: IRequest, env: env) {
9+
const authHeader = request.headers.get("Authorization");
10+
const reason = request.headers.get("X-Audit-Log-Reason");
11+
const roleId = decodeURI(request.params?.roleId ?? "");
12+
const { dev } = request.query;
13+
const devFlag = dev === "true";
14+
15+
if (!authHeader) {
16+
return new JSONResponse(response.BAD_SIGNATURE, { status: 401 });
17+
}
18+
19+
if (!devFlag) {
20+
return new JSONResponse(response.NOT_IMPLEMENTED, { status: 501 });
21+
}
22+
23+
if (!roleId) {
24+
return new JSONResponse(response.BAD_REQUEST, { status: 400 });
25+
}
26+
27+
try {
28+
await verifyNodejsBackendAuthToken(authHeader, env);
29+
const result = await deleteGuildRole(env, roleId, reason);
30+
31+
if (result === response.ROLE_REMOVED) {
32+
return new Response(null, { status: 204 });
33+
} else {
34+
return new JSONResponse(response.INTERNAL_SERVER_ERROR, {
35+
status: 500,
36+
});
37+
}
38+
} catch (err) {
39+
console.error("An error occurred while deleting discord role:", err);
40+
return new JSONResponse(response.INTERNAL_SERVER_ERROR, {
41+
status: 500,
42+
});
43+
}
44+
}

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { sendProfileBlockedMessage } from "./controllers/profileHandler";
2323
import { sendTaskUpdatesHandler } from "./controllers/taskUpdatesHandler";
2424

2525
import config, { loadEnv } from "./../config/config";
26+
import { deleteGuildRoleHandler } from "./controllers/deleteGuildRoleHandler";
2627

2728
const router = Router();
2829

@@ -34,6 +35,8 @@ router.get("/", async () => {
3435

3536
router.patch("/guild/member", changeNickname);
3637

38+
router.delete("/roles/:roleId", deleteGuildRoleHandler);
39+
3740
router.put("/roles/create", createGuildRoleHandler);
3841

3942
router.put("/roles/add", addGroupRoleHandler);

src/utils/deleteGuildRole.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { INTERNAL_SERVER_ERROR, ROLE_REMOVED } from "../constants/responses";
2+
import { DISCORD_BASE_URL } from "../constants/urls";
3+
import { env } from "../typeDefinitions/default.types";
4+
import createDiscordHeaders from "./createDiscordHeaders";
5+
6+
export async function deleteGuildRole(
7+
env: env,
8+
roleId: string,
9+
reason?: string
10+
) {
11+
const deleteGuildRoleUrl = `${DISCORD_BASE_URL}/guilds/${env.DISCORD_GUILD_ID}/roles/${roleId}`;
12+
const headers: HeadersInit = createDiscordHeaders({
13+
token: env.DISCORD_TOKEN,
14+
reason: reason,
15+
});
16+
try {
17+
const response = await fetch(deleteGuildRoleUrl, {
18+
method: "DELETE",
19+
headers,
20+
});
21+
if (response.ok) {
22+
return ROLE_REMOVED;
23+
} else {
24+
return INTERNAL_SERVER_ERROR;
25+
}
26+
} catch (err) {
27+
console.error("An error occurred while deleting discord role:", err);
28+
return INTERNAL_SERVER_ERROR;
29+
}
30+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { generateDummyRequestObject, guildEnv } from "../../fixtures/fixture";
2+
import * as responseConstants from "../../../src/constants/responses";
3+
import * as verifyTokenUtils from "../../../src/utils/verifyAuthToken";
4+
import { deleteGuildRoleHandler } from "../../../src/controllers/deleteGuildRoleHandler";
5+
import * as deleteGuildRoleUtils from "../../../src/utils/deleteGuildRole";
6+
7+
describe("deleteGuildRoleHandler", () => {
8+
afterEach(() => {
9+
jest.restoreAllMocks();
10+
});
11+
beforeEach(() => {
12+
jest.clearAllMocks();
13+
});
14+
const roleId = "1A32BEX04";
15+
it("should return NOT_IMPLEMENTED when dev is false", async () => {
16+
const mockRequest = generateDummyRequestObject({
17+
url: "/roles",
18+
params: {
19+
roleId: roleId,
20+
},
21+
query: {
22+
dev: "false",
23+
},
24+
method: "DELETE",
25+
headers: { Authorization: "Bearer testtoken" },
26+
});
27+
const response = await deleteGuildRoleHandler(mockRequest, guildEnv);
28+
const jsonResponse = await response.json();
29+
expect(jsonResponse).toEqual(responseConstants.NOT_IMPLEMENTED);
30+
});
31+
it("should return BAD_REQUEST when roleId is not valid", async () => {
32+
const mockRequest = generateDummyRequestObject({
33+
url: "/roles",
34+
params: {
35+
roleId: "",
36+
},
37+
query: {
38+
dev: "true",
39+
},
40+
method: "DELETE",
41+
headers: { Authorization: "Bearer testtoken" },
42+
});
43+
const response = await deleteGuildRoleHandler(mockRequest, guildEnv);
44+
const jsonResponse = await response.json();
45+
expect(jsonResponse).toEqual(responseConstants.BAD_REQUEST);
46+
});
47+
it("should return BAD_SIGNATURE when authorization header is not provided", async () => {
48+
const mockRequest = generateDummyRequestObject({
49+
url: "/roles",
50+
params: {
51+
roleId: roleId,
52+
},
53+
query: {
54+
dev: "true",
55+
},
56+
method: "DELETE",
57+
});
58+
const response = await deleteGuildRoleHandler(mockRequest, guildEnv);
59+
const jsonResponse = await response.json();
60+
expect(jsonResponse).toEqual(responseConstants.BAD_SIGNATURE);
61+
});
62+
it("should return INTERNAL_SERVER_ERROR when response is not ok", async () => {
63+
const expectedResponse = responseConstants.INTERNAL_SERVER_ERROR;
64+
const mockRequest = generateDummyRequestObject({
65+
url: "/roles",
66+
params: {
67+
roleId: roleId,
68+
},
69+
query: {
70+
dev: "true",
71+
},
72+
method: "DELETE",
73+
headers: { Authorization: "Bearer testtoken" },
74+
});
75+
jest
76+
.spyOn(deleteGuildRoleUtils, "deleteGuildRole")
77+
.mockResolvedValue(expectedResponse);
78+
const response = await deleteGuildRoleHandler(mockRequest, guildEnv);
79+
const jsonResponse = await response.json();
80+
expect(jsonResponse).toEqual(expectedResponse);
81+
});
82+
it("should return INTERNAL_SERVER_ERROR when token is not verified", async () => {
83+
const expectedResponse = responseConstants.INTERNAL_SERVER_ERROR;
84+
const mockRequest = generateDummyRequestObject({
85+
url: "/roles",
86+
method: "DELETE",
87+
params: {
88+
roleId: roleId,
89+
},
90+
query: {
91+
dev: "true",
92+
},
93+
headers: { Authorization: "Bearer testtoken" },
94+
});
95+
jest
96+
.spyOn(verifyTokenUtils, "verifyNodejsBackendAuthToken")
97+
.mockRejectedValue(expectedResponse);
98+
const response = await deleteGuildRoleHandler(mockRequest, guildEnv);
99+
const jsonResponse = await response.json();
100+
expect(jsonResponse).toEqual(expectedResponse);
101+
});
102+
it("should return ok response", async () => {
103+
const expectedResponse = new Response(null, {
104+
status: 204,
105+
});
106+
const mockRequest = generateDummyRequestObject({
107+
url: "/roles",
108+
method: "DELETE",
109+
params: {
110+
roleId: roleId,
111+
},
112+
query: {
113+
dev: "true",
114+
},
115+
headers: { Authorization: "Bearer testtoken" },
116+
});
117+
const verifyTokenSpy = jest
118+
.spyOn(verifyTokenUtils, "verifyNodejsBackendAuthToken")
119+
.mockResolvedValueOnce();
120+
const deleteGuildRoleSpy = jest
121+
.spyOn(deleteGuildRoleUtils, "deleteGuildRole")
122+
.mockResolvedValueOnce(responseConstants.ROLE_REMOVED);
123+
const response = await deleteGuildRoleHandler(mockRequest, guildEnv);
124+
expect(verifyTokenSpy).toHaveBeenCalledTimes(1);
125+
expect(deleteGuildRoleSpy).toHaveBeenCalledTimes(1);
126+
expect(response).toEqual(expectedResponse);
127+
expect(response.status).toEqual(expectedResponse.status);
128+
});
129+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { DISCORD_BASE_URL } from "../../../src/constants/urls";
2+
import { deleteGuildRole } from "../../../src/utils/deleteGuildRole";
3+
import JSONResponse from "../../../src/utils/JsonResponse";
4+
import { guildEnv } from "../../fixtures/fixture";
5+
import * as response from "../../../src/constants/responses";
6+
7+
describe("deleteGuildRole", () => {
8+
const roleId = "1A32BEX04";
9+
const deleteGuildRoleUrl = `${DISCORD_BASE_URL}/guilds/${guildEnv.DISCORD_GUILD_ID}/roles/${roleId}`;
10+
const mockRequestInit = {
11+
method: "DELETE",
12+
headers: {
13+
"Content-Type": "application/json",
14+
Authorization: `Bot ${guildEnv.DISCORD_TOKEN}`,
15+
"X-Audit-Log-Reason": "This is reason for this action",
16+
},
17+
};
18+
19+
beforeEach(() => {
20+
jest.clearAllMocks();
21+
});
22+
afterEach(() => {
23+
jest.restoreAllMocks();
24+
});
25+
26+
it("should pass the reason to discord as a X-Audit-Log-Reason header if provided", async () => {
27+
jest
28+
.spyOn(global, "fetch")
29+
.mockImplementation((inp) => Promise.resolve(new JSONResponse(inp)));
30+
31+
await deleteGuildRole(guildEnv, roleId, "This is reason for this action");
32+
33+
expect(global.fetch).toHaveBeenCalledWith(
34+
deleteGuildRoleUrl,
35+
mockRequestInit
36+
);
37+
});
38+
39+
it("should return ROLE_REMOVED when response is ok", async () => {
40+
const expectedResponse = new Response(null, {
41+
status: 204,
42+
});
43+
jest.spyOn(global, "fetch").mockResolvedValue(expectedResponse);
44+
const result = await deleteGuildRole(guildEnv, roleId);
45+
expect(result).toEqual(response.ROLE_REMOVED);
46+
expect(global.fetch).toHaveBeenCalledTimes(1);
47+
});
48+
49+
it("should return INTERNAL_SERVER_ERROR when response is not ok", async () => {
50+
const expectedErrorResponse = new Response(response.INTERNAL_SERVER_ERROR);
51+
jest.spyOn(global, "fetch").mockRejectedValue(expectedErrorResponse);
52+
const result = await deleteGuildRole(guildEnv, roleId);
53+
expect(result).toEqual(response.INTERNAL_SERVER_ERROR);
54+
expect(global.fetch).toHaveBeenCalledTimes(1);
55+
});
56+
});

0 commit comments

Comments
 (0)