adding monkeytype
Some checks failed
Mark Stale PRs / stale (push) Has been cancelled

This commit is contained in:
Benjamin Falch
2026-04-23 13:53:44 +02:00
parent e214a2fd35
commit 2bc741fb78
1930 changed files with 7590652 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
import { describe, it, expect } from "vitest";
import { ObjectId } from "mongodb";
import * as AdminUidsDal from "../../../src/dal/admin-uids";
describe("AdminUidsDal", () => {
describe("isAdmin", () => {
it("should return true for existing admin user", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
await AdminUidsDal.getCollection().insertOne({
_id: new ObjectId(),
uid: uid,
});
//WHEN / THEN
expect(await AdminUidsDal.isAdmin(uid)).toBe(true);
});
it("should return false for non-existing admin user", async () => {
//GIVEN
await AdminUidsDal.getCollection().insertOne({
_id: new ObjectId(),
uid: "admin",
});
//WHEN / THEN
expect(await AdminUidsDal.isAdmin("regularUser")).toBe(false);
});
});
});

View File

@@ -0,0 +1,107 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { ObjectId } from "mongodb";
import {
addApeKey,
DBApeKey,
editApeKey,
getApeKey,
updateLastUsedOn,
} from "../../../src/dal/ape-keys";
describe("ApeKeysDal", () => {
beforeEach(() => {
vi.useFakeTimers();
});
describe("addApeKey", () => {
it("should be able to add a new ape key", async () => {
const apeKey = buildApeKey();
const apeKeyId = await addApeKey(apeKey);
expect(apeKeyId).toBe(apeKey._id.toHexString());
const read = await getApeKey(apeKeyId);
expect(read).toEqual({
...apeKey,
});
});
});
describe("editApeKey", () => {
it("should edit name of an existing ape key", async () => {
//GIVEN
const apeKey = buildApeKey({ useCount: 5, enabled: true });
const apeKeyId = await addApeKey(apeKey);
//WHEN
const newName = "new name";
await editApeKey(apeKey.uid, apeKeyId, newName, undefined);
//THENa
const readAfterEdit = (await getApeKey(apeKeyId)) as DBApeKey;
expect(readAfterEdit).toEqual({
...apeKey,
name: newName,
modifiedOn: Date.now(),
});
});
it("should edit enabled of an existing ape key", async () => {
//GIVEN
const apeKey = buildApeKey({ useCount: 5, enabled: true });
const apeKeyId = await addApeKey(apeKey);
//WHEN
await editApeKey(apeKey.uid, apeKeyId, undefined, false);
//THEN
const readAfterEdit = (await getApeKey(apeKeyId)) as DBApeKey;
expect(readAfterEdit).toEqual({
...apeKey,
enabled: false,
modifiedOn: Date.now(),
});
});
});
describe("updateLastUsedOn", () => {
it("should update lastUsedOn and increment useCount when editing with lastUsedOn", async () => {
//GIVEN
const apeKey = buildApeKey({
useCount: 5,
lastUsedOn: 42,
});
const apeKeyId = await addApeKey(apeKey);
//WHEN
await updateLastUsedOn(apeKey.uid, apeKeyId);
await updateLastUsedOn(apeKey.uid, apeKeyId);
//THENa
const readAfterEdit = (await getApeKey(apeKeyId)) as DBApeKey;
expect(readAfterEdit).toEqual({
...apeKey,
modifiedOn: readAfterEdit.modifiedOn,
lastUsedOn: Date.now(),
useCount: 5 + 2,
});
});
});
});
function buildApeKey(overrides: Partial<DBApeKey> = {}): DBApeKey {
return {
_id: new ObjectId(),
uid: "123",
name: "test",
hash: "12345",
createdOn: Date.now(),
modifiedOn: Date.now(),
lastUsedOn: Date.now(),
useCount: 0,
enabled: true,
...overrides,
};
}

View File

