/** * Comprehensive Unit Tests for Validation Schemas * * These tests ensure proper input validation and content filtering */ import { describe, it, expect } from 'vitest'; import { usernameSchema, displayNameSchema, passwordSchema, bioSchema, personalLocationSchema, preferredPronounsSchema, profileEditSchema, } from '@/lib/validation'; import { z } from 'zod'; describe('usernameSchema', () => { describe('Valid Usernames', () => { it('should accept valid username with letters and numbers', () => { const result = usernameSchema.safeParse('user123'); expect(result.success).toBe(true); if (result.success) { expect(result.data).toBe('user123'); } }); it('should accept username with hyphens', () => { const result = usernameSchema.safeParse('user-name'); expect(result.success).toBe(true); }); it('should accept username with underscores', () => { const result = usernameSchema.safeParse('user_name'); expect(result.success).toBe(true); }); it('should accept minimum length username (3 chars)', () => { const result = usernameSchema.safeParse('abc'); expect(result.success).toBe(true); }); it('should accept maximum length username (30 chars)', () => { const result = usernameSchema.safeParse('a'.repeat(30)); expect(result.success).toBe(true); }); it('should convert username to lowercase', () => { const result = usernameSchema.safeParse('UserName123'); expect(result.success).toBe(true); if (result.success) { expect(result.data).toBe('username123'); } }); it('should accept username starting and ending with alphanumeric', () => { const result = usernameSchema.safeParse('a-b_c-d1'); expect(result.success).toBe(true); }); }); describe('Invalid Usernames', () => { it('should reject username shorter than 3 characters', () => { const result = usernameSchema.safeParse('ab'); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('at least 3 characters'); } }); it('should reject username longer than 30 characters', () => { const result = usernameSchema.safeParse('a'.repeat(31)); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('less than 30 characters'); } }); it('should reject username starting with hyphen', () => { const result = usernameSchema.safeParse('-username'); expect(result.success).toBe(false); }); it('should reject username starting with underscore', () => { const result = usernameSchema.safeParse('_username'); expect(result.success).toBe(false); }); it('should reject username ending with hyphen', () => { const result = usernameSchema.safeParse('username-'); expect(result.success).toBe(false); }); it('should reject username ending with underscore', () => { const result = usernameSchema.safeParse('username_'); expect(result.success).toBe(false); }); it('should reject username with consecutive hyphens', () => { const result = usernameSchema.safeParse('user--name'); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('consecutive'); } }); it('should reject username with consecutive underscores', () => { const result = usernameSchema.safeParse('user__name'); expect(result.success).toBe(false); }); it('should reject username with special characters', () => { const result = usernameSchema.safeParse('user@name'); expect(result.success).toBe(false); }); it('should reject username with spaces', () => { const result = usernameSchema.safeParse('user name'); expect(result.success).toBe(false); }); it('should reject username with dots', () => { const result = usernameSchema.safeParse('user.name'); expect(result.success).toBe(false); }); }); describe('Forbidden Usernames - Security', () => { it('should reject "admin"', () => { const result = usernameSchema.safeParse('admin'); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('not allowed'); } }); it('should reject "administrator"', () => { const result = usernameSchema.safeParse('administrator'); expect(result.success).toBe(false); }); it('should reject "moderator"', () => { const result = usernameSchema.safeParse('moderator'); expect(result.success).toBe(false); }); it('should reject "root"', () => { const result = usernameSchema.safeParse('root'); expect(result.success).toBe(false); }); it('should reject "system"', () => { const result = usernameSchema.safeParse('system'); expect(result.success).toBe(false); }); it('should reject offensive username', () => { const result = usernameSchema.safeParse('nazi'); expect(result.success).toBe(false); }); it('should reject case-insensitive forbidden username', () => { const result = usernameSchema.safeParse('ADMIN'); expect(result.success).toBe(false); }); it('should reject mixed-case forbidden username', () => { const result = usernameSchema.safeParse('AdMiN'); expect(result.success).toBe(false); }); }); }); describe('displayNameSchema', () => { describe('Valid Display Names', () => { it('should accept valid display name', () => { const result = displayNameSchema.safeParse('John Doe'); expect(result.success).toBe(true); }); it('should accept display name with special characters', () => { const result = displayNameSchema.safeParse('O\'Brien'); expect(result.success).toBe(true); }); it('should accept display name with numbers', () => { const result = displayNameSchema.safeParse('User 123'); expect(result.success).toBe(true); }); it('should accept Unicode characters', () => { const result = displayNameSchema.safeParse('José García 日本'); expect(result.success).toBe(true); }); it('should accept emojis', () => { const result = displayNameSchema.safeParse('Cool User 😎'); expect(result.success).toBe(true); }); it('should accept undefined (optional field)', () => { const result = displayNameSchema.safeParse(undefined); expect(result.success).toBe(true); }); it('should accept empty string', () => { const result = displayNameSchema.safeParse(''); expect(result.success).toBe(true); }); }); describe('Invalid Display Names', () => { it('should reject display name longer than 100 characters', () => { const result = displayNameSchema.safeParse('a'.repeat(101)); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('less than 100 characters'); } }); it('should reject display name with "nazi"', () => { const result = displayNameSchema.safeParse('NaziSymbol'); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('inappropriate content'); } }); it('should reject display name with "hitler"', () => { const result = displayNameSchema.safeParse('hitler fan'); expect(result.success).toBe(false); }); it('should reject display name with offensive terms (case insensitive)', () => { const result = displayNameSchema.safeParse('TERRORIST group'); expect(result.success).toBe(false); }); }); }); describe('passwordSchema', () => { describe('Valid Passwords', () => { it('should accept strong password with all requirements', () => { const data = { currentPassword: 'OldPass123!', newPassword: 'NewPass123!', confirmPassword: 'NewPass123!', }; const result = passwordSchema.safeParse(data); expect(result.success).toBe(true); }); it('should accept password with multiple special characters', () => { const data = { currentPassword: 'Old123!@#', newPassword: 'New123!@#$%', confirmPassword: 'New123!@#$%', }; const result = passwordSchema.safeParse(data); expect(result.success).toBe(true); }); it('should accept maximum length password (128 chars)', () => { const pwd = 'A1!' + 'a'.repeat(125); const data = { currentPassword: 'OldPass123!', newPassword: pwd, confirmPassword: pwd, }; const result = passwordSchema.safeParse(data); expect(result.success).toBe(true); }); }); describe('Invalid Passwords - Complexity Requirements', () => { it('should reject password shorter than 8 characters', () => { const data = { currentPassword: 'Old123!', newPassword: 'New12!', confirmPassword: 'New12!', }; const result = passwordSchema.safeParse(data); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('at least 8 characters'); } }); it('should reject password longer than 128 characters', () => { const pwd = 'A'.repeat(129); const data = { currentPassword: 'OldPass123!', newPassword: pwd, confirmPassword: pwd, }; const result = passwordSchema.safeParse(data); expect(result.success).toBe(false); }); it('should reject password without uppercase letter', () => { const data = { currentPassword: 'OldPass123!', newPassword: 'newpass123!', confirmPassword: 'newpass123!', }; const result = passwordSchema.safeParse(data); expect(result.success).toBe(false); if (!result.success) { const messages = result.error.issues.map(i => i.message).join(' '); expect(messages).toContain('uppercase letter'); } }); it('should reject password without lowercase letter', () => { const data = { currentPassword: 'OldPass123!', newPassword: 'NEWPASS123!', confirmPassword: 'NEWPASS123!', }; const result = passwordSchema.safeParse(data); expect(result.success).toBe(false); if (!result.success) { const messages = result.error.issues.map(i => i.message).join(' '); expect(messages).toContain('lowercase letter'); } }); it('should reject password without number', () => { const data = { currentPassword: 'OldPass123!', newPassword: 'NewPassword!', confirmPassword: 'NewPassword!', }; const result = passwordSchema.safeParse(data); expect(result.success).toBe(false); if (!result.success) { const messages = result.error.issues.map(i => i.message).join(' '); expect(messages).toContain('number'); } }); it('should reject password without special character', () => { const data = { currentPassword: 'OldPass123!', newPassword: 'NewPass123', confirmPassword: 'NewPass123', }; const result = passwordSchema.safeParse(data); expect(result.success).toBe(false); if (!result.success) { const messages = result.error.issues.map(i => i.message).join(' '); expect(messages).toContain('special character'); } }); it('should reject mismatched passwords', () => { const data = { currentPassword: 'OldPass123!', newPassword: 'NewPass123!', confirmPassword: 'DifferentPass123!', }; const result = passwordSchema.safeParse(data); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain("don't match"); } }); it('should reject empty current password', () => { const data = { currentPassword: '', newPassword: 'NewPass123!', confirmPassword: 'NewPass123!', }; const result = passwordSchema.safeParse(data); expect(result.success).toBe(false); }); }); }); describe('bioSchema', () => { describe('Valid Bios', () => { it('should accept valid bio', () => { const result = bioSchema.safeParse('Software developer from NYC'); expect(result.success).toBe(true); }); it('should accept bio with newlines', () => { const result = bioSchema.safeParse('Line 1\nLine 2'); expect(result.success).toBe(true); }); it('should accept bio with emojis', () => { const result = bioSchema.safeParse('Developer 💻 Coffee lover ☕'); expect(result.success).toBe(true); }); it('should accept maximum length bio (500 chars)', () => { const result = bioSchema.safeParse('a'.repeat(500)); expect(result.success).toBe(true); }); it('should accept undefined (optional field)', () => { const result = bioSchema.safeParse(undefined); expect(result.success).toBe(true); }); it('should trim whitespace', () => { const result = bioSchema.safeParse(' Bio text '); expect(result.success).toBe(true); if (result.success) { expect(result.data).toBe('Bio text'); } }); }); describe('Invalid Bios', () => { it('should reject bio longer than 500 characters', () => { const result = bioSchema.safeParse('a'.repeat(501)); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('less than 500 characters'); } }); it('should reject bio with HTML tags (< and >)', () => { const result = bioSchema.safeParse('Bio with '); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('HTML tags'); } }); it('should reject bio with angle brackets', () => { const result = bioSchema.safeParse('5 < 10 and 10 > 5'); expect(result.success).toBe(false); }); }); }); describe('personalLocationSchema', () => { describe('Valid Locations', () => { it('should accept valid location', () => { const result = personalLocationSchema.safeParse('New York, USA'); expect(result.success).toBe(true); }); it('should accept location with special characters', () => { const result = personalLocationSchema.safeParse('São Paulo, Brazil'); expect(result.success).toBe(true); }); it('should accept maximum length location (100 chars)', () => { const result = personalLocationSchema.safeParse('a'.repeat(100)); expect(result.success).toBe(true); }); it('should accept undefined (optional field)', () => { const result = personalLocationSchema.safeParse(undefined); expect(result.success).toBe(true); }); it('should trim whitespace', () => { const result = personalLocationSchema.safeParse(' Tokyo '); expect(result.success).toBe(true); if (result.success) { expect(result.data).toBe('Tokyo'); } }); }); describe('Invalid Locations', () => { it('should reject location longer than 100 characters', () => { const result = personalLocationSchema.safeParse('a'.repeat(101)); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('less than 100 characters'); } }); it('should reject location with angle brackets', () => { const result = personalLocationSchema.safeParse(''); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('special characters'); } }); it('should reject location with curly braces', () => { const result = personalLocationSchema.safeParse('Location {test}'); expect(result.success).toBe(false); }); }); }); describe('preferredPronounsSchema', () => { describe('Valid Pronouns', () => { it('should accept valid pronouns', () => { const result = preferredPronounsSchema.safeParse('they/them'); expect(result.success).toBe(true); }); it('should accept he/him', () => { const result = preferredPronounsSchema.safeParse('he/him'); expect(result.success).toBe(true); }); it('should accept she/her', () => { const result = preferredPronounsSchema.safeParse('she/her'); expect(result.success).toBe(true); }); it('should accept custom pronouns', () => { const result = preferredPronounsSchema.safeParse('xe/xem'); expect(result.success).toBe(true); }); it('should accept maximum length pronouns (20 chars)', () => { const result = preferredPronounsSchema.safeParse('a'.repeat(20)); expect(result.success).toBe(true); }); it('should accept undefined (optional field)', () => { const result = preferredPronounsSchema.safeParse(undefined); expect(result.success).toBe(true); }); it('should trim whitespace', () => { const result = preferredPronounsSchema.safeParse(' they/them '); expect(result.success).toBe(true); if (result.success) { expect(result.data).toBe('they/them'); } }); }); describe('Invalid Pronouns', () => { it('should reject pronouns longer than 20 characters', () => { const result = preferredPronounsSchema.safeParse('a'.repeat(21)); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues[0].message).toContain('less than 20 characters'); } }); }); }); describe('profileEditSchema', () => { describe('Valid Profiles', () => { it('should accept valid complete profile', () => { const data = { username: 'testuser', display_name: 'Test User', bio: 'Software developer', }; const result = profileEditSchema.safeParse(data); expect(result.success).toBe(true); }); it('should accept profile with optional fields omitted', () => { const data = { username: 'testuser', }; const result = profileEditSchema.safeParse(data); expect(result.success).toBe(true); }); it('should normalize username to lowercase', () => { const data = { username: 'TestUser', display_name: 'Test User', }; const result = profileEditSchema.safeParse(data); expect(result.success).toBe(true); if (result.success) { expect(result.data.username).toBe('testuser'); } }); }); describe('Invalid Profiles', () => { it('should reject profile with invalid username', () => { const data = { username: 'admin', // Forbidden display_name: 'Test User', }; const result = profileEditSchema.safeParse(data); expect(result.success).toBe(false); }); it('should reject profile with offensive display name', () => { const data = { username: 'testuser', display_name: 'nazi sympathizer', }; const result = profileEditSchema.safeParse(data); expect(result.success).toBe(false); }); it('should reject profile with HTML in bio', () => { const data = { username: 'testuser', bio: 'Bio with ', }; const result = profileEditSchema.safeParse(data); expect(result.success).toBe(false); }); it('should reject profile with missing required username', () => { const data = { display_name: 'Test User', }; const result = profileEditSchema.safeParse(data); expect(result.success).toBe(false); }); }); });