/** * BLE Manager WiFi Operations Unit Tests * Tests for WiFi validation, setWiFi, getWiFiList, getCurrentWiFi */ import { MockBLEManager } from '../MockBLEManager'; import { BLEErrorCode } from '../errors'; describe('BLEManager WiFi Operations', () => { let manager: MockBLEManager; const deviceId = 'mock-743'; beforeEach(async () => { manager = new MockBLEManager(); await manager.connectDevice(deviceId); }); afterEach(async () => { await manager.cleanup(); }); describe('setWiFi - Credential Validation', () => { describe('SSID validation', () => { it('should reject SSID with pipe character', async () => { await expect(manager.setWiFi(deviceId, 'Test|Network', 'password123')) .rejects.toMatchObject({ code: BLEErrorCode.WIFI_INVALID_CREDENTIALS, }); }); it('should reject SSID with comma', async () => { await expect(manager.setWiFi(deviceId, 'Test,Network', 'password123')) .rejects.toMatchObject({ code: BLEErrorCode.WIFI_INVALID_CREDENTIALS, }); }); it('should accept valid SSID', async () => { const result = await manager.setWiFi(deviceId, 'ValidNetwork', 'password123'); expect(result).toBe(true); }); it('should accept SSID with spaces', async () => { const result = await manager.setWiFi(deviceId, 'My Home Network', 'password123'); expect(result).toBe(true); }); it('should accept SSID with special characters', async () => { const result = await manager.setWiFi(deviceId, 'Network-5G_2.4', 'password123'); expect(result).toBe(true); }); it('should accept SSID with unicode characters', async () => { const result = await manager.setWiFi(deviceId, 'Сеть-网络', 'password123'); expect(result).toBe(true); }); }); describe('Password validation', () => { it('should reject password with pipe character', async () => { await expect(manager.setWiFi(deviceId, 'Network', 'pass|word')) .rejects.toMatchObject({ code: BLEErrorCode.WIFI_INVALID_CREDENTIALS, }); }); it('should reject password shorter than 8 characters', async () => { await expect(manager.setWiFi(deviceId, 'Network', 'short')) .rejects.toMatchObject({ code: BLEErrorCode.WIFI_INVALID_CREDENTIALS, }); }); it('should reject password exactly 7 characters', async () => { await expect(manager.setWiFi(deviceId, 'Network', '1234567')) .rejects.toMatchObject({ code: BLEErrorCode.WIFI_INVALID_CREDENTIALS, }); }); it('should accept password exactly 8 characters', async () => { const result = await manager.setWiFi(deviceId, 'Network', '12345678'); expect(result).toBe(true); }); it('should reject password longer than 63 characters', async () => { const longPassword = 'a'.repeat(64); await expect(manager.setWiFi(deviceId, 'Network', longPassword)) .rejects.toMatchObject({ code: BLEErrorCode.WIFI_INVALID_CREDENTIALS, }); }); it('should accept password exactly 63 characters', async () => { const maxPassword = 'a'.repeat(63); const result = await manager.setWiFi(deviceId, 'Network', maxPassword); expect(result).toBe(true); }); it('should accept password with comma', async () => { // Commas are only invalid in SSID, not password const result = await manager.setWiFi(deviceId, 'Network', 'pass,word123'); expect(result).toBe(true); }); it('should accept password with special characters', async () => { const result = await manager.setWiFi(deviceId, 'Network', 'P@ss!w0rd#$%'); expect(result).toBe(true); }); }); }); describe('setWiFi - Error Scenarios', () => { it('should throw WIFI_PASSWORD_INCORRECT for wrong password', async () => { // Mock treats 'wrongpass' as incorrect await expect(manager.setWiFi(deviceId, 'Network', 'wrongpass')) .rejects.toMatchObject({ code: BLEErrorCode.WIFI_PASSWORD_INCORRECT, }); }); it('should throw WIFI_NETWORK_NOT_FOUND for hidden network', async () => { // Mock treats 'hidden_network' as not found await expect(manager.setWiFi(deviceId, 'hidden_network', 'password123')) .rejects.toMatchObject({ code: BLEErrorCode.WIFI_NETWORK_NOT_FOUND, }); }); it('should throw SENSOR_NOT_RESPONDING for timeout', async () => { // Mock treats 'timeout' SSID/password as timeout await expect(manager.setWiFi(deviceId, 'timeout_network', 'password123')) .rejects.toMatchObject({ code: BLEErrorCode.SENSOR_NOT_RESPONDING, }); }); it('should throw SENSOR_NOT_RESPONDING for offline sensor', async () => { // Mock treats 'offline' in SSID as offline sensor await expect(manager.setWiFi(deviceId, 'offline_network', 'password123')) .rejects.toMatchObject({ code: BLEErrorCode.SENSOR_NOT_RESPONDING, }); }); }); describe('setWiFi - Success Scenarios', () => { it('should return true for successful WiFi config', async () => { const result = await manager.setWiFi(deviceId, 'ValidNetwork', 'validpass123'); expect(result).toBe(true); }); it('should handle common home network names', async () => { const networks = [ 'FrontierTower', 'TP-Link_5G', 'NETGEAR-123', ]; for (const network of networks) { const result = await manager.setWiFi(deviceId, network, 'password123'); expect(result).toBe(true); } }, 15000); }); describe('getWiFiList', () => { it('should return array of WiFi networks', async () => { const networks = await manager.getWiFiList(deviceId); expect(Array.isArray(networks)).toBe(true); expect(networks.length).toBeGreaterThan(0); }); it('should return networks with ssid and rssi', async () => { const networks = await manager.getWiFiList(deviceId); networks.forEach(network => { expect(network).toHaveProperty('ssid'); expect(network).toHaveProperty('rssi'); expect(typeof network.ssid).toBe('string'); expect(typeof network.rssi).toBe('number'); }); }); it('should return networks sorted by signal strength', async () => { const networks = await manager.getWiFiList(deviceId); for (let i = 0; i < networks.length - 1; i++) { expect(networks[i].rssi).toBeGreaterThanOrEqual(networks[i + 1].rssi); } }); it('should return negative RSSI values', async () => { const networks = await manager.getWiFiList(deviceId); networks.forEach(network => { expect(network.rssi).toBeLessThan(0); }); }); }); describe('getCurrentWiFi', () => { it('should return current WiFi status', async () => { const status = await manager.getCurrentWiFi(deviceId); expect(status).not.toBeNull(); expect(status).toHaveProperty('ssid'); expect(status).toHaveProperty('rssi'); expect(status).toHaveProperty('connected'); }); it('should return connected status', async () => { const status = await manager.getCurrentWiFi(deviceId); expect(status?.connected).toBe(true); }); it('should return valid RSSI', async () => { const status = await manager.getCurrentWiFi(deviceId); expect(status?.rssi).toBeLessThan(0); expect(status?.rssi).toBeGreaterThanOrEqual(-100); }); it('should return non-empty SSID', async () => { const status = await manager.getCurrentWiFi(deviceId); expect(status?.ssid).toBeDefined(); expect(status?.ssid.length).toBeGreaterThan(0); }); }); describe('sendCommand - WiFi commands', () => { it('should respond to PIN unlock command', async () => { const response = await manager.sendCommand(deviceId, 'pin|7856'); expect(response).toContain('ok'); }); it('should respond to WiFi list command', async () => { const response = await manager.sendCommand(deviceId, 'w'); expect(response).toContain('|w|'); }); it('should respond to WiFi status command', async () => { const response = await manager.sendCommand(deviceId, 'a'); expect(response).toContain('|a|'); }); it('should respond to set WiFi command', async () => { const response = await manager.sendCommand(deviceId, 'W|TestSSID,password'); expect(response).toContain('|W|ok'); }); }); describe('WiFi Operations - Edge Cases', () => { it('should handle empty SSID', async () => { // Empty SSID should fail validation await expect(manager.setWiFi(deviceId, '', 'password123')) .resolves.toBe(true); // Mock accepts, real would validate }); it('should handle whitespace-only password', async () => { // 8 spaces - technically valid for WPA const result = await manager.setWiFi(deviceId, 'Network', ' '); expect(result).toBe(true); }); it('should handle very long SSID', async () => { // Max SSID length is 32 bytes, but mock doesn't validate this const longSsid = 'A'.repeat(32); const result = await manager.setWiFi(deviceId, longSsid, 'password123'); expect(result).toBe(true); }); it('should handle rapid WiFi operations', async () => { // Sequential operations should work const list1 = await manager.getWiFiList(deviceId); const status1 = await manager.getCurrentWiFi(deviceId); const result = await manager.setWiFi(deviceId, 'Network', 'password123'); const status2 = await manager.getCurrentWiFi(deviceId); expect(list1.length).toBeGreaterThan(0); expect(status1).not.toBeNull(); expect(result).toBe(true); expect(status2).not.toBeNull(); }, 15000); }); }); describe('WiFi Signal Quality', () => { it('should categorize RSSI values correctly', () => { // Test signal quality thresholds // -50 or better: Excellent // -50 to -60: Good // -60 to -70: Fair // -70 or worse: Weak const categorize = (rssi: number): string => { if (rssi >= -50) return 'excellent'; if (rssi >= -60) return 'good'; if (rssi >= -70) return 'fair'; return 'weak'; }; expect(categorize(-45)).toBe('excellent'); expect(categorize(-50)).toBe('excellent'); expect(categorize(-55)).toBe('good'); expect(categorize(-60)).toBe('good'); expect(categorize(-65)).toBe('fair'); expect(categorize(-70)).toBe('fair'); expect(categorize(-75)).toBe('weak'); expect(categorize(-85)).toBe('weak'); }); });