Skip to content

Commit cd99d5a

Browse files
authored
test: add WeeklyXpLeaderboards integration tests (@fehmer) (monkeytypegame#6843)
1 parent c8a91ed commit cd99d5a

File tree

5 files changed

+211
-12
lines changed

5 files changed

+211
-12
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { getConnection, connect } from "../../src/init/redis";
2+
3+
export async function redisSetup(): Promise<void> {
4+
await connect();
5+
}
6+
export async function cleanupKeys(prefix: string): Promise<void> {
7+
// oxlint-disable-next-line no-non-null-assertion
8+
const connection = getConnection()!;
9+
const keys = await connection.keys(`${prefix}*`);
10+
await Promise.all(keys?.map((it) => connection.del(it)));
11+
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import * as WeeklyXpLeaderboard from "../../../src/services/weekly-xp-leaderboard";
2+
import { Configuration } from "@monkeytype/schemas/configuration";
3+
import { ObjectId } from "mongodb";
4+
import { RedisXpLeaderboardEntry } from "@monkeytype/schemas/leaderboards";
5+
import { cleanupKeys, redisSetup } from "../redis";
6+
7+
const leaderboardsConfig: Configuration["leaderboards"]["weeklyXp"] = {
8+
enabled: true,
9+
expirationTimeInDays: 7,
10+
xpRewardBrackets: [],
11+
};
12+
13+
describe("Weekly XP Leaderboards", () => {
14+
beforeAll(async () => {
15+
await redisSetup();
16+
});
17+
afterEach(async () => {
18+
await cleanupKeys(WeeklyXpLeaderboard.__testing.namespace);
19+
});
20+
21+
describe("get", () => {
22+
it("should get if enabled", () => {
23+
expect(WeeklyXpLeaderboard.get(leaderboardsConfig)).toBeInstanceOf(
24+
WeeklyXpLeaderboard.WeeklyXpLeaderboard
25+
);
26+
});
27+
it("should return null if disabled", () => {
28+
expect(WeeklyXpLeaderboard.get({ enabled: false } as any)).toBeNull();
29+
});
30+
});
31+
32+
describe("WeeklyXpLeaderboard class", () => {
33+
// oxlint-disable-next-line no-non-null-assertion
34+
const lb = WeeklyXpLeaderboard.get(leaderboardsConfig)!;
35+
36+
describe("addResult", () => {
37+
it("adds results for user", async () => {
38+
//GIVEN
39+
const user1 = await givenResult(100, { timeTypedSeconds: 5 });
40+
await givenResult(50, { ...user1, timeTypedSeconds: 5 });
41+
const user2 = await givenResult(100, {
42+
isPremium: true,
43+
timeTypedSeconds: 7,
44+
});
45+
46+
//WHEN
47+
const results = await lb.getResults(0, 10, leaderboardsConfig, true);
48+
49+
//THEN
50+
expect(results).toEqual([
51+
{
52+
...user1,
53+
rank: 1,
54+
timeTypedSeconds: 10,
55+
totalXp: 150,
56+
isPremium: false,
57+
},
58+
{
59+
...user2,
60+
rank: 2,
61+
timeTypedSeconds: 7,
62+
totalXp: 100,
63+
isPremium: true,
64+
},
65+
]);
66+
});
67+
});
68+
69+
describe("getResults", () => {
70+
it("gets results", async () => {
71+
//GIVEN
72+
const user1 = await givenResult(150);
73+
const user2 = await givenResult(100);
74+
75+
//WHEN
76+
const results = await lb.getResults(0, 10, leaderboardsConfig, true);
77+
78+
//THEN
79+
expect(results).toEqual([
80+
{ rank: 1, totalXp: 150, ...user1 },
81+
{ rank: 2, totalXp: 100, ...user2 },
82+
]);
83+
});
84+
85+
it("gets results for page", async () => {
86+
//GIVEN
87+
const _user1 = await givenResult(100);
88+
const _user2 = await givenResult(75);
89+
const user3 = await givenResult(50);
90+
const user4 = await givenResult(25);
91+
92+
//WHEN
93+
const results = await lb.getResults(1, 2, leaderboardsConfig, true);
94+
95+
//THEN
96+
expect(results).toEqual([
97+
{ rank: 3, totalXp: 50, ...user3 },
98+
{ rank: 4, totalXp: 25, ...user4 },
99+
]);
100+
});
101+
102+
it("gets results without premium", async () => {
103+
//GIVEN
104+
const user1 = await givenResult(150, { isPremium: true });
105+
const user2 = await givenResult(100);
106+
107+
//WHEN
108+
const results = await lb.getResults(0, 10, leaderboardsConfig, false);
109+
110+
//THEN
111+
expect(results).toEqual([
112+
{ rank: 1, totalXp: 150, ...user1, isPremium: undefined },
113+
{ rank: 2, totalXp: 100, ...user2, isPremium: undefined },
114+
]);
115+
});
116+
});
117+
118+
describe("getRank", () => {
119+
it("gets rank", async () => {
120+
//GIVEN
121+
const user1 = await givenResult(100);
122+
const _user2 = await givenResult(150);
123+
124+
//WHEN
125+
const rank = await lb.getRank(user1.uid, leaderboardsConfig);
126+
//THEN
127+
expect(rank).toEqual({ rank: 2, totalXp: 100, ...user1 });
128+
});
129+
});
130+
131+
describe("getCount", () => {
132+
it("gets count", async () => {
133+
//GIVEN
134+
await givenResult(100);
135+
await givenResult(150);
136+
137+
//WHEN
138+
const count = await lb.getCount();
139+
//THEN
140+
expect(count).toEqual(2);
141+
});
142+
});
143+
144+
it("purgeUserFromDailyLeaderboards", async () => {
145+
//GIVEN
146+
const cheater = await givenResult(50);
147+
const validUser = await givenResult(1000);
148+
149+
//WHEN
150+
await WeeklyXpLeaderboard.purgeUserFromXpLeaderboards(
151+
cheater.uid,
152+
leaderboardsConfig
153+
);
154+
//THEN
155+
expect(await lb.getRank(cheater.uid, leaderboardsConfig)).toBeNull();
156+
expect(await lb.getResults(0, 50, leaderboardsConfig, true)).toEqual([
157+
{ rank: 1, totalXp: 1000, ...validUser },
158+
]);
159+
});
160+
161+
async function givenResult(
162+
xpGained: number,
163+
entry?: Partial<RedisXpLeaderboardEntry>
164+
): Promise<RedisXpLeaderboardEntry> {
165+
const uid = new ObjectId().toHexString();
166+
const result: RedisXpLeaderboardEntry = {
167+
uid,
168+
name: `User ${uid}`,
169+
lastActivityTimestamp: Date.now(),
170+
timeTypedSeconds: 42,
171+
badgeId: 2,
172+
discordAvatar: `${uid}Avatar`,
173+
discordId: `${uid}DiscordId`,
174+
isPremium: false,
175+
...entry,
176+
};
177+
178+
await lb.addResult(leaderboardsConfig, { xpGained, entry: result });
179+
return result;
180+
}
181+
});
182+
});

backend/__tests__/__integration__/utils/daily-leaderboards.spec.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { Mode, Mode2 } from "@monkeytype/schemas/shared";
22
import * as DailyLeaderboards from "../../../src/utils/daily-leaderboards";
3-
import { getConnection, connect as redisSetup } from "../../../src/init/redis";
3+
import { cleanupKeys, redisSetup } from "../redis";
44
import { Language } from "@monkeytype/schemas/languages";
55

66
import { RedisDailyLeaderboardEntry } from "@monkeytype/schemas/leaderboards";
77
import { ObjectId } from "mongodb";
8+
import { Configuration } from "@monkeytype/schemas/configuration";
89

9-
const dailyLeaderboardsConfig = {
10+
const dailyLeaderboardsConfig: Configuration["dailyLeaderboards"] = {
1011
enabled: true,
1112
maxResults: 10,
1213
leaderboardExpirationTimeInDays: 1,
@@ -32,7 +33,7 @@ describe("Daily Leaderboards", () => {
3233
await redisSetup();
3334
});
3435
afterEach(async () => {
35-
await getConnection()?.flushall();
36+
await cleanupKeys(DailyLeaderboards.__testing.namespace);
3637
});
3738
describe("should properly handle valid and invalid modes", () => {
3839
const testCases: {
@@ -256,8 +257,7 @@ describe("Daily Leaderboards", () => {
256257
it("purgeUserFromDailyLeaderboards", async () => {
257258
//GIVEN
258259
const cheater = await givenResult({ wpm: 50 });
259-
await givenResult({ wpm: 60 });
260-
await givenResult({ wpm: 40 });
260+
const validUser = await givenResult();
261261

262262
//WHEN
263263
await DailyLeaderboards.purgeUserFromDailyLeaderboards(
@@ -266,15 +266,13 @@ describe("Daily Leaderboards", () => {
266266
);
267267
//THEN
268268
expect(await lb.getRank(cheater.uid, dailyLeaderboardsConfig)).toBeNull();
269-
expect(
270-
(await lb.getResults(0, 50, dailyLeaderboardsConfig, false)).filter(
271-
(it) => it.uid === cheater.uid
272-
)
273-
).toEqual([]);
269+
expect(await lb.getResults(0, 50, dailyLeaderboardsConfig, true)).toEqual(
270+
[{ rank: 1, ...validUser }]
271+
);
274272
});
275273

276274
async function givenResult(
277-
entry: Partial<RedisDailyLeaderboardEntry>
275+
entry?: Partial<RedisDailyLeaderboardEntry>
278276
): Promise<RedisDailyLeaderboardEntry> {
279277
const uid = new ObjectId().toHexString();
280278
const result = {

backend/src/services/weekly-xp-leaderboard.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { omit } from "lodash";
1313
import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json";
1414
import { tryCatchSync } from "@monkeytype/util/trycatch";
1515

16-
type AddResultOpts = {
16+
export type AddResultOpts = {
1717
entry: RedisXpLeaderboardEntry;
1818
xpGained: RedisXpLeaderboardScore;
1919
};
@@ -285,3 +285,7 @@ export async function purgeUserFromXpLeaderboards(
285285
weeklyXpLeaderboardLeaderboardNamespace
286286
);
287287
}
288+
289+
export const __testing = {
290+
namespace: weeklyXpLeaderboardLeaderboardNamespace,
291+
};

backend/src/utils/daily-leaderboards.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,3 +298,7 @@ export function getDailyLeaderboard(
298298

299299
return new DailyLeaderboard(modeRule, customTimestamp);
300300
}
301+
302+
export const __testing = {
303+
namespace: dailyLeaderboardNamespace,
304+
};

0 commit comments

Comments
 (0)