@@ -0,0 +1,363 @@
import {
describe,
it,
expect,
beforeAll,
beforeEach,
afterEach,
vi,
} from "vitest";
import { ObjectId } from "mongodb";
import * as BlacklistDal from "../../../src/dal/blocklist";
describe("BlocklistDal", () => {
beforeAll(async () => {
await BlacklistDal.createIndicies();
});
describe("add", () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it("adds user", async () => {
//GIVEN
const now = 1715082588;
vi.setSystemTime(now);
const name = "user" + new ObjectId().toHexString();
const email = `${name}@example.com`;
//WHEN
await BlacklistDal.add({ name, email });
//THEN
await expect(
BlacklistDal.getCollection().findOne({
emailHash: BlacklistDal.hash(email),
}),
).resolves.toMatchObject({
emailHash: BlacklistDal.hash(email),
timestamp: now,
});
await expect(
BlacklistDal.getCollection().findOne({
usernameHash: BlacklistDal.hash(name),
}),
).resolves.toMatchObject({
usernameHash: BlacklistDal.hash(name),
timestamp: now,
});
});
it("adds user with discordId", async () => {
//GIVEN
const now = 1715082588;
vi.setSystemTime(now);
const name = "user" + new ObjectId().toHexString();
const email = `${name}@example.com`;
const discordId = `${name}DiscordId`;
//WHEN
await BlacklistDal.add({ name, email, discordId });
//THEN
await expect(
BlacklistDal.getCollection().findOne({
discordIdHash: BlacklistDal.hash(discordId),
}),
).resolves.toMatchObject({
discordIdHash: BlacklistDal.hash(discordId),
timestamp: now,
});
});
it("adds user should not create duplicate name", async () => {
//GIVEN
const now = 1715082588;
vi.setSystemTime(now);
const name = "user" + new ObjectId().toHexString();
const email = `${name}@example.com`;
const email2 = `${name}@otherdomain.com`;
await BlacklistDal.add({ name, email });
//WHEN
await BlacklistDal.add({ name, email: email2 });
//THEN
await expect(
BlacklistDal.getCollection()
.find({
usernameHash: BlacklistDal.hash(name),
})
.toArray(),
).resolves.toHaveLength(1);
await expect(
BlacklistDal.getCollection()
.find({
emailHash: BlacklistDal.hash(email),
})
.toArray(),
).resolves.toHaveLength(1);
await expect(
BlacklistDal.getCollection()
.find({
emailHash: BlacklistDal.hash(email2),
})
.toArray(),
).resolves.toHaveLength(1);
});
it("adds user should not create duplicate email", async () => {
//GIVEN
const now = 1715082588;
vi.setSystemTime(now);
const name = "user" + new ObjectId().toHexString();
const email = `${name}@example.com`;
const name2 = "user" + new ObjectId().toHexString();
await BlacklistDal.add({ name, email });
//WHEN
await BlacklistDal.add({ name: name2, email });
//THEN
await expect(
BlacklistDal.getCollection()
.find({
emailHash: BlacklistDal.hash(email),
})
.toArray(),
).resolves.toHaveLength(1);
});
it("adds user should not create duplicate discordId", async () => {
//GIVEN
const now = 1715082588;
vi.setSystemTime(now);
const name = "user" + new ObjectId().toHexString();
const name2 = "user" + new ObjectId().toHexString();
const email = `${name}@example.com`;
const discordId = `${name}DiscordId`;
await BlacklistDal.add({ name, email, discordId });
//WHEN
await BlacklistDal.add({ name: name2, email, discordId });
//THEN
await expect(
BlacklistDal.getCollection()
.find({
discordIdHash: BlacklistDal.hash(discordId),
})
.toArray(),
).resolves.toHaveLength(1);
});
});
describe("contains", () => {
it("contains user", async () => {
//GIVEN
const name = "user" + new ObjectId().toHexString();
const email = `${name}@example.com`;
const discordId = `${name}DiscordId`;
await BlacklistDal.add({ name, email, discordId });
await BlacklistDal.add({ name: "test", email: "test@example.com" });
//WHEN / THEN
//by name
await expect(BlacklistDal.contains({ name })).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ name: name.toUpperCase() }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ name, email: "unknown", discordId: "unknown" }),
).resolves.toBeTruthy();
//by email
await expect(BlacklistDal.contains({ email })).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ email: email.toUpperCase() }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ name: "unknown", email, discordId: "unknown" }),
).resolves.toBeTruthy();
//by discordId
await expect(BlacklistDal.contains({ discordId })).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ discordId: discordId.toUpperCase() }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ name: "unknown", email: "unknown", discordId }),
).resolves.toBeTruthy();
//by name and email and discordId
await expect(
BlacklistDal.contains({ name, email, discordId }),
).resolves.toBeTruthy();
});
it("does not contain user", async () => {
//GIVEN
await BlacklistDal.add({ name: "test", email: "test@example.com" });
await BlacklistDal.add({ name: "test2", email: "test2@example.com" });
//WHEN / THEN
await expect(
BlacklistDal.contains({ name: "unknown" }),
).resolves.toBeFalsy();
await expect(
BlacklistDal.contains({ email: "unknown" }),
).resolves.toBeFalsy();
await expect(
BlacklistDal.contains({ discordId: "unknown" }),
).resolves.toBeFalsy();
await expect(
BlacklistDal.contains({
name: "unknown",
email: "unknown",
discordId: "unknown",
}),
).resolves.toBeFalsy();
await expect(BlacklistDal.contains({})).resolves.toBeFalsy();
});
});
describe("remove", () => {
it("removes existing username", async () => {
//GIVEN
const name = "user" + new ObjectId().toHexString();
const email = `${name}@example.com`;
await BlacklistDal.add({ name, email });
await BlacklistDal.add({ name: "test", email: "test@example.com" });
//WHEN
await BlacklistDal.remove({ name });
//THEN
await expect(BlacklistDal.contains({ name })).resolves.toBeFalsy();
await expect(BlacklistDal.contains({ email })).resolves.toBeTruthy();
//decoy still exists
await expect(
BlacklistDal.contains({ name: "test" }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ email: "test@example.com" }),
).resolves.toBeTruthy();
});
it("removes existing email", async () => {
//GIVEN
const name = "user" + new ObjectId().toHexString();
const email = `${name}@example.com`;
await BlacklistDal.add({ name, email });
await BlacklistDal.add({ name: "test", email: "test@example.com" });
//WHEN
await BlacklistDal.remove({ email });
//THEN
await expect(BlacklistDal.contains({ email })).resolves.toBeFalsy();
await expect(BlacklistDal.contains({ name })).resolves.toBeTruthy();
//decoy still exists
await expect(
BlacklistDal.contains({ name: "test" }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ email: "test@example.com" }),
).resolves.toBeTruthy();
});
it("removes existing discordId", async () => {
//GIVEN
const name = "user" + new ObjectId().toHexString();
const email = `${name}@example.com`;
const discordId = `${name}DiscordId`;
await BlacklistDal.add({ name, email, discordId });
await BlacklistDal.add({
name: "test",
email: "test@example.com",
discordId: "testDiscordId",
});
//WHEN
await BlacklistDal.remove({ discordId });
//THEN
await expect(BlacklistDal.contains({ discordId })).resolves.toBeFalsy();
await expect(BlacklistDal.contains({ name })).resolves.toBeTruthy();
await expect(BlacklistDal.contains({ email })).resolves.toBeTruthy();
//decoy still exists
await expect(
BlacklistDal.contains({ name: "test" }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ email: "test@example.com" }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ discordId: "testDiscordId" }),
).resolves.toBeTruthy();
});
it("removes existing username,email and discordId", async () => {
//GIVEN
const name = "user" + new ObjectId().toHexString();
const email = `${name}@example.com`;
const discordId = `${name}DiscordId`;
await BlacklistDal.add({ name, email, discordId });
await BlacklistDal.add({
name: "test",
email: "test@example.com",
discordId: "testDiscordId",
});
//WHEN
await BlacklistDal.remove({ name, email, discordId });
//THEN
await expect(BlacklistDal.contains({ email })).resolves.toBeFalsy();
await expect(BlacklistDal.contains({ name })).resolves.toBeFalsy();
await expect(BlacklistDal.contains({ discordId })).resolves.toBeFalsy();
//decoy still exists
await expect(
BlacklistDal.contains({ name: "test" }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ email: "test@example.com" }),
).resolves.toBeTruthy();
await expect(
BlacklistDal.contains({ discordId: "testDiscordId" }),
).resolves.toBeTruthy();
});
it("does not remove for empty user", async () => {
//GIVEN
const name = "user" + new ObjectId().toHexString();
const email = `${name}@example.com`;
const discordId = `${name}DiscordId`;
await BlacklistDal.add({ name, email, discordId });
await BlacklistDal.add({ name: "test", email: "test@example.com" });
//WHEN
await BlacklistDal.remove({});
//THEN
await expect(BlacklistDal.contains({ email })).resolves.toBeTruthy();
await expect(BlacklistDal.contains({ name })).resolves.toBeTruthy();
await expect(BlacklistDal.contains({ discordId })).resolves.toBeTruthy();
});
});
describe("hash", () => {
it("hashes case insensitive", () => {
["test", "TEST", "tESt"].forEach((value) =>
expect(BlacklistDal.hash(value)).toEqual(
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
),
);
});
});
});

View File

@@ -0,0 +1,42 @@
import { ObjectId } from "mongodb";
import { describe, expect, it } from "vitest";
import * as ConfigDal from "../../../src/dal/config";
const getConfigCollection = ConfigDal.__testing.getConfigCollection;
describe("ConfigDal", () => {
describe("saveConfig", () => {
it("should save and update user configuration correctly", async () => {
//GIVEN
const uid = new ObjectId().toString();
await getConfigCollection().insertOne({
uid,
config: {
ads: "on",
time: 60,
quickTab: true, //legacy value
},
} as any);
//WHEN
await ConfigDal.saveConfig(uid, {
ads: "on",
difficulty: "normal",
} as any);
//WHEN
await ConfigDal.saveConfig(uid, { ads: "off" });
//THEN
const savedConfig = (await ConfigDal.getConfig(
uid,
)) as ConfigDal.DBConfig;
expect(savedConfig.config.ads).toBe("off");
expect(savedConfig.config.time).toBe(60);
//should remove legacy values
expect((savedConfig.config as any)["quickTab"]).toBeUndefined();
});
});
});

