test: add initial unit tests (#56)
* chore: add initial db migration * test: add unit tests for packages auth, common, widgets * fix: deep source issues * fix: format issues * wip: add unit tests for api routers * fix: deep source issues * test: add missing unit tests for integration router * wip: board tests * test: add unit tests for board router * fix: remove unnecessary null assertions * fix: deepsource issues * fix: formatting * fix: pnpm lock * fix: lint and typecheck issues * chore: address pull request feedback * fix: non-null assertions * fix: lockfile broken
This commit is contained in:
153
packages/auth/test/callbacks.spec.ts
Normal file
153
packages/auth/test/callbacks.spec.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
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 type { Adapter, AdapterUser } from "@auth/core/adapters";
|
||||
import type { Account, User } from "next-auth";
|
||||
import type { JWT } from "next-auth/jwt";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createSignInCallback, sessionCallback } from "../callbacks";
|
||||
|
||||
describe("session callback", () => {
|
||||
it("should add id and name to session user", async () => {
|
||||
const user: AdapterUser = {
|
||||
id: "id",
|
||||
name: "name",
|
||||
email: "email",
|
||||
emailVerified: new Date("2023-01-13"),
|
||||
};
|
||||
const token: JWT = {};
|
||||
const result = await sessionCallback({
|
||||
session: {
|
||||
user: {
|
||||
id: "no-id",
|
||||
email: "no-email",
|
||||
emailVerified: new Date("2023-01-13"),
|
||||
},
|
||||
expires: "2023-01-13" as Date & string,
|
||||
sessionToken: "token",
|
||||
userId: "no-id",
|
||||
},
|
||||
user,
|
||||
token,
|
||||
trigger: "update",
|
||||
newSession: {},
|
||||
});
|
||||
expect(result.user).toBeDefined();
|
||||
expect(result.user!.id).toEqual(user.id);
|
||||
expect(result.user!.name).toEqual(user.name);
|
||||
});
|
||||
});
|
||||
|
||||
type AdapterSessionInput = Parameters<
|
||||
Exclude<Adapter["createSession"], undefined>
|
||||
>[0];
|
||||
|
||||
const createAdapter = () => {
|
||||
const result = {
|
||||
createSession: (input: AdapterSessionInput) => input,
|
||||
};
|
||||
|
||||
vi.spyOn(result, "createSession");
|
||||
return result;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
type SessionExport = typeof import("../session");
|
||||
const mockSessionToken = "e9ef3010-6981-4a81-b9d6-8495d09cf3b5" as const;
|
||||
const mockSessionExpiry = new Date("2023-07-01");
|
||||
vi.mock("../session", async (importOriginal) => {
|
||||
const mod = await importOriginal<SessionExport>();
|
||||
|
||||
const generateSessionToken = () => mockSessionToken;
|
||||
const expireDateAfter = (_seconds: number) => mockSessionExpiry;
|
||||
|
||||
return {
|
||||
...mod,
|
||||
generateSessionToken,
|
||||
expireDateAfter,
|
||||
} satisfies SessionExport;
|
||||
});
|
||||
// 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 = () => result;
|
||||
|
||||
return { ...mod, cookies } satisfies HeadersExport;
|
||||
});
|
||||
|
||||
describe("createSignInCallback", () => {
|
||||
it("should return true if not credentials request", async () => {
|
||||
const isCredentialsRequest = false;
|
||||
const signInCallback = createSignInCallback(
|
||||
createAdapter(),
|
||||
isCredentialsRequest,
|
||||
);
|
||||
const result = await signInCallback({
|
||||
user: { id: "1", emailVerified: new Date("2023-01-13") },
|
||||
account: {} as Account,
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true if no user", async () => {
|
||||
const isCredentialsRequest = true;
|
||||
const signInCallback = createSignInCallback(
|
||||
createAdapter(),
|
||||
isCredentialsRequest,
|
||||
);
|
||||
const result = await signInCallback({
|
||||
user: undefined as unknown as User,
|
||||
account: {} as Account,
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false if no adapter.createSession", async () => {
|
||||
const isCredentialsRequest = true;
|
||||
const signInCallback = createSignInCallback(
|
||||
// https://github.com/nextauthjs/next-auth/issues/6106
|
||||
undefined as unknown as Adapter,
|
||||
isCredentialsRequest,
|
||||
);
|
||||
const result = await signInCallback({
|
||||
user: { id: "1", emailVerified: new Date("2023-01-13") },
|
||||
account: {} as Account,
|
||||
});
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should call adapter.createSession with correct input", async () => {
|
||||
const adapter = createAdapter();
|
||||
const isCredentialsRequest = true;
|
||||
const signInCallback = createSignInCallback(adapter, isCredentialsRequest);
|
||||
const user = { id: "1", emailVerified: new Date("2023-01-13") };
|
||||
const account = {} as Account;
|
||||
await signInCallback({ user, account });
|
||||
expect(adapter.createSession).toHaveBeenCalledWith({
|
||||
sessionToken: mockSessionToken,
|
||||
userId: user.id,
|
||||
expires: mockSessionExpiry,
|
||||
});
|
||||
expect(cookies().set).toHaveBeenCalledWith(
|
||||
"next-auth.session-token",
|
||||
mockSessionToken,
|
||||
{
|
||||
path: "/",
|
||||
expires: mockSessionExpiry,
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secure: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
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 { createSalt, hashPassword } from "../security";
|
||||
|
||||
describe("createSalt should return a salt", () => {
|
||||
it("should return a salt", async () => {
|
||||
const result = await createSalt();
|
||||
expect(result).toBeDefined();
|
||||
expect(result.length).toBeGreaterThan(25);
|
||||
});
|
||||
it("should return a different salt each time", async () => {
|
||||
const result1 = await createSalt();
|
||||
const result2 = await createSalt();
|
||||
expect(result1).not.toEqual(result2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hashPassword should return a hash", () => {
|
||||
it("should return a hash", async () => {
|
||||
const password = "password";
|
||||
const salt = await createSalt();
|
||||
const result = await hashPassword(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 createSalt();
|
||||
|
||||
const result1 = await hashPassword(password, salt);
|
||||
const result2 = await hashPassword(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 createSalt();
|
||||
const salt2 = await createSalt();
|
||||
|
||||
const result1 = await hashPassword(password, salt1);
|
||||
const result2 = await hashPassword(password, salt2);
|
||||
|
||||
expect(result1).not.toEqual(result2);
|
||||
});
|
||||
});
|
||||
43
packages/auth/test/session.spec.ts
Normal file
43
packages/auth/test/session.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { z } from "@homarr/validation";
|
||||
|
||||
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().uuid().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