@profullstack/auth-system
Version:
Flexible authentication system with user registration, login/logout, password reset, and session management
273 lines (227 loc) • 11.4 kB
JavaScript
/**
* Tests for validation utilities
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { createValidationUtils } from '../../src/utils/validation.js';
describe('Validation Utils', () => {
let validationUtils;
beforeEach(() => {
// Create validation utils
validationUtils = createValidationUtils();
});
describe('isValidEmail', () => {
it('should reject empty emails', () => {
expect(validationUtils.isValidEmail('')).toBe(false);
expect(validationUtils.isValidEmail(null)).toBe(false);
expect(validationUtils.isValidEmail(undefined)).toBe(false);
});
it('should reject invalid email formats', () => {
expect(validationUtils.isValidEmail('not-an-email')).toBe(false);
expect(validationUtils.isValidEmail('missing@tld')).toBe(false);
expect(validationUtils.isValidEmail('@missing-local.com')).toBe(false);
expect(validationUtils.isValidEmail('spaces in@email.com')).toBe(false);
expect(validationUtils.isValidEmail('missing.domain@')).toBe(false);
});
it('should accept valid email formats', () => {
expect(validationUtils.isValidEmail('user@example.com')).toBe(true);
expect(validationUtils.isValidEmail('user.name@example.com')).toBe(true);
expect(validationUtils.isValidEmail('user+tag@example.com')).toBe(true);
expect(validationUtils.isValidEmail('user@subdomain.example.com')).toBe(true);
expect(validationUtils.isValidEmail('user@example.co.uk')).toBe(true);
expect(validationUtils.isValidEmail('123@example.com')).toBe(true);
});
});
describe('validateUsername', () => {
it('should reject empty usernames', () => {
const result = validationUtils.validateUsername('');
expect(result.valid).toBe(false);
expect(result.message).toContain('required');
});
it('should reject usernames that are too short', () => {
const result = validationUtils.validateUsername('ab');
expect(result.valid).toBe(false);
expect(result.message).toContain('at least 3 characters');
});
it('should reject usernames that are too long', () => {
const longUsername = 'a'.repeat(31);
const result = validationUtils.validateUsername(longUsername);
expect(result.valid).toBe(false);
expect(result.message).toContain('at most 30 characters');
});
it('should reject usernames with invalid characters', () => {
const result = validationUtils.validateUsername('user@name');
expect(result.valid).toBe(false);
expect(result.message).toContain('invalid characters');
});
it('should accept valid usernames', () => {
expect(validationUtils.validateUsername('username').valid).toBe(true);
expect(validationUtils.validateUsername('user123').valid).toBe(true);
expect(validationUtils.validateUsername('user_name').valid).toBe(true);
expect(validationUtils.validateUsername('user-name').valid).toBe(true);
expect(validationUtils.validateUsername('USERNAME').valid).toBe(true);
});
it('should respect custom validation options', () => {
// Custom options: min 5 chars, max 10 chars, only lowercase letters
const options = {
minLength: 5,
maxLength: 10,
allowedChars: /^[a-z]+$/
};
// Valid username with custom options
const result1 = validationUtils.validateUsername('abcdef', options);
expect(result1.valid).toBe(true);
// Too short with custom options
const result2 = validationUtils.validateUsername('abcd', options);
expect(result2.valid).toBe(false);
expect(result2.message).toContain('at least 5 characters');
// Too long with custom options
const result3 = validationUtils.validateUsername('abcdefghijk', options);
expect(result3.valid).toBe(false);
expect(result3.message).toContain('at most 10 characters');
// Invalid characters with custom options
const result4 = validationUtils.validateUsername('abcDEF', options);
expect(result4.valid).toBe(false);
expect(result4.message).toContain('invalid characters');
});
});
describe('validateName', () => {
it('should reject empty names', () => {
const result = validationUtils.validateName('');
expect(result.valid).toBe(false);
expect(result.message).toContain('required');
});
it('should reject names that are too long', () => {
const longName = 'a'.repeat(51);
const result = validationUtils.validateName(longName);
expect(result.valid).toBe(false);
expect(result.message).toContain('at most 50 characters');
});
it('should accept valid names', () => {
expect(validationUtils.validateName('John').valid).toBe(true);
expect(validationUtils.validateName('John Doe').valid).toBe(true);
expect(validationUtils.validateName('Jean-Claude').valid).toBe(true);
expect(validationUtils.validateName('O\'Connor').valid).toBe(true);
});
it('should respect the allowSpaces option', () => {
const options = { allowSpaces: false };
// Valid name without spaces
const result1 = validationUtils.validateName('John', options);
expect(result1.valid).toBe(true);
// Invalid name with spaces when spaces are not allowed
const result2 = validationUtils.validateName('John Doe', options);
expect(result2.valid).toBe(false);
expect(result2.message).toContain('cannot contain spaces');
});
it('should respect custom length options', () => {
const options = { minLength: 2, maxLength: 20 };
// Valid name with custom options
const result1 = validationUtils.validateName('Jo', options);
expect(result1.valid).toBe(true);
// Too short with custom options
const result2 = validationUtils.validateName('J', options);
expect(result2.valid).toBe(false);
expect(result2.message).toContain('at least 2 characters');
// Too long with custom options
const longName = 'a'.repeat(21);
const result3 = validationUtils.validateName(longName, options);
expect(result3.valid).toBe(false);
expect(result3.message).toContain('at most 20 characters');
});
});
describe('validatePhone', () => {
it('should reject empty phone numbers', () => {
const result = validationUtils.validatePhone('');
expect(result.valid).toBe(false);
expect(result.message).toContain('required');
});
it('should reject phone numbers that are too short', () => {
const result = validationUtils.validatePhone('123456');
expect(result.valid).toBe(false);
expect(result.message).toContain('too short');
});
it('should accept valid phone numbers in various formats', () => {
expect(validationUtils.validatePhone('1234567890').valid).toBe(true);
expect(validationUtils.validatePhone('123-456-7890').valid).toBe(true);
expect(validationUtils.validatePhone('(123) 456-7890').valid).toBe(true);
expect(validationUtils.validatePhone('+1 123 456 7890').valid).toBe(true);
});
});
describe('validateUrl', () => {
it('should reject empty URLs', () => {
const result = validationUtils.validateUrl('');
expect(result.valid).toBe(false);
expect(result.message).toContain('required');
});
it('should reject invalid URLs', () => {
expect(validationUtils.validateUrl('not-a-url').valid).toBe(false);
// Note: In modern browsers, 'http:example.com' is actually considered valid by the URL constructor
// It's interpreted as a URL with the 'http:' protocol and 'example.com' as the path
expect(validationUtils.validateUrl('http:').valid).toBe(false);
expect(validationUtils.validateUrl('://example.com').valid).toBe(false);
});
it('should accept valid URLs', () => {
expect(validationUtils.validateUrl('http://example.com').valid).toBe(true);
expect(validationUtils.validateUrl('https://example.com').valid).toBe(true);
expect(validationUtils.validateUrl('https://www.example.com').valid).toBe(true);
expect(validationUtils.validateUrl('https://example.com/path').valid).toBe(true);
expect(validationUtils.validateUrl('https://example.com/path?query=value').valid).toBe(true);
expect(validationUtils.validateUrl('https://example.com:8080').valid).toBe(true);
});
});
describe('validateDate', () => {
it('should reject empty dates', () => {
const result = validationUtils.validateDate('');
expect(result.valid).toBe(false);
expect(result.message).toContain('required');
});
it('should reject invalid dates', () => {
expect(validationUtils.validateDate('not-a-date').valid).toBe(false);
expect(validationUtils.validateDate('2023-13-01').valid).toBe(false); // Invalid month
// Note: JavaScript's Date constructor is lenient with invalid dates
// '2023-02-30' gets converted to '2023-03-02' (rolls over to next month)
// Let's use a different example that will definitely be invalid
expect(validationUtils.validateDate('invalid-date-format').valid).toBe(false);
});
it('should accept valid dates in various formats', () => {
expect(validationUtils.validateDate('2023-01-15').valid).toBe(true);
expect(validationUtils.validateDate(new Date('2023-01-15')).valid).toBe(true);
expect(validationUtils.validateDate('2023/01/15').valid).toBe(true);
expect(validationUtils.validateDate('01/15/2023').valid).toBe(true);
});
it('should respect the minDate option', () => {
const options = { minDate: new Date('2023-01-01') };
// Valid date after minDate
const result1 = validationUtils.validateDate('2023-01-15', options);
expect(result1.valid).toBe(true);
// Invalid date before minDate
const result2 = validationUtils.validateDate('2022-12-31', options);
expect(result2.valid).toBe(false);
expect(result2.message).toContain('on or after');
});
it('should respect the maxDate option', () => {
const options = { maxDate: new Date('2023-12-31') };
// Valid date before maxDate
const result1 = validationUtils.validateDate('2023-12-15', options);
expect(result1.valid).toBe(true);
// Invalid date after maxDate
const result2 = validationUtils.validateDate('2024-01-01', options);
expect(result2.valid).toBe(false);
expect(result2.message).toContain('on or before');
});
it('should respect both minDate and maxDate options', () => {
const options = {
minDate: new Date('2023-01-01'),
maxDate: new Date('2023-12-31')
};
// Valid date within range
const result1 = validationUtils.validateDate('2023-06-15', options);
expect(result1.valid).toBe(true);
// Invalid date before minDate
const result2 = validationUtils.validateDate('2022-12-31', options);
expect(result2.valid).toBe(false);
// Invalid date after maxDate
const result3 = validationUtils.validateDate('2024-01-01', options);
expect(result3.valid).toBe(false);
});
});
});