View File

@@ -0,0 +1,493 @@
import {
describe,
it,
expect,
vi,
beforeAll,
beforeEach,
afterEach,
} from "vitest";
import { ObjectId } from "mongodb";
import * as ConnectionsDal from "../../../src/dal/connections";
import { createConnection } from "../../__testData__/connections";
import { createUser } from "../../__testData__/users";
describe("ConnectionsDal", () => {
beforeAll(async () => {
await ConnectionsDal.createIndicies();
});
describe("getRequests", () => {
it("get by uid", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const initOne = await createConnection({ initiatorUid: uid });
const initTwo = await createConnection({ initiatorUid: uid });
const friendOne = await createConnection({ receiverUid: uid });
const _decoy = await createConnection({});
//WHEN / THEM
expect(
await ConnectionsDal.getConnections({
initiatorUid: uid,
receiverUid: uid,
}),
).toStrictEqual([initOne, initTwo, friendOne]);
});
it("get by uid and status", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const initAccepted = await createConnection({
initiatorUid: uid,
status: "accepted",
});
const _initPending = await createConnection({
initiatorUid: uid,
status: "pending",
});
const initBlocked = await createConnection({
initiatorUid: uid,
status: "blocked",
});
const friendAccepted = await createConnection({
receiverUid: uid,
status: "accepted",
});
const _friendPending = await createConnection({
receiverUid: uid,
status: "pending",
});
const _decoy = await createConnection({ status: "accepted" });
//WHEN / THEN
expect(
await ConnectionsDal.getConnections({
initiatorUid: uid,
receiverUid: uid,
status: ["accepted", "blocked"],
}),
).toStrictEqual([initAccepted, initBlocked, friendAccepted]);
});
});
describe("create", () => {
const now = 1715082588;
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(now);
});
afterEach(() => {
vi.useRealTimers();
});
it("should fail creating duplicates", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const first = await createConnection({
initiatorUid: uid,
});
//WHEN/THEN
await expect(
createConnection({
initiatorUid: first.receiverUid,
receiverUid: uid,
}),
).rejects.toThrow("Connection request already sent");
});
it("should create", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const receiverUid = new ObjectId().toHexString();
//WHEN
const created = await ConnectionsDal.create(
{ uid, name: "Bob" },
{ uid: receiverUid, name: "Kevin" },
2,
);
//THEN
expect(created).toEqual({
_id: created._id,
initiatorUid: uid,
initiatorName: "Bob",
receiverUid: receiverUid,
receiverName: "Kevin",
lastModified: now,
status: "pending",
key: `${uid}/${receiverUid}`,
});
});
it("should fail if maximum connections are reached", async () => {
//GIVEN
const initiatorUid = new ObjectId().toHexString();
await createConnection({ initiatorUid });
await createConnection({ initiatorUid });
//WHEN / THEM
await expect(createConnection({ initiatorUid }, 2)).rejects.toThrow(
"Maximum number of connections reached\nStack: create connection request",
);
});
it("should fail creating if blocked", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const first = await createConnection({
initiatorUid: uid,
status: "blocked",
});
//WHEN/THEN
await expect(
createConnection({
initiatorUid: first.receiverUid,
receiverUid: uid,
}),
).rejects.toThrow("Connection blocked");
});
});
describe("updateStatus", () => {
const now = 1715082588;
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(now);
});
afterEach(() => {
vi.useRealTimers();
});
it("should update the status", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const first = await createConnection({
receiverUid: uid,
lastModified: 100,
});
const second = await createConnection({
receiverUid: uid,
lastModified: 200,
});
//WHEN
await ConnectionsDal.updateStatus(
uid,
first._id.toHexString(),
"accepted",
);
//THEN
expect(await ConnectionsDal.getConnections({ receiverUid: uid })).toEqual(
[{ ...first, status: "accepted", lastModified: now }, second],
);
//can update twice to the same status
await ConnectionsDal.updateStatus(
uid,
first._id.toHexString(),
"accepted",
);
});
it("should fail if uid does not match the reeceiverUid", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const first = await createConnection({
initiatorUid: uid,
});
//WHEN / THEN
await expect(
ConnectionsDal.updateStatus(uid, first._id.toHexString(), "accepted"),
).rejects.toThrow("No permission or connection not found");
});
});
describe("deleteById", () => {
it("should delete by initiator", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const first = await createConnection({
initiatorUid: uid,
});
const second = await createConnection({
initiatorUid: uid,
});
//WHEN
await ConnectionsDal.deleteById(uid, first._id.toHexString());
//THEN
expect(
await ConnectionsDal.getConnections({ initiatorUid: uid }),
).toStrictEqual([second]);
});
it("should delete by receiver", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const first = await createConnection({
receiverUid: uid,
});
const second = await createConnection({
receiverUid: uid,
status: "accepted",
});
//WHEN
await ConnectionsDal.deleteById(uid, first._id.toHexString());
//THEN
expect(
await ConnectionsDal.getConnections({
initiatorUid: second.initiatorUid,
}),
).toStrictEqual([second]);
});
it("should fail if uid does not match", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const first = await createConnection({
initiatorUid: uid,
});
//WHEN / THEN
await expect(
ConnectionsDal.deleteById("Bob", first._id.toHexString()),
).rejects.toThrow("No permission or connection not found");
});
it("should fail if initiator deletes blocked by receiver", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const myRequestWasBlocked = await createConnection({
initiatorName: uid,
status: "blocked",
});
//WHEN / THEN
await expect(
ConnectionsDal.deleteById(uid, myRequestWasBlocked._id.toHexString()),
).rejects.toThrow("No permission or connection not found");
});
it("allow receiver to delete blocked", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const myBlockedUser = await createConnection({
receiverUid: uid,
status: "blocked",
});
//WHEN
await ConnectionsDal.deleteById(uid, myBlockedUser._id.toHexString());
//THEN
expect(await ConnectionsDal.getConnections({ receiverUid: uid })).toEqual(
[],
);
});
});
describe("deleteByUid", () => {
it("should delete by uid", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const _initOne = await createConnection({ initiatorUid: uid });
const _initTwo = await createConnection({ initiatorUid: uid });
const _friendOne = await createConnection({ receiverUid: uid });
const decoy = await createConnection({});
//WHEN
await ConnectionsDal.deleteByUid(uid);
//THEN
expect(
await ConnectionsDal.getConnections({
initiatorUid: uid,
receiverUid: uid,
}),
).toEqual([]);
expect(
await ConnectionsDal.getConnections({
initiatorUid: decoy.initiatorUid,
}),
).toEqual([decoy]);
});
});
describe("updateName", () => {
it("should update the name", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const initOne = await createConnection({
initiatorUid: uid,
initiatorName: "Bob",
});
const initTwo = await createConnection({
initiatorUid: uid,
initiatorName: "Bob",
});
const friendOne = await createConnection({
receiverUid: uid,
receiverName: "Bob",
});
const decoy = await createConnection({});
//WHEN
await ConnectionsDal.updateName(uid, "King Bob");
//THEN
expect(
await ConnectionsDal.getConnections({
initiatorUid: uid,
receiverUid: uid,
}),
).toEqual([
{ ...initOne, initiatorName: "King Bob" },
{ ...initTwo, initiatorName: "King Bob" },
{ ...friendOne, receiverName: "King Bob" },
]);
expect(
await ConnectionsDal.getConnections({
initiatorUid: decoy.initiatorUid,
}),
).toEqual([decoy]);
});
});
describe("getFriendsUids", () => {
it("should return friend uids", async () => {
//GIVE
const uid = new ObjectId().toHexString();
const friendOne = await createConnection({
initiatorUid: uid,
status: "accepted",
});
const friendTwo = await createConnection({
receiverUid: uid,
status: "accepted",
});
const friendThree = await createConnection({
receiverUid: uid,
status: "accepted",
});
const _pending = await createConnection({
initiatorUid: uid,
status: "pending",
});
const _blocked = await createConnection({
initiatorUid: uid,
status: "blocked",
});
const _decoy = await createConnection({});
//WHEN
const friendUids = await ConnectionsDal.getFriendsUids(uid);
//THEN
expect(friendUids).toEqual([
uid,
friendOne.receiverUid,
friendTwo.initiatorUid,
friendThree.initiatorUid,
]);
});
});
describe("aggregateWithAcceptedConnections", () => {
it("should return friend uids", async () => {
//GIVE
const uid = (await createUser()).uid;
const friendOne = await createConnection({
initiatorUid: uid,
receiverUid: (await createUser()).uid,
status: "accepted",
});
const friendTwo = await createConnection({
initiatorUid: (await createUser()).uid,
receiverUid: uid,
status: "accepted",
});
const friendThree = await createConnection({
initiatorUid: (await createUser()).uid,
receiverUid: uid,
status: "accepted",
});
const _pending = await createConnection({
initiatorUid: uid,
receiverUid: (await createUser()).uid,
status: "pending",
});
const _blocked = await createConnection({
initiatorUid: uid,
receiverUid: (await createUser()).uid,
status: "blocked",
});
const _decoy = await createConnection({
receiverUid: (await createUser()).uid,
status: "accepted",
});
//WHEN
const friendUids = await ConnectionsDal.aggregateWithAcceptedConnections<{
uid: string;
}>({ collectionName: "users", uid }, [{ $project: { uid: true } }]);
//THEN
expect(friendUids.flatMap((it) => it.uid).toSorted()).toEqual([
uid,
friendOne.receiverUid,
friendTwo.initiatorUid,
friendThree.initiatorUid,
]);
});
it("should return friend uids and metaData", async () => {
//GIVE
const me = await createUser();
const friend = await createUser();
const connection = await createConnection({
initiatorUid: me.uid,
receiverUid: friend.uid,
status: "accepted",
});
//WHEN
const friendUids = await ConnectionsDal.aggregateWithAcceptedConnections(
{ collectionName: "users", uid: me.uid, includeMetaData: true },
[
{
$project: {
uid: true,
lastModified: "$connectionMeta.lastModified",
connectionId: "$connectionMeta._id",
},
},
],
);
//THEN
expect(friendUids).toEqual([
{
_id: friend._id,
connectionId: connection._id,
lastModified: connection.lastModified,
uid: friend.uid,
},
{
_id: me._id,
uid: me.uid,
},
]);
});
});
});

