#!/usr/bin/env python3 """ Test script to verify the Unifi Hostname bug fix in the is_hostname_unknown function. This script tests: 1. A device affected by the Unifi Hostname bug (UniFi-reported hostname matches unknown patterns, but self-reported hostname doesn't) 2. A device not affected by the bug (both hostnames match or don't match unknown patterns) 3. Various combinations of parameters (with/without from_unifi_os, with/without IP) """ import logging import unittest from unittest.mock import patch, MagicMock # Configure logging logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) logger = logging.getLogger(__name__) # Import TasmotaManager class import sys import os sys.path.append(os.path.dirname(os.path.abspath(__file__))) from TasmotaManager import TasmotaDiscovery class TestUnifiHostnameBugFix(unittest.TestCase): """Test cases for the Unifi Hostname bug fix.""" def setUp(self): """Set up test environment.""" self.discovery = TasmotaDiscovery(debug=True) # Create a mock config self.discovery.config = { 'unifi': { 'network_filter': { 'test_network': { 'unknown_device_patterns': [ "^tasmota_*", "^tasmota-*", "^esp-*", "^ESP-*" ] } } } } # Define test patterns self.test_patterns = [ "^tasmota_*", "^tasmota-*", "^esp-*", "^ESP-*" ] @patch('requests.get') def test_bug_affected_device(self, mock_get): """Test a device affected by the Unifi Hostname bug.""" # Mock response for Status 5 command mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { 'StatusNET': { 'Hostname': 'my_proper_device' # Self-reported hostname doesn't match unknown patterns } } mock_get.return_value = mock_response # Test with a hostname that matches unknown patterns (as reported by UniFi) # but with a self-reported hostname that doesn't match unknown patterns result = self.discovery.is_hostname_unknown( hostname="tasmota_123", # UniFi-reported hostname (matches unknown patterns) patterns=self.test_patterns, from_unifi_os=True, # Enable Unifi Hostname bug handling ip="192.168.1.100" # Provide IP to query the device ) # The function should return False because the self-reported hostname doesn't match unknown patterns self.assertFalse(result) # Verify that requests.get was called with the correct URL mock_get.assert_called_once_with("http://192.168.1.100/cm?cmnd=Status%205", timeout=5) logger.info("Test for bug-affected device passed") @patch('requests.get') def test_non_bug_affected_device_both_match(self, mock_get): """Test a device not affected by the bug (both hostnames match unknown patterns).""" # Mock response for Status 5 command mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { 'StatusNET': { 'Hostname': 'tasmota_456' # Self-reported hostname matches unknown patterns } } mock_get.return_value = mock_response # Test with a hostname that matches unknown patterns (as reported by UniFi) # and with a self-reported hostname that also matches unknown patterns result = self.discovery.is_hostname_unknown( hostname="tasmota_123", # UniFi-reported hostname (matches unknown patterns) patterns=self.test_patterns, from_unifi_os=True, # Enable Unifi Hostname bug handling ip="192.168.1.100" # Provide IP to query the device ) # The function should return True because both hostnames match unknown patterns self.assertTrue(result) # Verify that requests.get was called with the correct URL mock_get.assert_called_once_with("http://192.168.1.100/cm?cmnd=Status%205", timeout=5) logger.info("Test for non-bug-affected device (both match) passed") @patch('requests.get') def test_non_bug_affected_device_both_dont_match(self, mock_get): """Test a device not affected by the bug (both hostnames don't match unknown patterns).""" # Mock response for Status 5 command mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { 'StatusNET': { 'Hostname': 'my_proper_device' # Self-reported hostname doesn't match unknown patterns } } mock_get.return_value = mock_response # Test with a hostname that doesn't match unknown patterns (as reported by UniFi) # and with a self-reported hostname that also doesn't match unknown patterns result = self.discovery.is_hostname_unknown( hostname="my_device", # UniFi-reported hostname (doesn't match unknown patterns) patterns=self.test_patterns, from_unifi_os=True, # Enable Unifi Hostname bug handling ip="192.168.1.100" # Provide IP to query the device ) # The function should return False because neither hostname matches unknown patterns self.assertFalse(result) # Verify that requests.get was called with the correct URL mock_get.assert_called_once_with("http://192.168.1.100/cm?cmnd=Status%205", timeout=5) logger.info("Test for non-bug-affected device (both don't match) passed") def test_without_from_unifi_os(self): """Test without the from_unifi_os parameter.""" # Test with a hostname that matches unknown patterns result = self.discovery.is_hostname_unknown( hostname="tasmota_123", # Matches unknown patterns patterns=self.test_patterns, from_unifi_os=False, # Disable Unifi Hostname bug handling ip="192.168.1.100" # Provide IP (should be ignored since from_unifi_os is False) ) # The function should return True because the hostname matches unknown patterns # and from_unifi_os is False, so no bug handling is performed self.assertTrue(result) logger.info("Test without from_unifi_os passed") def test_without_ip(self): """Test without the IP parameter.""" # Test with a hostname that matches unknown patterns result = self.discovery.is_hostname_unknown( hostname="tasmota_123", # Matches unknown patterns patterns=self.test_patterns, from_unifi_os=True, # Enable Unifi Hostname bug handling ip=None # No IP provided, so can't query the device ) # The function should return True because the hostname matches unknown patterns # and no IP is provided, so no bug handling is performed self.assertTrue(result) logger.info("Test without IP passed") @patch('requests.get') def test_request_exception(self, mock_get): """Test handling of request exceptions.""" # Mock requests.get to raise an exception mock_get.side_effect = Exception("Test exception") # Test with a hostname that matches unknown patterns result = self.discovery.is_hostname_unknown( hostname="tasmota_123", # Matches unknown patterns patterns=self.test_patterns, from_unifi_os=True, # Enable Unifi Hostname bug handling ip="192.168.1.100" # Provide IP to query the device ) # The function should return True because the hostname matches unknown patterns # and the request failed, so no bug handling is performed self.assertTrue(result) # Verify that requests.get was called with the correct URL mock_get.assert_called_once_with("http://192.168.1.100/cm?cmnd=Status%205", timeout=5) logger.info("Test for request exception passed") @patch('requests.get') def test_invalid_json_response(self, mock_get): """Test handling of invalid JSON responses.""" # Mock response for Status 5 command mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.side_effect = ValueError("Invalid JSON") mock_get.return_value = mock_response # Test with a hostname that matches unknown patterns result = self.discovery.is_hostname_unknown( hostname="tasmota_123", # Matches unknown patterns patterns=self.test_patterns, from_unifi_os=True, # Enable Unifi Hostname bug handling ip="192.168.1.100" # Provide IP to query the device ) # The function should return True because the hostname matches unknown patterns # and the JSON parsing failed, so no bug handling is performed self.assertTrue(result) # Verify that requests.get was called with the correct URL mock_get.assert_called_once_with("http://192.168.1.100/cm?cmnd=Status%205", timeout=5) logger.info("Test for invalid JSON response passed") def main(): """Run the tests.""" unittest.main() if __name__ == "__main__": main()