test: add auth tests
This commit is contained in:
@@ -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: "[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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user