View File

@@ -0,0 +1,544 @@
import { describe, it, expect, afterEach, vi } from "vitest";
import { ObjectId } from "mongodb";
import * as UserDal from "../../../src/dal/user";
import * as LeaderboardsDal from "../../../src/dal/leaderboards";
import * as PublicDal from "../../../src/dal/public";
import type { DBLeaderboardEntry } from "../../../src/dal/leaderboards";
import type { PersonalBest } from "@monkeytype/schemas/shared";
import * as DB from "../../../src/init/db";
import { LbPersonalBests } from "../../../src/utils/pb";
import { pb } from "../../__testData__/users";
import { createConnection } from "../../__testData__/connections";
import { omit } from "../../../src/utils/misc";
describe("LeaderboardsDal", () => {
afterEach(async () => {
await DB.collection("users").deleteMany({});
});
describe("update", () => {
it("should ignore unapplicable users on leaderboard", async () => {
//GIVEN
const lbPersonalBests = lbBests(pb(100), pb(90));
const applicableUser = await createUser(lbPersonalBests);
await createUser(lbPersonalBests, { banned: true });
await createUser(lbPersonalBests, { lbOptOut: true });
await createUser(lbPersonalBests, { needsToChangeName: true });
await createUser(lbPersonalBests, { timeTyping: 0 });
await createUser(lbBests(pb(0, 90, 1)));
await createUser(lbBests(pb(60, 0, 1)));
await createUser(lbBests(pb(60, 90, 0)));
await createUser(lbBests(undefined, pb(60)));
//WHEN
await LeaderboardsDal.update("time", "15", "english");
const results = await LeaderboardsDal.get("time", "15", "english", 0, 50);
//THEN
expect(results).toHaveLength(1);
expect(
(results as LeaderboardsDal.DBLeaderboardEntry[])[0],
).toHaveProperty("uid", applicableUser.uid);
});
it("should create leaderboard time english 15", async () => {
//GIVEN
const rank1 = await createUser(lbBests(pb(100, 90, 2)));
const rank2 = await createUser(lbBests(pb(100, 90, 1)));
const rank3 = await createUser(lbBests(pb(100, 80, 2)));
const rank4 = await createUser(lbBests(pb(90, 100, 1)));
//WHEN
await LeaderboardsDal.update("time", "15", "english");
const results = (await LeaderboardsDal.get(
"time",
"15",
"english",
0,
50,
)) as DBLeaderboardEntry[];
//THEN
const lb = results.map((it) => omit(it, ["_id"]));
expect(lb).toEqual([
expectedLbEntry("15", { rank: 1, user: rank1 }),
expectedLbEntry("15", { rank: 2, user: rank2 }),
expectedLbEntry("15", { rank: 3, user: rank3 }),
expectedLbEntry("15", { rank: 4, user: rank4 }),
]);
});
it("should create leaderboard time english 60", async () => {
//GIVEN
const rank1 = await createUser(lbBests(pb(90), pb(100, 90, 2)));
const rank2 = await createUser(lbBests(undefined, pb(100, 90, 1)));
const rank3 = await createUser(lbBests(undefined, pb(100, 80, 2)));
const rank4 = await createUser(lbBests(undefined, pb(90, 100, 1)));
//WHEN
await LeaderboardsDal.update("time", "60", "english");
const results = (await LeaderboardsDal.get(
"time",
"60",
"english",
0,
50,
)) as LeaderboardsDal.DBLeaderboardEntry[];
//THEN
const lb = results.map((it) => omit(it, ["_id"]));
expect(lb).toEqual([
expectedLbEntry("60", { rank: 1, user: rank1 }),
expectedLbEntry("60", { rank: 2, user: rank2 }),
expectedLbEntry("60", { rank: 3, user: rank3 }),
expectedLbEntry("60", { rank: 4, user: rank4 }),
]);
});
it("should not include discord properties for users without discord connection", async () => {
//GIVEN
await createUser(lbBests(pb(90), pb(100, 90, 2)), {
discordId: undefined,
discordAvatar: undefined,
});
//WHEN
await LeaderboardsDal.update("time", "60", "english");
const lb = (await LeaderboardsDal.get(
"time",
"60",
"english",
0,
50,
)) as DBLeaderboardEntry[];
//THEN
expect(lb[0]).not.toHaveProperty("discordId");
expect(lb[0]).not.toHaveProperty("discordAvatar");
});
it("should remove consistency from results if null", async () => {
//GIVEN
const stats = pb(100, 90, 2);
//@ts-ignore
stats.consistency = undefined;
await createUser(lbBests(stats));
//WHEN
//WHEN
await LeaderboardsDal.update("time", "15", "english");
const lb = (await LeaderboardsDal.get(
"time",
"15",
"english",
0,
50,
)) as DBLeaderboardEntry[];
//THEN
expect(lb[0]).not.toHaveProperty("consistency");
});
it("should update public speedHistogram for time english 15", async () => {
//GIVEN
await createUser(lbBests(pb(10), pb(60)));
await createUser(lbBests(pb(24)));
await createUser(lbBests(pb(28)));
await createUser(lbBests(pb(31)));
//WHEN
await LeaderboardsDal.update("time", "15", "english");
const result = await PublicDal.getSpeedHistogram("english", "time", "15");
//THEN
expect(result).toEqual({ "10": 1, "20": 2, "30": 1 });
});
it("should update public speedHistogram for time english 60", async () => {
//GIVEN
await createUser(lbBests(pb(60), pb(20)));
await createUser(lbBests(undefined, pb(21)));
await createUser(lbBests(undefined, pb(110)));
await createUser(lbBests(undefined, pb(115)));
//WHEN
await LeaderboardsDal.update("time", "60", "english");
const result = await PublicDal.getSpeedHistogram("english", "time", "60");
//THEN
expect(result).toEqual({ "20": 2, "110": 2 });
});
it("should create leaderboard with badges", async () => {
//GIVEN
const noBadge = await createUser(lbBests(pb(4)));
const oneBadgeSelected = await createUser(lbBests(pb(3)), {
inventory: { badges: [{ id: 1, selected: true }] },
});
const oneBadgeNotSelected = await createUser(lbBests(pb(2)), {
inventory: { badges: [{ id: 1, selected: false }] },
});
const multipleBadges = await createUser(lbBests(pb(1)), {
inventory: {
badges: [
{ id: 1, selected: false },
{ id: 2, selected: true },
{ id: 3, selected: true },
],
},
});
//WHEN
await LeaderboardsDal.update("time", "15", "english");
const result = (await LeaderboardsDal.get(
"time",
"15",
"english",
0,
50,
)) as DBLeaderboardEntry[];
//THEN
const lb = result.map((it) => omit(it, ["_id"]));
expect(lb).toEqual([
expectedLbEntry("15", { rank: 1, user: noBadge }),
expectedLbEntry("15", {
rank: 2,
user: oneBadgeSelected,
badgeId: 1,
}),
expectedLbEntry("15", { rank: 3, user: oneBadgeNotSelected }),
expectedLbEntry("15", {
rank: 4,
user: multipleBadges,
badgeId: 2,
}),
]);
});
it("should create leaderboard with premium", async () => {
//GIVEN
vi.useRealTimers(); //timestamp for premium is calculated in mongo
const noPremium = await createUser(lbBests(pb(4)));
const lifetime = await createUser(lbBests(pb(3)), premium(-1));
const validPremium = await createUser(lbBests(pb(2)), premium(1000));
const expiredPremium = await createUser(lbBests(pb(1)), premium(-10));
//WHEN
await LeaderboardsDal.update("time", "15", "english");
const result = (await LeaderboardsDal.get(
"time",
"15",
"english",
0,
50,
true,
)) as DBLeaderboardEntry[];
//THEN
const lb = result.map((it) => omit(it, ["_id"]));
expect(lb).toEqual([
expectedLbEntry("15", { rank: 1, user: noPremium }),
expectedLbEntry("15", {
rank: 2,
user: lifetime,
isPremium: true,
}),
expectedLbEntry("15", {
rank: 3,
user: validPremium,
isPremium: true,
}),
expectedLbEntry("15", { rank: 4, user: expiredPremium }),
]);
});
it("should create leaderboard without premium if feature disabled", async () => {
//GIVEN
// const lifetime = await createUser(lbBests(pb(3)), premium(-1));
//WHEN
await LeaderboardsDal.update("time", "15", "english");
const results = (await LeaderboardsDal.get(
"time",
"15",
"english",
0,
50,
false,
)) as DBLeaderboardEntry[];
//THEN
expect(results[0]?.isPremium).toBeUndefined();
});
});
describe("get", () => {
it("should get for page", async () => {
//GIVEN
const _rank1 = await createUser(lbBests(pb(90), pb(105, 90, 2)));
const _rank2 = await createUser(lbBests(undefined, pb(100, 90, 1)));
const rank3 = await createUser(lbBests(undefined, pb(95, 80, 2)));
const rank4 = await createUser(lbBests(undefined, pb(90, 100, 1)));
await LeaderboardsDal.update("time", "60", "english");
//WHEN
const results = (await LeaderboardsDal.get(
"time",
"60",
"english",
1,
2,
true,
)) as LeaderboardsDal.DBLeaderboardEntry[];
//THEN
const lb = results.map((it) => omit(it, ["_id"]));
expect(lb).toEqual([
expectedLbEntry("60", { rank: 3, user: rank3 }),
expectedLbEntry("60", { rank: 4, user: rank4 }),
]);
});
it("should get for friends only", async () => {
//GIVEN
const rank1 = await createUser(lbBests(pb(90), pb(100, 90, 2)));
const uid = rank1.uid;
const _rank2 = await createUser(lbBests(undefined, pb(100, 90, 1)));
const _rank3 = await createUser(lbBests(undefined, pb(100, 80, 2)));
const rank4 = await createUser(lbBests(undefined, pb(90, 100, 1)));
//two friends, one is not on the leaderboard
await createConnection({
initiatorUid: uid,
receiverUid: rank4.uid,
status: "accepted",
});
await createConnection({ initiatorUid: uid, status: "accepted" });
await LeaderboardsDal.update("time", "60", "english");
//WHEN
const results = (await LeaderboardsDal.get(
"time",
"60",
"english",
0,
50,
false,
uid,
)) as LeaderboardsDal.DBLeaderboardEntry[];
//THEN
const lb = results.map((it) => omit(it, ["_id"]));
expect(lb).toEqual([
expectedLbEntry("60", { rank: 1, user: rank1, friendsRank: 1 }),
expectedLbEntry("60", { rank: 4, user: rank4, friendsRank: 2 }),
]);
});
it("should get for friends only with page", async () => {
//GIVEN
const rank1 = await createUser(lbBests(pb(90), pb(105, 90, 2)));
const uid = rank1.uid;
const rank2 = await createUser(lbBests(undefined, pb(100, 90, 1)));
const _rank3 = await createUser(lbBests(undefined, pb(95, 80, 2)));
const rank4 = await createUser(lbBests(undefined, pb(90, 100, 1)));
await LeaderboardsDal.update("time", "60", "english");
await createConnection({
initiatorUid: uid,
receiverUid: rank2.uid,
status: "accepted",
});
await createConnection({
initiatorUid: rank4.uid,
receiverUid: uid,
status: "accepted",
});
//WHEN
const results = (await LeaderboardsDal.get(
"time",
"60",
"english",
1,
2,
false,
uid,
)) as LeaderboardsDal.DBLeaderboardEntry[];
//THEN
const lb = results.map((it) => omit(it, ["_id"]));
expect(lb).toEqual([
expectedLbEntry("60", { rank: 4, user: rank4, friendsRank: 3 }),
]);
});
it("should return empty list if no friends", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
//WHEN
const results = (await LeaderboardsDal.get(
"time",
"60",
"english",
1,
2,
false,
uid,
)) as LeaderboardsDal.DBLeaderboardEntry[];
//THEN
expect(results).toEqual([]);
});
});
describe("getCount / getRank", () => {
it("should get count", async () => {
//GIVEN
await createUser(lbBests(undefined, pb(105)), { name: "One" });
await createUser(lbBests(undefined, pb(100)), { name: "Two" });
const me = await createUser(lbBests(undefined, pb(95)), { name: "Me" });
await createUser(lbBests(undefined, pb(90)), { name: "Three" });
await LeaderboardsDal.update("time", "60", "english");
//WHEN / THEN
expect(await LeaderboardsDal.getCount("time", "60", "english")) //
.toEqual(4);
expect(await LeaderboardsDal.getRank("time", "60", "english", me.uid)) //
.toEqual(
expect.objectContaining({
wpm: 95,
rank: 3,
name: me.name,
uid: me.uid,
}),
);
});
it("should get for friends only", async () => {
//GIVEN
const friendOne = await createUser(lbBests(undefined, pb(105)));
await createUser(lbBests(undefined, pb(100)));
await createUser(lbBests(undefined, pb(95)));
const friendTwo = await createUser(lbBests(undefined, pb(90)));
const me = await createUser(lbBests(undefined, pb(99)));
await LeaderboardsDal.update("time", "60", "english");
await createConnection({
initiatorUid: me.uid,
receiverUid: friendOne.uid,
status: "accepted",
});
await createConnection({
initiatorUid: friendTwo.uid,
receiverUid: me.uid,
status: "accepted",
});
//WHEN / THEN
expect(await LeaderboardsDal.getCount("time", "60", "english", me.uid)) //
.toEqual(3);
expect(
await LeaderboardsDal.getRank("time", "60", "english", me.uid, true),
) //
.toEqual(
expect.objectContaining({
wpm: 99,
rank: 3,
friendsRank: 2,
name: me.name,
uid: me.uid,
}),
);
});
});
});
function expectedLbEntry(
time: string,
{ rank, user, badgeId, isPremium, friendsRank }: ExpectedLbEntry,
) {
// @ts-expect-error
const lbBest: PersonalBest =
// @ts-expect-error
user.lbPersonalBests?.time[Number.parseInt(time)].english;
return {
rank,
uid: user.uid,
name: user.name,
wpm: lbBest.wpm,
acc: lbBest.acc,
timestamp: lbBest.timestamp,
raw: lbBest.raw,
consistency: lbBest.consistency,
discordId: user.discordId,
discordAvatar: user.discordAvatar,
badgeId,
isPremium,
friendsRank,
};
}
async function createUser(
lbPersonalBests?: LbPersonalBests,
userProperties?: Partial<UserDal.DBUser>,
): Promise<UserDal.DBUser> {
const uid = new ObjectId().toHexString();
await UserDal.addUser("User " + uid, uid + "@example.com", uid);
await DB.getDb()
?.collection<UserDal.DBUser>("users")
.updateOne(
{ uid },
{
$set: {
timeTyping: 7200,
discordId: "discord " + uid,
discordAvatar: "avatar " + uid,
...userProperties,
lbPersonalBests,
},
},
);
return await UserDal.getUser(uid, "test");
}
function lbBests(pb15?: PersonalBest, pb60?: PersonalBest): LbPersonalBests {
const result: LbPersonalBests = { time: {} };
if (pb15) result.time["15"] = { english: pb15 };
if (pb60) result.time["60"] = { english: pb60 };
return result;
}
function premium(expirationDeltaSeconds: number) {
return {
premium: {
startTimestamp: 0,
expirationTimestamp:
expirationDeltaSeconds === -1
? -1
: Date.now() + expirationDeltaSeconds * 1000,
},
};
}
type ExpectedLbEntry = {
rank: number;
user: UserDal.DBUser;
badgeId?: number;
isPremium?: boolean;
friendsRank?: number;
};

