TasmotaManager/tests/test_unifi_hostname_bug_fix.py
2025-10-28 00:21:08 +00:00

237 lines
9.6 KiB
Python

#!/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()