Files
ubi-dan-itam/tests/auth.test.ts
2026-07-31 12:01:00 +07:00

119 lines
3.8 KiB
TypeScript

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: "[email protected]", name: "IT Staff", password });
const row = await prisma.user.findUnique({ where: { email: "[email protected]" } });
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: "[email protected]",
name: "No Hash Leak",
password: "correct-horse-battery-staple",
});
expect(created).not.toHaveProperty("passwordHash");
expect(created).not.toHaveProperty("password_hash");
expect(created).toMatchObject({ email: "[email protected]", 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: "[email protected]",
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: "[email protected]",
name: "Valid Name",
password: "a".repeat(MIN_PASSWORD_LENGTH),
});
expect(result.ok).toBe(true);
});
it("rejects an empty name", () => {
const result = validateCreateUserInput({
email: "[email protected]",
name: " ",
password: "a".repeat(MIN_PASSWORD_LENGTH),
});
expect(result.ok).toBe(false);
});
});
describe("verifyUserCredentials", () => {
const email = "[email protected]";
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("[email protected]", 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();
});
});