View File

@@ -0,0 +1,482 @@
import { describe, it, expect } from "vitest";
import { ObjectId } from "mongodb";
import * as PresetDal from "../../../src/dal/preset";
describe("PresetDal", () => {
describe("readPreset", () => {
it("should read", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const decoyUid = new ObjectId().toHexString();
const first = await PresetDal.addPreset(uid, {
name: "first",
config: { ads: "sellout" },
});
const second = await PresetDal.addPreset(uid, {
name: "second",
settingGroups: ["hideElements"],
config: {
showKeyTips: true,
capsLockWarning: true,
showOutOfFocusWarning: true,
showAverage: "off",
},
});
await PresetDal.addPreset(decoyUid, {
name: "unknown",
config: {},
});
//WHEN
const read = await PresetDal.getPresets(uid);
//THEN
expect(read).toHaveLength(2);
expect(read).toEqual(
expect.arrayContaining([
expect.objectContaining({
_id: new ObjectId(first.presetId),
uid: uid,
name: "first",
config: { ads: "sellout" },
}),
expect.objectContaining({
_id: new ObjectId(second.presetId),
uid: uid,
name: "second",
settingGroups: ["hideElements"],
config: {
showKeyTips: true,
capsLockWarning: true,
showOutOfFocusWarning: true,
showAverage: "off",
},
}),
]),
);
});
});
describe("addPreset", () => {
it("should return error if maximum is reached", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
for (let i = 0; i < 10; i++) {
await PresetDal.addPreset(uid, { name: "test", config: {} });
}
//WHEN / THEN
await expect(() =>
PresetDal.addPreset(uid, { name: "max", config: {} }),
).rejects.toThrow("Too many presets");
});
it("should add preset", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
for (let i = 0; i < 9; i++) {
await PresetDal.addPreset(uid, { name: "test", config: {} });
}
//WHEN
const newPreset = await PresetDal.addPreset(uid, {
name: "new",
config: {
ads: "sellout",
},
});
//THEN
const read = await PresetDal.getPresets(uid);
expect(read).toHaveLength(10);
expect(read).toEqual(
expect.arrayContaining([
expect.objectContaining({
_id: new ObjectId(newPreset.presetId),
uid: uid,
name: "new",
config: { ads: "sellout" },
}),
]),
);
});
});
describe("editPreset", () => {
it("should not fail if preset is unknown", async () => {
const uid = new ObjectId().toHexString();
await PresetDal.editPreset(uid, {
_id: new ObjectId().toHexString(),
name: "new",
config: {},
});
});
it("should edit", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const decoyUid = new ObjectId().toHexString();
const first = (
await PresetDal.addPreset(uid, {
name: "first",
config: { ads: "sellout" },
})
).presetId;
const second = (
await PresetDal.addPreset(uid, {
name: "second",
config: {
ads: "result",
},
})
).presetId;
const decoy = (
await PresetDal.addPreset(decoyUid, {
name: "unknown",
config: { ads: "result" },
})
).presetId;
//WHEN
await PresetDal.editPreset(uid, {
_id: first,
name: "newName",
config: { ads: "off" },
});
//THEN
const read = await PresetDal.getPresets(uid);
expect(read).toHaveLength(2);
expect(read).toEqual(
expect.arrayContaining([
expect.objectContaining({
_id: new ObjectId(first),
uid: uid,
name: "newName",
config: { ads: "off" },
}),
expect.objectContaining({
_id: new ObjectId(second),
uid: uid,
name: "second",
config: { ads: "result" },
}),
]),
);
expect(await PresetDal.getPresets(decoyUid)).toEqual(
expect.arrayContaining([
expect.objectContaining({
_id: new ObjectId(decoy),
uid: decoyUid,
name: "unknown",
config: { ads: "result" },
}),
]),
);
});
it("should edit with name only - full preset", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const first = (
await PresetDal.addPreset(uid, {
name: "first",
config: { ads: "sellout" },
})
).presetId;
//WHEN empty
await PresetDal.editPreset(uid, {
_id: first,
name: "newName",
});
expect(await PresetDal.getPresets(uid)).toEqual(
expect.arrayContaining([
expect.objectContaining({
_id: new ObjectId(first),
uid: uid,
name: "newName",
config: { ads: "sellout" },
}),
]),
);
});
it("should edit with name only - partial preset", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const first = (
await PresetDal.addPreset(uid, {
name: "first",
settingGroups: ["hideElements"],
config: {
showKeyTips: true,
capsLockWarning: true,
showOutOfFocusWarning: true,
showAverage: "off",
},
})
).presetId;
//WHEN empty
await PresetDal.editPreset(uid, {
_id: first,
name: "newName",
});
expect(await PresetDal.getPresets(uid)).toEqual(
expect.arrayContaining([
expect.objectContaining({
_id: new ObjectId(first),
uid: uid,
name: "newName",
settingGroups: ["hideElements"],
config: {
showKeyTips: true,
capsLockWarning: true,
showOutOfFocusWarning: true,
showAverage: "off",
},
}),
]),
);
});
it("should not edit present not matching uid", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const decoyUid = new ObjectId().toHexString();
const first = (
await PresetDal.addPreset(uid, {
name: "first",
config: { ads: "sellout" },
})
).presetId;
//WHEN
await PresetDal.editPreset(decoyUid, {
_id: first,
name: "newName",
config: { ads: "off" },
});
//THEN
const read = await PresetDal.getPresets(uid);
expect(read).toEqual(
expect.arrayContaining([
expect.objectContaining({
_id: new ObjectId(first),
uid: uid,
name: "first",
config: { ads: "sellout" },
}),
]),
);
});
it("should edit when partial is edited to full", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const first = (
await PresetDal.addPreset(uid, {
name: "first",
settingGroups: ["hideElements"],
config: {
showKeyTips: true,
capsLockWarning: true,
showOutOfFocusWarning: true,
showAverage: "off",
},
})
).presetId;
//WHEN
await PresetDal.editPreset(uid, {
_id: first,
name: "newName",
settingGroups: null,
config: { ads: "off" },
});
//THEN
expect(await PresetDal.getPresets(uid)).toEqual(
expect.arrayContaining([
expect.objectContaining({
_id: new ObjectId(first),
uid: uid,
name: "newName",
config: { ads: "off" },
settingGroups: null,
}),
]),
);
});
it("should edit when full is edited to partial", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const first = (
await PresetDal.addPreset(uid, {
name: "first",
config: {
ads: "off",
},
})
).presetId;
//WHEN
await PresetDal.editPreset(uid, {
_id: first,
name: "newName",
settingGroups: ["hideElements"],
config: {
showKeyTips: true,
capsLockWarning: true,
showOutOfFocusWarning: true,
showAverage: "off",
},
});
//THEN
expect(await PresetDal.getPresets(uid)).toEqual(
expect.arrayContaining([
expect.objectContaining({
_id: new ObjectId(first),
uid: uid,
name: "newName",
settingGroups: ["hideElements"],
config: {
showKeyTips: true,
capsLockWarning: true,
showOutOfFocusWarning: true,
showAverage: "off",
},
}),
]),
);
});
});
describe("removePreset", () => {
it("should fail if preset is unknown", async () => {
const uid = new ObjectId().toHexString();
await expect(() =>
PresetDal.removePreset(uid, new ObjectId().toHexString()),
).rejects.toThrow("Preset not found");
});
it("should remove", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const decoyUid = new ObjectId().toHexString();
const first = (
await PresetDal.addPreset(uid, { name: "first", config: {} })
).presetId;
const second = (
await PresetDal.addPreset(uid, {
name: "second",
config: { ads: "result" },
})
).presetId;
const decoy = (
await PresetDal.addPreset(decoyUid, {
name: "unknown",
config: { ads: "result" },
})
).presetId;
//WHEN
await PresetDal.removePreset(uid, first);
//THEN
const read = await PresetDal.getPresets(uid);
expect(read).toHaveLength(1);
expect(read).toEqual(
expect.arrayContaining([
expect.objectContaining({
_id: new ObjectId(second),
uid: uid,
name: "second",
config: { ads: "result" },
}),
]),
);
expect(await PresetDal.getPresets(decoyUid)).toEqual(
expect.arrayContaining([
expect.objectContaining({
_id: new ObjectId(decoy),
uid: decoyUid,
name: "unknown",
config: { ads: "result" },
}),
]),
);
});
it("should not remove present not matching uid", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const decoyUid = new ObjectId().toHexString();
const first = (
await PresetDal.addPreset(uid, {
name: "first",
config: { ads: "sellout" },
})
).presetId;
//WHEN
await expect(() =>
PresetDal.removePreset(decoyUid, first),
).rejects.toThrow("Preset not found");
//THEN
const read = await PresetDal.getPresets(uid);
expect(read).toEqual(
expect.arrayContaining([
expect.objectContaining({
_id: new ObjectId(first),
uid: uid,
name: "first",
config: { ads: "sellout" },
}),
]),
);
});
});
describe("deleteAllPresets", () => {
it("should not fail if preset is unknown", async () => {
const uid = new ObjectId().toHexString();
await PresetDal.deleteAllPresets(uid);
});
it("should delete all", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const decoyUid = new ObjectId().toHexString();
await PresetDal.addPreset(uid, { name: "first", config: {} });
await PresetDal.addPreset(uid, {
name: "second",
config: { ads: "result" },
});
const decoy = (
await PresetDal.addPreset(decoyUid, {
name: "unknown",
config: { ads: "result" },
})
).presetId;
//WHEN
await PresetDal.deleteAllPresets(uid);
//THEN
const read = await PresetDal.getPresets(uid);
expect(read).toHaveLength(0);
expect(await PresetDal.getPresets(decoyUid)).toEqual(
expect.arrayContaining([
expect.objectContaining({
_id: new ObjectId(decoy),
uid: decoyUid,
name: "unknown",
config: { ads: "result" },
}),
]),
);
});
});
});

