mirror of
				https://github.com/home-assistant/supervisor.git
				synced 2025-10-31 14:39:30 +00:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			reject-cor
			...
			copilot/ad
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 03d5cfcbe0 | ||
| ![copilot-swe-agent[bot]](/assets/img/avatar_default.png)  | 1b451ce357 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 02c4fd4a8c | ||
|   | 0bee5c6f37 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 9c0174f1fd | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | dc3d8b9266 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 06d96db55b | 
							
								
								
									
										9
									
								
								.github/workflows/builder.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/builder.yml
									
									
									
									
										vendored
									
									
								
							| @@ -320,15 +320,6 @@ jobs: | ||||
|             exit 1 | ||||
|           fi | ||||
|  | ||||
|       - name: Wait for Home Assistant Core to start | ||||
|         run: | | ||||
|           echo "Waiting for Home Assistant Core to start" | ||||
|           timeout 10m ha supervisor logs -f -n 10000 -b 0 | grep -q "Detect a running Home Assistant instance" | ||||
|           if [ "$?" != "0" ]; then | ||||
|             echo "Home Assistant Core did not start within 10 minutes" | ||||
|             exit 1 | ||||
|           fi | ||||
|  | ||||
|       - name: Create full backup | ||||
|         id: backup | ||||
|         run: | | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -386,7 +386,7 @@ jobs: | ||||
|             -o console_output_style=count \ | ||||
|             tests | ||||
|       - name: Upload coverage artifact | ||||
|         uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | ||||
|         uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 | ||||
|         with: | ||||
|           name: coverage | ||||
|           path: .coverage | ||||
| @@ -417,7 +417,7 @@ jobs: | ||||
|           echo "Failed to restore Python virtual environment from cache" | ||||
|           exit 1 | ||||
|       - name: Download all coverage artifacts | ||||
|         uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 | ||||
|         uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 | ||||
|         with: | ||||
|           name: coverage | ||||
|           path: coverage/ | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| aiodns==3.5.0 | ||||
| aiohttp==3.13.1 | ||||
| aiohttp==3.13.2 | ||||
| atomicwrites-homeassistant==1.4.1 | ||||
| attrs==25.4.0 | ||||
| awesomeversion==25.8.0 | ||||
| @@ -17,7 +17,7 @@ faust-cchardet==2.1.19 | ||||
| gitpython==3.1.45 | ||||
| jinja2==3.1.6 | ||||
| log-rate-limit==1.4.2 | ||||
| orjson==3.11.3 | ||||
| orjson==3.11.4 | ||||
| pulsectl==24.12.0 | ||||
| pyudev==0.24.4 | ||||
| PyYAML==6.0.3 | ||||
|   | ||||
| @@ -1562,7 +1562,15 @@ class Addon(AddonModel): | ||||
|                 ) | ||||
|                 break | ||||
|  | ||||
|             await asyncio.sleep(WATCHDOG_RETRY_SECONDS) | ||||
|             # Exponential backoff to spread retries over the throttle window | ||||
|             delay = WATCHDOG_RETRY_SECONDS * (1 << max(attempts - 1, 0)) | ||||
|             _LOGGER.debug( | ||||
|                 "Watchdog will retry addon %s in %s seconds (attempt %s)", | ||||
|                 self.name, | ||||
|                 delay, | ||||
|                 attempts + 1, | ||||
|             ) | ||||
|             await asyncio.sleep(delay) | ||||
|  | ||||
|     async def container_state_changed(self, event: DockerContainerStateEvent) -> None: | ||||
|         """Set addon state from container state.""" | ||||
|   | ||||
| @@ -371,12 +371,6 @@ class HomeAssistant(FileConfiguration, CoreSysAttributes): | ||||
|                 _LOGGER.error, | ||||
|             ) from err | ||||
|  | ||||
|         if not resp: | ||||
|             raise HomeAssistantBackupError( | ||||
|                 "Preparing backup of Home Assistant Core failed. No response from HA Core.", | ||||
|                 _LOGGER.error, | ||||
|             ) | ||||
|  | ||||
|         if resp and not resp.get(ATTR_SUCCESS): | ||||
|             raise HomeAssistantBackupError( | ||||
|                 f"Preparing backup of Home Assistant Core failed due to: {resp.get(ATTR_ERROR, {}).get(ATTR_MESSAGE, '')}. Check HA Core logs.", | ||||
|   | ||||
| @@ -225,10 +225,6 @@ class HomeAssistantWebSocket(CoreSysAttributes): | ||||
|         # since it makes a new socket connection and we already have one. | ||||
|         if not connected and not await self.sys_homeassistant.api.check_api_state(): | ||||
|             # No core access, don't try. | ||||
|             _LOGGER.debug( | ||||
|                 "Home Assistant API is not accessible. Not sending WS message: %s", | ||||
|                 message, | ||||
|             ) | ||||
|             return False | ||||
|  | ||||
|         if not self._client: | ||||
|   | ||||
| @@ -7,8 +7,8 @@ import pytest | ||||
|  | ||||
| from supervisor.coresys import CoreSys | ||||
| from supervisor.dbus.const import DeviceType | ||||
| from supervisor.host.configuration import Interface, VlanConfig | ||||
| from supervisor.host.const import InterfaceType | ||||
| from supervisor.host.configuration import Interface, VlanConfig, WifiConfig | ||||
| from supervisor.host.const import AuthMethod, InterfaceType, WifiMode | ||||
|  | ||||
| from tests.dbus_service_mocks.base import DBusServiceMock | ||||
| from tests.dbus_service_mocks.network_connection_settings import ( | ||||
| @@ -291,3 +291,237 @@ async def test_equals_dbus_interface_eth0_10_real( | ||||
|  | ||||
|     # Test should pass with matching VLAN config | ||||
|     assert test_vlan_interface.equals_dbus_interface(network_interface) is True | ||||
|  | ||||
|  | ||||
| def test_map_nm_wifi_non_wireless_interface(): | ||||
|     """Test _map_nm_wifi returns None for non-wireless interface.""" | ||||
|     # Mock non-wireless interface | ||||
|     mock_interface = Mock() | ||||
|     mock_interface.type = DeviceType.ETHERNET | ||||
|     mock_interface.settings = Mock() | ||||
|  | ||||
|     result = Interface._map_nm_wifi(mock_interface) | ||||
|     assert result is None | ||||
|  | ||||
|  | ||||
| def test_map_nm_wifi_no_settings(): | ||||
|     """Test _map_nm_wifi returns None when interface has no settings.""" | ||||
|     # Mock wireless interface without settings | ||||
|     mock_interface = Mock() | ||||
|     mock_interface.type = DeviceType.WIRELESS | ||||
|     mock_interface.settings = None | ||||
|  | ||||
|     result = Interface._map_nm_wifi(mock_interface) | ||||
|     assert result is None | ||||
|  | ||||
|  | ||||
| def test_map_nm_wifi_open_authentication(): | ||||
|     """Test _map_nm_wifi with open authentication (no security).""" | ||||
|     # Mock wireless interface with open authentication | ||||
|     mock_interface = Mock() | ||||
|     mock_interface.type = DeviceType.WIRELESS | ||||
|     mock_interface.settings = Mock() | ||||
|     mock_interface.settings.wireless_security = None | ||||
|     mock_interface.settings.wireless = Mock() | ||||
|     mock_interface.settings.wireless.ssid = "TestSSID" | ||||
|     mock_interface.settings.wireless.mode = "infrastructure" | ||||
|     mock_interface.wireless = None | ||||
|     mock_interface.interface_name = "wlan0" | ||||
|  | ||||
|     result = Interface._map_nm_wifi(mock_interface) | ||||
|  | ||||
|     assert result is not None | ||||
|     assert isinstance(result, WifiConfig) | ||||
|     assert result.mode == WifiMode.INFRASTRUCTURE | ||||
|     assert result.ssid == "TestSSID" | ||||
|     assert result.auth == AuthMethod.OPEN | ||||
|     assert result.psk is None | ||||
|     assert result.signal is None | ||||
|  | ||||
|  | ||||
| def test_map_nm_wifi_wep_authentication(): | ||||
|     """Test _map_nm_wifi with WEP authentication.""" | ||||
|     # Mock wireless interface with WEP authentication | ||||
|     mock_interface = Mock() | ||||
|     mock_interface.type = DeviceType.WIRELESS | ||||
|     mock_interface.settings = Mock() | ||||
|     mock_interface.settings.wireless_security = Mock() | ||||
|     mock_interface.settings.wireless_security.key_mgmt = "none" | ||||
|     mock_interface.settings.wireless_security.psk = None | ||||
|     mock_interface.settings.wireless = Mock() | ||||
|     mock_interface.settings.wireless.ssid = "WEPNetwork" | ||||
|     mock_interface.settings.wireless.mode = "infrastructure" | ||||
|     mock_interface.wireless = None | ||||
|     mock_interface.interface_name = "wlan0" | ||||
|  | ||||
|     result = Interface._map_nm_wifi(mock_interface) | ||||
|  | ||||
|     assert result is not None | ||||
|     assert isinstance(result, WifiConfig) | ||||
|     assert result.auth == AuthMethod.WEP | ||||
|     assert result.ssid == "WEPNetwork" | ||||
|     assert result.psk is None | ||||
|  | ||||
|  | ||||
| def test_map_nm_wifi_wpa_psk_authentication(): | ||||
|     """Test _map_nm_wifi with WPA-PSK authentication.""" | ||||
|     # Mock wireless interface with WPA-PSK authentication | ||||
|     mock_interface = Mock() | ||||
|     mock_interface.type = DeviceType.WIRELESS | ||||
|     mock_interface.settings = Mock() | ||||
|     mock_interface.settings.wireless_security = Mock() | ||||
|     mock_interface.settings.wireless_security.key_mgmt = "wpa-psk" | ||||
|     mock_interface.settings.wireless_security.psk = "SecretPassword123" | ||||
|     mock_interface.settings.wireless = Mock() | ||||
|     mock_interface.settings.wireless.ssid = "SecureNetwork" | ||||
|     mock_interface.settings.wireless.mode = "infrastructure" | ||||
|     mock_interface.wireless = None | ||||
|     mock_interface.interface_name = "wlan0" | ||||
|  | ||||
|     result = Interface._map_nm_wifi(mock_interface) | ||||
|  | ||||
|     assert result is not None | ||||
|     assert isinstance(result, WifiConfig) | ||||
|     assert result.auth == AuthMethod.WPA_PSK | ||||
|     assert result.ssid == "SecureNetwork" | ||||
|     assert result.psk == "SecretPassword123" | ||||
|  | ||||
|  | ||||
| def test_map_nm_wifi_unsupported_authentication(): | ||||
|     """Test _map_nm_wifi returns None for unsupported authentication method.""" | ||||
|     # Mock wireless interface with unsupported authentication | ||||
|     mock_interface = Mock() | ||||
|     mock_interface.type = DeviceType.WIRELESS | ||||
|     mock_interface.settings = Mock() | ||||
|     mock_interface.settings.wireless_security = Mock() | ||||
|     mock_interface.settings.wireless_security.key_mgmt = "wpa-eap"  # Unsupported | ||||
|     mock_interface.settings.wireless = Mock() | ||||
|     mock_interface.settings.wireless.ssid = "EnterpriseNetwork" | ||||
|     mock_interface.interface_name = "wlan0" | ||||
|  | ||||
|     result = Interface._map_nm_wifi(mock_interface) | ||||
|  | ||||
|     assert result is None | ||||
|  | ||||
|  | ||||
| def test_map_nm_wifi_different_modes(): | ||||
|     """Test _map_nm_wifi with different wifi modes.""" | ||||
|     modes_to_test = [ | ||||
|         ("infrastructure", WifiMode.INFRASTRUCTURE), | ||||
|         ("mesh", WifiMode.MESH), | ||||
|         ("adhoc", WifiMode.ADHOC), | ||||
|         ("ap", WifiMode.AP), | ||||
|     ] | ||||
|  | ||||
|     for mode_value, expected_mode in modes_to_test: | ||||
|         mock_interface = Mock() | ||||
|         mock_interface.type = DeviceType.WIRELESS | ||||
|         mock_interface.settings = Mock() | ||||
|         mock_interface.settings.wireless_security = None | ||||
|         mock_interface.settings.wireless = Mock() | ||||
|         mock_interface.settings.wireless.ssid = "TestSSID" | ||||
|         mock_interface.settings.wireless.mode = mode_value | ||||
|         mock_interface.wireless = None | ||||
|         mock_interface.interface_name = "wlan0" | ||||
|  | ||||
|         result = Interface._map_nm_wifi(mock_interface) | ||||
|  | ||||
|         assert result is not None | ||||
|         assert result.mode == expected_mode | ||||
|  | ||||
|  | ||||
| def test_map_nm_wifi_with_signal(): | ||||
|     """Test _map_nm_wifi with wireless signal strength.""" | ||||
|     # Mock wireless interface with active connection and signal | ||||
|     mock_interface = Mock() | ||||
|     mock_interface.type = DeviceType.WIRELESS | ||||
|     mock_interface.settings = Mock() | ||||
|     mock_interface.settings.wireless_security = None | ||||
|     mock_interface.settings.wireless = Mock() | ||||
|     mock_interface.settings.wireless.ssid = "TestSSID" | ||||
|     mock_interface.settings.wireless.mode = "infrastructure" | ||||
|     mock_interface.wireless = Mock() | ||||
|     mock_interface.wireless.active = Mock() | ||||
|     mock_interface.wireless.active.strength = 75 | ||||
|     mock_interface.interface_name = "wlan0" | ||||
|  | ||||
|     result = Interface._map_nm_wifi(mock_interface) | ||||
|  | ||||
|     assert result is not None | ||||
|     assert result.signal == 75 | ||||
|  | ||||
|  | ||||
| def test_map_nm_wifi_without_signal(): | ||||
|     """Test _map_nm_wifi without wireless signal (no active connection).""" | ||||
|     # Mock wireless interface without active connection | ||||
|     mock_interface = Mock() | ||||
|     mock_interface.type = DeviceType.WIRELESS | ||||
|     mock_interface.settings = Mock() | ||||
|     mock_interface.settings.wireless_security = None | ||||
|     mock_interface.settings.wireless = Mock() | ||||
|     mock_interface.settings.wireless.ssid = "TestSSID" | ||||
|     mock_interface.settings.wireless.mode = "infrastructure" | ||||
|     mock_interface.wireless = None | ||||
|     mock_interface.interface_name = "wlan0" | ||||
|  | ||||
|     result = Interface._map_nm_wifi(mock_interface) | ||||
|  | ||||
|     assert result is not None | ||||
|     assert result.signal is None | ||||
|  | ||||
|  | ||||
| def test_map_nm_wifi_wireless_no_active_ap(): | ||||
|     """Test _map_nm_wifi with wireless object but no active access point.""" | ||||
|     # Mock wireless interface with wireless object but no active AP | ||||
|     mock_interface = Mock() | ||||
|     mock_interface.type = DeviceType.WIRELESS | ||||
|     mock_interface.settings = Mock() | ||||
|     mock_interface.settings.wireless_security = None | ||||
|     mock_interface.settings.wireless = Mock() | ||||
|     mock_interface.settings.wireless.ssid = "TestSSID" | ||||
|     mock_interface.settings.wireless.mode = "infrastructure" | ||||
|     mock_interface.wireless = Mock() | ||||
|     mock_interface.wireless.active = None | ||||
|     mock_interface.interface_name = "wlan0" | ||||
|  | ||||
|     result = Interface._map_nm_wifi(mock_interface) | ||||
|  | ||||
|     assert result is not None | ||||
|     assert result.signal is None | ||||
|  | ||||
|  | ||||
| def test_map_nm_wifi_no_wireless_settings(): | ||||
|     """Test _map_nm_wifi when wireless settings are missing.""" | ||||
|     # Mock wireless interface without wireless settings | ||||
|     mock_interface = Mock() | ||||
|     mock_interface.type = DeviceType.WIRELESS | ||||
|     mock_interface.settings = Mock() | ||||
|     mock_interface.settings.wireless_security = None | ||||
|     mock_interface.settings.wireless = None | ||||
|     mock_interface.wireless = None | ||||
|     mock_interface.interface_name = "wlan0" | ||||
|  | ||||
|     result = Interface._map_nm_wifi(mock_interface) | ||||
|  | ||||
|     assert result is not None | ||||
|     assert result.ssid == "" | ||||
|     assert result.mode == WifiMode.INFRASTRUCTURE  # Default mode | ||||
|  | ||||
|  | ||||
| def test_map_nm_wifi_no_wireless_mode(): | ||||
|     """Test _map_nm_wifi when wireless mode is not specified.""" | ||||
|     # Mock wireless interface without mode specified | ||||
|     mock_interface = Mock() | ||||
|     mock_interface.type = DeviceType.WIRELESS | ||||
|     mock_interface.settings = Mock() | ||||
|     mock_interface.settings.wireless_security = None | ||||
|     mock_interface.settings.wireless = Mock() | ||||
|     mock_interface.settings.wireless.ssid = "TestSSID" | ||||
|     mock_interface.settings.wireless.mode = None | ||||
|     mock_interface.wireless = None | ||||
|     mock_interface.interface_name = "wlan0" | ||||
|  | ||||
|     result = Interface._map_nm_wifi(mock_interface) | ||||
|  | ||||
|     assert result is not None | ||||
|     assert result.mode == WifiMode.INFRASTRUCTURE  # Default mode | ||||
|   | ||||
		Reference in New Issue
	
	Block a user