From b6e3a4e386d6e895c8c8fbcd0b0eacf81f81b6ca Mon Sep 17 00:00:00 2001 From: Toan Hoang Minh Date: Fri, 31 Jul 2026 12:01:00 +0700 Subject: [PATCH] test: add auth tests --- tests/auth.test.ts | 118 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 tests/auth.test.ts diff --git a/tests/auth.test.ts b/tests/auth.test.ts new file mode 100644 index 0000000..177bbd2 --- /dev/null +++ b/tests/auth.test.ts @@ -0,0 +1,118 @@ +import bcrypt from "bcryptjs"; +import { beforeEach, describe, expect, it } from "vitest"; +import { createUser, validateCreateUserInput, verifyUserCredentials } from "@/lib/users"; +import { prisma } from "@/lib/prisma"; + +// MIN_PASSWORD_LENGTH chosen in src/lib/users.ts is 8 — mirrored here as a +// local constant so the "too short" test stays intentional/self-documenting +// instead of a magic number. +const MIN_PASSWORD_LENGTH = 8; + +beforeEach(async () => { + await prisma.user.deleteMany(); +}); + +describe("createUser", () => { + it("hashes the password instead of storing it in plaintext", async () => { + const password = "correct-horse-battery-staple"; + await createUser({ email: "it-staff@example.com", name: "IT Staff", password }); + + const row = await prisma.user.findUnique({ where: { email: "it-staff@example.com" } }); + expect(row).not.toBeNull(); + expect(row?.passwordHash).not.toBe(password); + expect(row?.passwordHash.length).toBeGreaterThan(0); + await expect(bcrypt.compare(password, row?.passwordHash ?? "")).resolves.toBe(true); + }); + + it("does not expose the password hash on the returned record", async () => { + const created = await createUser({ + email: "no-hash-leak@example.com", + name: "No Hash Leak", + password: "correct-horse-battery-staple", + }); + + expect(created).not.toHaveProperty("passwordHash"); + expect(created).not.toHaveProperty("password_hash"); + expect(created).toMatchObject({ email: "no-hash-leak@example.com", name: "No Hash Leak" }); + }); +}); + +describe("validateCreateUserInput", () => { + it("rejects an invalid email format", () => { + const result = validateCreateUserInput({ + email: "not-an-email", + name: "Someone", + password: "a-long-enough-password", + }); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toMatch(/email/i); + } + }); + + it(`rejects a password shorter than ${MIN_PASSWORD_LENGTH} characters`, () => { + const result = validateCreateUserInput({ + email: "short-pw@example.com", + name: "Someone", + password: "a".repeat(MIN_PASSWORD_LENGTH - 1), + }); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toMatch(/password/i); + } + }); + + it("accepts a valid email, non-empty name, and long-enough password", () => { + const result = validateCreateUserInput({ + email: "valid@example.com", + name: "Valid Name", + password: "a".repeat(MIN_PASSWORD_LENGTH), + }); + + expect(result.ok).toBe(true); + }); + + it("rejects an empty name", () => { + const result = validateCreateUserInput({ + email: "valid@example.com", + name: " ", + password: "a".repeat(MIN_PASSWORD_LENGTH), + }); + + expect(result.ok).toBe(false); + }); +}); + +describe("verifyUserCredentials", () => { + const email = "login-test@example.com"; + const password = "correct-horse-battery-staple"; + + beforeEach(async () => { + await createUser({ email, name: "Login Test", password }); + }); + + it("succeeds with the correct email and password", async () => { + const user = await verifyUserCredentials(email, password); + + expect(user).not.toBeNull(); + expect(user).toMatchObject({ email, name: "Login Test" }); + }); + + it("fails with the wrong password", async () => { + const user = await verifyUserCredentials(email, "wrong-password"); + expect(user).toBeNull(); + }); + + it("fails with a nonexistent email", async () => { + const user = await verifyUserCredentials("nobody@example.com", password); + expect(user).toBeNull(); + }); + + it("fails with non-string email/password input (e.g. from a malformed request)", async () => { + expect(await verifyUserCredentials(undefined, password)).toBeNull(); + expect(await verifyUserCredentials(email, undefined)).toBeNull(); + expect(await verifyUserCredentials(123, password)).toBeNull(); + }); +});