1492 lines
39 KiB
TypeScript
1492 lines
39 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
import { setup } from "../../__testData__/controller-test";
|
|
import { ObjectId } from "mongodb";
|
|
import * as LeaderboardDal from "../../../src/dal/leaderboards";
|
|
import * as ConnectionsDal from "../../../src/dal/connections";
|
|
import * as DailyLeaderboards from "../../../src/utils/daily-leaderboards";
|
|
import * as WeeklyXpLeaderboard from "../../../src/services/weekly-xp-leaderboard";
|
|
import * as Configuration from "../../../src/init/configuration";
|
|
import { mockAuthenticateWithApeKey } from "../../__testData__/auth";
|
|
import { XpLeaderboardEntry } from "@monkeytype/schemas/leaderboards";
|
|
|
|
const { mockApp, uid } = setup();
|
|
const configuration = Configuration.getCachedConfiguration();
|
|
|
|
const allModes = [
|
|
"10",
|
|
"25",
|
|
"50",
|
|
"100",
|
|
"15",
|
|
"30",
|
|
"60",
|
|
"120",
|
|
"zen",
|
|
"custom",
|
|
];
|
|
|
|
describe("Loaderboard Controller", () => {
|
|
describe("get leaderboard", () => {
|
|
const getLeaderboardMock = vi.spyOn(LeaderboardDal, "get");
|
|
const getLeaderboardCountMock = vi.spyOn(LeaderboardDal, "getCount");
|
|
|
|
beforeEach(() => {
|
|
getLeaderboardMock.mockClear();
|
|
getLeaderboardCountMock.mockClear();
|
|
getLeaderboardCountMock.mockResolvedValue(42);
|
|
});
|
|
|
|
it("should get for english time 60", async () => {
|
|
//GIVEN
|
|
|
|
const resultData = {
|
|
count: 42,
|
|
pageSize: 50,
|
|
entries: [
|
|
{
|
|
wpm: 20,
|
|
acc: 90,
|
|
timestamp: 1000,
|
|
raw: 92,
|
|
consistency: 80,
|
|
uid: "user1",
|
|
name: "user1",
|
|
discordId: "discordId",
|
|
discordAvatar: "discordAvatar",
|
|
rank: 1,
|
|
badgeId: 1,
|
|
isPremium: true,
|
|
},
|
|
{
|
|
wpm: 10,
|
|
acc: 80,
|
|
timestamp: 1200,
|
|
raw: 82,
|
|
uid: "user2",
|
|
name: "user2",
|
|
rank: 2,
|
|
},
|
|
],
|
|
};
|
|
const mockData = resultData.entries.map((it) => ({
|
|
...it,
|
|
_id: new ObjectId(),
|
|
}));
|
|
getLeaderboardMock.mockResolvedValue(mockData);
|
|
getLeaderboardCountMock.mockResolvedValue(42);
|
|
|
|
//WHEN
|
|
|
|
const { body } = await mockApp
|
|
.get("/leaderboards")
|
|
.query({ language: "english", mode: "time", mode2: "60" })
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body).toEqual({
|
|
message: "Leaderboard retrieved",
|
|
data: resultData,
|
|
});
|
|
|
|
expect(getLeaderboardMock).toHaveBeenCalledWith(
|
|
"time",
|
|
"60",
|
|
"english",
|
|
0,
|
|
50,
|
|
false,
|
|
undefined,
|
|
);
|
|
|
|
expect(getLeaderboardCountMock).toHaveBeenCalledWith(
|
|
"time",
|
|
"60",
|
|
"english",
|
|
undefined,
|
|
);
|
|
});
|
|
|
|
it("should get for english time 60 with page", async () => {
|
|
//GIVEN
|
|
getLeaderboardMock.mockResolvedValue([]);
|
|
getLeaderboardCountMock.mockResolvedValue(0);
|
|
const page = 0;
|
|
const pageSize = 25;
|
|
|
|
//WHEN
|
|
|
|
const { body } = await mockApp
|
|
.get("/leaderboards")
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
page,
|
|
pageSize,
|
|
})
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body).toEqual({
|
|
message: "Leaderboard retrieved",
|
|
data: {
|
|
count: 0,
|
|
pageSize: 25,
|
|
entries: [],
|
|
},
|
|
});
|
|
|
|
expect(getLeaderboardMock).toHaveBeenCalledWith(
|
|
"time",
|
|
"60",
|
|
"english",
|
|
page,
|
|
pageSize,
|
|
false,
|
|
undefined,
|
|
);
|
|
});
|
|
|
|
it("should get for friendsOnly", async () => {
|
|
//GIVEN
|
|
await enableConnectionsFeature(true);
|
|
getLeaderboardMock.mockResolvedValue([]);
|
|
getLeaderboardCountMock.mockResolvedValue(2);
|
|
|
|
//WHEN
|
|
|
|
const { body } = await mockApp
|
|
.get("/leaderboards")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
friendsOnly: true,
|
|
})
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body.data.count).toEqual(2);
|
|
|
|
expect(getLeaderboardMock).toHaveBeenCalledWith(
|
|
"time",
|
|
"60",
|
|
"english",
|
|
0,
|
|
50,
|
|
false,
|
|
uid,
|
|
);
|
|
expect(getLeaderboardCountMock).toHaveBeenCalledWith(
|
|
"time",
|
|
"60",
|
|
"english",
|
|
uid,
|
|
);
|
|
});
|
|
|
|
describe("should get for modes", async () => {
|
|
beforeEach(() => {
|
|
getLeaderboardMock.mockResolvedValue([]);
|
|
});
|
|
|
|
const testCases = [
|
|
{ mode: "time", mode2: "15", language: "english", expectStatus: 200 },
|
|
{ mode: "time", mode2: "60", language: "english", expectStatus: 200 },
|
|
{ mode: "time", mode2: "30", language: "english", expectStatus: 404 },
|
|
{ mode: "words", mode2: "15", language: "english", expectStatus: 404 },
|
|
{ mode: "time", mode2: "15", language: "spanish", expectStatus: 404 },
|
|
];
|
|
it.for(testCases)(
|
|
`expect $expectStatus for mode $mode, mode2 $mode2, lang $language`,
|
|
async ({ mode, mode2, language, expectStatus }) => {
|
|
await mockApp
|
|
.get("/leaderboards")
|
|
.query({ language, mode, mode2 })
|
|
.expect(expectStatus);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("fails for missing query", async () => {
|
|
const { body } = await mockApp.get("/leaderboards").expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: [
|
|
'"language" Required',
|
|
'"mode" Required',
|
|
'"mode2" Needs to be either a number, "zen" or "custom".',
|
|
],
|
|
});
|
|
});
|
|
it("fails for invalid query", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards")
|
|
.query({
|
|
language: "en?gli.sh",
|
|
mode: "unknownMode",
|
|
mode2: "unknownMode2",
|
|
page: -1,
|
|
pageSize: 500,
|
|
})
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: [
|
|
'"language" Invalid enum value. Must be a supported language',
|
|
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
|
|
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
|
|
'"page" Number must be greater than or equal to 0',
|
|
'"pageSize" Number must be less than or equal to 200',
|
|
],
|
|
});
|
|
});
|
|
it("fails for unknown query", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards")
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
extra: "value",
|
|
})
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: ["Unrecognized key(s) in object: 'extra'"],
|
|
});
|
|
});
|
|
it("fails while leaderboard is updating", async () => {
|
|
//GIVEN
|
|
getLeaderboardMock.mockResolvedValue(false);
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards")
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
})
|
|
.expect(503);
|
|
|
|
expect(body.message).toEqual(
|
|
"Leaderboard is currently updating. Please try again in a few seconds.",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("get rank", () => {
|
|
const getLeaderboardRankMock = vi.spyOn(LeaderboardDal, "getRank");
|
|
|
|
afterEach(() => {
|
|
getLeaderboardRankMock.mockClear();
|
|
});
|
|
|
|
it("fails withouth authentication", async () => {
|
|
await mockApp
|
|
.get("/leaderboards/rank")
|
|
.query({ language: "english", mode: "time", mode2: "60" })
|
|
.expect(401);
|
|
});
|
|
|
|
it("should get for english time 60", async () => {
|
|
//GIVEN
|
|
|
|
const entryId = new ObjectId();
|
|
const resultEntry = {
|
|
_id: entryId,
|
|
wpm: 10,
|
|
acc: 80,
|
|
timestamp: 1200,
|
|
raw: 82,
|
|
uid: "user2",
|
|
name: "user2",
|
|
rank: 2,
|
|
};
|
|
getLeaderboardRankMock.mockResolvedValue(resultEntry);
|
|
|
|
//WHEN
|
|
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/rank")
|
|
.query({ language: "english", mode: "time", mode2: "60" })
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body).toEqual({
|
|
message: "Rank retrieved",
|
|
data: { ...resultEntry, _id: undefined },
|
|
});
|
|
|
|
expect(getLeaderboardRankMock).toHaveBeenCalledWith(
|
|
"time",
|
|
"60",
|
|
"english",
|
|
uid,
|
|
false,
|
|
);
|
|
});
|
|
it("should get for english time 60 friends only", async () => {
|
|
//GIVEN
|
|
await enableConnectionsFeature(true);
|
|
getLeaderboardRankMock.mockResolvedValue({} as any);
|
|
|
|
//WHEN
|
|
await mockApp
|
|
.get("/leaderboards/rank")
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
friendsOnly: true,
|
|
})
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(getLeaderboardRankMock).toHaveBeenCalledWith(
|
|
"time",
|
|
"60",
|
|
"english",
|
|
uid,
|
|
true,
|
|
);
|
|
});
|
|
it("should get null if no rank", async () => {
|
|
//GIVEN
|
|
await enableConnectionsFeature(true);
|
|
getLeaderboardRankMock.mockResolvedValue(null);
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/rank")
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
friendsOnly: true,
|
|
})
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(getLeaderboardRankMock).toHaveBeenCalledWith(
|
|
"time",
|
|
"60",
|
|
"english",
|
|
uid,
|
|
true,
|
|
);
|
|
expect(body).toEqual({
|
|
message: "Rank retrieved",
|
|
data: null,
|
|
});
|
|
});
|
|
it("should get with ape key", async () => {
|
|
await acceptApeKeys(true);
|
|
const apeKey = await mockAuthenticateWithApeKey(uid, await configuration);
|
|
|
|
await mockApp
|
|
.get("/leaderboards/rank")
|
|
.query({ language: "english", mode: "time", mode2: "60" })
|
|
.set("authorization", "ApeKey " + apeKey)
|
|
.expect(200);
|
|
});
|
|
it("should get for mode", async () => {
|
|
getLeaderboardRankMock.mockResolvedValue({} as any);
|
|
for (const mode of ["time", "words", "quote", "zen", "custom"]) {
|
|
const response = await mockApp
|
|
.get("/leaderboards/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.query({ language: "english", mode, mode2: "custom" });
|
|
expect(response.status, "for mode " + mode).toEqual(200);
|
|
}
|
|
});
|
|
|
|
it("should get for mode2", async () => {
|
|
getLeaderboardRankMock.mockResolvedValue({} as any);
|
|
for (const mode2 of allModes) {
|
|
const response = await mockApp
|
|
.get("/leaderboards/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.query({ language: "english", mode: "words", mode2 });
|
|
|
|
expect(response.status, "for mode2 " + mode2).toEqual(200);
|
|
}
|
|
});
|
|
it("fails for missing query", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: [
|
|
'"language" Required',
|
|
'"mode" Required',
|
|
'"mode2" Needs to be either a number, "zen" or "custom".',
|
|
],
|
|
});
|
|
});
|
|
it("fails for invalid query", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/rank")
|
|
.query({
|
|
language: "en?gli.sh",
|
|
mode: "unknownMode",
|
|
mode2: "unknownMode2",
|
|
})
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: [
|
|
'"language" Invalid enum value. Must be a supported language',
|
|
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
|
|
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
|
|
],
|
|
});
|
|
});
|
|
it("fails for unknown query", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/rank")
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
extra: "value",
|
|
})
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: ["Unrecognized key(s) in object: 'extra'"],
|
|
});
|
|
});
|
|
it("fails while leaderboard is updating", async () => {
|
|
//GIVEN
|
|
getLeaderboardRankMock.mockResolvedValue(false);
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/rank")
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
})
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(503);
|
|
|
|
expect(body.message).toEqual(
|
|
"Leaderboard is currently updating. Please try again in a few seconds.",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("get daily leaderboard", () => {
|
|
const getDailyLeaderboardMock = vi.spyOn(
|
|
DailyLeaderboards,
|
|
"getDailyLeaderboard",
|
|
);
|
|
const getFriendsUidsMock = vi.spyOn(ConnectionsDal, "getFriendsUids");
|
|
const getResultMock = vi.fn();
|
|
|
|
beforeEach(async () => {
|
|
[getDailyLeaderboardMock, getFriendsUidsMock, getResultMock].forEach(
|
|
(it) => it.mockClear(),
|
|
);
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(1722606812000);
|
|
await dailyLeaderboardEnabled(true);
|
|
|
|
getDailyLeaderboardMock.mockReturnValue({
|
|
getResults: getResultMock,
|
|
} as any);
|
|
|
|
getResultMock.mockResolvedValue(null);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("should get for english time 60", async () => {
|
|
//GIVEN
|
|
const lbConf = (await configuration).dailyLeaderboards;
|
|
const premiumEnabled = (await configuration).users.premium.enabled;
|
|
|
|
const resultData = {
|
|
minWpm: 10,
|
|
entries: [
|
|
{
|
|
name: "user1",
|
|
rank: 1,
|
|
wpm: 20,
|
|
acc: 90,
|
|
timestamp: 1000,
|
|
raw: 92,
|
|
consistency: 80,
|
|
uid: "user1",
|
|
discordId: "discordId",
|
|
discordAvatar: "discordAvatar",
|
|
},
|
|
{
|
|
wpm: 10,
|
|
rank: 2,
|
|
acc: 80,
|
|
timestamp: 1200,
|
|
raw: 82,
|
|
consistency: 72,
|
|
uid: "user2",
|
|
name: "user2",
|
|
},
|
|
],
|
|
};
|
|
|
|
getResultMock.mockResolvedValue({
|
|
count: 2,
|
|
minWpm: 10,
|
|
entries: resultData,
|
|
});
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/daily")
|
|
.query({ language: "english", mode: "time", mode2: "60" })
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body).toEqual({
|
|
message: "Daily leaderboard retrieved",
|
|
data: {
|
|
count: 2,
|
|
pageSize: 50,
|
|
minWpm: 10,
|
|
entries: resultData,
|
|
},
|
|
});
|
|
|
|
expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
|
|
"english",
|
|
"time",
|
|
"60",
|
|
lbConf,
|
|
-1,
|
|
);
|
|
|
|
expect(getResultMock).toHaveBeenCalledWith(
|
|
0,
|
|
50,
|
|
lbConf,
|
|
premiumEnabled,
|
|
undefined,
|
|
);
|
|
});
|
|
|
|
it("should get for english time 60 for yesterday", async () => {
|
|
//GIVEN
|
|
const lbConf = (await configuration).dailyLeaderboards;
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/daily")
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
daysBefore: 1,
|
|
})
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body).toEqual({
|
|
message: "Daily leaderboard retrieved",
|
|
data: {
|
|
entries: [],
|
|
count: 0,
|
|
pageSize: 50,
|
|
minWpm: 0,
|
|
},
|
|
});
|
|
|
|
expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
|
|
"english",
|
|
"time",
|
|
"60",
|
|
lbConf,
|
|
1722470400000,
|
|
);
|
|
});
|
|
it("should get for english time 60 with page and pageSize", async () => {
|
|
//GIVEN
|
|
const lbConf = (await configuration).dailyLeaderboards;
|
|
const premiumEnabled = (await configuration).users.premium.enabled;
|
|
const page = 2;
|
|
const pageSize = 25;
|
|
|
|
getResultMock.mockResolvedValue({ entries: [] });
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/daily")
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
page,
|
|
pageSize,
|
|
})
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body).toEqual({
|
|
message: "Daily leaderboard retrieved",
|
|
data: {
|
|
entries: [],
|
|
count: 0,
|
|
pageSize,
|
|
minWpm: 0,
|
|
},
|
|
});
|
|
|
|
expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
|
|
"english",
|
|
"time",
|
|
"60",
|
|
lbConf,
|
|
-1,
|
|
);
|
|
|
|
expect(getResultMock).toHaveBeenCalledWith(
|
|
page,
|
|
pageSize,
|
|
lbConf,
|
|
premiumEnabled,
|
|
undefined,
|
|
);
|
|
});
|
|
|
|
it("should get for friends", async () => {
|
|
//GIVEN
|
|
const lbConf = (await configuration).dailyLeaderboards;
|
|
const premiumEnabled = (await configuration).users.premium.enabled;
|
|
await enableConnectionsFeature(true);
|
|
const friends = [
|
|
new ObjectId().toHexString(),
|
|
new ObjectId().toHexString(),
|
|
];
|
|
getFriendsUidsMock.mockResolvedValue(friends);
|
|
|
|
//WHEN
|
|
await mockApp
|
|
.get("/leaderboards/daily")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
friendsOnly: true,
|
|
})
|
|
.expect(200);
|
|
|
|
//THEN
|
|
|
|
expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
|
|
"english",
|
|
"time",
|
|
"60",
|
|
lbConf,
|
|
-1,
|
|
);
|
|
|
|
expect(getResultMock).toHaveBeenCalledWith(
|
|
0,
|
|
50,
|
|
lbConf,
|
|
premiumEnabled,
|
|
friends,
|
|
);
|
|
});
|
|
|
|
it("fails for daysBefore not one", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/daily")
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
daysBefore: 2,
|
|
})
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: ['"daysBefore" Invalid literal value, expected 1'],
|
|
});
|
|
});
|
|
|
|
it("fails if daily leaderboards are disabled", async () => {
|
|
await dailyLeaderboardEnabled(false);
|
|
|
|
const { body } = await mockApp.get("/leaderboards/daily").expect(503);
|
|
|
|
expect(body.message).toEqual(
|
|
"Daily leaderboards are not available at this time.",
|
|
);
|
|
});
|
|
|
|
it("should get for mode", async () => {
|
|
for (const mode of ["time", "words", "quote", "zen", "custom"]) {
|
|
const response = await mockApp
|
|
.get("/leaderboards/daily")
|
|
.query({ language: "english", mode, mode2: "custom" });
|
|
expect(response.status, "for mode " + mode).toEqual(200);
|
|
}
|
|
});
|
|
|
|
it("should get for mode2", async () => {
|
|
for (const mode2 of allModes) {
|
|
const response = await mockApp
|
|
.get("/leaderboards/daily")
|
|
.query({ language: "english", mode: "words", mode2 });
|
|
|
|
expect(response.status, "for mode2 " + mode2).toEqual(200);
|
|
}
|
|
});
|
|
|
|
it("fails for missing query", async () => {
|
|
const { body } = await mockApp.get("/leaderboards").expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: [
|
|
'"language" Required',
|
|
'"mode" Required',
|
|
'"mode2" Needs to be either a number, "zen" or "custom".',
|
|
],
|
|
});
|
|
});
|
|
|
|
it("fails for invalid query", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/daily")
|
|
.query({
|
|
language: "en?gli.sh",
|
|
mode: "unknownMode",
|
|
mode2: "unknownMode2",
|
|
})
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: [
|
|
'"language" Invalid enum value. Must be a supported language',
|
|
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
|
|
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
|
|
],
|
|
});
|
|
});
|
|
it("fails for unknown query", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/daily")
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
extra: "value",
|
|
})
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: ["Unrecognized key(s) in object: 'extra'"],
|
|
});
|
|
});
|
|
it("fails while leaderboard is missing", async () => {
|
|
//GIVEN
|
|
getDailyLeaderboardMock.mockReturnValue(null);
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/daily")
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
})
|
|
.expect(404);
|
|
|
|
expect(body.message).toEqual(
|
|
"There is no daily leaderboard for this mode",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("get daily leaderboard rank", () => {
|
|
const getDailyLeaderboardMock = vi.spyOn(
|
|
DailyLeaderboards,
|
|
"getDailyLeaderboard",
|
|
);
|
|
const getRankMock = vi.fn();
|
|
const getFriendsUidsMock = vi.spyOn(ConnectionsDal, "getFriendsUids");
|
|
|
|
beforeEach(async () => {
|
|
[getDailyLeaderboardMock, getRankMock, getFriendsUidsMock].forEach((it) =>
|
|
it.mockClear(),
|
|
);
|
|
|
|
getDailyLeaderboardMock.mockReturnValue({
|
|
getRank: getRankMock,
|
|
} as any);
|
|
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(1722606812000);
|
|
await dailyLeaderboardEnabled(true);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("fails withouth authentication", async () => {
|
|
await mockApp
|
|
.get("/leaderboards/daily/rank")
|
|
|
|
.query({ language: "english", mode: "time", mode2: "60" })
|
|
.expect(401);
|
|
});
|
|
it("should get for english time 60", async () => {
|
|
//GIVEN
|
|
const lbConf = (await configuration).dailyLeaderboards;
|
|
const rankData = {
|
|
min: 100,
|
|
count: 1000,
|
|
rank: 12,
|
|
entry: {
|
|
wpm: 10,
|
|
rank: 2,
|
|
acc: 80,
|
|
timestamp: 1200,
|
|
raw: 82,
|
|
consistency: 72,
|
|
uid: "user2",
|
|
name: "user2",
|
|
},
|
|
};
|
|
|
|
getRankMock.mockResolvedValue(rankData);
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/daily/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.query({ language: "english", mode: "time", mode2: "60" })
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body).toEqual({
|
|
message: "Daily leaderboard rank retrieved",
|
|
data: rankData,
|
|
});
|
|
|
|
expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
|
|
"english",
|
|
"time",
|
|
"60",
|
|
lbConf,
|
|
-1,
|
|
);
|
|
|
|
expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, undefined);
|
|
});
|
|
|
|
it("should get for english time 60 friends only", async () => {
|
|
//GIVEN
|
|
await enableConnectionsFeature(true);
|
|
const lbConf = (await configuration).dailyLeaderboards;
|
|
getRankMock.mockResolvedValue({});
|
|
const friends = ["friendOne", "friendTwo"];
|
|
getFriendsUidsMock.mockResolvedValue(friends);
|
|
|
|
//WHEN
|
|
await mockApp
|
|
.get("/leaderboards/daily/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
friendsOnly: true,
|
|
})
|
|
.expect(200);
|
|
|
|
//THEN
|
|
|
|
expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
|
|
"english",
|
|
"time",
|
|
"60",
|
|
lbConf,
|
|
-1,
|
|
);
|
|
|
|
expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, friends);
|
|
expect(getFriendsUidsMock).toHaveBeenCalledWith(uid);
|
|
});
|
|
|
|
it("fails if daily leaderboards are disabled", async () => {
|
|
await dailyLeaderboardEnabled(false);
|
|
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/daily/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(503);
|
|
|
|
expect(body.message).toEqual(
|
|
"Daily leaderboards are not available at this time.",
|
|
);
|
|
});
|
|
|
|
it("should get for mode", async () => {
|
|
for (const mode of ["time", "words", "quote", "zen", "custom"]) {
|
|
const response = await mockApp
|
|
.get("/leaderboards/daily/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.query({ language: "english", mode, mode2: "custom" });
|
|
expect(response.status, "for mode " + mode).toEqual(200);
|
|
}
|
|
});
|
|
|
|
it("should get for mode2", async () => {
|
|
for (const mode2 of allModes) {
|
|
const response = await mockApp
|
|
.get("/leaderboards/daily/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.query({ language: "english", mode: "words", mode2 });
|
|
|
|
expect(response.status, "for mode2 " + mode2).toEqual(200);
|
|
}
|
|
});
|
|
|
|
it("fails for missing query", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/daily/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: [
|
|
'"language" Required',
|
|
'"mode" Required',
|
|
'"mode2" Needs to be either a number, "zen" or "custom".',
|
|
],
|
|
});
|
|
});
|
|
|
|
it("fails for invalid query", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/daily/rank")
|
|
.query({
|
|
language: "en?gli.sh",
|
|
mode: "unknownMode",
|
|
mode2: "unknownMode2",
|
|
})
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: [
|
|
'"language" Invalid enum value. Must be a supported language',
|
|
`"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
|
|
'"mode2" Needs to be a number or a number represented as a string e.g. "10".',
|
|
],
|
|
});
|
|
});
|
|
|
|
it("fails for unknown query", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/daily/rank")
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
extra: "value",
|
|
})
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: ["Unrecognized key(s) in object: 'extra'"],
|
|
});
|
|
});
|
|
|
|
it("fails while leaderboard is missing", async () => {
|
|
//GIVEN
|
|
getDailyLeaderboardMock.mockReturnValue(null);
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/daily/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.query({
|
|
language: "english",
|
|
mode: "time",
|
|
mode2: "60",
|
|
})
|
|
.expect(404);
|
|
|
|
expect(body.message).toEqual(
|
|
"There is no daily leaderboard for this mode",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("get xp weekly leaderboard", () => {
|
|
const getXpWeeklyLeaderboardMock = vi.spyOn(WeeklyXpLeaderboard, "get");
|
|
const getResultMock = vi.fn();
|
|
const getFriendsUidsMock = vi.spyOn(ConnectionsDal, "getFriendsUids");
|
|
|
|
beforeEach(async () => {
|
|
[getXpWeeklyLeaderboardMock, getResultMock, getFriendsUidsMock].forEach(
|
|
(it) => it.mockClear(),
|
|
);
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(1722606812000);
|
|
await weeklyLeaderboardEnabled(true);
|
|
|
|
getXpWeeklyLeaderboardMock.mockReturnValue({
|
|
getResults: getResultMock,
|
|
} as any);
|
|
|
|
getResultMock.mockResolvedValue(null);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("should get", async () => {
|
|
//GIVEN
|
|
const lbConf = (await configuration).leaderboards.weeklyXp;
|
|
|
|
const resultData: XpLeaderboardEntry[] = [
|
|
{
|
|
totalXp: 100,
|
|
rank: 1,
|
|
timeTypedSeconds: 100,
|
|
uid: "user1",
|
|
name: "user1",
|
|
discordId: "discordId",
|
|
discordAvatar: "discordAvatar",
|
|
lastActivityTimestamp: 1000,
|
|
},
|
|
{
|
|
totalXp: 75,
|
|
rank: 2,
|
|
timeTypedSeconds: 200,
|
|
uid: "user2",
|
|
name: "user2",
|
|
discordId: "discordId2",
|
|
discordAvatar: "discordAvatar2",
|
|
lastActivityTimestamp: 2000,
|
|
},
|
|
];
|
|
|
|
getResultMock.mockResolvedValue({ count: 2, entries: resultData });
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/xp/weekly")
|
|
.query({})
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body).toEqual({
|
|
message: "Weekly xp leaderboard retrieved",
|
|
data: {
|
|
entries: resultData,
|
|
count: 2,
|
|
pageSize: 50,
|
|
},
|
|
});
|
|
|
|
expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1);
|
|
|
|
expect(getResultMock).toHaveBeenCalledWith(
|
|
0,
|
|
50,
|
|
lbConf,
|
|
false,
|
|
undefined,
|
|
);
|
|
});
|
|
|
|
it("should get for last week", async () => {
|
|
//GIVEN
|
|
const lbConf = (await configuration).leaderboards.weeklyXp;
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/xp/weekly")
|
|
.query({
|
|
weeksBefore: 1,
|
|
})
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body).toEqual({
|
|
message: "Weekly xp leaderboard retrieved",
|
|
data: {
|
|
count: 0,
|
|
entries: [],
|
|
pageSize: 50,
|
|
},
|
|
});
|
|
|
|
expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(
|
|
lbConf,
|
|
1721606400000,
|
|
);
|
|
});
|
|
|
|
it("should get with skip and limit", async () => {
|
|
//GIVEN
|
|
const lbConf = (await configuration).leaderboards.weeklyXp;
|
|
const page = 2;
|
|
const pageSize = 25;
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/xp/weekly")
|
|
.query({
|
|
page,
|
|
pageSize,
|
|
})
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body).toEqual({
|
|
message: "Weekly xp leaderboard retrieved",
|
|
data: {
|
|
entries: [],
|
|
count: 0,
|
|
pageSize,
|
|
},
|
|
});
|
|
|
|
expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1);
|
|
|
|
expect(getResultMock).toHaveBeenCalledWith(
|
|
page,
|
|
pageSize,
|
|
lbConf,
|
|
false,
|
|
undefined,
|
|
);
|
|
});
|
|
|
|
it("should get for friends", async () => {
|
|
//GIVEN
|
|
const lbConf = (await configuration).leaderboards.weeklyXp;
|
|
await enableConnectionsFeature(true);
|
|
const page = 2;
|
|
const pageSize = 25;
|
|
const friends = [
|
|
new ObjectId().toHexString(),
|
|
new ObjectId().toHexString(),
|
|
];
|
|
getFriendsUidsMock.mockResolvedValue(friends);
|
|
|
|
//WHEN
|
|
await mockApp
|
|
.get("/leaderboards/xp/weekly")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.query({
|
|
page,
|
|
pageSize,
|
|
friendsOnly: true,
|
|
})
|
|
.expect(200);
|
|
|
|
//THEN
|
|
|
|
expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1);
|
|
|
|
expect(getResultMock).toHaveBeenCalledWith(
|
|
page,
|
|
pageSize,
|
|
lbConf,
|
|
false,
|
|
friends,
|
|
);
|
|
});
|
|
|
|
it("fails if daily leaderboards are disabled", async () => {
|
|
await weeklyLeaderboardEnabled(false);
|
|
|
|
const { body } = await mockApp.get("/leaderboards/xp/weekly").expect(503);
|
|
|
|
expect(body.message).toEqual(
|
|
"Weekly XP leaderboards are not available at this time.",
|
|
);
|
|
});
|
|
|
|
it("fails for weeksBefore not one", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/xp/weekly")
|
|
.query({
|
|
weeksBefore: 2,
|
|
})
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: ['"weeksBefore" Invalid literal value, expected 1'],
|
|
});
|
|
});
|
|
|
|
it("fails for unknown query", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/xp/weekly")
|
|
.query({
|
|
extra: "value",
|
|
})
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: ["Unrecognized key(s) in object: 'extra'"],
|
|
});
|
|
});
|
|
|
|
it("fails while leaderboard is missing", async () => {
|
|
//GIVEN
|
|
getXpWeeklyLeaderboardMock.mockReturnValue(null);
|
|
|
|
//WHEN
|
|
const { body } = await mockApp.get("/leaderboards/xp/weekly").expect(404);
|
|
|
|
expect(body.message).toEqual("XP leaderboard for this week not found.");
|
|
});
|
|
});
|
|
|
|
describe("get xp weekly leaderboard rank", () => {
|
|
const getXpWeeklyLeaderboardMock = vi.spyOn(WeeklyXpLeaderboard, "get");
|
|
const getRankMock = vi.fn();
|
|
const getFriendsUidsMock = vi.spyOn(ConnectionsDal, "getFriendsUids");
|
|
|
|
beforeEach(async () => {
|
|
[getXpWeeklyLeaderboardMock, getRankMock, getFriendsUidsMock].forEach(
|
|
(it) => it.mockClear(),
|
|
);
|
|
|
|
await weeklyLeaderboardEnabled(true);
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(1722606812000);
|
|
|
|
getXpWeeklyLeaderboardMock.mockReturnValue({
|
|
getRank: getRankMock,
|
|
} as any);
|
|
});
|
|
|
|
it("fails withouth authentication", async () => {
|
|
await mockApp.get("/leaderboards/xp/weekly/rank").expect(401);
|
|
});
|
|
|
|
it("should get", async () => {
|
|
//GIVEN
|
|
const lbConf = (await configuration).leaderboards.weeklyXp;
|
|
|
|
const resultData: XpLeaderboardEntry = {
|
|
totalXp: 100,
|
|
rank: 1,
|
|
timeTypedSeconds: 100,
|
|
uid: "user1",
|
|
name: "user1",
|
|
discordId: "discordId",
|
|
discordAvatar: "discordAvatar",
|
|
lastActivityTimestamp: 1000,
|
|
};
|
|
|
|
getRankMock.mockResolvedValue(resultData);
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/xp/weekly/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body).toEqual({
|
|
message: "Weekly xp leaderboard rank retrieved",
|
|
data: resultData,
|
|
});
|
|
|
|
expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1);
|
|
|
|
expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, undefined);
|
|
});
|
|
|
|
it("should get for last week", async () => {
|
|
//GIVEN
|
|
const lbConf = (await configuration).leaderboards.weeklyXp;
|
|
getRankMock.mockResolvedValue({});
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/xp/weekly/rank")
|
|
.query({ weeksBefore: 1 })
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body).toEqual({
|
|
message: "Weekly xp leaderboard rank retrieved",
|
|
data: {},
|
|
});
|
|
|
|
expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(
|
|
lbConf,
|
|
1721606400000,
|
|
);
|
|
|
|
expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, undefined);
|
|
});
|
|
|
|
it("should get for friendsOnly", async () => {
|
|
//GIVEN
|
|
const lbConf = (await configuration).leaderboards.weeklyXp;
|
|
await enableConnectionsFeature(true);
|
|
getRankMock.mockResolvedValue({});
|
|
const friends = ["friendOne", "friendTwo"];
|
|
getFriendsUidsMock.mockResolvedValue(friends);
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/xp/weekly/rank")
|
|
.query({ friendsOnly: true })
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(200);
|
|
|
|
//THEN
|
|
expect(body).toEqual({
|
|
message: "Weekly xp leaderboard rank retrieved",
|
|
data: {},
|
|
});
|
|
|
|
expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1);
|
|
|
|
expect(getRankMock).toHaveBeenCalledWith(uid, lbConf, friends);
|
|
});
|
|
|
|
it("fails if daily leaderboards are disabled", async () => {
|
|
await weeklyLeaderboardEnabled(false);
|
|
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/xp/weekly/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(503);
|
|
|
|
expect(body.message).toEqual(
|
|
"Weekly XP leaderboards are not available at this time.",
|
|
);
|
|
});
|
|
|
|
it("fails for weeksBefore not one", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/xp/weekly/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.query({
|
|
weeksBefore: 2,
|
|
})
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: ['"weeksBefore" Invalid literal value, expected 1'],
|
|
});
|
|
});
|
|
|
|
it("fails for unknown query", async () => {
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/xp/weekly/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.query({
|
|
extra: "value",
|
|
})
|
|
.expect(422);
|
|
|
|
expect(body).toEqual({
|
|
message: "Invalid query schema",
|
|
validationErrors: ["Unrecognized key(s) in object: 'extra'"],
|
|
});
|
|
});
|
|
|
|
it("fails while leaderboard is missing", async () => {
|
|
//GIVEN
|
|
getXpWeeklyLeaderboardMock.mockReturnValue(null);
|
|
|
|
//WHEN
|
|
const { body } = await mockApp
|
|
.get("/leaderboards/xp/weekly/rank")
|
|
.set("Authorization", `Bearer ${uid}`)
|
|
.expect(404);
|
|
|
|
expect(body.message).toEqual("XP leaderboard for this week not found.");
|
|
});
|
|
});
|
|
});
|
|
|
|
async function acceptApeKeys(enabled: boolean): Promise<void> {
|
|
const mockConfig = await configuration;
|
|
mockConfig.apeKeys = { ...mockConfig.apeKeys, acceptKeys: enabled };
|
|
|
|
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
|
|
mockConfig,
|
|
);
|
|
}
|
|
|
|
async function dailyLeaderboardEnabled(enabled: boolean): Promise<void> {
|
|
const mockConfig = await configuration;
|
|
mockConfig.dailyLeaderboards = {
|
|
...mockConfig.dailyLeaderboards,
|
|
enabled: enabled,
|
|
};
|
|
|
|
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
|
|
mockConfig,
|
|
);
|
|
}
|
|
async function weeklyLeaderboardEnabled(enabled: boolean): Promise<void> {
|
|
const mockConfig = await configuration;
|
|
mockConfig.leaderboards.weeklyXp = {
|
|
...mockConfig.leaderboards.weeklyXp,
|
|
enabled,
|
|
};
|
|
|
|
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
|
|
mockConfig,
|
|
);
|
|
}
|
|
async function enableConnectionsFeature(enabled: boolean): Promise<void> {
|
|
const mockConfig = await configuration;
|
|
mockConfig.connections = { ...mockConfig.connections, enabled };
|
|
|
|
vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
|
|
mockConfig,
|
|
);
|
|
}
|