Files
test/backend/__tests__/__integration__/dal/user.spec.ts
Benjamin Falch 2bc741fb78
Some checks failed
Mark Stale PRs / stale (push) Has been cancelled
adding monkeytype
2026-04-23 13:53:44 +02:00

2219 lines
58 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { CustomThemeColors } from "@monkeytype/schemas/configs";
import { PersonalBest, PersonalBests } from "@monkeytype/schemas/shared";
import { MonkeyMail, ResultFilters } from "@monkeytype/schemas/users";
import { ObjectId } from "mongodb";
import * as UserDAL from "../../../src/dal/user";
import { createConnection as createFriend } from "../../__testData__/connections";
import * as UserTestData from "../../__testData__/users";
const mockPersonalBest: PersonalBest = {
acc: 1,
consistency: 1,
difficulty: "normal" as const,
lazyMode: true,
language: "polish",
punctuation: false,
raw: 230,
wpm: 215,
timestamp: 13123123,
};
const mockResultFilter: ResultFilters = {
_id: "id",
name: "sfdkjhgdf",
pb: {
no: true,
yes: true,
},
difficulty: {
normal: true,
expert: false,
master: false,
},
mode: {
words: false,
time: true,
quote: false,
zen: false,
custom: false,
},
words: {
"10": false,
"25": false,
"50": false,
"100": false,
custom: false,
},
time: {
"15": false,
"30": true,
"60": false,
"120": false,
custom: false,
},
quoteLength: {
short: false,
medium: false,
long: false,
thicc: false,
},
punctuation: {
on: false,
off: true,
},
numbers: {
on: false,
off: true,
},
date: {
last_day: false,
last_week: false,
last_month: false,
last_3months: false,
all: true,
},
tags: {
none: true,
},
language: {
english: true,
},
funbox: {
none: true,
},
};
const mockDbResultFilter = { ...mockResultFilter, _id: new ObjectId() };
describe("UserDal", () => {
it("should be able to insert users", async () => {
// given
const uid = new ObjectId().toHexString();
const newUser = {
name: "Test",
email: "mockemail@email.com",
uid,
};
// when
await UserDAL.addUser(newUser.name, newUser.email, newUser.uid);
const insertedUser = await UserDAL.getUser(newUser.uid, "test");
// then
expect(insertedUser.email).toBe(newUser.email);
expect(insertedUser.uid).toBe(newUser.uid);
expect(insertedUser.name).toBe(newUser.name);
});
it("should error if the user already exists", async () => {
// given
const uid = new ObjectId().toHexString();
const newUser = {
name: "Test",
email: "mockemail@email.com",
uid: uid,
};
// when
await UserDAL.addUser(newUser.name, newUser.email, newUser.uid);
// then
// should error because user already exists
await expect(
UserDAL.addUser(newUser.name, newUser.email, newUser.uid),
).rejects.toThrow("User document already exists");
});
it("isNameAvailable should correctly check if a username is available", async () => {
// given
const name1 = "user" + new ObjectId().toHexString();
const name2 = "user" + new ObjectId().toHexString();
const { uid: user1 } = await UserTestData.createUser({ name: name1 });
await UserTestData.createUser({ name: name2 });
const testCases = [
{
name: name1,
whosChecking: user1,
expected: true,
},
{
name: name1.toUpperCase(),
whosChecking: user1,
expected: true,
},
{
name: name2,
whosChecking: user1,
expected: false,
},
];
// when, then
for (const { name, expected, whosChecking } of testCases) {
const isAvailable = await UserDAL.isNameAvailable(name, whosChecking);
expect(isAvailable).toBe(expected);
}
});
it("updatename should not allow unavailable usernames", async () => {
// given
const name1 = "user" + new ObjectId().toHexString();
const name2 = "user" + new ObjectId().toHexString();
const user1 = await UserTestData.createUser({ name: name1 });
const user2 = await UserTestData.createUser({ name: name2 });
const _decoy = await UserTestData.createUser();
// when, then
await expect(
UserDAL.updateName(user1.uid, user2.name, user1.name),
).rejects.toThrow("Username already taken");
});
it("same usernames (different casing) should be available only for the same user", async () => {
const name1 = "user" + new ObjectId().toHexString();
const name2 = "user" + new ObjectId().toHexString();
const user1 = await UserTestData.createUser({ name: name1 });
const user2 = await UserTestData.createUser({ name: name2 });
await UserDAL.updateName(user1.uid, name1.toUpperCase(), user1.name);
const updatedUser1 = await UserDAL.getUser(user1.uid, "test");
// when, then
expect(updatedUser1.name).toBe(name1.toUpperCase());
await expect(
UserDAL.updateName(user2.uid, name1, user2.name),
).rejects.toThrow("Username already taken");
});
it("UserDAL.updateName should change the name of a user", async () => {
// given
const name = "user" + new ObjectId().toHexString();
const renamed = "renamed" + new ObjectId().toHexString();
const testUser = await UserTestData.createUser({ name: name });
// when
await UserDAL.updateName(testUser.uid, renamed, testUser.name);
// then
const updatedUser = await UserDAL.getUser(testUser.uid, "test");
expect(updatedUser.name).toBe(renamed);
});
it("clearPb should clear the personalBests of a user", async () => {
// given
const testUser = await UserTestData.createUser();
await UserDAL.getUsersCollection().updateOne(
{ uid: testUser.uid },
{
$set: {
personalBests: {
time: { 20: [mockPersonalBest] },
words: {},
quote: {},
zen: {},
custom: {},
},
},
},
);
const { personalBests } =
(await UserDAL.getUser(testUser.uid, "test")) ?? {};
expect(personalBests).toStrictEqual({
time: { 20: [mockPersonalBest] },
words: {},
quote: {},
custom: {},
zen: {},
});
// when
await UserDAL.clearPb(testUser.uid);
// then
const updatedUser = (await UserDAL.getUser(testUser.uid, "test")) ?? {};
expect(updatedUser.personalBests).toStrictEqual({
time: {},
words: {},
quote: {},
custom: {},
zen: {},
});
});
it("autoBan should automatically ban after configured anticheat triggers", async () => {
// given
const testUser = await UserTestData.createUser();
// when
Date.now = vi.fn(() => 0);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
// then
const updatedUser = await UserDAL.getUser(testUser.uid, "test");
expect(updatedUser.banned).toBe(true);
expect(updatedUser.autoBanTimestamps).toEqual([0, 0, 0]);
});
it("autoBan should not ban ban if triggered once", async () => {
// given
const testUser = await UserTestData.createUser();
// when
Date.now = vi.fn(() => 0);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
// then
const updatedUser = await UserDAL.getUser(testUser.uid, "test");
expect(updatedUser.banned).toBe(undefined);
expect(updatedUser.autoBanTimestamps).toEqual([0]);
});
it("autoBan should correctly remove old anticheat triggers", async () => {
// given
const testUser = await UserTestData.createUser();
// when
Date.now = vi.fn(() => 0);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
Date.now = vi.fn(() => 36000000);
await UserDAL.recordAutoBanEvent(testUser.uid, 2, 1);
// then
const updatedUser = await UserDAL.getUser(testUser.uid, "test");
expect(updatedUser.banned).toBe(undefined);
expect(updatedUser.autoBanTimestamps).toEqual([36000000]);
});
describe("addResultFilterPreset", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.addResultFilterPreset("non existing uid", mockResultFilter, 5),
).rejects.toThrow(
"Maximum number of custom filters reached\nStack: add result filter preset",
);
});
it("should return error if user has reached maximum", async () => {
// given
const { uid } = await UserTestData.createUser({
resultFilterPresets: [mockDbResultFilter],
});
// when, then
await expect(
UserDAL.addResultFilterPreset(uid, mockResultFilter, 1),
).rejects.toThrow(
"Maximum number of custom filters reached\nStack: add result filter preset",
);
});
it("should handle zero maximum", async () => {
// given
const { uid } = await UserTestData.createUser();
// when, then
await expect(
UserDAL.addResultFilterPreset(uid, mockResultFilter, 0),
).rejects.toThrow(
"Maximum number of custom filters reached\nStack: add result filter preset",
);
});
it("addResultFilterPreset success", async () => {
// given
const { uid } = await UserTestData.createUser({
resultFilterPresets: [mockDbResultFilter],
});
// when
const result = await UserDAL.addResultFilterPreset(
uid,
{ ...mockResultFilter },
2,
);
// then
const read = await UserDAL.getUser(uid, "read");
const createdFilter = read.resultFilterPresets ?? [];
expect(result).toStrictEqual(createdFilter[1]?._id);
});
});
describe("removeResultFilterPreset", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.removeResultFilterPreset(
"non existing uid",
new ObjectId().toHexString(),
),
).rejects.toThrow("Custom filter not found\nStack: remove result filter");
});
it("should return error if filter is unknown", async () => {
// given
const { uid } = await UserTestData.createUser({
resultFilterPresets: [mockDbResultFilter],
});
// when, then
await expect(
UserDAL.removeResultFilterPreset(uid, new ObjectId().toHexString()),
).rejects.toThrow("Custom filter not found\nStack: remove result filter");
});
it("should remove filter", async () => {
// given
const filterOne = { ...mockDbResultFilter, _id: new ObjectId() };
const filterTwo = { ...mockDbResultFilter, _id: new ObjectId() };
const filterThree = { ...mockDbResultFilter, _id: new ObjectId() };
const { uid } = await UserTestData.createUser({
resultFilterPresets: [filterOne, filterTwo, filterThree],
});
// when, then
await UserDAL.removeResultFilterPreset(uid, filterTwo._id.toHexString());
const read = await UserDAL.getUser(uid, "read");
expect(read.resultFilterPresets).toStrictEqual([filterOne, filterThree]);
});
});
describe("addTag", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.addTag("non existing uid", "tagName"),
).rejects.toThrow("Maximum number of tags reached\nStack: add tag");
});
it("should return error if user has reached maximum", async () => {
// given
const { uid } = await UserTestData.createUser({
tags: new Array(15).fill(0).map(() => ({
_id: new ObjectId(),
name: "any",
personalBests: {} as any,
})),
});
// when, then
await expect(UserDAL.addTag(uid, "new")).rejects.toThrow(
"Maximum number of tags reached\nStack: add tag",
);
});
it("addTag success", async () => {
// given
const emptyPb: PersonalBests = {
time: {},
words: {},
quote: {},
zen: {},
custom: {},
};
const { uid } = await UserTestData.createUser({
tags: [
{
_id: new ObjectId(),
name: "first",
personalBests: emptyPb,
},
],
});
// when
await UserDAL.addTag(uid, "newTag");
// then
const read = await UserDAL.getUser(uid, "read");
expect(read.tags).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: "first", personalBests: emptyPb }),
expect.objectContaining({ name: "newTag", personalBests: emptyPb }),
]),
);
});
});
describe("editTag", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.editTag(
"non existing uid",
new ObjectId().toHexString(),
"newName",
),
).rejects.toThrow("Tag not found\nStack: edit tag");
});
it("should fail if tag not found", async () => {
// given
const tagOne: UserDAL.DBUserTag = {
_id: new ObjectId(),
name: "one",
personalBests: {} as any,
};
const { uid } = await UserTestData.createUser({
tags: [tagOne],
});
// when, then
await expect(
UserDAL.editTag(uid, new ObjectId().toHexString(), "newName"),
).rejects.toThrow("Tag not found\nStack: edit tag");
});
it("editTag success", async () => {
// given
const tagOne: UserDAL.DBUserTag = {
_id: new ObjectId(),
name: "one",
personalBests: {} as any,
};
const { uid } = await UserTestData.createUser({
tags: [tagOne],
});
// when
await UserDAL.editTag(uid, tagOne._id.toHexString(), "newTagName");
// then
const read = await UserDAL.getUser(uid, "read");
expect(read.tags ?? [][0]).toStrictEqual([
{ ...tagOne, name: "newTagName" },
]);
});
});
describe("removeTag", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.removeTag("non existing uid", new ObjectId().toHexString()),
).rejects.toThrow("Tag not found\nStack: remove tag");
});
it("should return error if tag is unknown", async () => {
// given
const tagOne: UserDAL.DBUserTag = {
_id: new ObjectId(),
name: "one",
personalBests: {} as any,
};
const { uid } = await UserTestData.createUser({
tags: [tagOne],
});
// when, then
await expect(
UserDAL.removeTag(uid, new ObjectId().toHexString()),
).rejects.toThrow("Tag not found\nStack: remove tag");
});
it("should remove tag", async () => {
// given
const tagOne = {
_id: new ObjectId(),
name: "tagOne",
personalBests: {} as any,
};
const tagTwo = {
_id: new ObjectId(),
name: "tagTwo",
personalBests: {} as any,
};
const tagThree = {
_id: new ObjectId(),
name: "tagThree",
personalBests: {} as any,
};
const { uid } = await UserTestData.createUser({
tags: [tagOne, tagTwo, tagThree],
});
// when, then
await UserDAL.removeTag(uid, tagTwo._id.toHexString());
const read = await UserDAL.getUser(uid, "read");
expect(read.tags).toStrictEqual([tagOne, tagThree]);
});
});
describe("removeTagPb", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.removeTagPb("non existing uid", new ObjectId().toHexString()),
).rejects.toThrow("Tag not found\nStack: remove tag pb");
});
it("should return error if tag is unknown", async () => {
// given
const tagOne: UserDAL.DBUserTag = {
_id: new ObjectId(),
name: "one",
personalBests: {} as any,
};
const { uid } = await UserTestData.createUser({
tags: [tagOne],
});
// when, then
await expect(
UserDAL.removeTagPb(uid, new ObjectId().toHexString()),
).rejects.toThrow("Tag not found\nStack: remove tag pb");
});
it("should remove tag pb", async () => {
// given
const tagOne = {
_id: new ObjectId(),
name: "tagOne",
personalBests: {
custom: { custom: [mockPersonalBest] },
} as PersonalBests,
};
const tagTwo = {
_id: new ObjectId(),
name: "tagTwo",
personalBests: {
custom: { custom: [mockPersonalBest] },
} as PersonalBests,
};
const tagThree = {
_id: new ObjectId(),
name: "tagThree",
personalBests: {
custom: { custom: [mockPersonalBest] },
} as PersonalBests,
};
const { uid } = await UserTestData.createUser({
tags: [tagOne, tagTwo, tagThree],
});
// when, then
await UserDAL.removeTagPb(uid, tagTwo._id.toHexString());
const read = await UserDAL.getUser(uid, "read");
expect(read.tags).toStrictEqual([
tagOne,
{
...tagTwo,
personalBests: {
time: {},
words: {},
quote: {},
zen: {},
custom: {},
},
},
tagThree,
]);
});
});
describe("updateProfile", () => {
it("updateProfile should appropriately handle multiple profile updates", async () => {
const uid = new ObjectId().toHexString();
await UserDAL.addUser("test name", "test email", uid);
await UserDAL.updateProfile(
uid,
{
bio: "test bio",
},
{
badges: [],
},
);
const user = await UserDAL.getUser(uid, "test add result filters");
expect(user.profileDetails).toStrictEqual({
bio: "test bio",
});
expect(user.inventory).toStrictEqual({
badges: [],
});
await UserDAL.updateProfile(
uid,
{
keyboard: "test keyboard",
socialProfiles: {
twitter: "test twitter",
},
},
{
badges: [
{
id: 1,
selected: true,
},
],
},
);
const updatedUser = await UserDAL.getUser(uid, "test add result filters");
expect(updatedUser.profileDetails).toStrictEqual({
bio: "test bio",
keyboard: "test keyboard",
socialProfiles: {
twitter: "test twitter",
},
});
expect(updatedUser.inventory).toStrictEqual({
badges: [
{
id: 1,
selected: true,
},
],
});
await UserDAL.updateProfile(
uid,
{
bio: "test bio 2",
socialProfiles: {
github: "test github",
website: "test website",
},
},
{
badges: [
{
id: 1,
},
],
},
);
const updatedUser2 = await UserDAL.getUser(
uid,
"test add result filters",
);
expect(updatedUser2.profileDetails).toStrictEqual({
bio: "test bio 2",
keyboard: "test keyboard",
socialProfiles: {
twitter: "test twitter",
github: "test github",
website: "test website",
},
});
expect(updatedUser2.inventory).toStrictEqual({
badges: [
{
id: 1,
},
],
});
});
it("should omit undefined or empty object values", async () => {
//GIVEN
const givenUser = await UserTestData.createUser({
profileDetails: {
bio: "test bio",
keyboard: "test keyboard",
socialProfiles: {
twitter: "test twitter",
github: "test github",
},
},
});
//WHEN
await UserDAL.updateProfile(givenUser.uid, {
bio: undefined, //ignored
keyboard: "updates",
socialProfiles: {}, //ignored
});
//THEN
const read = await UserDAL.getUser(givenUser.uid, "read");
expect(read.profileDetails).toStrictEqual({
...givenUser.profileDetails,
keyboard: "updates",
});
});
});
it("resetUser should reset user", async () => {
const uid = new ObjectId().toHexString();
await UserDAL.addUser("test name", "test email", uid);
await UserDAL.updateProfile(
uid,
{
bio: "test bio",
keyboard: "test keyboard",
socialProfiles: {
twitter: "test twitter",
github: "test github",
},
},
{
badges: [],
},
);
await UserDAL.incrementBananas(uid, 100);
await UserDAL.incrementXp(uid, 15);
await UserDAL.resetUser(uid);
const resetUser = await UserDAL.getUser(uid, "test add result filters");
expect(resetUser.profileDetails).toStrictEqual({
bio: "",
keyboard: "",
socialProfiles: {},
});
expect(resetUser.inventory).toStrictEqual({
badges: [],
});
expect(resetUser.bananas).toStrictEqual(0);
expect(resetUser.xp).toStrictEqual(0);
expect(resetUser.streak).toStrictEqual({
length: 0,
lastResultTimestamp: 0,
maxLength: 0,
});
});
it("getInbox should return the user's inbox", async () => {
const uid = new ObjectId().toHexString();
await UserDAL.addUser("test name", "test email", uid);
const emptyInbox = await UserDAL.getInbox(uid);
expect(emptyInbox).toStrictEqual([]);
await UserDAL.addToInbox(
uid,
[
{
subject: `Hello!`,
} as any,
],
{
enabled: true,
maxMail: 100,
},
);
const inbox = await UserDAL.getInbox(uid);
expect(inbox).toStrictEqual([
{
subject: "Hello!",
},
]);
});
it("addToInbox discards mail if inbox is full", async () => {
const uid = new ObjectId().toHexString();
await UserDAL.addUser("test name", "test email", uid);
const config = {
enabled: true,
maxMail: 1,
};
await UserDAL.addToInbox(
uid,
[
{
subject: "Hello 1!",
} as any,
],
config,
);
await UserDAL.addToInbox(
uid,
[
{
subject: "Hello 2!",
} as any,
],
config,
);
const inbox = await UserDAL.getInbox(uid);
expect(inbox).toStrictEqual([
{
subject: "Hello 2!",
},
]);
});
it("addToInboxBulk should add mail to multiple users", async () => {
const { uid: user1 } = await UserTestData.createUser();
const { uid: user2 } = await UserTestData.createUser();
await UserDAL.addToInboxBulk(
[
{
uid: user1,
mail: [
{
subject: `Hello!`,
} as any,
],
},
{
uid: user2,
mail: [
{
subject: `Hello 2!`,
} as any,
],
},
],
{
enabled: true,
maxMail: 100,
},
);
const inbox = await UserDAL.getInbox(user1);
const inbox2 = await UserDAL.getInbox(user2);
expect(inbox).toStrictEqual([
{
subject: "Hello!",
},
]);
expect(inbox2).toStrictEqual([
{
subject: "Hello 2!",
},
]);
});
describe("updateStreak", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(UserDAL.updateStreak("non existing uid", 0)).rejects.toThrow(
"User not found\nStack: calculate streak",
);
});
it("updateStreak should update streak", async () => {
const { uid } = await UserTestData.createUser();
const testSteps = [
{
date: "2023/06/07 21:00:00 UTC",
expectedStreak: 1,
},
{
date: "2023/06/07 23:00:00 UTC",
expectedStreak: 1,
},
{
date: "2023/06/08 00:00:00 UTC",
expectedStreak: 2,
},
{
date: "2023/06/08 23:00:00 UTC",
expectedStreak: 2,
},
{
date: "2023/06/09 00:00:00 UTC",
expectedStreak: 3,
},
{
date: "2023/06/11 00:00:00 UTC",
expectedStreak: 1,
},
];
for (const { date, expectedStreak } of testSteps) {
const milis = new Date(date).getTime();
Date.now = vi.fn(() => milis);
const streak = await UserDAL.updateStreak(uid, milis);
expect(streak).toBe(expectedStreak);
}
});
it("positive streak offset should award streak correctly", async () => {
const { uid } = await UserTestData.createUser({
streak: { hourOffset: 10 } as any,
});
const testSteps = [
{
date: "2023/06/06 21:00:00 UTC",
expectedStreak: 1,
},
{
date: "2023/06/07 01:00:00 UTC",
expectedStreak: 1,
},
{
date: "2023/06/07 09:00:00 UTC",
expectedStreak: 1,
},
{
date: "2023/06/07 10:00:00 UTC",
expectedStreak: 2,
},
{
date: "2023/06/07 23:00:00 UTC",
expectedStreak: 2,
},
{
date: "2023/06/08 00:00:00 UTC",
expectedStreak: 2,
},
{
date: "2023/06/08 01:00:00 UTC",
expectedStreak: 2,
},
{
date: "2023/06/08 09:00:00 UTC",
expectedStreak: 2,
},
{
date: "2023/06/08 10:00:00 UTC",
expectedStreak: 3,
},
{
date: "2023/06/10 10:00:00 UTC",
expectedStreak: 1,
},
];
for (const { date, expectedStreak } of testSteps) {
const milis = new Date(date).getTime();
Date.now = vi.fn(() => milis);
const streak = await UserDAL.updateStreak(uid, milis);
expect(streak).toBe(expectedStreak);
}
});
it("negative streak offset should award streak correctly", async () => {
const { uid } = await UserTestData.createUser({
streak: { hourOffset: -4 } as any,
});
const testSteps = [
{
date: "2023/06/06 19:00:00 UTC",
expectedStreak: 1,
},
{
date: "2023/06/06 20:00:00 UTC",
expectedStreak: 2,
},
{
date: "2023/06/07 01:00:00 UTC",
expectedStreak: 2,
},
{
date: "2023/06/07 19:00:00 UTC",
expectedStreak: 2,
},
{
date: "2023/06/07 20:00:00 UTC",
expectedStreak: 3,
},
{
date: "2023/06/09 23:00:00 UTC",
expectedStreak: 1,
},
];
for (const { date, expectedStreak } of testSteps) {
const milis = new Date(date).getTime();
Date.now = vi.fn(() => milis);
const streak = await UserDAL.updateStreak(uid, milis);
expect(streak).toBe(expectedStreak);
}
});
});
describe("incrementTestActivity", () => {
it("ignores user without migration", async () => {
// given
const user = await UserTestData.createUserWithoutMigration();
//when
await UserDAL.incrementTestActivity(user, 1712102400000);
//then
const read = await UserDAL.getUser(user.uid, "");
expect(read.testActivity).toBeUndefined();
});
it("increments for new year", async () => {
// given
const user = await UserTestData.createUser({
testActivity: { "2023": [null, 1] },
});
//when
await UserDAL.incrementTestActivity(user, 1712102400000);
//then
const read = (await UserDAL.getUser(user.uid, "")).testActivity || {};
expect(read).toHaveProperty("2024");
const year2024 = read["2024"] as any;
expect(year2024).toHaveLength(94);
//fill previous days with null
expect(year2024.slice(0, 93)).toEqual(new Array(93).fill(null));
expect(year2024[93]).toEqual(1);
});
it("increments for existing year", async () => {
// given
const user = await UserTestData.createUser({
testActivity: { "2024": [null, 5] },
});
//when
await UserDAL.incrementTestActivity(user, 1712102400000);
//then
const read = (await UserDAL.getUser(user.uid, "")).testActivity || {};
expect(read).toHaveProperty("2024");
const year2024 = read["2024"] as any;
expect(year2024).toHaveLength(94);
expect(year2024[0]).toBeNull();
expect(year2024[1]).toEqual(5);
expect(year2024.slice(2, 91)).toEqual(new Array(89).fill(null));
expect(year2024[93]).toEqual(1);
});
it("increments for existing day", async () => {
// given
let user = await UserTestData.createUser({ testActivity: {} });
await UserDAL.incrementTestActivity(user, 1712102400000);
user = await UserDAL.getUser(user.uid, "");
//when
await UserDAL.incrementTestActivity(user, 1712102400000);
//then
const read = (await UserDAL.getUser(user.uid, "")).testActivity || {};
const year2024 = read["2024"] as any;
expect(year2024[93]).toEqual(2);
});
});
describe("getUser", () => {
it("should get with missing personalBests", async () => {
//GIVEN
let user = await UserTestData.createUser({ personalBests: undefined });
//WHEN
const read = await UserDAL.getUser(user.uid, "read");
expect(read.personalBests).toEqual({
custom: {},
quote: {},
time: {},
words: {},
zen: {},
});
});
});
describe("getUserByName", () => {
it("should get with missing personalBests", async () => {
//GIVEN
let user = await UserTestData.createUser({ personalBests: undefined });
//WHEN
const read = await UserDAL.getUserByName(user.name, "read");
expect(read.personalBests).toEqual({
custom: {},
quote: {},
time: {},
words: {},
zen: {},
});
});
});
describe("getPersonalBests", () => {
it("should get with missing personalBests", async () => {
//GIVEN
let user = await UserTestData.createUser({ personalBests: undefined });
//WHEN
const read = await UserDAL.getPersonalBests(user.uid, "time", "15");
expect(read).toBeUndefined();
});
});
describe("getPartialUser", () => {
it("should throw for unknown user", async () => {
await expect(async () =>
UserDAL.getPartialUser("1234", "stack", []),
).rejects.toThrow("User not found\nStack: stack");
});
it("should get streak", async () => {
//GIVEN
let user = await UserTestData.createUser({
streak: {
hourOffset: 1,
length: 5,
lastResultTimestamp: 4711,
maxLength: 23,
},
});
//WHEN
const partial = await UserDAL.getPartialUser(user.uid, "streak", [
"streak",
]);
//THEN
expect(partial).toStrictEqual({
_id: user._id,
streak: {
hourOffset: 1,
length: 5,
lastResultTimestamp: 4711,
maxLength: 23,
},
});
});
it("should get with missing personalBests", async () => {
//GIVEN
let user = await UserTestData.createUser({ personalBests: undefined });
//WHEN
const read = await UserDAL.getPartialUser(user.uid, "read", [
"uid",
"personalBests",
]);
expect(read.personalBests).toEqual({
custom: {},
quote: {},
time: {},
words: {},
zen: {},
});
});
});
describe("updateEmail", () => {
it("throws for nonexisting user", async () => {
await expect(async () =>
UserDAL.updateEmail("unknown", "test@example.com"),
).rejects.toThrow("User not found\nStack: update email");
});
it("should update", async () => {
//given
const { uid } = await UserTestData.createUser({ email: "init" });
//when
await expect(UserDAL.updateEmail(uid, "next")).resolves.toBe(true);
//then
const read = await UserDAL.getUser(uid, "read");
expect(read.email).toEqual("next");
});
});
describe("resetPb", () => {
it("throws for nonexisting user", async () => {
await expect(async () => UserDAL.resetPb("unknown")).rejects.toThrow(
"User not found\nStack: reset pb",
);
});
it("should reset", async () => {
//given
const { uid } = await UserTestData.createUser({
personalBests: { custom: { custom: [{ acc: 1 } as any] } } as any,
});
//when
await UserDAL.resetPb(uid);
//then
const read = await UserDAL.getUser(uid, "read");
expect(read.personalBests).toStrictEqual({
time: {},
words: {},
quote: {},
zen: {},
custom: {},
});
});
});
describe("linkDiscord", () => {
it("throws for nonexisting user", async () => {
await expect(async () =>
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",
});
//when
await UserDAL.linkDiscord(uid, "newId", "newAvatar");
//then
const read = await UserDAL.getUser(uid, "read");
expect(read.discordId).toEqual("newId");
expect(read.discordAvatar).toEqual("newAvatar");
});
it("should update without avatar", async () => {
//given
const { uid } = await UserTestData.createUser({
discordId: "discordId",
discordAvatar: "discordAvatar",
});
//when
await UserDAL.linkDiscord(uid, "newId");
//then
const read = await UserDAL.getUser(uid, "read");
expect(read.discordId).toEqual("newId");
expect(read.discordAvatar).toEqual("discordAvatar");
});
});
describe("unlinkDiscord", () => {
it("throws for nonexisting user", async () => {
await expect(async () =>
UserDAL.unlinkDiscord("unknown"),
).rejects.toThrow("User not found\nStack: unlink discord");
});
it("should update", async () => {
//given
const { uid } = await UserTestData.createUser({
discordId: "discordId",
discordAvatar: "discordAvatar",
});
//when
await UserDAL.unlinkDiscord(uid);
//then
const read = await UserDAL.getUser(uid, "read");
expect(read.discordId).toBeUndefined();
expect(read.discordAvatar).toBeUndefined();
});
});
describe("updateInbox", () => {
it("claims rewards on read", async () => {
//GIVEN
const rewardOne: MonkeyMail = {
id: "b5866d4c-0749-41b6-b101-3656249d39b9",
body: "test",
subject: "reward one",
timestamp: 1,
read: false,
rewards: [
{ type: "xp", item: 400 },
{ type: "xp", item: 600 },
{ type: "badge", item: { id: 4 } },
],
};
const rewardTwo: MonkeyMail = {
id: "3692b9f5-84fb-4d9b-bd39-9a3217b3a33a",
body: "test",
subject: "reward two",
timestamp: 2,
read: false,
rewards: [{ type: "xp", item: 2000 }],
};
const rewardThree: MonkeyMail = {
id: "0d73b3e0-dc79-4abb-bcaf-66fa6b09a58a",
body: "test",
subject: "reward three",
timestamp: 3,
read: true,
rewards: [{ type: "xp", item: 3000 }],
};
const rewardFour: MonkeyMail = {
id: "d852d2cf-1802-4cd0-9fb4-336650fc470a",
body: "test",
subject: "reward four",
timestamp: 4,
read: false,
rewards: [{ type: "xp", item: 4000 }],
};
let user = await UserTestData.createUser({
xp: 100,
inbox: [rewardOne, rewardTwo, rewardThree, rewardFour],
});
//WNEN
await UserDAL.updateInbox(
user.uid,
[rewardOne.id, rewardTwo.id, rewardThree.id],
[],
);
//THEN
const read = await UserDAL.getUser(user.uid, "");
expect(read).not.toHaveProperty("tmp");
const { xp, inbox } = read;
expect(xp).toEqual(3100); //100 existing + 1000 from rewardOne, 2000 from rewardTwo
//inbox is sorted by timestamp
expect(inbox).toStrictEqual([
{ ...rewardFour },
{ ...rewardThree },
{ ...rewardTwo, read: true, rewards: [] },
{ ...rewardOne, read: true, rewards: [] },
]);
});
it("claims rewards on delete", async () => {
//GIVEN
//GIVEN
const rewardOne: MonkeyMail = {
id: "b5866d4c-0749-41b6-b101-3656249d39b9",
body: "test",
subject: "reward one",
timestamp: 1,
read: false,
rewards: [
{ type: "xp", item: 400 },
{ type: "xp", item: 600 },
{ type: "badge", item: { id: 4 } },
],
};
const rewardTwo: MonkeyMail = {
id: "3692b9f5-84fb-4d9b-bd39-9a3217b3a33a",
body: "test",
subject: "reward two",
timestamp: 2,
read: true,
rewards: [{ type: "xp", item: 2000 }],
};
const rewardThree: MonkeyMail = {
id: "0d73b3e0-dc79-4abb-bcaf-66fa6b09a58a",
body: "test",
subject: "reward three",
timestamp: 4,
read: false,
rewards: [{ type: "xp", item: 3000 }],
};
let user = await UserTestData.createUser({
xp: 100,
inbox: [rewardOne, rewardTwo, rewardThree],
});
//WNEN
await UserDAL.updateInbox(user.uid, [], [rewardOne.id, rewardTwo.id]);
//THEN
const { xp, inbox } = await UserDAL.getUser(user.uid, "");
expect(xp).toBe(1100);
expect(inbox).toStrictEqual([rewardThree]);
});
it("updates badge", async () => {
//GIVEN
const rewardOne: MonkeyMail = {
id: "b5866d4c-0749-41b6-b101-3656249d39b9",
body: "test",
subject: "reward one",
timestamp: 2,
read: false,
rewards: [
{ type: "xp", item: 400 },
{ type: "badge", item: { id: 4 } },
],
};
const rewardTwo: MonkeyMail = {
id: "3692b9f5-84fb-4d9b-bd39-9a3217b3a33a",
body: "test",
subject: "reward two",
timestamp: 1,
read: false,
rewards: [
{ type: "badge", item: { id: 3 } },
{ type: "badge", item: { id: 4 } },
{ type: "badge", item: { id: 5 } },
],
};
const rewardThree: MonkeyMail = {
id: "0d73b3e0-dc79-4abb-bcaf-66fa6b09a58a",
body: "test",
subject: "reward three",
timestamp: 0,
read: true,
rewards: [{ type: "badge", item: { id: 6 } }],
};
let user = await UserTestData.createUser({
inbox: [rewardOne, rewardTwo, rewardThree],
inventory: {
badges: [
{ id: 1, selected: true },
{ id: 3, selected: false },
],
},
});
//WNEN
await UserDAL.updateInbox(
user.uid,
[rewardOne.id, rewardTwo.id, rewardThree.id, rewardOne.id],
[],
);
//THEN
const { inbox, inventory } = await UserDAL.getUser(user.uid, "");
expect(inbox).toStrictEqual([
{ ...rewardOne, read: true, rewards: [] },
{ ...rewardTwo, read: true, rewards: [] },
{ ...rewardThree },
]);
expect(inventory?.badges).toStrictEqual([
{ id: 1, selected: true }, //previously owned
{ id: 3, selected: false }, // previously owned, no duplicate
{ id: 4 }, // gets only added once
{ id: 5 },
]);
});
it("read and delete the same message does not claim reward twice", async () => {
//GIVEN
const rewardOne: MonkeyMail = {
id: "b5866d4c-0749-41b6-b101-3656249d39b9",
body: "test",
subject: "reward one",
timestamp: 0,
read: false,
rewards: [{ type: "xp", item: 1000 }],
};
const rewardTwo: MonkeyMail = {
id: "3692b9f5-84fb-4d9b-bd39-9a3217b3a33a",
body: "test",
subject: "reward two",
timestamp: 0,
read: false,
rewards: [{ type: "xp", item: 2000 }],
};
let user = await UserTestData.createUser({
xp: 100,
inbox: [rewardOne, rewardTwo],
});
await UserDAL.updateInbox(
user.uid,
[rewardOne.id, rewardTwo.id],
[rewardOne.id, rewardTwo.id],
);
//THEN
const { xp } = await UserDAL.getUser(user.uid, "");
expect(xp).toEqual(3100);
});
it("concurrent calls dont claim a reward multiple times", async () => {
//GIVEN
const rewardOne: MonkeyMail = {
id: "b5866d4c-0749-41b6-b101-3656249d39b9",
body: "test",
subject: "reward one",
timestamp: 0,
read: false,
rewards: [
{ type: "xp", item: 400 },
{ type: "xp", item: 600 },
{ type: "badge", item: { id: 4 } },
],
};
const rewardTwo: MonkeyMail = {
id: "3692b9f5-84fb-4d9b-bd39-9a3217b3a33a",
body: "test",
subject: "reward two",
timestamp: 0,
read: false,
rewards: [{ type: "xp", item: 2000 }],
};
const rewardThree: MonkeyMail = {
id: "0d73b3e0-dc79-4abb-bcaf-66fa6b09a58a",
body: "test",
subject: "reward three",
timestamp: 0,
read: true,
rewards: [{ type: "xp", item: 2000 }],
};
let user = await UserTestData.createUser({
xp: 100,
inbox: [rewardOne, rewardTwo, rewardThree],
});
const count = 100;
const calls = new Array(count)
.fill(0)
.map(() =>
UserDAL.updateInbox(
user.uid,
[rewardOne.id, rewardTwo.id, rewardThree.id],
[],
),
);
await Promise.all(calls);
//THEN
const { xp } = await UserDAL.getUser(user.uid, "");
expect(xp).toEqual(3100);
});
});
describe("isDiscordIdAvailable", () => {
it("should return true for available discordId", async () => {
const discordId = new ObjectId().toHexString();
await expect(UserDAL.isDiscordIdAvailable(discordId)).resolves.toBe(true);
});
it("should return false if discordId is taken", async () => {
// given
const discordId = new ObjectId().toHexString();
await UserTestData.createUser({
discordId: discordId,
});
// when, then
await expect(UserDAL.isDiscordIdAvailable(discordId)).resolves.toBe(
false,
);
});
});
describe("updateLbMemory", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.updateLbMemory(
"non existing uid",
"time",
"15",
"english",
4711,
),
).rejects.toThrow("User not found\nStack: update lb memory");
});
it("updates on empty lbMemory", async () => {
//GIVEN
const { uid } = await UserTestData.createUser({});
//WHEN
await UserDAL.updateLbMemory(uid, "time", "15", "english", 4711);
//THEN
const read = await UserDAL.getUser(uid, "read");
expect(read.lbMemory).toStrictEqual({
time: {
"15": {
english: 4711,
},
},
});
});
it("updates on empty lbMemory.mode", async () => {
//GIVEN
const { uid } = await UserTestData.createUser({
lbMemory: { custom: {} },
});
//WHEN
await UserDAL.updateLbMemory(uid, "time", "15", "english", 4711);
//THEN
const read = await UserDAL.getUser(uid, "read");
expect(read.lbMemory).toStrictEqual({
custom: {},
time: {
"15": {
english: 4711,
},
},
});
});
it("updates on empty lbMemory.mode.mode2", async () => {
//GIVEN
const { uid } = await UserTestData.createUser({
lbMemory: { time: { "30": {} } },
});
//WHEN
await UserDAL.updateLbMemory(uid, "time", "15", "english", 4711);
//THEN
const read = await UserDAL.getUser(uid, "read");
expect(read.lbMemory).toStrictEqual({
time: {
"15": {
english: 4711,
},
"30": {},
},
});
});
});
describe("incrementBananas", () => {
it("should not return error if uuid not found", async () => {
// when, then
await UserDAL.incrementBananas("non existing uid", 60);
});
it("increments bananas", async () => {
//GIVEN
const name = "user" + new ObjectId().toHexString();
const { uid } = await UserTestData.createUser({
name,
bananas: 1,
personalBests: {
time: {
"60": [
{ wpm: 100 } as PersonalBest,
{ wpm: 30 } as PersonalBest, //highest PB should be used
],
},
} as any,
});
//within 25% of PB
await UserDAL.incrementBananas(uid, 75);
const read = await UserDAL.getUser(uid, "read");
expect(read.bananas).toEqual(2);
expect(read.name).toEqual(name);
//NOT within 25% of PB
await UserDAL.incrementBananas(uid, 74);
expect((await UserDAL.getUser(uid, "read")).bananas).toEqual(2);
});
it("ignores missing personalBests", async () => {
//GIVEN
const { uid } = await UserTestData.createUser({
bananas: 1,
});
//WHEN
await UserDAL.incrementBananas(uid, 75);
//THEM
expect((await UserDAL.getUser(uid, "read")).bananas).toBe(1);
});
it("ignores missing personalBests time", async () => {
//GIVEN
const { uid } = await UserTestData.createUser({
bananas: 1,
personalBests: {} as any,
});
//WHEN
await UserDAL.incrementBananas(uid, 75);
//THEM
expect((await UserDAL.getUser(uid, "read")).bananas).toBe(1);
});
it("ignores missing personalBests time 60", async () => {
//GIVEN
const { uid } = await UserTestData.createUser({
bananas: 1,
personalBests: { time: {} } as any,
});
//WHEN
await UserDAL.incrementBananas(uid, 75);
//THEM
expect((await UserDAL.getUser(uid, "read")).bananas).toBe(1);
});
it("ignores empty personalBests time 60", async () => {
//GIVEN
const { uid } = await UserTestData.createUser({
bananas: 1,
personalBests: { time: { "60": [] } } as any,
});
//WHEN
await UserDAL.incrementBananas(uid, 75);
//THEM
expect((await UserDAL.getUser(uid, "read")).bananas).toBe(1);
});
it("should increment missing bananas", async () => {
//GIVEN
const { uid } = await UserTestData.createUser({
personalBests: { time: { "60": [{ wpm: 100 }] } } as any,
});
//WHEN
await UserDAL.incrementBananas(uid, 75);
//THEM
expect((await UserDAL.getUser(uid, "read")).bananas).toBe(1);
});
});
describe("addTheme", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.addTheme("non existing uid", {
name: "new",
colors: [] as any,
}),
).rejects.toThrow(
"Maximum number of custom themes reached\nStack: add theme",
);
});
it("should return error if user has reached maximum", async () => {
// given
const { uid } = await UserTestData.createUser({
customThemes: new Array(20).fill(0).map(() => ({
_id: new ObjectId(),
name: "any",
colors: [] as any,
})),
});
// when, then
await expect(
UserDAL.addTheme(uid, { name: "new", colors: [] as any }),
).rejects.toThrow(
"Maximum number of custom themes reached\nStack: add theme",
);
});
it("addTheme success", async () => {
// given
const themeOne = {
_id: new ObjectId(),
name: "first",
colors: new Array(10).fill("#123456") as CustomThemeColors,
};
const { uid } = await UserTestData.createUser({
customThemes: [themeOne],
});
const newTheme = {
name: "newTheme",
colors: new Array(10).fill("#000000") as CustomThemeColors,
};
// when
await UserDAL.addTheme(uid, { ...newTheme });
// then
const read = await UserDAL.getUser(uid, "read");
expect(read.customThemes).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: "first",
colors: themeOne.colors,
}),
expect.objectContaining({
name: "newTheme",
colors: newTheme.colors,
}),
]),
);
});
});
describe("editTheme", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.editTheme("non existing uid", new ObjectId().toHexString(), {
name: "newName",
colors: [] as any,
}),
).rejects.toThrow("Custom theme not found\nStack: edit theme");
});
it("should fail if theme not found", async () => {
// given
const themeOne = {
_id: new ObjectId(),
name: "first",
colors: ["green", "white", "red"] as any,
};
const { uid } = await UserTestData.createUser({
customThemes: [themeOne],
});
// when, then
await expect(
UserDAL.editTheme(uid, new ObjectId().toHexString(), {
name: "newName",
colors: [] as any,
}),
).rejects.toThrow("Custom theme not found\nStack: edit theme");
});
it("editTheme success", async () => {
// given
const themeOne = {
_id: new ObjectId(),
name: "first",
colors: ["green", "white", "red"] as any,
};
const { uid } = await UserTestData.createUser({
customThemes: [themeOne],
});
// when
await UserDAL.editTheme(uid, themeOne._id.toHexString(), {
name: "newThemeName",
colors: ["red", "white", "blue"] as any,
});
// then
const read = await UserDAL.getUser(uid, "read");
expect(read.customThemes ?? [][0]).toStrictEqual([
{ ...themeOne, name: "newThemeName", colors: ["red", "white", "blue"] },
]);
});
});
describe("removeTheme", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.removeTheme("non existing uid", new ObjectId().toHexString()),
).rejects.toThrow("Custom theme not found\nStack: remove theme");
});
it("should return error if theme is unknown", async () => {
// given
const themeOne = {
_id: new ObjectId(),
name: "first",
colors: ["green", "white", "red"] as any,
};
const { uid } = await UserTestData.createUser({
customThemes: [themeOne],
});
// when, then
await expect(
UserDAL.removeTheme(uid, new ObjectId().toHexString()),
).rejects.toThrow("Custom theme not found\nStack: remove theme");
});
it("should remove theme", async () => {
// given
const themeOne = {
_id: new ObjectId(),
name: "first",
colors: [] as any,
};
const themeTwo = {
_id: new ObjectId(),
name: "second",
colors: [] as any,
};
const themeThree = {
_id: new ObjectId(),
name: "third",
colors: [] as any,
};
const { uid } = await UserTestData.createUser({
customThemes: [themeOne, themeTwo, themeThree],
});
// when, then
await UserDAL.removeTheme(uid, themeTwo._id.toHexString());
const read = await UserDAL.getUser(uid, "read");
expect(read.customThemes).toStrictEqual([themeOne, themeThree]);
});
});
describe("addFavoriteQuote", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.addFavoriteQuote("non existing uid", "english", "1", 5),
).rejects.toThrow(
"Maximum number of favorite quotes reached\nStack: add favorite quote",
);
});
it("should return error if user has reached maximum", async () => {
// given
const { uid } = await UserTestData.createUser({
favoriteQuotes: {
english: ["1", "2"],
german: ["3", "4"],
polish: ["5"],
},
});
// when, then
await expect(
UserDAL.addFavoriteQuote(uid, "polish", "6", 5),
).rejects.toThrow(
"Maximum number of favorite quotes reached\nStack: add favorite quote",
);
});
it("addFavoriteQuote success", async () => {
// given
const { uid } = await UserTestData.createUser({
favoriteQuotes: {
english: ["1"],
german: ["2"],
polish: ["3"],
},
});
// when
await UserDAL.addFavoriteQuote(uid, "english", "4", 5);
// then
const read = await UserDAL.getUser(uid, "read");
expect(read.favoriteQuotes).toStrictEqual({
english: ["1", "4"],
german: ["2"],
polish: ["3"],
});
});
it("should not add a quote twice", async () => {
// given
const { uid } = await UserTestData.createUser({
favoriteQuotes: {
english: ["1", "3", "4"],
german: ["2"],
},
});
// when
await UserDAL.addFavoriteQuote(uid, "english", "4", 5);
// then
const read = await UserDAL.getUser(uid, "read");
expect(read.favoriteQuotes).toStrictEqual({
english: ["1", "3", "4"],
german: ["2"],
});
});
});
describe("removeFavoriteQuote", () => {
it("should return error if uid not found", async () => {
// when, then
await expect(
UserDAL.removeFavoriteQuote("non existing uid", "english", "0"),
).rejects.toThrow("User not found\nStack: remove favorite quote");
});
it("should not fail if quote is not favorite", async () => {
// given
const { uid } = await UserTestData.createUser({
favoriteQuotes: {
english: ["1", "2"],
},
});
// when
await UserDAL.removeFavoriteQuote(uid, "english", "3");
await UserDAL.removeFavoriteQuote(uid, "german", "1");
//then
const read = await UserDAL.getUser(uid, "read");
expect(read.favoriteQuotes).toStrictEqual({
english: ["1", "2"],
});
});
it("should remove", async () => {
// given
const { uid } = await UserTestData.createUser({
favoriteQuotes: {
english: ["1", "2", "3"],
},
});
// when
await UserDAL.removeFavoriteQuote(uid, "english", "2");
//%hen
const read = await UserDAL.getUser(uid, "read");
expect(read.favoriteQuotes).toStrictEqual({
english: ["1", "3"],
});
});
});
describe("clearStreakHourOffset", () => {
it("should clear streak hour offset", async () => {
// given
const { uid } = await UserTestData.createUser({
//@ts-expect-error
streak: {
hourOffset: 1,
},
});
// when
await UserDAL.clearStreakHourOffset(uid);
//then
const read = await UserDAL.getUser(uid, "read");
expect(read.streak?.hourOffset).toBeUndefined();
});
});
describe("getFriends", () => {
it("get list of friends", async () => {
//GIVEN
const me = await UserTestData.createUser({ name: "Me" });
const uid = me.uid;
const friendOne = await UserTestData.createUser({
name: "One",
personalBests: {
time: {
"15": [UserTestData.pb(100)],
"60": [UserTestData.pb(85), UserTestData.pb(90)],
},
} as any,
inventory: {
badges: [{ id: 42, selected: true }, { id: 23 }, { id: 5 }],
},
banned: true,
lbOptOut: true,
premium: { expirationTimestamp: -1 } as any,
});
const friendOneRequest = await createFriend({
initiatorUid: uid,
receiverUid: friendOne.uid,
status: "accepted",
lastModified: 100,
});
const friendTwo = await UserTestData.createUser({
name: "Two",
discordId: "discordId",
discordAvatar: "discordAvatar",
timeTyping: 600,
startedTests: 150,
completedTests: 125,
streak: {
length: 10,
maxLength: 50,
lastResultTimestamp: 0,
hourOffset: -1,
} as any,
xp: 42,
inventory: {
badges: [{ id: 23 }, { id: 5 }],
},
premium: {
expirationTimestamp: vi.getRealSystemTime() + 5000,
} as any,
});
const friendTwoRequest = await createFriend({
initiatorUid: uid,
receiverUid: friendTwo.uid,
status: "accepted",
lastModified: 200,
});
const friendThree = await UserTestData.createUser({ name: "Three" });
const friendThreeRequest = await createFriend({
receiverUid: uid,
initiatorUid: friendThree.uid,
status: "accepted",
lastModified: 300,
});
//non accepted
await createFriend({ receiverUid: uid, status: "pending" });
await createFriend({ initiatorUid: uid, status: "blocked" });
//WHEN
const friends = await UserDAL.getFriends(uid);
//THEN
expect(friends).toEqual([
{
uid: friendOne.uid,
name: "One",
lastModified: 100,
connectionId: friendOneRequest._id,
// oxlint-disable-next-line no-non-null-assertion
top15: friendOne.personalBests.time["15"]![0] as any,
// oxlint-disable-next-line no-non-null-assertion
top60: friendOne.personalBests.time["60"]![1] as any,
badgeId: 42,
banned: true,
lbOptOut: true,
isPremium: true,
},
{
uid: friendTwo.uid,
name: "Two",
lastModified: 200,
connectionId: friendTwoRequest._id,
discordId: friendTwo.discordId,
discordAvatar: friendTwo.discordAvatar,
timeTyping: friendTwo.timeTyping,
startedTests: friendTwo.startedTests,
completedTests: friendTwo.completedTests,
streak: {
length: friendTwo.streak?.length,
maxLength: friendTwo.streak?.maxLength,
},
xp: friendTwo.xp,
isPremium: true,
},
{
uid: friendThree.uid,
name: "Three",
lastModified: 300,
connectionId: friendThreeRequest._id,
},
{
uid: me.uid,
name: "Me",
},
]);
});
});
});