← back to Wine Finder Next
__tests__/unit/inputValidation.test.ts
251 lines
// Unit Tests for Input Validation
import {
validateBottleId,
validateVoteId,
validateMembershipUnitId,
validateUserId,
validateVoteChoice,
validateEmail,
validatePrice,
validateQuantity,
sanitizeString,
validateShippingAddress,
ValidationError,
validateRequest,
} from '@/lib/inputValidation';
describe('Input Validation', () => {
describe('validateBottleId', () => {
it('should accept valid bottle IDs', () => {
expect(validateBottleId('bottle_1700000000000_abc123def')).toBe(true);
expect(validateBottleId('bottle_1234567890123_xyz987poi')).toBe(true);
});
it('should reject invalid bottle IDs', () => {
expect(validateBottleId('invalid_id')).toBe(false);
expect(validateBottleId('bottle_123')).toBe(false);
expect(validateBottleId('bottle_abc_123')).toBe(false);
expect(validateBottleId('')).toBe(false);
expect(validateBottleId('bottle_1700000000000_ABC')).toBe(false); // uppercase not allowed
});
it('should reject SQL injection attempts', () => {
expect(validateBottleId("bottle_1700000000000_abc'; DROP TABLE--")).toBe(false);
expect(validateBottleId("bottle_1700000000000_abc' OR '1'='1")).toBe(false);
});
});
describe('validateVoteId', () => {
it('should accept valid vote IDs', () => {
expect(validateVoteId('vote_1700000000000_abc123def')).toBe(true);
});
it('should reject invalid vote IDs', () => {
expect(validateVoteId('invalid_id')).toBe(false);
expect(validateVoteId('vote_123')).toBe(false);
});
});
describe('validateMembershipUnitId', () => {
it('should accept valid unit IDs', () => {
expect(validateMembershipUnitId('unit_1700000000000_abc123def')).toBe(true);
});
it('should reject invalid unit IDs', () => {
expect(validateMembershipUnitId('invalid_id')).toBe(false);
expect(validateMembershipUnitId('unit_123')).toBe(false);
});
});
describe('validateUserId', () => {
it('should accept valid user IDs', () => {
expect(validateUserId('user_1')).toBe(true);
expect(validateUserId('user_12345')).toBe(true);
expect(validateUserId('a'.repeat(40))).toBe(true); // long hash format
});
it('should reject invalid user IDs', () => {
expect(validateUserId('user')).toBe(false);
expect(validateUserId('invalid')).toBe(false);
expect(validateUserId('')).toBe(false);
});
});
describe('validateVoteChoice', () => {
it('should accept valid vote choices', () => {
expect(validateVoteChoice('for')).toBe(true);
expect(validateVoteChoice('against')).toBe(true);
expect(validateVoteChoice('abstain')).toBe(true);
});
it('should reject invalid vote choices', () => {
expect(validateVoteChoice('yes')).toBe(false);
expect(validateVoteChoice('no')).toBe(false);
expect(validateVoteChoice('')).toBe(false);
expect(validateVoteChoice('FOR')).toBe(false); // case sensitive
});
});
describe('validateEmail', () => {
it('should accept valid email addresses', () => {
expect(validateEmail('test@example.com')).toBe(true);
expect(validateEmail('user.name+tag@example.co.uk')).toBe(true);
expect(validateEmail('test_user@domain.org')).toBe(true);
});
it('should reject invalid email addresses', () => {
expect(validateEmail('invalid')).toBe(false);
expect(validateEmail('test@')).toBe(false);
expect(validateEmail('@example.com')).toBe(false);
expect(validateEmail('test@example')).toBe(false);
expect(validateEmail('')).toBe(false);
});
it('should reject emails longer than 254 characters', () => {
const longEmail = 'a'.repeat(250) + '@example.com';
expect(validateEmail(longEmail)).toBe(false);
});
});
describe('validatePrice', () => {
it('should accept valid prices', () => {
expect(validatePrice(10.50)).toBe(true);
expect(validatePrice(0.01)).toBe(true);
expect(validatePrice(999999.99)).toBe(true);
});
it('should reject invalid prices', () => {
expect(validatePrice(0)).toBe(false);
expect(validatePrice(-10)).toBe(false);
expect(validatePrice(1000000)).toBe(false);
expect(validatePrice(Infinity)).toBe(false);
expect(validatePrice(NaN)).toBe(false);
});
});
describe('validateQuantity', () => {
it('should accept valid quantities', () => {
expect(validateQuantity(1)).toBe(true);
expect(validateQuantity(100)).toBe(true);
expect(validateQuantity(10000)).toBe(true);
});
it('should reject invalid quantities', () => {
expect(validateQuantity(0)).toBe(false);
expect(validateQuantity(-1)).toBe(false);
expect(validateQuantity(10001)).toBe(false);
expect(validateQuantity(1.5)).toBe(false); // must be integer
});
});
describe('sanitizeString', () => {
it('should remove dangerous HTML tags', () => {
expect(sanitizeString('<script>alert("XSS")</script>')).toBe('scriptalert("XSS")/script');
expect(sanitizeString('<img src=x onerror=alert(1)>')).toBe('img src=x alert(1)');
});
it('should remove javascript: protocol', () => {
expect(sanitizeString('javascript:alert(1)')).toBe('alert(1)');
expect(sanitizeString('JAVASCRIPT:alert(1)')).toBe('alert(1)');
});
it('should remove event handlers', () => {
expect(sanitizeString('onclick=alert(1)')).toBe('alert(1)');
expect(sanitizeString('onload=malicious()')).toBe('malicious()');
});
it('should trim and limit length', () => {
const longString = 'a'.repeat(2000);
expect(sanitizeString(longString, 100)).toHaveLength(100);
expect(sanitizeString(' test ')).toBe('test');
});
it('should preserve safe content', () => {
expect(sanitizeString('Hello World')).toBe('Hello World');
expect(sanitizeString('Test 123 !@#$%')).toBe('Test 123 !@#$%');
});
});
describe('validateShippingAddress', () => {
const validAddress = {
name: 'John Doe',
addressLine1: '123 Wine Street',
city: 'Napa',
state: 'CA',
zipCode: '94558',
country: 'USA',
phone: '+1-555-123-4567',
};
it('should accept valid shipping addresses', () => {
expect(validateShippingAddress(validAddress)).toBe(true);
});
it('should reject missing required fields', () => {
expect(validateShippingAddress({ ...validAddress, name: '' })).toBe(false);
expect(validateShippingAddress({ ...validAddress, addressLine1: '' })).toBe(false);
expect(validateShippingAddress({ ...validAddress, city: '' })).toBe(false);
});
it('should validate ZIP code format', () => {
expect(validateShippingAddress({ ...validAddress, zipCode: '12345' })).toBe(true);
expect(validateShippingAddress({ ...validAddress, zipCode: '12345-6789' })).toBe(true);
expect(validateShippingAddress({ ...validAddress, zipCode: 'ABCDE' })).toBe(false);
expect(validateShippingAddress({ ...validAddress, zipCode: '123' })).toBe(false);
});
it('should validate phone format', () => {
expect(validateShippingAddress({ ...validAddress, phone: '+1-555-123-4567' })).toBe(true);
expect(validateShippingAddress({ ...validAddress, phone: '(555) 123-4567' })).toBe(true);
expect(validateShippingAddress({ ...validAddress, phone: '123' })).toBe(false);
});
});
describe('validateRequest', () => {
it('should validate data against schema', () => {
const schema = {
email: validateEmail,
price: validatePrice,
};
const validData = {
email: 'test@example.com',
price: 10.50,
};
expect(() => validateRequest(validData, schema)).not.toThrow();
});
it('should throw ValidationError for invalid data', () => {
const schema = {
email: validateEmail,
price: validatePrice,
};
const invalidData = {
email: 'invalid-email',
price: -10,
};
expect(() => validateRequest(invalidData, schema)).toThrow(ValidationError);
});
it('should include field names in error message', () => {
const schema = {
email: validateEmail,
};
const invalidData = {
email: 'invalid',
};
try {
validateRequest(invalidData, schema);
} catch (error) {
expect(error).toBeInstanceOf(ValidationError);
expect((error as Error).message).toContain('email');
}
});
});
});