alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
139 lines (113 loc) • 5.49 kB
text/typescript
import { Alepha } from "alepha";
import { AlephaOrmPostgres } from "alepha/orm/postgres";
import { AlephaSecurity } from "alepha/security";
import { describe, expect, it } from "vitest";
import { realmAuthSettingsAtom } from "../atoms/realmAuthSettingsAtom.ts";
import { AlephaApiUsers, RealmProvider, UsernameSlugger } from "../index.ts";
const setup = async (realmSettings?: Record<string, unknown>) => {
const alepha = Alepha.create();
alepha.with(AlephaOrmPostgres);
alepha.with(AlephaSecurity);
alepha.with(AlephaApiUsers);
await alepha.start();
if (realmSettings) {
alepha.inject(RealmProvider).register("default", {
settings: realmSettings as never,
});
}
// Wipe users table between cases so collision tests don't interfere.
const users = alepha.inject(RealmProvider).userRepository();
await users.deleteMany({});
return {
alepha,
slugger: alepha.inject(UsernameSlugger),
users,
};
};
// ---------------------------------------------------------------------------------------------------------------------
describe("UsernameSlugger.slug — pure rule", () => {
it("strips the domain and the gmail '+suffix' is kept verbatim", async () => {
const { slugger } = await setup();
expect(slugger.slug("ni.foures+testkv@gmail.com")).toBe("ni-foures-testkv");
});
it("collapses runs of non-alphanumeric chars and trims edges", async () => {
const { slugger } = await setup();
expect(slugger.slug("john...doe@example.com")).toBe("john-doe");
expect(slugger.slug("---weird---name---@x.io")).toBe("weird-name");
});
it("lowercases the result", async () => {
const { slugger } = await setup();
expect(slugger.slug("Alice.SMITH@Example.com")).toBe("alice-smith");
});
it("drops every non-[a-z0-9] char (no NFD normalize)", async () => {
// `é` is dropped, so `josé` becomes `jos` — and then padded to MIN_LENGTH.
const { slugger } = await setup();
const result = slugger.slug("josé@example.com");
expect(result.startsWith("jos")).toBe(true);
expect(result.length).toBeGreaterThanOrEqual(UsernameSlugger.MIN_LENGTH);
});
it("pads short slugs with random alphanumerics", async () => {
const { slugger } = await setup();
const result = slugger.slug("a@example.com");
expect(result.startsWith("a")).toBe(true);
expect(result.length).toBeGreaterThanOrEqual(UsernameSlugger.MIN_LENGTH);
expect(result).toMatch(/^[a-z0-9-]+$/);
});
it("falls back to the EMPTY_LOCAL_FALLBACK when the local part has no [a-z0-9]", async () => {
const { slugger } = await setup();
expect(slugger.slug("é@example.com")).toBe(
UsernameSlugger.EMPTY_LOCAL_FALLBACK,
);
});
it("clamps to MAX_LENGTH", async () => {
const { slugger } = await setup();
const long = `${"a".repeat(60)}@example.com`;
expect(slugger.slug(long).length).toBe(UsernameSlugger.MAX_LENGTH);
});
it("treats null/empty input by returning the fallback", async () => {
const { slugger } = await setup();
expect(slugger.slug(null)).toBe(UsernameSlugger.EMPTY_LOCAL_FALLBACK);
expect(slugger.slug("")).toBe(UsernameSlugger.EMPTY_LOCAL_FALLBACK);
expect(slugger.slug(undefined)).toBe(UsernameSlugger.EMPTY_LOCAL_FALLBACK);
});
});
// ---------------------------------------------------------------------------------------------------------------------
describe("UsernameSlugger.pickAvailable — DB-backed availability + retry", () => {
it("returns the base when nothing collides", async () => {
const { slugger } = await setup();
const picked = await slugger.pickAvailable("default", "alice");
expect(picked).toBe("alice");
});
it("appends a 4-char random suffix when the base is taken", async () => {
const { slugger, users } = await setup();
await users.create({ realm: "default", username: "alice", email: "a@x" });
const picked = await slugger.pickAvailable("default", "alice");
expect(picked).toMatch(/^alice-[a-z0-9]{4}$/);
});
it("treats blocklisted candidates as collisions and falls through to the suffix path", async () => {
const { slugger } = await setup({ usernameBlocklist: ["admin", "root"] });
const picked = await slugger.pickAvailable("default", "admin");
expect(picked).toMatch(/^admin-[a-z0-9]{4}$/);
});
it("trims the base before adding the suffix when MAX_LENGTH would be exceeded", async () => {
const { slugger, users } = await setup();
const long = "a".repeat(UsernameSlugger.MAX_LENGTH);
await users.create({ realm: "default", username: long, email: "a@x" });
const picked = await slugger.pickAvailable("default", long);
expect(picked.length).toBeLessThanOrEqual(UsernameSlugger.MAX_LENGTH);
expect(picked).toMatch(/-[a-z0-9]{4}$/);
});
it("blocklist match is case-insensitive", async () => {
const { slugger } = await setup({ usernameBlocklist: ["Admin"] });
expect(await slugger.isBlocked("default", "admin")).toBe(true);
expect(await slugger.isBlocked("default", "ADMIN")).toBe(true);
expect(await slugger.isBlocked("default", "user")).toBe(false);
});
it("default blocklist is empty — no name is implicitly reserved", async () => {
const { alepha, slugger } = await setup();
const settings = alepha.get(realmAuthSettingsAtom);
expect(settings.usernameBlocklist).toEqual([]);
const picked = await slugger.pickAvailable("default", "admin");
expect(picked).toBe("admin");
});
});