Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
44 changes: 41 additions & 3 deletions backend/__tests__/__integration__/dal/user.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1271,22 +1271,26 @@ describe("UserDal", () => {
describe("linkDiscord", () => {
it("throws for nonexisting user", async () => {
await expect(async () =>
UserDAL.linkDiscord("unknown", "", ""),
UserDAL.linkDiscord("unknown", "", "", {}),
).rejects.toThrow("User not found\nStack: link discord");
});
it("should update", async () => {
//given
const { uid } = await UserTestData.createUser({
discordId: "discordId",
discordAvatar: "discordAvatar",
challenges: {
"100hours": {},
},
});
//when
await UserDAL.linkDiscord(uid, "newId", "newAvatar");
await UserDAL.linkDiscord(uid, "newId", "newAvatar", { "250hours": {} });

//then
const read = await UserDAL.getUser(uid, "read");
expect(read.discordId).toEqual("newId");
expect(read.discordAvatar).toEqual("newAvatar");
expect(read.challenges).toEqual({ "250hours": {} });
});
it("should update without avatar", async () => {
//given
Expand All @@ -1312,9 +1316,13 @@ describe("UserDal", () => {
});
it("should update", async () => {
//given
const { uid } = await UserTestData.createUser({
const { uid, challenges } = await UserTestData.createUser({
discordId: "discordId",
discordAvatar: "discordAvatar",
challenges: {
"100hours": {},
"250hours": { addedAt: Date.now() },
},
});

//when
Expand All @@ -1324,6 +1332,36 @@ describe("UserDal", () => {
const read = await UserDAL.getUser(uid, "read");
expect(read.discordId).toBeUndefined();
expect(read.discordAvatar).toBeUndefined();
expect(read.challenges).toEqual(challenges);
});
});

describe("updateChallenge", () => {
it("throws for nonexisting user", async () => {
await expect(async () =>
UserDAL.updateChallenge("unknown", "69"),
).rejects.toThrow("User not found\nStack: update challenge");
});
it("should update", async () => {
//given
vi.useFakeTimers();
const { uid } = await UserTestData.createUser({
challenges: {
"100hours": {},
"250hours": { addedAt: 1 },
},
});

//when
await UserDAL.updateChallenge(uid, "69");

//then
const read = await UserDAL.getUser(uid, "read");
expect(read.challenges).toEqual({
"100hours": {},
"250hours": { addedAt: 1 },
"69": { addedAt: Date.now() },
});
});
});
describe("updateInbox", () => {
Expand Down
36 changes: 36 additions & 0 deletions backend/__tests__/api/controllers/result.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as ResultDal from "../../../src/dal/result";
import * as UserDal from "../../../src/dal/user";
import * as LogsDal from "../../../src/dal/logs";
import * as PublicDal from "../../../src/dal/public";
import * as GeorgeQueue from "../../../src/queues/george-queue";
import { ObjectId } from "mongodb";
import { mockAuthenticateWithApeKey } from "../../__testData__/auth";
import { enableRateLimitExpects } from "../../__testData__/rate-limit";
Expand Down Expand Up @@ -583,6 +584,11 @@ describe("result controller test", () => {
const userCheckIfPbMock = vi.spyOn(UserDal, "checkIfPb");
const userIncrementXpMock = vi.spyOn(UserDal, "incrementXp");
const userUpdateTypingStatsMock = vi.spyOn(UserDal, "updateTypingStats");
const userUpdateChallengeMock = vi.spyOn(UserDal, "updateChallenge");
const georgeAwardChallengeMock = vi.spyOn(
GeorgeQueue.default,
"awardChallenge",
);
const resultAddMock = vi.spyOn(ResultDal, "addResult");
const publicUpdateStatsMock = vi.spyOn(PublicDal, "updateStats");

Expand All @@ -597,6 +603,8 @@ describe("result controller test", () => {
userCheckIfPbMock,
userIncrementXpMock,
userUpdateTypingStatsMock,
userUpdateChallengeMock,
georgeAwardChallengeMock,
resultAddMock,
publicUpdateStatsMock,
].forEach((it) => it.mockClear());
Expand All @@ -605,6 +613,8 @@ describe("result controller test", () => {
userUpdateStreakMock.mockResolvedValue(0);
userCheckIfTagPbMock.mockResolvedValue([]);
userCheckIfPbMock.mockResolvedValue(true);
userUpdateChallengeMock.mockResolvedValue();
georgeAwardChallengeMock.mockResolvedValue();
resultAddMock.mockResolvedValue({ insertedId });
userIncrementXpMock.mockResolvedValue();
});
Expand Down Expand Up @@ -687,6 +697,32 @@ describe("result controller test", () => {
15.1 + 2 - 5, //duration + incompleteTestSeconds-afk
);
});

it("should add result with challenge", async () => {
//GIVEN
userGetMock.mockClear();
userGetMock.mockResolvedValue({
uid,
name: "bob",
discordId: "discordId",
} as any);

const completedEvent = buildCompletedEvent({
challenge: "69",
});
//WHEN
await mockApp
.post("/results")
.set("Authorization", `Bearer ${uid}`)
.send({
result: completedEvent,
})
.expect(200);

//THEN
expect(userUpdateChallengeMock).toHaveBeenCalledWith(uid, "69");
expect(georgeAwardChallengeMock).toHaveBeenCalledWith("discordId", "69");
});
it("should fail if result saving is disabled", async () => {
//GIVEN
await enableResultsSaving(false);
Expand Down
52 changes: 46 additions & 6 deletions backend/__tests__/api/controllers/user.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import * as WeeklyXpLeaderboard from "../../../src/services/weekly-xp-leaderboar
import * as ConnectionsDal from "../../../src/dal/connections";
import { pb } from "../../__testData__/users";
import Test from "supertest/lib/test";
import { getChallenge } from "@monkeytype/challenges";

const { mockApp, uid, mockAuth } = setup();
const configuration = Configuration.getCachedConfiguration();
Expand Down Expand Up @@ -1552,7 +1553,7 @@ describe("user controller test", () => {
it("should get oauth link", async () => {
//WHEN
const { body } = await mockApp
.get("/users/discord/oauth")
.get("/users/discord/oauth?includeRoles=true")
.set("Authorization", `Bearer ${uid}`)
.expect(200);

Expand All @@ -1561,7 +1562,9 @@ describe("user controller test", () => {
message: "Discord oauth link generated",
data: { url },
});
expect(getOauthLinkMock).toHaveBeenCalledWith(uid);
expect(getOauthLinkMock).toHaveBeenCalledWith(uid, {
includeRoles: true,
});
});
it("should fail if feature is not enabled", async () => {
//GIVEN
Expand All @@ -1587,18 +1590,24 @@ describe("user controller test", () => {
"iStateValidForUser",
);
const getDiscordUserMock = vi.spyOn(DiscordUtils, "getDiscordUser");
const getDiscordRoleIdsMock = vi.spyOn(DiscordUtils, "getDiscordRoleIds");
const blocklistContainsMock = vi.spyOn(BlocklistDal, "contains");
const userLinkDiscordMock = vi.spyOn(UserDal, "linkDiscord");
const georgeLinkDiscordMock = vi.spyOn(GeorgeQueue, "linkDiscord");
const addImportantLogMock = vi.spyOn(LogDal, "addImportantLog");

beforeEach(async () => {
vi.useFakeTimers();
isStateValidForUserMock.mockResolvedValue(true);
getUserMock.mockResolvedValue({} as any);
getDiscordUserMock.mockResolvedValue({
id: "discordUserId",
avatar: "discordUserAvatar",
});
getDiscordRoleIdsMock.mockResolvedValue([
getChallenge("100hours").discordRoleId,
getChallenge("250hours").discordRoleId,
]);
isDiscordIdAvailableMock.mockResolvedValue(true);
blocklistContainsMock.mockResolvedValue(false);
userLinkDiscordMock.mockResolvedValue();
Expand All @@ -1610,15 +1619,18 @@ describe("user controller test", () => {
isStateValidForUserMock,
isDiscordIdAvailableMock,
getDiscordUserMock,
getDiscordRoleIdsMock,
blocklistContainsMock,
userLinkDiscordMock,
georgeLinkDiscordMock,
addImportantLogMock,
].forEach((it) => it.mockClear());
vi.useRealTimers();
});

it("should link discord", async () => {
//GIVEN

getUserMock.mockResolvedValue({} as any);

//WHEN
Expand All @@ -1629,6 +1641,7 @@ describe("user controller test", () => {
tokenType: "tokenType",
accessToken: "accessToken",
state: "statestatestatestate",
scope: ["scopeOne", "scopeTwo"],
})
.expect(200);

Expand All @@ -1653,6 +1666,11 @@ describe("user controller test", () => {
"tokenType",
"accessToken",
);
expect(getDiscordRoleIdsMock).toHaveBeenCalledWith(
"tokenType",
"accessToken",
["scopeOne", "scopeTwo"],
);
expect(isDiscordIdAvailableMock).toHaveBeenCalledWith("discordUserId");
expect(blocklistContainsMock).toHaveBeenCalledWith({
discordId: "discordUserId",
Expand All @@ -1661,6 +1679,10 @@ describe("user controller test", () => {
uid,
"discordUserId",
"discordUserAvatar",
{
"100hours": { addedAt: Date.now() },
"250hours": { addedAt: Date.now() },
},
);
expect(georgeLinkDiscordMock).toHaveBeenCalledWith(
"discordUserId",
Expand All @@ -1676,7 +1698,10 @@ describe("user controller test", () => {

it("should update existing discord avatar", async () => {
//GIVEN
getUserMock.mockResolvedValue({ discordId: "existingDiscordId" } as any);
getUserMock.mockResolvedValue({
discordId: "existingDiscordId",
challenges: { "100hours": { addedAt: 1 } },
} as any);

//WHEN
const { body } = await mockApp
Expand All @@ -1701,6 +1726,7 @@ describe("user controller test", () => {
uid,
"existingDiscordId",
"discordUserAvatar",
{ "250hours": { addedAt: Date.now() } }, //only newly added
);
expect(isDiscordIdAvailableMock).not.toHaveBeenCalled();
expect(blocklistContainsMock).not.toHaveBeenCalled();
Expand Down Expand Up @@ -2962,6 +2988,9 @@ describe("user controller test", () => {
testActivity: {
"2024": fillYearWithDay(94),
},
challenges: {
"100hours": { addedAt: 1 },
},
};

beforeEach(async () => {
Expand Down Expand Up @@ -3033,12 +3062,15 @@ describe("user controller test", () => {
expect(getUserByNameMock).toHaveBeenCalledWith("bob", "get user profile");
expect(getUserMock).not.toHaveBeenCalled();
});
it("should get testActivity if enabled", async () => {
it("should get testActivity/challenges if enabled", async () => {
//GIVEN
vi.useFakeTimers().setSystemTime(1712102400000);
getUserByNameMock.mockResolvedValue({
...foundUser,
profileDetails: { showActivityOnPublicProfile: true },
profileDetails: {
showActivityOnPublicProfile: true,
showChallengesOnPublicProfile: true,
},
} as any);
const rank = { rank: 24 } as LeaderboardDal.DBLeaderboardEntry;
leaderboardGetRankMock.mockResolvedValue(rank);
Expand All @@ -3054,13 +3086,18 @@ describe("user controller test", () => {
testsByDays: expect.arrayContaining([]),
}),
);

expect(body.data.challenges).toEqual({ "100hours": { addedAt: 1 } });
});
it("should not get testActivity if disabled", async () => {
//GIVEN
vi.useFakeTimers().setSystemTime(1712102400000);
getUserByNameMock.mockResolvedValue({
...foundUser,
profileDetails: { showActivityOnPublicProfile: false },
profileDetails: {
showActivityOnPublicProfile: false,
showChallengesOnPublicProfile: false,
},
} as any);
const rank = { rank: 24 } as LeaderboardDal.DBLeaderboardEntry;
leaderboardGetRankMock.mockResolvedValue(rank);
Expand All @@ -3071,6 +3108,7 @@ describe("user controller test", () => {

//THEN
expect(body.data.testActivity).toBeUndefined();
expect(body.data.challenges).toBeUndefined();
});

it("should get base profile for banned user", async () => {
Expand Down Expand Up @@ -3188,6 +3226,7 @@ describe("user controller test", () => {
website: "https://monkeytype.com",
},
showActivityOnPublicProfile: false,
showChallengesOnPublicProfile: false,
};

//WHEN
Expand Down Expand Up @@ -3216,6 +3255,7 @@ describe("user controller test", () => {
website: "https://monkeytype.com",
},
showActivityOnPublicProfile: false,
showChallengesOnPublicProfile: false,
},
{
badges: [{ id: 4 }, { id: 2, selected: true }, { id: 3 }],
Expand Down
9 changes: 5 additions & 4 deletions backend/src/api/controllers/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,11 +459,12 @@ export async function addResult(
if (
completedEvent.challenge !== null &&
completedEvent.challenge !== undefined &&
AutoRoleList.includes(completedEvent.challenge) &&
user.discordId !== undefined &&
user.discordId !== ""
AutoRoleList.includes(completedEvent.challenge)
) {
void GeorgeQueue.awardChallenge(user.discordId, completedEvent.challenge);
await UserDAL.updateChallenge(uid, completedEvent.challenge);
if (user.discordId !== undefined && user.discordId !== "") {
void GeorgeQueue.awardChallenge(user.discordId, completedEvent.challenge);
}
} else {
delete completedEvent.challenge;
}
Expand Down
Loading
Loading