Implement WiFi SSID and password validation following IEEE 802.11 standards: Features: - SSID validation: length (max 32 bytes), control characters, whitespace warnings - Password validation for WPA/WPA2/WPA3 (8-63 chars), WEP, and Open networks - Weak password detection with warnings - Non-ASCII character warnings - Input sanitization (trim SSID, preserve password spaces) - User-friendly error messages Integration: - Added validation to wifi-setup.tsx (ESP provisioning flow) - Added validation to setup-wifi.tsx (batch sensor setup flow) - Pre-provisioning validation with error/warning alerts - Password requirements hints in UI Tests: - Comprehensive test suite with 42 test cases - 100% coverage of validation logic - Tests for all auth types (WPA, WEP, Open) - Edge cases and warning scenarios 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
356 lines
12 KiB
TypeScript
356 lines
12 KiB
TypeScript
/**
|
|
* Tests for WiFi Credentials Validation
|
|
*/
|
|
|
|
import {
|
|
validateSSID,
|
|
validatePassword,
|
|
validateWiFiCredentials,
|
|
isStrongPassword,
|
|
sanitizeWiFiCredentials,
|
|
getValidationErrorMessage,
|
|
getValidationMessage,
|
|
} from '../wifiValidation';
|
|
|
|
describe('WiFi Validation', () => {
|
|
describe('validateSSID', () => {
|
|
it('should accept valid SSIDs', () => {
|
|
const validSSIDs = [
|
|
'MyHomeWiFi',
|
|
'Guest Network',
|
|
'Office-5G',
|
|
'Café WiFi',
|
|
'Network_2.4GHz',
|
|
'a',
|
|
'1234567890123456789012345678901', // 31 chars
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ123456', // 32 chars
|
|
];
|
|
|
|
validSSIDs.forEach((ssid) => {
|
|
const result = validateSSID(ssid);
|
|
expect(result.valid).toBe(true);
|
|
expect(result.errors).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
it('should reject empty SSID', () => {
|
|
const result = validateSSID('');
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('Network name (SSID) cannot be empty');
|
|
});
|
|
|
|
it('should reject whitespace-only SSID', () => {
|
|
const result = validateSSID(' ');
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('Network name (SSID) cannot be empty');
|
|
});
|
|
|
|
it('should reject SSID longer than 32 bytes', () => {
|
|
const longSSID = 'A'.repeat(33);
|
|
const result = validateSSID(longSSID);
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('Network name is too long (max 32 bytes)');
|
|
});
|
|
|
|
it('should reject SSID with control characters', () => {
|
|
const result = validateSSID('Network\x00Name');
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('Network name contains invalid control characters');
|
|
});
|
|
|
|
it('should warn about leading/trailing spaces', () => {
|
|
const result1 = validateSSID(' MyWiFi');
|
|
expect(result1.valid).toBe(true);
|
|
expect(result1.warnings).toContain(
|
|
'Network name has leading or trailing spaces - this may cause connection issues'
|
|
);
|
|
|
|
const result2 = validateSSID('MyWiFi ');
|
|
expect(result2.valid).toBe(true);
|
|
expect(result2.warnings).toContain(
|
|
'Network name has leading or trailing spaces - this may cause connection issues'
|
|
);
|
|
});
|
|
|
|
it('should warn about multiple consecutive spaces', () => {
|
|
const result = validateSSID('My WiFi');
|
|
expect(result.valid).toBe(true);
|
|
expect(result.warnings).toContain('Network name has multiple consecutive spaces');
|
|
});
|
|
});
|
|
|
|
describe('validatePassword', () => {
|
|
describe('WPA/WPA2/WPA3 networks', () => {
|
|
it('should accept valid WPA passwords', () => {
|
|
const validPasswords = [
|
|
'MySecurePass123',
|
|
'abcdefgh', // 8 chars minimum
|
|
'A'.repeat(63), // 63 chars maximum
|
|
'Pass with spaces!@#',
|
|
'ComplexP@ssw0rd!',
|
|
];
|
|
|
|
validPasswords.forEach((password) => {
|
|
const result = validatePassword(password, 'WPA2 PSK');
|
|
expect(result.valid).toBe(true);
|
|
expect(result.errors).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
it('should reject passwords shorter than 8 characters', () => {
|
|
const result = validatePassword('1234567', 'WPA2 PSK');
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain(
|
|
'Password must be at least 8 characters for WPA/WPA2/WPA3 networks'
|
|
);
|
|
});
|
|
|
|
it('should reject passwords longer than 63 characters', () => {
|
|
const longPassword = 'A'.repeat(64);
|
|
const result = validatePassword(longPassword, 'WPA2 PSK');
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain(
|
|
'Password cannot exceed 63 characters for WPA/WPA2/WPA3 networks'
|
|
);
|
|
});
|
|
|
|
it('should warn about weak passwords', () => {
|
|
const result = validatePassword('12345678', 'WPA2 PSK');
|
|
expect(result.valid).toBe(true);
|
|
expect(result.warnings).toContain('Password is weak - consider using at least 12 characters');
|
|
});
|
|
|
|
it('should warn about commonly used weak passwords', () => {
|
|
const weakPasswords = ['password', '12345678', 'qwerty123'];
|
|
|
|
weakPasswords.forEach((password) => {
|
|
const result = validatePassword(password, 'WPA2 PSK');
|
|
expect(result.warnings).toContain('This is a commonly used weak password');
|
|
});
|
|
});
|
|
|
|
it('should warn about numeric-only passwords', () => {
|
|
const result = validatePassword('12345678', 'WPA2 PSK');
|
|
expect(result.warnings).toContain(
|
|
'Password contains only numbers - consider adding letters and symbols'
|
|
);
|
|
});
|
|
|
|
it('should warn about non-ASCII characters', () => {
|
|
const result = validatePassword('Password™123', 'WPA2 PSK');
|
|
expect(result.valid).toBe(true);
|
|
expect(result.warnings).toContain(
|
|
'Password contains non-ASCII characters - this may cause issues on some devices'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Open networks', () => {
|
|
it('should accept empty password for open networks', () => {
|
|
const result = validatePassword('', 'Open');
|
|
expect(result.valid).toBe(true);
|
|
expect(result.errors).toHaveLength(0);
|
|
});
|
|
|
|
it('should accept any password for open networks (will be ignored)', () => {
|
|
const result = validatePassword('any password', 'Open');
|
|
expect(result.valid).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('WEP networks', () => {
|
|
it('should accept valid 5-character WEP password', () => {
|
|
const result = validatePassword('abcde', 'WEP');
|
|
expect(result.valid).toBe(true);
|
|
expect(result.warnings).toContain(
|
|
'WEP is deprecated and insecure - consider upgrading your router to WPA2/WPA3'
|
|
);
|
|
});
|
|
|
|
it('should accept valid 13-character WEP password', () => {
|
|
const result = validatePassword('abcdefghijklm', 'WEP');
|
|
expect(result.valid).toBe(true);
|
|
});
|
|
|
|
it('should accept valid 10-digit hex WEP password', () => {
|
|
const result = validatePassword('1234567890', 'WEP');
|
|
expect(result.valid).toBe(true);
|
|
});
|
|
|
|
it('should accept valid 26-digit hex WEP password', () => {
|
|
const result = validatePassword('12345678901234567890123456', 'WEP');
|
|
expect(result.valid).toBe(true);
|
|
});
|
|
|
|
it('should reject invalid WEP password length', () => {
|
|
const result = validatePassword('abc', 'WEP');
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain(
|
|
'WEP password must be 5 or 13 ASCII characters, or 10/26 hexadecimal digits'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Secured networks without auth type', () => {
|
|
it('should require password', () => {
|
|
const result = validatePassword('');
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('Password is required for secured networks');
|
|
});
|
|
|
|
it('should apply WPA rules by default', () => {
|
|
const result = validatePassword('1234567'); // 7 chars
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain(
|
|
'Password must be at least 8 characters for WPA/WPA2/WPA3 networks'
|
|
);
|
|
});
|
|
});
|
|
|
|
it('should reject passwords with control characters', () => {
|
|
const result = validatePassword('Pass\x00word123', 'WPA2 PSK');
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('Password contains invalid control characters');
|
|
});
|
|
});
|
|
|
|
describe('validateWiFiCredentials', () => {
|
|
it('should validate both SSID and password', () => {
|
|
const result = validateWiFiCredentials('MyWiFi', 'SecurePass123', 'WPA2 PSK');
|
|
expect(result.valid).toBe(true);
|
|
expect(result.errors).toHaveLength(0);
|
|
});
|
|
|
|
it('should combine errors from both validations', () => {
|
|
const result = validateWiFiCredentials('', '123', 'WPA2 PSK');
|
|
expect(result.valid).toBe(false);
|
|
expect(result.errors).toContain('Network name (SSID) cannot be empty');
|
|
expect(result.errors).toContain(
|
|
'Password must be at least 8 characters for WPA/WPA2/WPA3 networks'
|
|
);
|
|
});
|
|
|
|
it('should combine warnings from both validations', () => {
|
|
const result = validateWiFiCredentials(' MyWiFi ', 'password123', 'WPA2 PSK');
|
|
expect(result.valid).toBe(true);
|
|
expect(result.warnings.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should validate open network correctly', () => {
|
|
const result = validateWiFiCredentials('GuestNetwork', '', 'Open');
|
|
expect(result.valid).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('isStrongPassword', () => {
|
|
it('should accept strong passwords', () => {
|
|
const strongPasswords = [
|
|
'MyP@ssw0rd123!',
|
|
'Secure_Password_2024',
|
|
'Compl3x!P@ssw0rd',
|
|
'abCD1234!@#$',
|
|
];
|
|
|
|
strongPasswords.forEach((password) => {
|
|
expect(isStrongPassword(password)).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('should reject passwords shorter than 12 characters', () => {
|
|
expect(isStrongPassword('P@ssw0rd')).toBe(false);
|
|
});
|
|
|
|
it('should reject passwords with less than 3 character types', () => {
|
|
expect(isStrongPassword('onlylowercase')).toBe(false);
|
|
expect(isStrongPassword('12345678901234')).toBe(false);
|
|
expect(isStrongPassword('ONLYUPPERCASE')).toBe(false);
|
|
});
|
|
|
|
it('should accept passwords with 3 or 4 character types', () => {
|
|
// 3 types: lowercase, uppercase, numbers
|
|
expect(isStrongPassword('Password1234')).toBe(true);
|
|
|
|
// 4 types: lowercase, uppercase, numbers, special
|
|
expect(isStrongPassword('P@ssword1234')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('sanitizeWiFiCredentials', () => {
|
|
it('should trim SSID whitespace', () => {
|
|
const result = sanitizeWiFiCredentials({
|
|
ssid: ' MyWiFi ',
|
|
password: 'password',
|
|
});
|
|
expect(result.ssid).toBe('MyWiFi');
|
|
});
|
|
|
|
it('should preserve password whitespace', () => {
|
|
const result = sanitizeWiFiCredentials({
|
|
ssid: 'MyWiFi',
|
|
password: ' password with spaces ',
|
|
});
|
|
expect(result.password).toBe(' password with spaces ');
|
|
});
|
|
|
|
it('should not modify valid credentials', () => {
|
|
const result = sanitizeWiFiCredentials({
|
|
ssid: 'MyWiFi',
|
|
password: 'SecurePass123',
|
|
});
|
|
expect(result.ssid).toBe('MyWiFi');
|
|
expect(result.password).toBe('SecurePass123');
|
|
});
|
|
});
|
|
|
|
describe('getValidationErrorMessage', () => {
|
|
it('should return empty string for valid credentials', () => {
|
|
const result = validateWiFiCredentials('MyWiFi', 'SecurePass123', 'WPA2 PSK');
|
|
expect(getValidationErrorMessage(result)).toBe('');
|
|
});
|
|
|
|
it('should return first error message', () => {
|
|
const result = validateWiFiCredentials('', '123', 'WPA2 PSK');
|
|
expect(getValidationErrorMessage(result)).toBe('Network name (SSID) cannot be empty');
|
|
});
|
|
|
|
it('should return default message if no specific error', () => {
|
|
const result = { valid: false, errors: [], warnings: [] };
|
|
expect(getValidationErrorMessage(result)).toBe('Invalid WiFi credentials');
|
|
});
|
|
});
|
|
|
|
describe('getValidationMessage', () => {
|
|
it('should return empty string for valid credentials with no warnings', () => {
|
|
const result = { valid: true, errors: [], warnings: [] };
|
|
expect(getValidationMessage(result)).toBe('');
|
|
});
|
|
|
|
it('should return errors only', () => {
|
|
const result = {
|
|
valid: false,
|
|
errors: ['Error 1', 'Error 2'],
|
|
warnings: [],
|
|
};
|
|
expect(getValidationMessage(result)).toBe('Error 1\nError 2');
|
|
});
|
|
|
|
it('should return warnings only', () => {
|
|
const result = {
|
|
valid: true,
|
|
errors: [],
|
|
warnings: ['Warning 1', 'Warning 2'],
|
|
};
|
|
expect(getValidationMessage(result)).toBe('Warning 1\nWarning 2');
|
|
});
|
|
|
|
it('should return both errors and warnings', () => {
|
|
const result = {
|
|
valid: false,
|
|
errors: ['Error 1'],
|
|
warnings: ['Warning 1'],
|
|
};
|
|
expect(getValidationMessage(result)).toBe('Error 1\nWarning 1');
|
|
});
|
|
});
|
|
});
|