WellNuo/utils/__tests__/wifiValidation.test.ts
Sergei 0962b5e35b Add comprehensive WiFi credential validation
- Add real-time validation for WiFi credentials with password strength indicator
- Add validateRealTime() for immediate UI feedback as user types
- Add parseWiFiErrorResponse() for user-friendly error messages
- Add prepareCredentialsForDevice() for pre-transmission validation
- Block BLE protocol delimiter characters (| and ,) in credentials
- Create WiFiPasswordInput component with strength bar and validation hints
- Update BLEManager with improved error messages and pre-validation
- Update MockBLEManager with simulation scenarios for testing
- Add 62 comprehensive tests for all validation functions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-31 18:23:31 -08:00

527 lines
18 KiB
TypeScript

/**
* Tests for WiFi Credentials Validation
*/
import {
validateSSID,
validatePassword,
validateWiFiCredentials,
isStrongPassword,
sanitizeWiFiCredentials,
getValidationErrorMessage,
getValidationMessage,
validateRealTime,
parseWiFiErrorResponse,
prepareCredentialsForDevice,
} 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');
});
});
describe('validateRealTime', () => {
it('should return valid state for valid credentials', () => {
const result = validateRealTime('MyWiFi', 'SecurePass123', 'WPA2 PSK');
expect(result.ssidValid).toBe(true);
expect(result.passwordValid).toBe(true);
expect(result.canSubmit).toBe(true);
expect(result.ssidError).toBeNull();
expect(result.passwordError).toBeNull();
});
it('should indicate password strength', () => {
// Weak password (8-9 chars)
const weak = validateRealTime('MyWiFi', 'password', 'WPA2 PSK');
expect(weak.passwordStrength).toBe('weak');
// Medium password (10+ chars but not complex)
const medium = validateRealTime('MyWiFi', 'mediumpassword', 'WPA2 PSK');
expect(medium.passwordStrength).toBe('medium');
// Strong password (12+ chars with variety)
const strong = validateRealTime('MyWiFi', 'Str0ng!Pass123', 'WPA2 PSK');
expect(strong.passwordStrength).toBe('strong');
});
it('should handle open networks', () => {
const result = validateRealTime('GuestNetwork', '', 'Open');
expect(result.passwordValid).toBe(true);
expect(result.passwordStrength).toBeNull();
expect(result.canSubmit).toBe(true);
});
it('should not allow submit with short password', () => {
const result = validateRealTime('MyWiFi', '1234567', 'WPA2 PSK');
expect(result.canSubmit).toBe(false);
expect(result.passwordValid).toBe(false);
});
it('should not allow submit with empty SSID', () => {
const result = validateRealTime('', 'password123', 'WPA2 PSK');
expect(result.canSubmit).toBe(false);
});
it('should collect warnings', () => {
const result = validateRealTime(' MyWiFi', 'password', 'WPA2 PSK');
expect(result.warnings.length).toBeGreaterThan(0);
});
it('should return null strength for empty password', () => {
const result = validateRealTime('MyWiFi', '', 'WPA2 PSK');
expect(result.passwordStrength).toBeNull();
});
});
describe('parseWiFiErrorResponse', () => {
it('should detect wrong password errors', () => {
const errors = [
'WiFi credentials rejected by sensor',
'wrong password',
'check password',
'W|fail response',
];
errors.forEach((error) => {
const result = parseWiFiErrorResponse(error);
expect(result.type).toBe('password_wrong');
expect(result.suggestion).toContain('check');
});
});
it('should detect network not found errors', () => {
const errors = [
'Network not found',
'no network available',
'SSID not available',
];
errors.forEach((error) => {
const result = parseWiFiErrorResponse(error);
expect(result.type).toBe('network_not_found');
expect(result.suggestion).toContain('range');
});
});
it('should detect timeout errors', () => {
const errors = [
'Command timeout',
'Request timed out',
'Sensor did not respond in time',
];
errors.forEach((error) => {
const result = parseWiFiErrorResponse(error);
expect(result.type).toBe('timeout');
expect(result.suggestion).toContain('closer');
});
});
it('should detect connection errors', () => {
const errors = [
'BLE connection failed',
'Bluetooth error',
'Could not connect to device',
];
errors.forEach((error) => {
const result = parseWiFiErrorResponse(error);
expect(result.type).toBe('connection_failed');
expect(result.suggestion).toContain('try again');
});
});
it('should return unknown for unrecognized errors', () => {
const result = parseWiFiErrorResponse('Some random error message');
expect(result.type).toBe('unknown');
expect(result.message).toBe('WiFi configuration failed');
});
});
describe('prepareCredentialsForDevice', () => {
it('should return sanitized credentials for valid input', () => {
const result = prepareCredentialsForDevice(' MyWiFi ', 'password123', 'WPA2 PSK');
expect(result.ssid).toBe('MyWiFi');
expect(result.password).toBe('password123');
});
it('should throw for invalid SSID with pipe character', () => {
expect(() => {
prepareCredentialsForDevice('My|WiFi', 'password123', 'WPA2 PSK');
}).toThrow('Network name contains invalid characters');
});
it('should throw for invalid SSID with comma character', () => {
expect(() => {
prepareCredentialsForDevice('My,WiFi', 'password123', 'WPA2 PSK');
}).toThrow('Network name contains invalid characters');
});
it('should throw for password with pipe character', () => {
expect(() => {
prepareCredentialsForDevice('MyWiFi', 'pass|word', 'WPA2 PSK');
}).toThrow('Password contains invalid character');
});
it('should allow password with comma character', () => {
// Comma is allowed in password (only pipe is delimiter between command and payload)
const result = prepareCredentialsForDevice('MyWiFi', 'pass,word123', 'WPA2 PSK');
expect(result.password).toBe('pass,word123');
});
it('should throw for empty SSID', () => {
expect(() => {
prepareCredentialsForDevice('', 'password123', 'WPA2 PSK');
}).toThrow('Network name (SSID) cannot be empty');
});
it('should throw for short password', () => {
expect(() => {
prepareCredentialsForDevice('MyWiFi', 'short', 'WPA2 PSK');
}).toThrow('at least 8 characters');
});
it('should allow empty password for open networks', () => {
const result = prepareCredentialsForDevice('GuestNetwork', '', 'Open');
expect(result.ssid).toBe('GuestNetwork');
expect(result.password).toBe('');
});
});
});