mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-02-02 03:50:45 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
2e95c618a5
5 changed files with 172 additions and 18 deletions
|
@ -1,30 +1,55 @@
|
|||
import { addUser, getUser, updateName } from "../../src/dal/user";
|
||||
import _ from "lodash";
|
||||
import {
|
||||
addUser,
|
||||
clearPb,
|
||||
getUser,
|
||||
getUsersCollection,
|
||||
updateName,
|
||||
} from "../../src/dal/user";
|
||||
|
||||
const mockPersonalBest = {
|
||||
acc: 1,
|
||||
consistency: 1,
|
||||
difficulty: "normal" as const,
|
||||
lazyMode: true,
|
||||
language: "no",
|
||||
punctuation: false,
|
||||
raw: 230,
|
||||
wpm: 215,
|
||||
timestamp: 13123123,
|
||||
};
|
||||
|
||||
describe("UserDal", () => {
|
||||
it("should be able to insert users", async () => {
|
||||
// given
|
||||
const newUser = {
|
||||
name: "Test",
|
||||
email: "mockemail@email.com",
|
||||
uid: "userId",
|
||||
};
|
||||
|
||||
// when
|
||||
await addUser(newUser.name, newUser.email, newUser.uid);
|
||||
const insertedUser = await getUser("userId", "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 newUser = {
|
||||
name: "Test",
|
||||
email: "mockemail@email.com",
|
||||
uid: "userId",
|
||||
};
|
||||
|
||||
// when
|
||||
await addUser(newUser.name, newUser.email, newUser.uid);
|
||||
|
||||
// then
|
||||
// should error because user already exists
|
||||
await expect(
|
||||
addUser(newUser.name, newUser.email, newUser.uid)
|
||||
|
@ -32,6 +57,7 @@ describe("UserDal", () => {
|
|||
});
|
||||
|
||||
it("updatename should not allow unavailable usernames", async () => {
|
||||
// given
|
||||
const mockUsers = [...Array(3).keys()]
|
||||
.map((id) => ({
|
||||
name: `Test${id}`,
|
||||
|
@ -39,14 +65,125 @@ describe("UserDal", () => {
|
|||
uid: `userId${id}`,
|
||||
}))
|
||||
.map(({ name, email, uid }) => addUser(name, email, uid));
|
||||
|
||||
await Promise.all(mockUsers);
|
||||
|
||||
const userToUpdateNameFor = await getUser("userId0", "test");
|
||||
const userWithNameTaken = await getUser("userId1", "test");
|
||||
|
||||
// when, then
|
||||
await expect(
|
||||
updateName(userToUpdateNameFor.uid, userWithNameTaken.name)
|
||||
).rejects.toThrow("Username already taken");
|
||||
});
|
||||
|
||||
it("updatename should not allow invalid usernames", async () => {
|
||||
// given
|
||||
const testUser = {
|
||||
name: "Test",
|
||||
email: "mockemail@email.com",
|
||||
uid: "userId",
|
||||
};
|
||||
|
||||
await addUser(testUser.name, testUser.email, testUser.uid);
|
||||
|
||||
const invalidNames = [
|
||||
null, // falsy
|
||||
undefined, // falsy
|
||||
"", // empty
|
||||
" ".repeat(16), // too long
|
||||
".testName", // cant begin with period
|
||||
"miodec", // profanity
|
||||
"asdasdAS$", // invalid characters
|
||||
];
|
||||
|
||||
// when, then
|
||||
invalidNames.forEach(
|
||||
async (invalidName) =>
|
||||
await expect(
|
||||
updateName(testUser.uid, invalidName as unknown as string)
|
||||
).rejects.toThrow("Invalid username")
|
||||
);
|
||||
});
|
||||
|
||||
it("updateName should fail if user has changed name recently", async () => {
|
||||
// given
|
||||
const testUser = {
|
||||
name: "Test",
|
||||
email: "mockemail@email.com",
|
||||
uid: "userId",
|
||||
};
|
||||
|
||||
await addUser(testUser.name, testUser.email, testUser.uid);
|
||||
|
||||
// when
|
||||
await updateName(testUser.uid, "renamedTestUser");
|
||||
|
||||
const updatedUser = await getUser(testUser.uid, "test");
|
||||
|
||||
// then
|
||||
expect(updatedUser.name).toBe("renamedTestUser");
|
||||
|
||||
await expect(updateName(updatedUser.uid, "NewValidName")).rejects.toThrow(
|
||||
"You can change your name once every 30 days"
|
||||
);
|
||||
});
|
||||
|
||||
it("updateName should change the name of a user", async () => {
|
||||
// given
|
||||
const testUser = {
|
||||
name: "Test",
|
||||
email: "mockemail@email.com",
|
||||
uid: "userId",
|
||||
};
|
||||
|
||||
await addUser(testUser.name, testUser.email, testUser.uid);
|
||||
|
||||
// when
|
||||
await updateName(testUser.uid, "renamedTestUser");
|
||||
|
||||
// then
|
||||
const updatedUser = await getUser(testUser.uid, "test");
|
||||
expect(updatedUser.name).toBe("renamedTestUser");
|
||||
});
|
||||
|
||||
it("clearPb should clear the personalBests of a user", async () => {
|
||||
// given
|
||||
const testUser = {
|
||||
name: "Test",
|
||||
email: "mockemail@email.com",
|
||||
uid: "userId",
|
||||
};
|
||||
await addUser(testUser.name, testUser.email, testUser.uid);
|
||||
await getUsersCollection().updateOne(
|
||||
{ uid: testUser.uid },
|
||||
{
|
||||
$set: {
|
||||
personalBests: {
|
||||
time: { 20: [mockPersonalBest] },
|
||||
words: {},
|
||||
quote: {},
|
||||
custom: {},
|
||||
zen: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const { personalBests } = (await getUser(testUser.uid, "test")) ?? {};
|
||||
expect(personalBests).toStrictEqual({
|
||||
time: { 20: [mockPersonalBest] },
|
||||
words: {},
|
||||
quote: {},
|
||||
custom: {},
|
||||
zen: {},
|
||||
});
|
||||
// when
|
||||
await clearPb(testUser.uid);
|
||||
|
||||
// then
|
||||
const updatedUser = (await getUser(testUser.uid, "test")) ?? {};
|
||||
expect(_.values(updatedUser.personalBests).filter(_.isEmpty)).toHaveLength(
|
||||
5
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,10 +6,10 @@ export default {
|
|||
setupFilesAfterEnv: ["<rootDir>/setup-tests.ts"],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 0,
|
||||
functions: 0,
|
||||
lines: 0,
|
||||
statements: 0,
|
||||
branches: 36,
|
||||
functions: 18,
|
||||
lines: 39,
|
||||
statements: 35,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -13,15 +13,9 @@ import {
|
|||
WithId,
|
||||
} from "mongodb";
|
||||
|
||||
let usersCollection: Collection<WithId<MonkeyTypes.User>>;
|
||||
|
||||
function getUsersCollection(): Collection<WithId<MonkeyTypes.User>> {
|
||||
if (!usersCollection) {
|
||||
usersCollection = db.collection<MonkeyTypes.User>("users");
|
||||
}
|
||||
|
||||
return usersCollection;
|
||||
}
|
||||
// Export for use in tests
|
||||
export const getUsersCollection = (): Collection<WithId<MonkeyTypes.User>> =>
|
||||
db.collection<MonkeyTypes.User>("users");
|
||||
|
||||
export async function addUser(
|
||||
name: string,
|
||||
|
@ -47,6 +41,9 @@ export async function deleteUser(uid: string): Promise<DeleteResult> {
|
|||
return await getUsersCollection().deleteOne({ uid });
|
||||
}
|
||||
|
||||
const DAY_IN_SECONDS = 24 * 60 * 60;
|
||||
const THIRTY_DAYS_IN_SECONDS = DAY_IN_SECONDS * 30;
|
||||
|
||||
export async function updateName(
|
||||
uid: string,
|
||||
name: string
|
||||
|
@ -62,7 +59,7 @@ export async function updateName(
|
|||
|
||||
if (
|
||||
!user?.needsToChangeName &&
|
||||
Date.now() - (user.lastNameChange ?? 0) < 2592000000
|
||||
Date.now() - (user.lastNameChange ?? 0) < THIRTY_DAYS_IN_SECONDS
|
||||
) {
|
||||
throw new MonkeyError(409, "You can change your name once every 30 days");
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ export function inRange(value: number, min: number, max: number): boolean {
|
|||
return value >= min && value <= max;
|
||||
}
|
||||
|
||||
const VALID_NAME_PATTERN = /^[\da-zA-Z_.-]+$/;
|
||||
|
||||
export function isUsernameValid(name: string): boolean {
|
||||
if (_.isNil(name) || !inRange(name.length, 1, 16)) {
|
||||
return false;
|
||||
|
@ -24,7 +26,7 @@ export function isUsernameValid(name: string): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
return /^[0-9a-zA-Z_.-]+$/.test(name);
|
||||
return VALID_NAME_PATTERN.test(name);
|
||||
}
|
||||
|
||||
export function isTagPresetNameValid(name: string): boolean {
|
||||
|
@ -32,7 +34,7 @@ export function isTagPresetNameValid(name: string): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
return /^[0-9a-zA-Z_.-]+$/.test(name);
|
||||
return VALID_NAME_PATTERN.test(name);
|
||||
}
|
||||
|
||||
export function isTestTooShort(result: MonkeyTypes.CompletedEvent): boolean {
|
||||
|
|
|
@ -34980,6 +34980,24 @@
|
|||
"source": "IT Crowd",
|
||||
"length": 307,
|
||||
"id": 5934
|
||||
},
|
||||
{
|
||||
"text": "It's a long, hard, dusty road you'll journey on through a world you never known. Only time will tell you if or not if your life has been in vain. Each morning sunrise brings question and query, each sunset brings you more doubts. Always you're searching for what, you're not even sure, melting away like an icicle on fire",
|
||||
"source": "Xavier: Renegade Angel",
|
||||
"length": 321,
|
||||
"id": 5935
|
||||
},
|
||||
{
|
||||
"text": "Powers are for the weak. I have no powers. I mean, unless you count the power to blow minds with my weapons-grade philosophical insights.",
|
||||
"source": "Xavier: Renegade Angel",
|
||||
"length": 137,
|
||||
"id": 5936
|
||||
},
|
||||
{
|
||||
"text": "But would you kindly ponder this question: What would your good do if evil didn't exist, and what would the earth look like if all the shadows disappeared? After all, shadows are cast by things and people. Here is the shadow of my sword. But shadows also come from trees and living beings. Do you want to strip the earth of all trees and living things just because of your fantasy of enjoying naked light? You're stupid.",
|
||||
"source": "The Master and Margarita",
|
||||
"length": 420,
|
||||
"id": 5937
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue