From d12118a425dd550b392556dbf46e898488a3c66c Mon Sep 17 00:00:00 2001
From: epenet <6771947+epenet@users.noreply.github.com>
Date: Tue, 8 Mar 2022 11:02:00 +0100
Subject: [PATCH] Fix reauth trigger in SamsungTV (#67850)

Co-authored-by: epenet <epenet@users.noreply.github.com>
---
 homeassistant/components/samsungtv/bridge.py  | 17 ++++--
 .../components/samsungtv/test_media_player.py | 57 +++++++++++++------
 2 files changed, 52 insertions(+), 22 deletions(-)

diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py
index 99e9f877413..62c94e17f4d 100644
--- a/homeassistant/components/samsungtv/bridge.py
+++ b/homeassistant/components/samsungtv/bridge.py
@@ -14,7 +14,7 @@ from samsungtvws.async_rest import SamsungTVAsyncRest
 from samsungtvws.command import SamsungTVCommand
 from samsungtvws.exceptions import ConnectionFailure, HttpApiError
 from samsungtvws.remote import ChannelEmitCommand, SendRemoteKey
-from websockets.exceptions import WebSocketException
+from websockets.exceptions import ConnectionClosedError, WebSocketException
 
 from homeassistant.const import (
     CONF_HOST,
@@ -461,15 +461,24 @@ class SamsungTVWSBridge(SamsungTVBridge):
             )
             try:
                 await self._remote.start_listening()
-            # This is only happening when the auth was switched to DENY
-            # A removed auth will lead to socket timeout because waiting for auth popup is just an open socket
-            except ConnectionFailure as err:
+            except ConnectionClosedError as err:
+                # This is only happening when the auth was switched to DENY
+                # A removed auth will lead to socket timeout because waiting
+                # for auth popup is just an open socket
                 LOGGER.info(
                     "Failed to get remote for %s, re-authentication required: %s",
                     self.host,
                     err.__repr__(),
                 )
                 self._notify_reauth_callback()
+            except ConnectionFailure as err:
+                LOGGER.warning(
+                    "Unexpected ConnectionFailure trying to get remote for %s, "
+                    "please report this issue: %s",
+                    self.host,
+                    err.__repr__(),
+                )
+                self._remote = None
             except (WebSocketException, AsyncioTimeoutError, OSError) as err:
                 LOGGER.debug(
                     "Failed to get remote for %s: %s", self.host, err.__repr__()
diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py
index e5520098a4a..c38dd2639b1 100644
--- a/tests/components/samsungtv/test_media_player.py
+++ b/tests/components/samsungtv/test_media_player.py
@@ -10,7 +10,7 @@ from samsungtvws.async_remote import SamsungTVWSAsyncRemote
 from samsungtvws.command import SamsungTVSleepCommand
 from samsungtvws.exceptions import ConnectionFailure, HttpApiError
 from samsungtvws.remote import ChannelEmitCommand, SendRemoteKey
-from websockets.exceptions import WebSocketException
+from websockets.exceptions import ConnectionClosedError, WebSocketException
 
 from homeassistant.components.media_player import MediaPlayerDeviceClass
 from homeassistant.components.media_player.const import (
@@ -369,27 +369,48 @@ async def test_update_access_denied(hass: HomeAssistant, mock_now: datetime) ->
     assert state.state == STATE_UNAVAILABLE
 
 
-async def test_update_connection_failure(
+async def test_update_ws_connection_failure(
+    hass: HomeAssistant,
+    mock_now: datetime,
+    remotews: Mock,
+    caplog: pytest.LogCaptureFixture,
+) -> None:
+    """Testing update tv connection failure exception."""
+    await setup_samsungtv(hass, MOCK_CONFIGWS)
+
+    with patch.object(
+        remotews,
+        "start_listening",
+        side_effect=ConnectionFailure('{"event": "ms.voiceApp.hide"}'),
+    ), patch.object(remotews, "is_alive", return_value=False):
+        next_update = mock_now + timedelta(minutes=5)
+        with patch("homeassistant.util.dt.utcnow", return_value=next_update):
+            async_fire_time_changed(hass, next_update)
+        await hass.async_block_till_done()
+
+    assert (
+        "Unexpected ConnectionFailure trying to get remote for fake_host, please "
+        'report this issue: ConnectionFailure(\'{"event": "ms.voiceApp.hide"}\')'
+        in caplog.text
+    )
+
+    state = hass.states.get(ENTITY_ID)
+    assert state.state == STATE_OFF
+
+
+async def test_update_ws_connection_closed(
     hass: HomeAssistant, mock_now: datetime, remotews: Mock
 ) -> None:
     """Testing update tv connection failure exception."""
-    with patch(
-        "homeassistant.components.samsungtv.bridge.Remote",
-        side_effect=[OSError("Boom"), DEFAULT_MOCK],
-    ):
-        await setup_samsungtv(hass, MOCK_CONFIGWS)
+    await setup_samsungtv(hass, MOCK_CONFIGWS)
 
-        with patch.object(
-            remotews, "start_listening", side_effect=ConnectionFailure("Boom")
-        ), patch.object(remotews, "is_alive", return_value=False):
-            next_update = mock_now + timedelta(minutes=5)
-            with patch("homeassistant.util.dt.utcnow", return_value=next_update):
-                async_fire_time_changed(hass, next_update)
-            await hass.async_block_till_done()
-            next_update = mock_now + timedelta(minutes=10)
-            with patch("homeassistant.util.dt.utcnow", return_value=next_update):
-                async_fire_time_changed(hass, next_update)
-            await hass.async_block_till_done()
+    with patch.object(
+        remotews, "start_listening", side_effect=ConnectionClosedError(None, None)
+    ), patch.object(remotews, "is_alive", return_value=False):
+        next_update = mock_now + timedelta(minutes=5)
+        with patch("homeassistant.util.dt.utcnow", return_value=next_update):
+            async_fire_time_changed(hass, next_update)
+        await hass.async_block_till_done()
 
     assert [
         flow
-- 
GitLab