View File

@@ -0,0 +1,26 @@
import { describe, it, expect } from "vitest";
import * as PublicDAL from "../../../src/dal/public";
describe("PublicDAL", function () {
it("should be able to update stats", async function () {
// checks it doesn't throw an error. the actual values are checked in another test.
await PublicDAL.updateStats(1, 15);
});
it("should be able to get typing stats", async function () {
const typingStats = await PublicDAL.getTypingStats();
expect(typingStats).toHaveProperty("testsCompleted");
expect(typingStats).toHaveProperty("testsStarted");
expect(typingStats).toHaveProperty("timeTyping");
});
it("should increment stats on update", async function () {
// checks that both functions are working on the same data in mongo
const priorStats = await PublicDAL.getTypingStats();
await PublicDAL.updateStats(1, 60);
const afterStats = await PublicDAL.getTypingStats();
expect(afterStats.testsCompleted).toBe(priorStats.testsCompleted + 1);
expect(afterStats.testsStarted).toBe(priorStats.testsStarted + 2);
expect(afterStats.timeTyping).toBe(priorStats.timeTyping + 60);
});
});

View File

@@ -0,0 +1,190 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import * as ResultDal from "../../../src/dal/result";
import { ObjectId } from "mongodb";
import * as UserDal from "../../../src/dal/user";
import { DBResult } from "../../../src/utils/result";
import * as ResultUtils from "../../../src/utils/result";
let uid: string;
const timestamp = Date.now() - 60000;
async function createDummyData(
uid: string,
count: number,
modify?: Partial<DBResult>,
): Promise<void> {
const dummyUser: UserDal.DBUser = {
_id: new ObjectId(),
uid,
addedAt: 0,
email: "test@example.com",
name: "Bob",
personalBests: {
time: {},
words: {},
quote: {},
custom: {},
zen: {},
},
};
vi.spyOn(UserDal, "getUser").mockResolvedValue(dummyUser);
for (let i = 0; i < count; i++) {
await ResultDal.addResult(uid, {
...{
_id: new ObjectId(),
wpm: i,
rawWpm: i,
charStats: [0, 0, 0, 0],
acc: 0,
mode: "time",
mode2: "10" as never,
quoteLength: 1,
timestamp,
restartCount: 0,
incompleteTestSeconds: 0,
incompleteTests: [],
testDuration: 10,
afkDuration: 0,
tags: [],
consistency: 100,
keyConsistency: 100,
chartData: { wpm: [], burst: [], err: [] },
uid,
keySpacingStats: { average: 0, sd: 0 },
keyDurationStats: { average: 0, sd: 0 },
difficulty: "normal",
language: "english",
isPb: false,
name: "Test",
funbox: ["58008", "read_ahead"],
},
...modify,
});
}
}
describe("ResultDal", () => {
const replaceLegacyValuesMock = vi.spyOn(ResultUtils, "replaceLegacyValues");
beforeEach(() => {
uid = new ObjectId().toHexString();
});
afterEach(async () => {
if (uid) await ResultDal.deleteAll(uid);
replaceLegacyValuesMock.mockClear();
});
describe("getResults", () => {
it("should read lastest 10 results ordered by timestamp", async () => {
//GIVEN
await createDummyData(uid, 10, { timestamp: timestamp - 2000 });
await createDummyData(uid, 20, { tags: ["current"] });
//WHEN
const results = await ResultDal.getResults(uid, { limit: 10 });
//THEN
expect(results).toHaveLength(10);
let last = results[0]?.timestamp as number;
results.forEach((it) => {
expect(it.tags).toContain("current");
expect(it.timestamp).toBeGreaterThanOrEqual(last);
last = it.timestamp;
});
});
it("should read all if not limited", async () => {
//GIVEN
await createDummyData(uid, 10, { timestamp: timestamp - 2000 });
await createDummyData(uid, 20);
//WHEN
const results = await ResultDal.getResults(uid, {});
//THEN
expect(results).toHaveLength(30);
});
it("should read results onOrAfterTimestamp", async () => {
//GIVEN
await createDummyData(uid, 10, { timestamp: timestamp - 2000 });
await createDummyData(uid, 20, { tags: ["current"] });
//WHEN
const results = await ResultDal.getResults(uid, {
onOrAfterTimestamp: timestamp,
});
//THEN
expect(results).toHaveLength(20);
results.forEach((it) => {
expect(it.tags).toContain("current");
});
});
it("should read next 10 results", async () => {
//GIVEN
await createDummyData(uid, 10, {
timestamp: timestamp - 2000,
tags: ["old"],
});
await createDummyData(uid, 20);
//WHEN
const results = await ResultDal.getResults(uid, {
limit: 10,
offset: 20,
});
//THEN
expect(results).toHaveLength(10);
results.forEach((it) => {
expect(it.tags).toContain("old");
});
});
it("should call replaceLegacyValues", async () => {
//GIVEN
await createDummyData(uid, 1);
//WHEN
await ResultDal.getResults(uid);
//THEN
expect(replaceLegacyValuesMock).toHaveBeenCalled();
});
});
describe("getResult", () => {
it("should call replaceLegacyValues", async () => {
//GIVEN
await createDummyData(uid, 1);
const resultId = (await ResultDal.getLastResult(uid))._id.toHexString();
//WHEN
await ResultDal.getResult(uid, resultId);
//THEN
expect(replaceLegacyValuesMock).toHaveBeenCalled();
});
});
describe("getLastResult", () => {
it("should call replaceLegacyValues", async () => {
//GIVEN
await createDummyData(uid, 1);
//WHEN
await ResultDal.getLastResult(uid);
//THEN
expect(replaceLegacyValuesMock).toHaveBeenCalled();
});
});
describe("getResultByTimestamp", () => {
it("should call replaceLegacyValues", async () => {
//GIVEN
await createDummyData(uid, 1);
//WHEN
await ResultDal.getResultByTimestamp(uid, timestamp);
//THEN
expect(replaceLegacyValuesMock).toHaveBeenCalled();
});
});
});

File diff suppressed because it is too large Load Diff