From 50925aab4825dd6ece8b3b5295c697082684ea3b Mon Sep 17 00:00:00 2001 From: moses-codes Date: Thu, 25 Sep 2025 11:30:44 -0700 Subject: [PATCH 1/2] feat: check for server nickname property --- server/config/passport.js | 54 +++++++++++++++--- server/test/unit/passport.test.js | 91 +++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 server/test/unit/passport.test.js diff --git a/server/config/passport.js b/server/config/passport.js index d22e5388..a02fe2f7 100644 --- a/server/config/passport.js +++ b/server/config/passport.js @@ -23,14 +23,14 @@ module.exports = function (passport) { clientSecret: process.env.DISCORD_CLIENT_SECRET, callbackURL: "/api/auth/discord/callback", // pulls discord username without email, and returns basic information about all the user's current guilds / servers. - scope: ["identify", "guilds"], + scope: ["identify", "guilds", "guilds.members.read"], passReqToCallback: true, }, async function (currentReq, accessToken, refreshToken, profile, cb) { - const displayName = - profile.discriminator.length === 4 - ? `${profile.username}#${profile.discriminator}` - : profile.username; + // Check if user exists in DB + let user = await User.findOne({ discordId: profile.id }).exec(); + + //if user is not in 100Devs, return (end the function) const is100Dever = profile.guilds.some( (server) => server.id === "735923219315425401" ); @@ -39,8 +39,15 @@ module.exports = function (passport) { currentReq.session.isNot100Dever = true; return cb(null, false); } - // Check if user exists in DB - let user = await User.findOne({ discordId: profile.id }).exec(); + + //check to see if a global name is set for the user. if not, fallback to username + let displayName = profile.global_name ?? profile.username; + + // check to see if there is a .nick property in the current user's guild + const serverName = await getServerName(accessToken); + + //if there is a serverName, use that instead of username or global_name + if (serverName) displayName = serverName; try { // Create user if it doesn't exist @@ -73,3 +80,36 @@ module.exports = function (passport) { ) ); }; + +/** + * a function that gets a user's server name. + * @param {string} accessToken represents the current access token of the auth instance + */ +async function getServerName(accessToken) { + //if in the parameters, can be reusable + testable + //test the happy path + try { + const devMemberInfo = await fetch( + "https://discord.com/api/users/@me/guilds/735923219315425401/member", + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + } + ); + const devMemberData = await devMemberInfo.json(); + //return a 200, something that is jsonable + //if .nick, that indicates a nickname + if (devMemberData.nick) { + return devMemberData.nick; + } + //if 500, return the response, server name + //if no .nick, return null (prior server name remains) + console.log(`The nickname could not be found.`); + } catch (error) { + console.log(`The member's guild info could not be found: ${error}`); + } + return null; +} + +module.exports.getServerName = getServerName; diff --git a/server/test/unit/passport.test.js b/server/test/unit/passport.test.js new file mode 100644 index 00000000..deb03ce9 --- /dev/null +++ b/server/test/unit/passport.test.js @@ -0,0 +1,91 @@ +"use strict"; + +// Assuming your passport config file is two directories up in /config +const { getServerName } = require("../../config/passport"); + +describe("getServerName", () => { + beforeEach(() => { + global.fetch = jest.fn(); + }); + + afterEach(() => { + // restore all mocks after each test + jest.restoreAllMocks(); + global.fetch.mockClear(); + }); + + /* given nick property, return the nick property */ + + it("nickname is present", async () => { + // define the mock response for this specific test case + const mockApiResponse = { + nick: "notLegmother", + user: { id: "123", username: "discord-user" }, + }; + + // resolve with an object that has a .json() method, + // which in turn resolves with our mock API response. + jest.spyOn(global, "fetch").mockResolvedValue({ + json: jest.fn().mockResolvedValue(mockApiResponse), + }); + + // call the function, expect it to be the nickname + const result = await getServerName("fake-token"); + console.log(result); + expect(result).toBe("notLegmother"); + }); + + //test the cases in which it goes wrong, i.e. no nick property + //capture the user behavior rather than the specific output + it("nickname is not present", async () => { + const mockApiResponse = { + user: { id: "123", username: "discord-user" }, + }; + + // resolve with an object that has a .json() method, + // which in turn resolves with our mock API response. + jest.spyOn(global, "fetch").mockResolvedValue({ + json: jest.fn().mockResolvedValue(mockApiResponse), + }); + + // what is logging? check on that + const consoleLogSpy = jest + .spyOn(console, "log") + .mockImplementation(() => {}); + + const result = await getServerName("fake-token"); + + // assertions: return null and the expected log message + expect(result).toBe(null); // The function should return null + expect(consoleLogSpy).toHaveBeenCalledWith( + "The nickname could not be found." + ); + }); + + //server / API error + it("returns no data", async () => { + const mockApiResponse = { + status: 400, + statusText: "Bad Request", + }; + + // resolve with an object that has a .json() method, + // which in turn resolves with our mock API response. + jest.spyOn(global, "fetch").mockResolvedValue({ + json: jest.fn().mockResolvedValue(mockApiResponse), + }); + + // what is logging? check on that + const consoleLogSpy = jest + .spyOn(console, "log") + .mockImplementation(() => {}); + + const result = await getServerName("fake-token"); + + // assertions: return null and the expected log message + expect(result).toBe(null); // The function should return null + expect(consoleLogSpy).toHaveBeenCalledWith( + "The nickname could not be found." + ); + }); +}); From 451b8b5843254fc493fb18460d131dfbfd9b7760 Mon Sep 17 00:00:00 2001 From: moses-codes Date: Mon, 6 Oct 2025 12:50:45 -0700 Subject: [PATCH 2/2] deleted username discriminators from affected unit tests --- cypress/e2e/auth.cy.js | 6 +++--- cypress/e2e/create-event-form.cy.js | 6 +++--- cypress/e2e/view-event-modal.cy.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cypress/e2e/auth.cy.js b/cypress/e2e/auth.cy.js index 207fdcea..4e039f22 100644 --- a/cypress/e2e/auth.cy.js +++ b/cypress/e2e/auth.cy.js @@ -21,16 +21,16 @@ describe("Discord OAuth works", () => { describe("Welcome Model", () => { it("Is shown until dismissed", () => { cy.login("100_DEVER"); - tgt.modal.get().contains("Hello, 100Dever#0001"); + tgt.modal.get().contains("Hello, 100Dever"); cy.reload(); - tgt.modal.get().contains("Hello, 100Dever#0001"); + tgt.modal.get().contains("Hello, 100Dever"); }); for (const backdrop of [false, true]) { it(`Dismissible via ${backdrop ? "backdrop" : "close button"}`, () => { cy.login("100_DEVER"); - tgt.modal.get().contains("Hello, 100Dever#0001"); + tgt.modal.get().contains("Hello, 100Dever"); tgt.modal.close(backdrop); cy.reload(); diff --git a/cypress/e2e/create-event-form.cy.js b/cypress/e2e/create-event-form.cy.js index 8a4f5c6e..ed8f1f96 100644 --- a/cypress/e2e/create-event-form.cy.js +++ b/cypress/e2e/create-event-form.cy.js @@ -72,7 +72,7 @@ describe("Event Creation Form", () => { tgt.createForm.input.description().type("Test Description"); cy.get('input[name="discordName"]') - .should("have.value", "100Dever#0001") + .should("have.value", "100Dever") .should("be.disabled"); cy.contains("button", "Back").then(($button) => expect($button.css("cursor")).to.equal("not-allowed") @@ -95,7 +95,7 @@ describe("Event Creation Form", () => { "Test Title", "Test Description", "Test Location", - "100Dever#0001", + "100Dever", startDate, startTime, endDate, @@ -115,7 +115,7 @@ describe("Event Creation Form", () => { title: "Test Title", description: "Test Description", location: "Test Location", - discordName: "100Dever#0001", + discordName: "100Dever", initialDate: startDate, finalDate: endDate, startTime, diff --git a/cypress/e2e/view-event-modal.cy.js b/cypress/e2e/view-event-modal.cy.js index 2fb4e063..89fe3546 100644 --- a/cypress/e2e/view-event-modal.cy.js +++ b/cypress/e2e/view-event-modal.cy.js @@ -73,7 +73,7 @@ describe("View Event Modal", () => { tgt.modal.get(0).contains("Location: Test Location"); tgt.modal .get(0) - .contains("Event Author: 100Dever#0001") + .contains("Event Author: 100Dever") .should(by ? "exist" : "not.exist"); };