Files
prompts.chat/src/__tests__/api/register.test.ts
T

416 lines
12 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { Prisma } from "@prisma/client";
import { POST } from "@/app/api/auth/register/route";
import { db } from "@/lib/db";
import { getConfig } from "@/lib/config";
// Mock dependencies
vi.mock("@/lib/db", () => ({
db: {
user: {
findUnique: vi.fn(),
findFirst: vi.fn(),
create: vi.fn(),
},
},
}));
vi.mock("@/lib/config", () => ({
getConfig: vi.fn(),
}));
vi.mock("bcryptjs", () => ({
default: {
hash: vi.fn().mockResolvedValue("hashed_password"),
},
}));
function createRequest(body: object): Request {
return new Request("http://localhost:3000/api/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
}
describe("POST /api/auth/register", () => {
beforeEach(() => {
vi.resetAllMocks();
// Default: registration is enabled
vi.mocked(getConfig).mockResolvedValue({
auth: { allowRegistration: true, providers: [] },
features: {},
});
// Default: no existing users
vi.mocked(db.user.findUnique).mockResolvedValue(null);
vi.mocked(db.user.findFirst).mockResolvedValue(null);
});
describe("validation", () => {
it("should return 400 for missing name", async () => {
const request = createRequest({
username: "testuser",
email: "test@example.com",
password: "password123",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.error).toBe("validation_error");
});
it("should return 400 for name too short", async () => {
const request = createRequest({
name: "A",
username: "testuser",
email: "test@example.com",
password: "password123",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.error).toBe("validation_error");
});
it("should return 400 for missing username", async () => {
const request = createRequest({
name: "Test User",
email: "test@example.com",
password: "password123",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.error).toBe("validation_error");
});
it("should return 400 for invalid username format", async () => {
const request = createRequest({
name: "Test User",
username: "invalid-username!",
email: "test@example.com",
password: "password123",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.error).toBe("validation_error");
});
it("should return 400 for invalid email", async () => {
const request = createRequest({
name: "Test User",
username: "testuser",
email: "invalid-email",
password: "password123",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.error).toBe("validation_error");
});
it("should return 400 for password too short", async () => {
const request = createRequest({
name: "Test User",
username: "testuser",
email: "test@example.com",
password: "12345",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.error).toBe("validation_error");
});
});
describe("registration disabled", () => {
it("should return 403 when registration is disabled", async () => {
vi.mocked(getConfig).mockResolvedValue({
auth: { allowRegistration: false, providers: [] },
features: {},
});
const request = createRequest({
name: "Test User",
username: "testuser",
email: "test@example.com",
password: "password123",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(403);
expect(data.error).toBe("registration_disabled");
});
});
describe("duplicate checks", () => {
it("should return 409 when email already exists", async () => {
// Mock: create throws P2002 unique violation on email
vi.mocked(db.user.create).mockRejectedValue(
new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
code: "P2002",
meta: { target: ["email"] },
clientVersion: "5.0.0",
})
);
const request = createRequest({
name: "Test User",
username: "testuser",
email: "test@example.com",
password: "password123",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(409);
expect(data.error).toBe("email_taken");
});
it("should return 409 for case-insensitive email collision", async () => {
// Mock: create throws P2002 unique violation on CI email index
vi.mocked(db.user.create).mockRejectedValue(
new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
code: "P2002",
meta: { target: ["users_email_ci_unique"] },
clientVersion: "5.0.0",
})
);
const request = createRequest({
name: "Test User",
username: "testuser",
email: "Test@Example.COM",
password: "password123",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(409);
expect(data.error).toBe("email_taken");
});
it("should return 409 when username already exists", async () => {
// Mock: create throws P2002 unique violation on username
vi.mocked(db.user.create).mockRejectedValue(
new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
code: "P2002",
meta: { target: ["username"] },
clientVersion: "5.0.0",
})
);
const request = createRequest({
name: "Test User",
username: "testuser",
email: "test@example.com",
password: "password123",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(409);
expect(data.error).toBe("username_taken");
});
it("should return 400 for uppercase username", async () => {
const request = createRequest({
name: "Test User",
username: "TestUser",
email: "test@example.com",
password: "password123",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.error).toBe("validation_error");
});
it("should return 409 for case-insensitive username collision", async () => {
// Mock: create throws P2002 unique violation on CI username index
vi.mocked(db.user.create).mockRejectedValue(
new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
code: "P2002",
meta: { target: ["users_username_ci_unique"] },
clientVersion: "5.0.0",
})
);
const request = createRequest({
name: "Test User",
username: "testuser",
email: "test@example.com",
password: "password123",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(409);
expect(data.error).toBe("username_taken");
});
});
describe("successful registration", () => {
it("should create user and return user data", async () => {
vi.mocked(db.user.create).mockResolvedValue({
id: "user-123",
name: "Test User",
username: "testuser",
email: "test@example.com",
password: "hashed_password",
emailVerified: null,
image: null,
role: "USER",
bio: null,
credits: 0,
createdAt: new Date(),
updatedAt: new Date(),
});
const request = createRequest({
name: "Test User",
username: "testuser",
email: "test@example.com",
password: "password123",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.id).toBe("user-123");
expect(data.name).toBe("Test User");
expect(data.username).toBe("testuser");
expect(data.email).toBe("test@example.com");
expect(data.password).toBeUndefined(); // Password should not be returned
});
it("should lowercase and trim email and username before saving", async () => {
vi.mocked(db.user.create).mockResolvedValue({
id: "user-123",
name: "Test User",
username: "testuser",
email: "test@example.com",
password: "hashed_password",
emailVerified: null,
image: null,
role: "USER",
bio: null,
credits: 0,
createdAt: new Date(),
updatedAt: new Date(),
});
const request = createRequest({
name: " Test User ",
username: " testuser ",
email: " Test@Example.COM ",
password: "password123",
});
const response = await POST(request);
expect(response.status).toBe(200);
expect(db.user.create).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
name: "Test User",
username: "testuser",
email: "test@example.com",
}),
})
);
});
it("should accept valid username with underscores", async () => {
vi.mocked(db.user.create).mockResolvedValue({
id: "user-123",
name: "Test User",
username: "test_user_123",
email: "test@example.com",
password: "hashed_password",
emailVerified: null,
image: null,
role: "USER",
bio: null,
credits: 0,
createdAt: new Date(),
updatedAt: new Date(),
});
const request = createRequest({
name: "Test User",
username: "test_user_123",
email: "test@example.com",
password: "password123",
});
const response = await POST(request);
expect(response.status).toBe(200);
// Verify that db.user.create was called with the correct username
expect(db.user.create).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
username: "test_user_123",
}),
})
);
});
});
describe("error handling", () => {
it("should return 500 on database error", async () => {
vi.mocked(db.user.findUnique).mockRejectedValue(new Error("DB Error"));
const request = createRequest({
name: "Test User",
username: "testuser",
email: "test@example.com",
password: "password123",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(500);
expect(data.error).toBe("server_error");
});
it("should return 500 on invalid JSON body", async () => {
const request = new Request("http://localhost:3000/api/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: "invalid json",
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(500);
expect(data.error).toBe("server_error");
});
});
});