From 6d12e764b7dddb9eb5e7b7f889174dad9ff12e46 Mon Sep 17 00:00:00 2001
From: Allen Porter <allen@thebends.org>
Date: Sat, 12 Dec 2020 01:17:43 -0800
Subject: [PATCH] Increase test coverage for nest camera (#44144)

---
 .coveragerc                                 |  1 -
 homeassistant/components/nest/camera_sdm.py |  6 +-
 tests/components/nest/camera_sdm_test.py    | 85 +++++++++++++++++++++
 3 files changed, 88 insertions(+), 4 deletions(-)

diff --git a/.coveragerc b/.coveragerc
index b267f6967ef..9e1ec415006 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -578,7 +578,6 @@ omit =
     homeassistant/components/nest/binary_sensor.py
     homeassistant/components/nest/camera.py
     homeassistant/components/nest/camera_legacy.py
-    homeassistant/components/nest/camera_sdm.py
     homeassistant/components/nest/climate.py
     homeassistant/components/nest/climate_legacy.py
     homeassistant/components/nest/climate_sdm.py
diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py
index cec35eeca29..37bd2fed8a6 100644
--- a/homeassistant/components/nest/camera_sdm.py
+++ b/homeassistant/components/nest/camera_sdm.py
@@ -95,9 +95,10 @@ class NestCamera(Camera):
     @property
     def supported_features(self):
         """Flag supported features."""
+        supported_features = 0
         if CameraLiveStreamTrait.NAME in self._device.traits:
-            return SUPPORT_STREAM
-        return 0
+            supported_features |= SUPPORT_STREAM
+        return supported_features
 
     async def stream_source(self):
         """Return the source of the stream."""
@@ -131,7 +132,6 @@ class NestCamera(Camera):
         if not self._stream:
             return
         _LOGGER.debug("Extending stream url")
-        self._stream_refresh_unsub = None
         try:
             self._stream = await self._stream.extend_rtsp_stream()
         except GoogleNestException as err:
diff --git a/tests/components/nest/camera_sdm_test.py b/tests/components/nest/camera_sdm_test.py
index 4a018305bcf..69b413ba51a 100644
--- a/tests/components/nest/camera_sdm_test.py
+++ b/tests/components/nest/camera_sdm_test.py
@@ -9,9 +9,11 @@ import datetime
 
 import aiohttp
 from google_nest_sdm.device import Device
+import pytest
 
 from homeassistant.components import camera
 from homeassistant.components.camera import STATE_IDLE
+from homeassistant.exceptions import HomeAssistantError
 from homeassistant.util.dt import utcnow
 
 from .common import async_setup_sdm_platform
@@ -140,6 +142,36 @@ async def test_camera_stream(hass, auth):
     assert image.content == b"image bytes"
 
 
+async def test_camera_stream_missing_trait(hass, auth):
+    """Test fetching a video stream when not supported by the API."""
+    traits = {
+        "sdm.devices.traits.Info": {
+            "customName": "My Camera",
+        },
+        "sdm.devices.traits.CameraImage": {
+            "maxImageResolution": {
+                "width": 800,
+                "height": 600,
+            }
+        },
+    }
+
+    await async_setup_camera(hass, traits, auth=auth)
+
+    assert len(hass.states.async_all()) == 1
+    cam = hass.states.get("camera.my_camera")
+    assert cam is not None
+    assert cam.state == STATE_IDLE
+
+    stream_source = await camera.async_get_stream_source(hass, "camera.my_camera")
+    assert stream_source is None
+
+    # Currently on support getting the image from a live stream
+    with pytest.raises(HomeAssistantError):
+        image = await camera.async_get_image(hass, "camera.my_camera")
+        assert image is None
+
+
 async def test_refresh_expired_stream_token(hass, auth):
     """Test a camera stream expiration and refresh."""
     now = utcnow()
@@ -220,6 +252,59 @@ async def test_refresh_expired_stream_token(hass, auth):
     assert stream_source == "rtsp://some/url?auth=g.3.streamingToken"
 
 
+async def test_stream_response_already_expired(hass, auth):
+    """Test a API response returning an expired stream url."""
+    now = utcnow()
+    stream_1_expiration = now + datetime.timedelta(seconds=-90)
+    stream_2_expiration = now + datetime.timedelta(seconds=+90)
+    auth.responses = [
+        aiohttp.web.json_response(
+            {
+                "results": {
+                    "streamUrls": {
+                        "rtspUrl": "rtsp://some/url?auth=g.1.streamingToken"
+                    },
+                    "streamExtensionToken": "g.1.extensionToken",
+                    "streamToken": "g.1.streamingToken",
+                    "expiresAt": stream_1_expiration.isoformat(timespec="seconds"),
+                },
+            }
+        ),
+        aiohttp.web.json_response(
+            {
+                "results": {
+                    "streamUrls": {
+                        "rtspUrl": "rtsp://some/url?auth=g.2.streamingToken"
+                    },
+                    "streamExtensionToken": "g.2.extensionToken",
+                    "streamToken": "g.2.streamingToken",
+                    "expiresAt": stream_2_expiration.isoformat(timespec="seconds"),
+                },
+            }
+        ),
+    ]
+    await async_setup_camera(
+        hass,
+        DEVICE_TRAITS,
+        auth=auth,
+    )
+
+    assert len(hass.states.async_all()) == 1
+    cam = hass.states.get("camera.my_camera")
+    assert cam is not None
+    assert cam.state == STATE_IDLE
+
+    # The stream is expired, but we return it anyway
+    stream_source = await camera.async_get_stream_source(hass, "camera.my_camera")
+    assert stream_source == "rtsp://some/url?auth=g.1.streamingToken"
+
+    await fire_alarm(hass, now)
+
+    # Second attempt sees that the stream is expired and refreshes
+    stream_source = await camera.async_get_stream_source(hass, "camera.my_camera")
+    assert stream_source == "rtsp://some/url?auth=g.2.streamingToken"
+
+
 async def test_camera_removed(hass, auth):
     """Test case where entities are removed and stream tokens expired."""
     now = utcnow()
-- 
GitLab