11"""ESPHome set up tests."""
22
3+ from unittest .mock import AsyncMock
4+
5+ from aioesphomeapi import APIConnectionError
36import pytest
47
58from homeassistant .components .esphome import DOMAIN
9+ from homeassistant .components .esphome .const import CONF_NOISE_PSK
10+ from homeassistant .components .esphome .encryption_key_storage import (
11+ async_get_encryption_key_storage ,
12+ )
613from homeassistant .const import CONF_HOST , CONF_PASSWORD , CONF_PORT
714from homeassistant .core import HomeAssistant
815
916from tests .common import MockConfigEntry
1017
1118
1219@pytest .mark .usefixtures ("mock_client" , "mock_zeroconf" )
13- async def test_delete_entry (hass : HomeAssistant ) -> None :
14- """Test we can delete an entry without error."""
20+ async def test_remove_entry (hass : HomeAssistant ) -> None :
21+ """Test we can remove an entry without error."""
1522 entry = MockConfigEntry (
1623 domain = DOMAIN ,
1724 data = {CONF_HOST : "test.local" , CONF_PORT : 6053 , CONF_PASSWORD : "" },
@@ -22,3 +29,128 @@ async def test_delete_entry(hass: HomeAssistant) -> None:
2229 await hass .async_block_till_done ()
2330 assert await hass .config_entries .async_remove (entry .entry_id )
2431 await hass .async_block_till_done ()
32+
33+
34+ @pytest .mark .usefixtures ("mock_zeroconf" )
35+ async def test_remove_entry_clears_dynamic_encryption_key (
36+ hass : HomeAssistant ,
37+ mock_client ,
38+ mock_config_entry : MockConfigEntry ,
39+ ) -> None :
40+ """Test that removing an entry clears the dynamic encryption key from device and storage."""
41+ # Store the encryption key to simulate it was dynamically generated
42+ storage = await async_get_encryption_key_storage (hass )
43+ await storage .async_store_key (
44+ mock_config_entry .unique_id , mock_config_entry .data [CONF_NOISE_PSK ]
45+ )
46+ assert (
47+ await storage .async_get_key (mock_config_entry .unique_id )
48+ == mock_config_entry .data [CONF_NOISE_PSK ]
49+ )
50+
51+ mock_client .noise_encryption_set_key = AsyncMock (return_value = True )
52+
53+ assert await hass .config_entries .async_remove (mock_config_entry .entry_id )
54+ await hass .async_block_till_done ()
55+
56+ mock_client .connect .assert_called_once ()
57+ mock_client .noise_encryption_set_key .assert_called_once_with (b"" )
58+ mock_client .disconnect .assert_called_once ()
59+
60+ assert await storage .async_get_key (mock_config_entry .unique_id ) is None
61+
62+
63+ @pytest .mark .usefixtures ("mock_zeroconf" )
64+ async def test_remove_entry_no_noise_psk (hass : HomeAssistant , mock_client ) -> None :
65+ """Test that removing an entry without noise_psk does not attempt to clear encryption key."""
66+ mock_config_entry = MockConfigEntry (
67+ domain = DOMAIN ,
68+ data = {
69+ CONF_HOST : "test.local" ,
70+ CONF_PORT : 6053 ,
71+ # No CONF_NOISE_PSK
72+ },
73+ unique_id = "11:22:33:44:55:aa" ,
74+ )
75+ mock_config_entry .add_to_hass (hass )
76+
77+ mock_client .noise_encryption_set_key = AsyncMock (return_value = True )
78+
79+ assert await hass .config_entries .async_remove (mock_config_entry .entry_id )
80+ await hass .async_block_till_done ()
81+
82+ mock_client .noise_encryption_set_key .assert_not_called ()
83+
84+
85+ @pytest .mark .usefixtures ("mock_zeroconf" )
86+ async def test_remove_entry_user_provided_key (
87+ hass : HomeAssistant ,
88+ mock_client ,
89+ mock_config_entry : MockConfigEntry ,
90+ ) -> None :
91+ """Test that removing an entry with user-provided key does not clear it."""
92+ # Do not store the key in storage - simulates user-provided key
93+ storage = await async_get_encryption_key_storage (hass )
94+ assert await storage .async_get_key (mock_config_entry .unique_id ) is None
95+
96+ mock_client .noise_encryption_set_key = AsyncMock (return_value = True )
97+
98+ assert await hass .config_entries .async_remove (mock_config_entry .entry_id )
99+ await hass .async_block_till_done ()
100+
101+ mock_client .noise_encryption_set_key .assert_not_called ()
102+
103+
104+ @pytest .mark .usefixtures ("mock_zeroconf" )
105+ async def test_remove_entry_device_rejects_key_removal (
106+ hass : HomeAssistant ,
107+ mock_client ,
108+ mock_config_entry : MockConfigEntry ,
109+ ) -> None :
110+ """Test that when device rejects key removal, key remains in storage."""
111+ # Store the encryption key to simulate it was dynamically generated
112+ storage = await async_get_encryption_key_storage (hass )
113+ await storage .async_store_key (
114+ mock_config_entry .unique_id , mock_config_entry .data [CONF_NOISE_PSK ]
115+ )
116+
117+ mock_client .noise_encryption_set_key = AsyncMock (return_value = False )
118+
119+ assert await hass .config_entries .async_remove (mock_config_entry .entry_id )
120+ await hass .async_block_till_done ()
121+
122+ mock_client .connect .assert_called_once ()
123+ mock_client .noise_encryption_set_key .assert_called_once_with (b"" )
124+ mock_client .disconnect .assert_called_once ()
125+
126+ assert (
127+ await storage .async_get_key (mock_config_entry .unique_id )
128+ == mock_config_entry .data [CONF_NOISE_PSK ]
129+ )
130+
131+
132+ @pytest .mark .usefixtures ("mock_zeroconf" )
133+ async def test_remove_entry_connection_error (
134+ hass : HomeAssistant ,
135+ mock_client ,
136+ mock_config_entry : MockConfigEntry ,
137+ ) -> None :
138+ """Test that connection error during key clearing does not remove key from storage."""
139+ # Store the encryption key to simulate it was dynamically generated
140+ storage = await async_get_encryption_key_storage (hass )
141+ await storage .async_store_key (
142+ mock_config_entry .unique_id , mock_config_entry .data [CONF_NOISE_PSK ]
143+ )
144+
145+ mock_client .connect = AsyncMock (side_effect = APIConnectionError ("Connection failed" ))
146+
147+ assert await hass .config_entries .async_remove (mock_config_entry .entry_id )
148+ await hass .async_block_till_done ()
149+
150+ mock_client .connect .assert_called_once ()
151+ mock_client .disconnect .assert_called_once ()
152+
153+ assert (
154+ await storage .async_get_key (mock_config_entry .unique_id )
155+ == mock_config_entry .data [CONF_NOISE_PSK ]
156+ )
0 commit comments