Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
1 change: 1 addition & 0 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
enableConsoleLogs: false,
discordUnverifiedRoleId: "<discordUnverifiedRoleId>",
discordDeveloperRoleId: "<discordDeveloperRoleId>",
discordNewRoleId: "<discordNewRoleId>",
discordMavenRoleId: "<discordMavenRoleId>",
discordMissedUpdatesRoleId: "<discordMissedUpdatesRoleId>",
githubApi: {
Expand Down
1 change: 1 addition & 0 deletions config/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
module.exports = {
discordUnverifiedRoleId: "1103047289330745386",
discordDeveloperRoleId: "915490782939582485",
discordNewRoleId: "1013636976647348395",
discordNewComersChannelId: "709080951824842783",
discordMavenRoleId: "875564640438997043",
discordMissedUpdatesRoleId: "1183553844811153458",
Expand Down
1 change: 1 addition & 0 deletions config/staging.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
module.exports = {
discordUnverifiedRoleId: "1120875993771544687",
discordDeveloperRoleId: "1121445071213056071",
discordNewRoleId: "1458172929190924573",
discordMavenRoleId: "1152361736456896586",
discordMissedUpdatesRoleId: "1184201657404362772",
discordNewComersChannelId: "896184507080769559",
Expand Down
2 changes: 2 additions & 0 deletions constants/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const logType = {
ADD_UNVERIFIED_ROLE: "ADD_UNVERIFIED_ROLE",
REMOVE_ROLE_FROM_USER_SUCCESS: "REMOVE_ROLE_FROM_USER_SUCCESS",
REMOVE_ROLE_FROM_USER_FAILED: "REMOVE_ROLE_FROM_USER_FAILED",
ADD_ROLE_TO_USER_SUCCESS: "ADD_ROLE_TO_USER_SUCCESS",
ADD_ROLE_TO_USER_FAILED: "ADD_ROLE_TO_USER_FAILED",
EXTENSION_REQUESTS: "extensionRequests",
TASK: "task",
TASK_REQUESTS: "taskRequests",
Expand Down
16 changes: 15 additions & 1 deletion controllers/external-accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { addOrUpdate, getUsersByRole, updateUsersInBatch } = require("../models/u
const { retrieveDiscordUsers, fetchUsersForKeyValues } = require("../services/dataAccessLayer");
const { EXTERNAL_ACCOUNTS_POST_ACTIONS } = require("../constants/external-accounts");
const removeDiscordRoleUtils = require("../utils/removeDiscordRoleFromUser");
const addDiscordRoleUtils = require("../utils/addDiscordRoleToUser");
const config = require("config");
const logger = require("../utils/logger");
const { markUnDoneTasksOfArchivedUsersBacklog } = require("../models/tasks");
Expand Down Expand Up @@ -83,7 +84,20 @@ const linkExternalAccount = async (req, res) => {
return res.boom.internal(message, { message });
}

return res.status(204).json({ message: "Your discord profile has been linked successfully" });
const developerRoleId = config.get("discordDeveloperRoleId");
const newRoleId = config.get("discordNewRoleId");

try {
await addDiscordRoleUtils.addDiscordRoleToUser(attributes.discordId, developerRoleId, "Developer");
await addDiscordRoleUtils.addDiscordRoleToUser(attributes.discordId, newRoleId, "New");
logger.info(`Roles (Developer, New) assigned successfully for Discord ID: ${attributes.discordId}`);
} catch (roleError) {
logger.error(`Error assigning roles after verification: ${roleError}`);
const message = `Your discord profile has been linked but role assignment failed. Please contact admin`;
return res.boom.internal(message, { message });
}

return res.status(200).json({ message: "Your discord profile has been linked successfully" });
} catch (error) {
logger.error(`Error getting external account data: ${error}`);
return res.boom.serverUnavailable(SOMETHING_WENT_WRONG);
Expand Down
23 changes: 15 additions & 8 deletions services/discordService.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const config = require("config");
const logger = require("../utils/logger");
const firestore = require("../utils/firestore");
const { fetchAllUsers } = require("../models/users");
const { generateAuthTokenForCloudflare, generateCloudFlareHeaders } = require("../utils/discord-actions");
Expand Down Expand Up @@ -60,14 +62,19 @@ const setInDiscordFalseScript = async () => {
};

const addRoleToUser = async (userid, roleid) => {
const authToken = generateAuthTokenForCloudflare();
const data = await fetch(`${DISCORD_BASE_URL}/roles/add`, {
method: "PUT",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${authToken}` },
body: JSON.stringify({ userid, roleid }),
});
const response = await data.json();
return response;
try {
const authToken = generateAuthTokenForCloudflare();
const data = await fetch(`${DISCORD_BASE_URL}/roles/add`, {
method: "PUT",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${authToken}` },
body: JSON.stringify({ userid, roleid }),
});
const response = await data.json();
return response;
} catch (error) {
logger.error(`Error adding role: ${error}`);
return { success: false, message: error.message };
}
};

const removeRoleFromUser = async (roleId, discordId, userData) => {
Expand Down
1 change: 1 addition & 0 deletions test/config/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
enableConsoleLogs: true,
discordUnverifiedRoleId: "1234567890",
discordDeveloperRoleId: "9876543210",
discordNewRoleId: "1111111111",
discordNewComersChannelId: "709080951824842783",
discordMavenRoleId: "1212121212",
discordMissedUpdatesRoleId: "<discordMissedUpdatesRoleId>",
Expand Down
49 changes: 47 additions & 2 deletions test/integration/external-accounts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const { usersFromRds, getDiscordMembers } = require("../fixtures/discordResponse
const Sinon = require("sinon");
const { INTERNAL_SERVER_ERROR } = require("../../constants/errorMessages");
const removeDiscordRoleUtils = require("../../utils/removeDiscordRoleFromUser");
const addDiscordRoleUtils = require("../../utils/addDiscordRoleToUser");
const firestore = require("../../utils/firestore");
const userData = require("../fixtures/user/user")();
const userModel = firestore.collection("users");
Expand Down Expand Up @@ -526,7 +527,7 @@ describe("External Accounts", function () {
});
});

it("Should return 204 when valid action is provided", async function () {
it("Should return 204 when valid action is provided and assign Developer and New roles", async function () {
await externalAccountsModel.addExternalAccountData(externalAccountData[2]);
const getUserResponseBeforeUpdate = await chai
.request(app)
Expand All @@ -543,13 +544,24 @@ describe("External Accounts", function () {
message: "Role deleted successfully",
});

const addDiscordRoleStub = Sinon.stub(addDiscordRoleUtils, "addDiscordRoleToUser").resolves({
success: true,
message: "Role added successfully",
});

const response = await chai
.request(app)
.patch(`/external-accounts/link/${externalAccountData[2].token}`)
.query({ action: EXTERNAL_ACCOUNTS_POST_ACTIONS.DISCORD_USERS_SYNC })
.set("Cookie", `${cookieName}=${newUserJWT}`);

expect(response).to.have.status(204);
expect(response).to.have.status(200);
expect(response.body).to.have.property("message");
expect(response.body.message).to.equal("Your discord profile has been linked successfully");

expect(addDiscordRoleStub.calledTwice).to.equal(true);
expect(addDiscordRoleStub.firstCall.args[2]).to.equal("Developer");
expect(addDiscordRoleStub.secondCall.args[2]).to.equal("New");

const updatedUserDetails = await chai
.request(app)
Expand All @@ -561,6 +573,39 @@ describe("External Accounts", function () {
expect(updatedUserDetails.body).to.have.property("discordJoinedAt");

removeDiscordRoleStub.restore();
addDiscordRoleStub.restore();
});

it("Should return 500 when addDiscordRole fails after successful verification", async function () {
await externalAccountsModel.addExternalAccountData(externalAccountData[2]);

const removeDiscordRoleStub = Sinon.stub(removeDiscordRoleUtils, "removeDiscordRoleFromUser").resolves({
success: true,
message: "Role deleted successfully",
});

const addDiscordRoleStub = Sinon.stub(addDiscordRoleUtils, "addDiscordRoleToUser").rejects(
new Error("Role assignment failed")
);

const response = await chai
.request(app)
.patch(`/external-accounts/link/${externalAccountData[2].token}`)
.query({ action: EXTERNAL_ACCOUNTS_POST_ACTIONS.DISCORD_USERS_SYNC })
.set("Cookie", `${cookieName}=${newUserJWT}`);

expect(response).to.have.status(500);
expect(response.body).to.be.an("object");
expect(response.body).to.have.property("message");
expect(response.body.message).to.equal(
"Your discord profile has been linked but role assignment failed. Please contact admin"
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expecation for unverified tag and negative expectation for new and dev roles are missing


expect(removeDiscordRoleStub.calledOnce).to.equal(true);
expect(addDiscordRoleStub.called).to.equal(true);

removeDiscordRoleStub.restore();
addDiscordRoleStub.restore();
});

it("Should return 500 when removeDiscordRole fails because role doesn't exist", async function () {
Expand Down
101 changes: 101 additions & 0 deletions test/unit/utils/addDiscordRoleToUser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import chai from "chai";
import Sinon from "sinon";
import { logType } from "../../../constants/logs";
import { addLog } from "../../../models/logs";
import firestore from "../../../utils/firestore";
import { addDiscordRoleToUser } from "../../../utils/addDiscordRoleToUser";
import discordServices from "../../../services/discordService";
import cleanDb from "../../utils/cleanDb";
const { expect } = chai;
const logsModel = firestore.collection("logs");

describe("addDiscordRoleToUser", function () {
let discordId;
let roleid;
let addRoleToUserStub;

beforeEach(async function () {
discordId = "1234567890";
roleid = "9876543210";
addRoleToUserStub = Sinon.stub(discordServices, "addRoleToUser");
});

afterEach(async function () {
await cleanDb();
addRoleToUserStub.restore();
});

it("should add discord role successfully", async function () {
await addLog(
logType.ADD_ROLE_TO_USER_SUCCESS,
{ roleId: roleid, discordId: discordId },
{ message: "Developer role added successfully to user" }
);

addRoleToUserStub.returns(
Promise.resolve({ success: true, message: "Role added successfully" })
);

const isDiscordRoleAdded = await addDiscordRoleToUser(discordId, roleid, "Developer");
const successLog = await logsModel
.where("type", "==", logType.ADD_ROLE_TO_USER_SUCCESS)
.where("meta.roleId", "==", roleid)
.where("meta.discordId", "==", discordId)
.limit(1)
.get();

expect(isDiscordRoleAdded.success).to.be.equal(true);
expect(isDiscordRoleAdded.message).to.be.equal("Developer role added successfully");
expect(successLog.docs[0].data().body.message).to.be.equal("Developer role added successfully to user");
});

it("should return failure when discord service returns unsuccessful response", async function () {
addRoleToUserStub.returns(
Promise.resolve({ success: false, message: "Role not found" })
);

const isDiscordRoleAdded = await addDiscordRoleToUser(discordId, roleid, "New");
const failedLog = await logsModel
.where("type", "==", logType.ADD_ROLE_TO_USER_FAILED)
.where("meta.roleId", "==", roleid)
.where("meta.discordId", "==", discordId)
.limit(1)
.get();

expect(isDiscordRoleAdded.success).to.be.equal(false);
expect(isDiscordRoleAdded.message).to.include("Adding New role to discord failed");
expect(failedLog.docs[0].data().body.message).to.include("Adding New role to discord failed");
});

it("should throw an error if adding role to discord failed", async function () {
addRoleToUserStub.rejects(new Error("Discord API error"));

const isDiscordRoleAdded = await addDiscordRoleToUser(discordId, roleid, "Developer");
const failedLog = await logsModel
.where("type", "==", logType.ADD_ROLE_TO_USER_FAILED)
.where("meta.roleId", "==", roleid)
.where("meta.discordId", "==", discordId)
.limit(1)
.get();

expect(isDiscordRoleAdded.success).to.be.equal(false);
expect(isDiscordRoleAdded.message).to.be.equal("Discord API error");
expect(failedLog.docs[0].data().body.message).to.be.equal("Discord API error");
});

it("should handle unknown error response from discord service", async function () {
addRoleToUserStub.returns(Promise.resolve({ success: false }));

const isDiscordRoleAdded = await addDiscordRoleToUser(discordId, roleid, "Developer");
const failedLog = await logsModel
.where("type", "==", logType.ADD_ROLE_TO_USER_FAILED)
.where("meta.roleId", "==", roleid)
.where("meta.discordId", "==", discordId)
.limit(1)
.get();

expect(isDiscordRoleAdded.success).to.be.equal(false);
expect(isDiscordRoleAdded.message).to.include("Unknown error");
expect(failedLog.docs[0].data().body.message).to.include("Unknown error");
});
});
44 changes: 44 additions & 0 deletions utils/addDiscordRoleToUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { logType } from "../constants/logs";
import { addLog } from "../models/logs";
import discordServices from "../services/discordService";

const logger = require("./logger");

/**
* Adds a Discord role to a user using Discord Id.
*
* @param {string} discordId - User's Discord ID.
* @param {string} roleId - Discord Role ID.
* @param {string} roleName - Role name for logging purposes.
*
* @returns {Promise<{ success: boolean; message: string }>} - Result with success status and message.
*/
export const addDiscordRoleToUser = async (
discordId: string,
roleId: string,
roleName: string = "role"
): Promise<{ success: boolean; message: string }> => {
try {
const response = await discordServices.addRoleToUser(discordId, roleId);

if (!response.success) {
const message = `Adding ${roleName} role to discord failed: ${response.message || "Unknown error"}`;
await addLog(logType.ADD_ROLE_TO_USER_FAILED, { roleId, discordId }, { message });
return { success: false, message };
}

await addLog(
logType.ADD_ROLE_TO_USER_SUCCESS,
{ roleId, discordId },
{ message: `${roleName} role added successfully to user` }
);

return { success: true, message: `${roleName} role added successfully` };
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
logger.error(`Error adding role ${roleId} for user ${discordId}: ${msg}`);
await addLog(logType.ADD_ROLE_TO_USER_FAILED, { roleId, discordId }, { message: msg });

return { success: false, message: msg };
}
};
Loading