Files
Yash SinghandBandhan Majumder 1599dfd515 fix: zero-pad username suggestion suffix consistently (#29571)
generateUsernameSuggestion built its numeric suffix with
String(rand).padStart(4 - rand.toString().length, "0"). padStart's first
argument is the target total length, not the number of pad characters, so the
expression only padded single-digit numbers: rand=1 became "001" but rand=42
became "42" and rand=999 stayed "999". Suggested usernames therefore had
inconsistent suffix widths, and the magic 4 didn't match the 3-wide output the
existing test pins ("johnny001").

Pull the suffix into a small helper that always pads to 3 digits, so collisions
and the returned value share one definition and every suggestion is uniformly
zero-padded. Add unit tests for the first-attempt path and for two- and
three-digit suffixes; the two-digit case fails on the old code.

Co-authored-by: Bandhan Majumder <133476557+bandhan-majumder@users.noreply.github.com>
2026-06-19 05:37:42 +00:00

115 lines
3.6 KiB
TypeScript

import prismaMock from "@calcom/testing/lib/__mocks__/prismaMock";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { generateUsernameSuggestion, usernameCheckForSignup } from "./username";
describe("usernameCheckForSignup ", async () => {
beforeEach(() => {
// @ts-expect-error
prismaMock.user.findUnique.mockImplementation(() => {
return null;
});
// @ts-expect-error
prismaMock.user.findMany.mockImplementation(() => {
return [];
});
});
it("should return available true for an email that doesn't exist", async () => {
const res = await usernameCheckForSignup({ username: "johnny", email: "johnny@example.com" });
expect(res).toEqual({
available: true,
premium: false,
suggestedUsername: "",
});
});
it("should return available false for an email that exists and a different username is provided", async () => {
mockUserInDB({
id: 1,
email: "john@example.com",
username: "john",
});
const res = await usernameCheckForSignup({ username: "johnny", email: "john@example.com" });
expect(res).toEqual({
available: false,
premium: false,
suggestedUsername: "johnny001",
});
});
it("should return available true for an email that exists but the user is signing up for an organization", async () => {
const userId = 1;
mockUserInDB({
id: userId,
email: "john@example.com",
username: "john",
});
mockMembership({ userId });
const res = await usernameCheckForSignup({ username: "john", email: "john@example.com" });
expect(res).toEqual({
available: true,
// An organization can't have premium username
premium: false,
suggestedUsername: "",
});
});
});
describe("generateUsernameSuggestion", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("appends a zero-padded suffix when the username is free on the first attempt", async () => {
const suggestion = await generateUsernameSuggestion([], "john");
expect(suggestion).toBe("john001");
});
it("zero-pads a two-digit suffix to a consistent width", async () => {
// Force the first candidate (john001) to collide so a random suffix is generated,
// and pin Math.random so the generated suffix is the two-digit number 42.
// username length >= 2 -> limit 999 -> rand = ceil(1 + random * 998); 40.5/998 -> ceil(41.5) = 42.
vi.spyOn(Math, "random").mockReturnValue(40.5 / 998);
const suggestion = await generateUsernameSuggestion(["john001"], "john");
expect(suggestion).toBe("john042");
});
it("zero-pads a three-digit suffix to a consistent width", async () => {
// 122.5/998 -> ceil(1 + 122.5) = ceil(123.5) = 124.
vi.spyOn(Math, "random").mockReturnValue(122.5 / 998);
const suggestion = await generateUsernameSuggestion(["john001"], "john");
expect(suggestion).toBe("john124");
});
});
function mockUserInDB({ id, email, username }: { id: number; email: string; username: string }) {
// @ts-expect-error
prismaMock.user.findUnique.mockImplementation((arg) => {
if (arg.where.email === email) {
return {
id,
email,
username,
};
}
return null;
});
}
function mockMembership({ userId }: { userId: number }) {
// @ts-expect-error
prismaMock.membership.findFirst.mockImplementation((arg) => {
const isOrganizationWhereClause =
// @ts-expect-error
arg?.where?.team?.metadata?.path[0] === "isOrganization" && arg?.where?.team?.metadata?.equals === true;
if (arg?.where?.userId === userId && isOrganizationWhereClause) {
return {
userId,
teamId: 1,
};
}
});
}