Replace entire codebase with homarr-labs/homarr
This commit is contained in:
67
packages/auth/test/adapter.spec.ts
Normal file
67
packages/auth/test/adapter.spec.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { users } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
|
||||
import { createAdapter } from "../adapter";
|
||||
|
||||
describe("createAdapter should create drizzle adapter", () => {
|
||||
test.each([["credentials" as const], ["ldap" as const], ["oidc" as const]])(
|
||||
"createAdapter getUserByEmail should return user for provider %s when this provider provided",
|
||||
async (provider) => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
const adapter = createAdapter(db, provider);
|
||||
const email = "test@example.com";
|
||||
await db.insert(users).values({ id: "1", name: "test", email, provider });
|
||||
|
||||
// Act
|
||||
const user = await adapter.getUserByEmail?.(email);
|
||||
|
||||
// Assert
|
||||
expect(user).toEqual({
|
||||
id: "1",
|
||||
name: "test",
|
||||
email,
|
||||
emailVerified: null,
|
||||
image: null,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.each([
|
||||
["credentials", ["ldap", "oidc"]],
|
||||
["ldap", ["credentials", "oidc"]],
|
||||
["oidc", ["credentials", "ldap"]],
|
||||
] as const)(
|
||||
"createAdapter getUserByEmail should return null if only for other providers than %s exist",
|
||||
async (requestedProvider, existingProviders) => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
const adapter = createAdapter(db, requestedProvider);
|
||||
const email = "test@example.com";
|
||||
for (const provider of existingProviders) {
|
||||
await db.insert(users).values({ id: provider, name: `test-${provider}`, email, provider });
|
||||
}
|
||||
|
||||
// Act
|
||||
const user = await adapter.getUserByEmail?.(email);
|
||||
|
||||
// Assert
|
||||
expect(user).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
test("createAdapter getUserByEmail should throw error if provider is unknown", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
const adapter = createAdapter(db, "unknown");
|
||||
const email = "test@example.com";
|
||||
|
||||
// Act
|
||||
const actAsync = async () => await adapter.getUserByEmail?.(email);
|
||||
|
||||
// Assert
|
||||
await expect(actAsync()).rejects.toThrow("Unable to get user by email for unknown provider");
|
||||
});
|
||||
});
|
||||
144
packages/auth/test/callbacks.spec.ts
Normal file
144
packages/auth/test/callbacks.spec.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import type { AdapterUser } from "@auth/core/adapters";
|
||||
import type { JWT } from "next-auth/jwt";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import * as definitions from "@homarr/definitions";
|
||||
|
||||
import { createSessionCallback, getCurrentUserPermissionsAsync } from "../callbacks";
|
||||
|
||||
// This one is placed here because it's used in multiple tests and needs to be the same reference
|
||||
const setCookies = vi.fn();
|
||||
vi.mock("next/headers", () => ({
|
||||
cookies: () => ({
|
||||
set: setCookies,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("getCurrentUserPermissions", () => {
|
||||
test("should return empty permissions when non existing user requested", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
|
||||
await db.insert(groups).values({
|
||||
id: "2",
|
||||
name: "test",
|
||||
position: 1,
|
||||
});
|
||||
await db.insert(groupPermissions).values({
|
||||
groupId: "2",
|
||||
permission: "admin",
|
||||
});
|
||||
await db.insert(users).values({
|
||||
id: "2",
|
||||
});
|
||||
|
||||
const userId = "1";
|
||||
|
||||
// Act
|
||||
const result = await getCurrentUserPermissionsAsync(db, userId);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
test("should return empty permissions when user has no groups", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
const userId = "1";
|
||||
|
||||
await db.insert(groups).values({
|
||||
id: "2",
|
||||
name: "test",
|
||||
position: 1,
|
||||
});
|
||||
await db.insert(groupPermissions).values({
|
||||
groupId: "2",
|
||||
permission: "admin",
|
||||
});
|
||||
await db.insert(users).values({
|
||||
id: userId,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await getCurrentUserPermissionsAsync(db, userId);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
test("should return permissions for user", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
const getPermissionsWithChildrenMock = vi
|
||||
.spyOn(definitions, "getPermissionsWithChildren")
|
||||
.mockReturnValue(["board-create"]);
|
||||
const mockId = "1";
|
||||
|
||||
await db.insert(users).values({
|
||||
id: mockId,
|
||||
});
|
||||
await db.insert(groups).values({
|
||||
id: mockId,
|
||||
name: "test",
|
||||
position: 1,
|
||||
});
|
||||
await db.insert(groupMembers).values({
|
||||
userId: mockId,
|
||||
groupId: mockId,
|
||||
});
|
||||
await db.insert(groupPermissions).values({
|
||||
groupId: mockId,
|
||||
permission: "admin",
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await getCurrentUserPermissionsAsync(db, mockId);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(["board-create"]);
|
||||
expect(getPermissionsWithChildrenMock).toHaveBeenCalledWith(["admin"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("session callback", () => {
|
||||
test("should add id and name to session user", async () => {
|
||||
// Arrange
|
||||
const user: AdapterUser = {
|
||||
id: "id",
|
||||
name: "name",
|
||||
email: "email",
|
||||
emailVerified: new Date("2023-01-13"),
|
||||
};
|
||||
const token: JWT = {};
|
||||
const db = createDb();
|
||||
const callback = createSessionCallback(db);
|
||||
|
||||
// Act
|
||||
const result = await callback({
|
||||
session: {
|
||||
user: {
|
||||
id: "no-id",
|
||||
email: "no-email",
|
||||
emailVerified: new Date("2023-01-13"),
|
||||
permissions: [],
|
||||
colorScheme: "dark",
|
||||
},
|
||||
expires: "2023-01-13" as Date & string,
|
||||
sessionToken: "token",
|
||||
userId: "no-id",
|
||||
},
|
||||
user,
|
||||
token,
|
||||
trigger: "update",
|
||||
newSession: {},
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(result.user).toBeDefined();
|
||||
expect(result.user!.id).toEqual(user.id);
|
||||
expect(result.user!.name).toEqual(user.name);
|
||||
});
|
||||
});
|
||||
264
packages/auth/test/events.spec.ts
Normal file
264
packages/auth/test/events.spec.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
import type { ResponseCookie } from "next/dist/compiled/@edge-runtime/cookies";
|
||||
import type { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
|
||||
import { cookies } from "next/headers";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { eq } from "@homarr/db";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { groupMembers, groups, users } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions";
|
||||
|
||||
import { createSignInEventHandler } from "../events";
|
||||
|
||||
vi.mock("next-auth", () => ({}));
|
||||
vi.mock("../env", () => {
|
||||
return {
|
||||
env: {
|
||||
AUTH_OIDC_GROUPS_ATTRIBUTE: "someRandomGroupsKey",
|
||||
},
|
||||
};
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
type HeadersExport = typeof import("next/headers");
|
||||
vi.mock("next/headers", async (importOriginal) => {
|
||||
const mod = await importOriginal<HeadersExport>();
|
||||
|
||||
const result = {
|
||||
set: (name: string, value: string, options: Partial<ResponseCookie>) => options as ResponseCookie,
|
||||
} as unknown as ReadonlyRequestCookies;
|
||||
|
||||
vi.spyOn(result, "set");
|
||||
|
||||
const cookies = () => Promise.resolve(result);
|
||||
|
||||
return { ...mod, cookies } satisfies HeadersExport;
|
||||
});
|
||||
|
||||
describe("createSignInEventHandler should create signInEventHandler", () => {
|
||||
describe("signInEventHandler should add users to everyone group", () => {
|
||||
test("should add user to everyone group if he isn't already", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
await createUserAsync(db);
|
||||
await createGroupAsync(db, everyoneGroup);
|
||||
const eventHandler = createSignInEventHandler(db);
|
||||
|
||||
// Act
|
||||
await eventHandler?.({
|
||||
user: { id: "1", name: "test" },
|
||||
profile: undefined,
|
||||
account: null,
|
||||
});
|
||||
|
||||
// Assert
|
||||
const dbGroupMembers = await db.query.groupMembers.findFirst({
|
||||
where: eq(groupMembers.userId, "1"),
|
||||
});
|
||||
expect(dbGroupMembers?.groupId).toBe("1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("signInEventHandler should synchronize ldap groups", () => {
|
||||
test("should add missing group membership", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
await createUserAsync(db);
|
||||
await createGroupAsync(db);
|
||||
const eventHandler = createSignInEventHandler(db);
|
||||
|
||||
// Act
|
||||
await eventHandler?.({
|
||||
user: { id: "1", name: "test", groups: ["test"] } as never,
|
||||
profile: undefined,
|
||||
account: null,
|
||||
});
|
||||
|
||||
// Assert
|
||||
const dbGroupMembers = await db.query.groupMembers.findFirst({
|
||||
where: eq(groupMembers.userId, "1"),
|
||||
});
|
||||
expect(dbGroupMembers?.groupId).toBe("1");
|
||||
});
|
||||
test("should remove group membership", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
await createUserAsync(db);
|
||||
await createGroupAsync(db);
|
||||
await db.insert(groupMembers).values({
|
||||
userId: "1",
|
||||
groupId: "1",
|
||||
});
|
||||
const eventHandler = createSignInEventHandler(db);
|
||||
|
||||
// Act
|
||||
await eventHandler?.({
|
||||
user: { id: "1", name: "test", groups: [] } as never,
|
||||
profile: undefined,
|
||||
account: null,
|
||||
});
|
||||
|
||||
// Assert
|
||||
const dbGroupMembers = await db.query.groupMembers.findFirst({
|
||||
where: eq(groupMembers.userId, "1"),
|
||||
});
|
||||
expect(dbGroupMembers).toBeUndefined();
|
||||
});
|
||||
test("should not remove group membership for everyone group", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
await createUserAsync(db);
|
||||
await createGroupAsync(db, everyoneGroup);
|
||||
await db.insert(groupMembers).values({
|
||||
userId: "1",
|
||||
groupId: "1",
|
||||
});
|
||||
const eventHandler = createSignInEventHandler(db);
|
||||
|
||||
// Act
|
||||
await eventHandler?.({
|
||||
user: { id: "1", name: "test", groups: [] } as never,
|
||||
profile: undefined,
|
||||
account: null,
|
||||
});
|
||||
|
||||
// Assert
|
||||
const dbGroupMembers = await db.query.groupMembers.findFirst({
|
||||
where: eq(groupMembers.userId, "1"),
|
||||
});
|
||||
expect(dbGroupMembers?.groupId).toBe("1");
|
||||
});
|
||||
});
|
||||
describe("signInEventHandler should synchronize oidc groups", () => {
|
||||
test("should add missing group membership", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
await createUserAsync(db);
|
||||
await createGroupAsync(db);
|
||||
const eventHandler = createSignInEventHandler(db);
|
||||
|
||||
// Act
|
||||
await eventHandler?.({
|
||||
user: { id: "1", name: "test" },
|
||||
profile: { preferred_username: "test", someRandomGroupsKey: ["test"] },
|
||||
account: null,
|
||||
});
|
||||
|
||||
// Assert
|
||||
const dbGroupMembers = await db.query.groupMembers.findFirst({
|
||||
where: eq(groupMembers.userId, "1"),
|
||||
});
|
||||
expect(dbGroupMembers?.groupId).toBe("1");
|
||||
});
|
||||
test("should remove group membership", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
await createUserAsync(db);
|
||||
await createGroupAsync(db);
|
||||
await db.insert(groupMembers).values({
|
||||
userId: "1",
|
||||
groupId: "1",
|
||||
});
|
||||
const eventHandler = createSignInEventHandler(db);
|
||||
|
||||
// Act
|
||||
await eventHandler?.({
|
||||
user: { id: "1", name: "test" },
|
||||
profile: { preferred_username: "test", someRandomGroupsKey: [] },
|
||||
account: null,
|
||||
});
|
||||
|
||||
// Assert
|
||||
const dbGroupMembers = await db.query.groupMembers.findFirst({
|
||||
where: eq(groupMembers.userId, "1"),
|
||||
});
|
||||
expect(dbGroupMembers).toBeUndefined();
|
||||
});
|
||||
test("should not remove group membership for everyone group", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
await createUserAsync(db);
|
||||
await createGroupAsync(db, everyoneGroup);
|
||||
await db.insert(groupMembers).values({
|
||||
userId: "1",
|
||||
groupId: "1",
|
||||
});
|
||||
const eventHandler = createSignInEventHandler(db);
|
||||
|
||||
// Act
|
||||
await eventHandler?.({
|
||||
user: { id: "1", name: "test" },
|
||||
profile: { preferred_username: "test", someRandomGroupsKey: [] },
|
||||
account: null,
|
||||
});
|
||||
|
||||
// Assert
|
||||
const dbGroupMembers = await db.query.groupMembers.findFirst({
|
||||
where: eq(groupMembers.userId, "1"),
|
||||
});
|
||||
expect(dbGroupMembers?.groupId).toBe("1");
|
||||
});
|
||||
});
|
||||
test.each([
|
||||
["ldap" as const, { name: "test-new" }, undefined],
|
||||
["oidc" as const, { name: "test" }, { preferred_username: "test-new" }],
|
||||
["oidc" as const, { name: "test" }, { preferred_username: "test@example.com", name: "test-new" }],
|
||||
])("signInEventHandler should update username for %s provider", async (_provider, user, profile) => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
await createUserAsync(db);
|
||||
const eventHandler = createSignInEventHandler(db);
|
||||
|
||||
// Act
|
||||
await eventHandler?.({
|
||||
user: { id: "1", ...user },
|
||||
profile,
|
||||
account: null,
|
||||
});
|
||||
|
||||
// Assert
|
||||
const dbUser = await db.query.users.findFirst({
|
||||
where: eq(users.id, "1"),
|
||||
columns: {
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
expect(dbUser?.name).toBe("test-new");
|
||||
});
|
||||
test("signInEventHandler should set color-scheme cookie", async () => {
|
||||
// Arrange
|
||||
const db = createDb();
|
||||
await createUserAsync(db);
|
||||
const eventHandler = createSignInEventHandler(db);
|
||||
|
||||
// Act
|
||||
await eventHandler?.({
|
||||
user: { id: "1", name: "test" },
|
||||
profile: undefined,
|
||||
account: null,
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect((await cookies()).set).toHaveBeenCalledWith(
|
||||
colorSchemeCookieKey,
|
||||
"dark",
|
||||
expect.objectContaining({
|
||||
path: "/",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const createUserAsync = async (db: Database) =>
|
||||
await db.insert(users).values({
|
||||
id: "1",
|
||||
name: "test",
|
||||
colorScheme: "dark",
|
||||
});
|
||||
|
||||
const createGroupAsync = async (db: Database, name = "test") =>
|
||||
await db.insert(groups).values({
|
||||
id: "1",
|
||||
name,
|
||||
position: 1,
|
||||
});
|
||||
59
packages/auth/test/redirect.spec.ts
Normal file
59
packages/auth/test/redirect.spec.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { createRedirectUri } from "../redirect";
|
||||
|
||||
describe("redirect", () => {
|
||||
test("Callback should return http url when not defining protocol", () => {
|
||||
// Arrange
|
||||
const headers = new Map<string, string>([["x-forwarded-host", "localhost:3000"]]) as unknown as ReadonlyHeaders;
|
||||
|
||||
// Act
|
||||
const result = createRedirectUri(headers, "/api/auth/callback/oidc");
|
||||
|
||||
// Assert
|
||||
expect(result).toBe("http://localhost:3000/api/auth/callback/oidc");
|
||||
});
|
||||
|
||||
test("Callback should return https url when defining protocol", () => {
|
||||
// Arrange
|
||||
const headers = new Map<string, string>([
|
||||
["x-forwarded-proto", "https"],
|
||||
["x-forwarded-host", "localhost:3000"],
|
||||
]) as unknown as ReadonlyHeaders;
|
||||
|
||||
// Act
|
||||
const result = createRedirectUri(headers, "/api/auth/callback/oidc");
|
||||
|
||||
// Assert
|
||||
expect(result).toBe("https://localhost:3000/api/auth/callback/oidc");
|
||||
});
|
||||
|
||||
test("Callback should return https url when defining protocol and host", () => {
|
||||
// Arrange
|
||||
const headers = new Map<string, string>([
|
||||
["x-forwarded-proto", "https"],
|
||||
["host", "something.else"],
|
||||
]) as unknown as ReadonlyHeaders;
|
||||
|
||||
// Act
|
||||
const result = createRedirectUri(headers, "/api/auth/callback/oidc");
|
||||
|
||||
// Assert
|
||||
expect(result).toBe("https://something.else/api/auth/callback/oidc");
|
||||
});
|
||||
|
||||
test("Callback should return https url when defining protocol as http,https and host", () => {
|
||||
// Arrange
|
||||
const headers = new Map<string, string>([
|
||||
["x-forwarded-proto", "http,https"],
|
||||
["x-forwarded-host", "hello.world"],
|
||||
]) as unknown as ReadonlyHeaders;
|
||||
|
||||
// Act
|
||||
const result = createRedirectUri(headers, "/api/auth/callback/oidc");
|
||||
|
||||
// Assert
|
||||
expect(result).toBe("https://hello.world/api/auth/callback/oidc");
|
||||
});
|
||||
});
|
||||
47
packages/auth/test/security.spec.ts
Normal file
47
packages/auth/test/security.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { createSaltAsync, hashPasswordAsync } from "../security";
|
||||
|
||||
describe("createSalt should return a salt", () => {
|
||||
it("should return a salt", async () => {
|
||||
const result = await createSaltAsync();
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBeGreaterThan(25);
|
||||
});
|
||||
it("should return a different salt each time", async () => {
|
||||
const result1 = await createSaltAsync();
|
||||
const result2 = await createSaltAsync();
|
||||
expect(result1).not.toEqual(result2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hashPassword should return a hash", () => {
|
||||
it("should return a hash", async () => {
|
||||
const password = "password";
|
||||
const salt = await createSaltAsync();
|
||||
const result = await hashPasswordAsync(password, salt);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBeGreaterThan(55);
|
||||
expect(result).not.toEqual(password);
|
||||
});
|
||||
it("should return a different hash each time", async () => {
|
||||
const password = "password";
|
||||
const password2 = "another password";
|
||||
const salt = await createSaltAsync();
|
||||
|
||||
const result1 = await hashPasswordAsync(password, salt);
|
||||
const result2 = await hashPasswordAsync(password2, salt);
|
||||
|
||||
expect(result1).not.toEqual(result2);
|
||||
});
|
||||
it("should return a different hash for the same password with different salts", async () => {
|
||||
const password = "password";
|
||||
const salt1 = await createSaltAsync();
|
||||
const salt2 = await createSaltAsync();
|
||||
|
||||
const result1 = await hashPasswordAsync(password, salt1);
|
||||
const result2 = await hashPasswordAsync(password, salt2);
|
||||
|
||||
expect(result1).not.toEqual(result2);
|
||||
});
|
||||
});
|
||||
44
packages/auth/test/session.spec.ts
Normal file
44
packages/auth/test/session.spec.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { expireDateAfter, generateSessionToken } from "../session";
|
||||
|
||||
describe("expireDateAfter should calculate date after specified seconds", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["2023-07-01T00:00:00Z", 60, "2023-07-01T00:01:00Z"], // 1 minute
|
||||
["2023-07-01T00:00:00Z", 60 * 60, "2023-07-01T01:00:00Z"], // 1 hour
|
||||
["2023-07-01T00:00:00Z", 60 * 60 * 24, "2023-07-02T00:00:00Z"], // 1 day
|
||||
["2023-07-01T00:00:00Z", 60 * 60 * 24 * 30, "2023-07-31T00:00:00Z"], // 30 days
|
||||
["2023-07-01T00:00:00Z", 60 * 60 * 24 * 365, "2024-06-30T00:00:00Z"], // 1 year
|
||||
["2023-07-01T00:00:00Z", 60 * 60 * 24 * 365 * 10, "2033-06-28T00:00:00Z"], // 10 years
|
||||
])("should calculate date %s and after %i seconds to equal %s", (initialDate, seconds, expectedDate) => {
|
||||
vi.setSystemTime(new Date(initialDate));
|
||||
const result = expireDateAfter(seconds);
|
||||
expect(result).toEqual(new Date(expectedDate));
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateSessionToken should return a random UUID", () => {
|
||||
it("should return a random UUID", () => {
|
||||
const result = generateSessionToken();
|
||||
expect(
|
||||
z
|
||||
.string()
|
||||
.regex(/^[a-f0-9]+$/)
|
||||
.safeParse(result).success,
|
||||
).toBe(true);
|
||||
});
|
||||
it("should return a different token each time", () => {
|
||||
const result1 = generateSessionToken();
|
||||
const result2 = generateSessionToken();
|
||||
expect(result1).not.toEqual(result2);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user