- Created loginUser server action with credential validation - Implemented password verification using bcrypt (via NextAuth authorize) - Session creation on successful login handled by NextAuth - Invalid credentials handled with appropriate error messages - Added comprehensive unit tests for login functionality - All tests passing (11/11) Requirements: 2.1, 2.2
223 lines
6.2 KiB
TypeScript
223 lines
6.2 KiB
TypeScript
/**
|
|
* Tests for user registration functionality
|
|
* This file tests the core registration logic without session creation
|
|
*/
|
|
|
|
import { describe, it, expect, afterEach, vi } from 'vitest'
|
|
import { prisma } from '../db'
|
|
import bcrypt from 'bcryptjs'
|
|
import { z } from 'zod'
|
|
|
|
// Mock the auth module to avoid Next.js dependencies in tests
|
|
vi.mock('@/auth', () => ({
|
|
signIn: vi.fn(),
|
|
}))
|
|
|
|
// Import after mocking
|
|
import { registerUser, loginUser } from '../auth-actions'
|
|
import { signIn } from '@/auth'
|
|
|
|
describe('registerUser', () => {
|
|
// Clean up test users after each test
|
|
afterEach(async () => {
|
|
await prisma.user.deleteMany({
|
|
where: {
|
|
email: {
|
|
contains: 'test-',
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
it('should create a new user with valid credentials', async () => {
|
|
const email = `test-${Date.now()}@example.com`
|
|
const password = 'password123'
|
|
const name = 'Test User'
|
|
|
|
const result = await registerUser(email, password, name)
|
|
|
|
expect(result.success).toBe(true)
|
|
if (result.success) {
|
|
expect(result.userId).toBeDefined()
|
|
expect(typeof result.userId).toBe('string')
|
|
|
|
// Verify user was created in database
|
|
const user = await prisma.user.findUnique({
|
|
where: { email },
|
|
})
|
|
|
|
expect(user).toBeDefined()
|
|
expect(user?.email).toBe(email)
|
|
expect(user?.name).toBe(name)
|
|
expect(user?.passwordHash).toBeDefined()
|
|
expect(user?.passwordHash).not.toBe(password) // Password should be hashed
|
|
}
|
|
})
|
|
|
|
it('should reject registration with duplicate email', async () => {
|
|
const email = `test-${Date.now()}@example.com`
|
|
const password = 'password123'
|
|
|
|
// Create first user
|
|
const result1 = await registerUser(email, password)
|
|
expect(result1.success).toBe(true)
|
|
|
|
// Try to create second user with same email
|
|
const result2 = await registerUser(email, password)
|
|
expect(result2.success).toBe(false)
|
|
if (!result2.success) {
|
|
expect(result2.error).toContain('already registered')
|
|
}
|
|
})
|
|
|
|
it('should hash the password before storing', async () => {
|
|
const email = `test-${Date.now()}@example.com`
|
|
const password = 'password123'
|
|
|
|
const result = await registerUser(email, password)
|
|
expect(result.success).toBe(true)
|
|
|
|
const user = await prisma.user.findUnique({
|
|
where: { email },
|
|
})
|
|
|
|
expect(user?.passwordHash).toBeDefined()
|
|
expect(user?.passwordHash).not.toBe(password)
|
|
|
|
// Verify the hash is valid
|
|
const isValid = await bcrypt.compare(password, user!.passwordHash)
|
|
expect(isValid).toBe(true)
|
|
})
|
|
|
|
it('should reject password shorter than 8 characters', async () => {
|
|
const email = `test-${Date.now()}@example.com`
|
|
const password = 'short'
|
|
|
|
const result = await registerUser(email, password)
|
|
expect(result.success).toBe(false)
|
|
if (!result.success) {
|
|
expect(result.error).toContain('at least 8 characters')
|
|
}
|
|
})
|
|
|
|
it('should reject invalid email format', async () => {
|
|
const email = 'invalid-email'
|
|
const password = 'password123'
|
|
|
|
const result = await registerUser(email, password)
|
|
expect(result.success).toBe(false)
|
|
if (!result.success) {
|
|
expect(result.error).toContain('Invalid email')
|
|
}
|
|
})
|
|
|
|
it('should create user without name (optional field)', async () => {
|
|
const email = `test-${Date.now()}@example.com`
|
|
const password = 'password123'
|
|
|
|
const result = await registerUser(email, password)
|
|
expect(result.success).toBe(true)
|
|
|
|
const user = await prisma.user.findUnique({
|
|
where: { email },
|
|
})
|
|
|
|
expect(user).toBeDefined()
|
|
expect(user?.name).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('loginUser', () => {
|
|
// Clean up test users after each test
|
|
afterEach(async () => {
|
|
await prisma.user.deleteMany({
|
|
where: {
|
|
email: {
|
|
contains: 'test-login-',
|
|
},
|
|
},
|
|
})
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('should successfully login with valid credentials', async () => {
|
|
const email = `test-login-${Date.now()}@example.com`
|
|
const password = 'password123'
|
|
|
|
// Create a test user first
|
|
await registerUser(email, password, 'Test User')
|
|
|
|
// Mock signIn to succeed
|
|
vi.mocked(signIn).mockResolvedValueOnce(undefined as any)
|
|
|
|
// Attempt login
|
|
const result = await loginUser(email, password)
|
|
|
|
expect(result.success).toBe(true)
|
|
expect(signIn).toHaveBeenCalledWith('credentials', {
|
|
email,
|
|
password,
|
|
redirect: false,
|
|
})
|
|
})
|
|
|
|
it('should reject login with invalid credentials', async () => {
|
|
const email = `test-login-${Date.now()}@example.com`
|
|
const password = 'password123'
|
|
|
|
// Create a test user first
|
|
await registerUser(email, password, 'Test User')
|
|
|
|
// Mock signIn to throw error (invalid credentials)
|
|
vi.mocked(signIn).mockRejectedValueOnce(new Error('Invalid credentials'))
|
|
|
|
// Attempt login with wrong password
|
|
const result = await loginUser(email, 'wrongpassword')
|
|
|
|
expect(result.success).toBe(false)
|
|
if (!result.success) {
|
|
expect(result.error).toContain('Invalid email or password')
|
|
}
|
|
})
|
|
|
|
it('should reject login with non-existent email', async () => {
|
|
const email = `test-login-nonexistent-${Date.now()}@example.com`
|
|
const password = 'password123'
|
|
|
|
// Mock signIn to throw error (user not found)
|
|
vi.mocked(signIn).mockRejectedValueOnce(new Error('User not found'))
|
|
|
|
// Attempt login with non-existent email
|
|
const result = await loginUser(email, password)
|
|
|
|
expect(result.success).toBe(false)
|
|
if (!result.success) {
|
|
expect(result.error).toContain('Invalid email or password')
|
|
}
|
|
})
|
|
|
|
it('should reject login with invalid email format', async () => {
|
|
const email = 'invalid-email'
|
|
const password = 'password123'
|
|
|
|
const result = await loginUser(email, password)
|
|
|
|
expect(result.success).toBe(false)
|
|
if (!result.success) {
|
|
expect(result.error).toContain('Invalid email')
|
|
}
|
|
})
|
|
|
|
it('should reject login with empty password', async () => {
|
|
const email = `test-login-${Date.now()}@example.com`
|
|
const password = ''
|
|
|
|
const result = await loginUser(email, password)
|
|
|
|
expect(result.success).toBe(false)
|
|
if (!result.success) {
|
|
expect(result.error).toContain('Password is required')
|
|
}
|
|
})
|
|
})
|