From 3cba25a89263d06065ede85041f24c4aca2ee232 Mon Sep 17 00:00:00 2001
From: epenet <6771947+epenet@users.noreply.github.com>
Date: Wed, 14 Oct 2020 10:19:12 +0200
Subject: [PATCH] Add test coverage for onewire (#40786)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
---
 .coveragerc                                   |   2 -
 homeassistant/components/onewire/sensor.py    |  28 +--
 tests/components/onewire/__init__.py          |   1 +
 .../onewire/test_entity_owserver.py           | 200 +++++++++++++++++
 .../components/onewire/test_entity_sysbus.py  | 210 +++++++++---------
 tests/components/onewire/test_sensor.py       |  43 ++++
 6 files changed, 359 insertions(+), 125 deletions(-)
 create mode 100644 tests/components/onewire/__init__.py
 create mode 100644 tests/components/onewire/test_entity_owserver.py
 create mode 100644 tests/components/onewire/test_sensor.py

diff --git a/.coveragerc b/.coveragerc
index 8ecba062363..2467ef9fda0 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -609,8 +609,6 @@ omit =
     homeassistant/components/omnilogic/__init__.py
     homeassistant/components/omnilogic/common.py
     homeassistant/components/omnilogic/sensor.py
-    homeassistant/components/onewire/const.py
-    homeassistant/components/onewire/sensor.py
     homeassistant/components/onkyo/media_player.py
     homeassistant/components/onvif/__init__.py
     homeassistant/components/onvif/base.py
diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py
index 4c6564b2fec..d6e8ef50f46 100644
--- a/homeassistant/components/onewire/sensor.py
+++ b/homeassistant/components/onewire/sensor.py
@@ -3,12 +3,7 @@ from glob import glob
 import logging
 import os
 
-from pi1wire import (
-    InvalidCRCException,
-    NotFoundSensorException,
-    Pi1Wire,
-    UnsupportResponseException,
-)
+from pi1wire import InvalidCRCException, Pi1Wire, UnsupportResponseException
 from pyownet import protocol
 import voluptuous as vol
 
@@ -145,7 +140,9 @@ def get_entities(config):
         conf_type = CONF_TYPE_OWSERVER
     elif base_dir == DEFAULT_SYSBUS_MOUNT_DIR:
         conf_type = CONF_TYPE_SYSBUS
-    else:
+    else:  # pragma: no cover
+        # This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated.
+        # https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices
         conf_type = CONF_TYPE_OWFS
 
     entities = []
@@ -230,7 +227,9 @@ def get_entities(config):
             )
 
     # We have an owfs mounted
-    else:
+    else:  # pragma: no cover
+        # This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated.
+        # https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices
         _LOGGER.debug("Initializing using OWFS %s", base_dir)
         for family_file_path in glob(os.path.join(base_dir, "*", "family")):
             with open(family_file_path) as family_file:
@@ -303,9 +302,7 @@ class OneWireProxy(OneWire):
 
     def _read_value_ownet(self):
         """Read a value from the owserver."""
-        if self._owproxy:
-            return self._owproxy.read(self._device_file).decode().lstrip()
-        return None
+        return self._owproxy.read(self._device_file).decode().lstrip()
 
     def update(self):
         """Get the latest data from the device."""
@@ -339,15 +336,18 @@ class OneWireDirect(OneWire):
         except (
             FileNotFoundError,
             InvalidCRCException,
-            NotFoundSensorException,
             UnsupportResponseException,
         ) as ex:
             _LOGGER.warning("Cannot read from sensor %s: %s", self._device_file, ex)
         self._state = value
 
 
-class OneWireOWFS(OneWire):
-    """Implementation of a 1-Wire sensor through owfs."""
+class OneWireOWFS(OneWire):  # pragma: no cover
+    """Implementation of a 1-Wire sensor through owfs.
+
+    This part of the implementation does not conform to policy regarding 3rd-party libraries, and will not longer be updated.
+    https://developers.home-assistant.io/docs/creating_platform_code_review/#5-communication-with-devicesservices
+    """
 
     def _read_value_raw(self):
         """Read the value as it is returned by the sensor."""
diff --git a/tests/components/onewire/__init__.py b/tests/components/onewire/__init__.py
new file mode 100644
index 00000000000..e5de65bc71e
--- /dev/null
+++ b/tests/components/onewire/__init__.py
@@ -0,0 +1 @@
+"""Tests for 1-Wire integration."""
diff --git a/tests/components/onewire/test_entity_owserver.py b/tests/components/onewire/test_entity_owserver.py
new file mode 100644
index 00000000000..290c744ab94
--- /dev/null
+++ b/tests/components/onewire/test_entity_owserver.py
@@ -0,0 +1,200 @@
+"""Tests for 1-Wire devices connected on OWServer."""
+from unittest.mock import patch
+
+from pyownet.protocol import Error as ProtocolError
+import pytest
+
+from homeassistant.components.onewire.const import DEFAULT_OWSERVER_PORT, DOMAIN
+from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
+from homeassistant.const import PERCENTAGE, TEMP_CELSIUS
+from homeassistant.setup import async_setup_component
+
+from tests.common import mock_registry
+
+MOCK_CONFIG = {
+    "sensor": {
+        "platform": DOMAIN,
+        "host": "localhost",
+        "port": DEFAULT_OWSERVER_PORT,
+        "names": {
+            "10.111111111111": "My DS18B20",
+        },
+    }
+}
+
+MOCK_DEVICE_SENSORS = {
+    "00.111111111111": {"sensors": []},
+    "10.111111111111": {
+        "sensors": [
+            {
+                "entity_id": "sensor.my_ds18b20_temperature",
+                "unique_id": "/10.111111111111/temperature",
+                "injected_value": b"    25.123",
+                "result": "25.1",
+                "unit": TEMP_CELSIUS,
+            },
+        ]
+    },
+    "1D.111111111111": {
+        "sensors": [
+            {
+                "entity_id": "sensor.1d_111111111111_counter_a",
+                "unique_id": "/1D.111111111111/counter.A",
+                "injected_value": b"    251123",
+                "result": "251123",
+                "unit": "count",
+            },
+            {
+                "entity_id": "sensor.1d_111111111111_counter_b",
+                "unique_id": "/1D.111111111111/counter.B",
+                "injected_value": b"    248125",
+                "result": "248125",
+                "unit": "count",
+            },
+        ]
+    },
+    "22.111111111111": {
+        "sensors": [
+            {
+                "entity_id": "sensor.22_111111111111_temperature",
+                "unique_id": "/22.111111111111/temperature",
+                "injected_value": ProtocolError,
+                "result": "unknown",
+                "unit": TEMP_CELSIUS,
+            },
+        ]
+    },
+    "28.111111111111": {
+        "sensors": [
+            {
+                "entity_id": "sensor.28_111111111111_temperature",
+                "unique_id": "/28.111111111111/temperature",
+                "injected_value": b"    26.984",
+                "result": "27.0",
+                "unit": TEMP_CELSIUS,
+            },
+        ]
+    },
+    "3B.111111111111": {
+        "sensors": [
+            {
+                "entity_id": "sensor.3b_111111111111_temperature",
+                "unique_id": "/3B.111111111111/temperature",
+                "injected_value": b"    28.243",
+                "result": "28.2",
+                "unit": TEMP_CELSIUS,
+            },
+        ]
+    },
+    "42.111111111111": {
+        "sensors": [
+            {
+                "entity_id": "sensor.42_111111111111_temperature",
+                "unique_id": "/42.111111111111/temperature",
+                "injected_value": b"    29.123",
+                "result": "29.1",
+                "unit": TEMP_CELSIUS,
+            },
+        ]
+    },
+    "EF.111111111111": {
+        "inject_reads": [
+            b"HobbyBoards_EF",  # read type
+        ],
+        "sensors": [
+            {
+                "entity_id": "sensor.ef_111111111111_humidity",
+                "unique_id": "/EF.111111111111/humidity/humidity_corrected",
+                "injected_value": b"    67.745",
+                "result": "67.7",
+                "unit": PERCENTAGE,
+            },
+            {
+                "entity_id": "sensor.ef_111111111111_humidity_raw",
+                "unique_id": "/EF.111111111111/humidity/humidity_raw",
+                "injected_value": b"    65.541",
+                "result": "65.5",
+                "unit": PERCENTAGE,
+            },
+            {
+                "entity_id": "sensor.ef_111111111111_temperature",
+                "unique_id": "/EF.111111111111/humidity/temperature",
+                "injected_value": b"    25.123",
+                "result": "25.1",
+                "unit": TEMP_CELSIUS,
+            },
+        ],
+    },
+    "EF.111111111112": {
+        "inject_reads": [
+            b"HB_MOISTURE_METER",  # read type
+            b"         1",  # read is_leaf_0
+            b"         1",  # read is_leaf_1
+            b"         0",  # read is_leaf_2
+            b"         0",  # read is_leaf_3
+        ],
+        "sensors": [
+            {
+                "entity_id": "sensor.ef_111111111112_wetness_0",
+                "unique_id": "/EF.111111111112/moisture/sensor.0",
+                "injected_value": b"    41.745",
+                "result": "41.7",
+                "unit": PERCENTAGE,
+            },
+            {
+                "entity_id": "sensor.ef_111111111112_wetness_1",
+                "unique_id": "/EF.111111111112/moisture/sensor.1",
+                "injected_value": b"    42.541",
+                "result": "42.5",
+                "unit": PERCENTAGE,
+            },
+            {
+                "entity_id": "sensor.ef_111111111112_moisture_2",
+                "unique_id": "/EF.111111111112/moisture/sensor.2",
+                "injected_value": b"    43.123",
+                "result": "43.1",
+                "unit": "cb",
+            },
+            {
+                "entity_id": "sensor.ef_111111111112_moisture_3",
+                "unique_id": "/EF.111111111112/moisture/sensor.3",
+                "injected_value": b"    44.123",
+                "result": "44.1",
+                "unit": "cb",
+            },
+        ],
+    },
+}
+
+
+@pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys())
+async def test_owserver_setup_valid_device(hass, device_id):
+    """Test for 1-Wire device."""
+    entity_registry = mock_registry(hass)
+
+    dir_return_value = [f"/{device_id}/"]
+    read_side_effect = [device_id[0:2].encode()]
+    if "inject_reads" in MOCK_DEVICE_SENSORS[device_id]:
+        read_side_effect += MOCK_DEVICE_SENSORS[device_id]["inject_reads"]
+
+    expected_sensors = MOCK_DEVICE_SENSORS[device_id]["sensors"]
+    for expected_sensor in expected_sensors:
+        read_side_effect.append(expected_sensor["injected_value"])
+
+    with patch("homeassistant.components.onewire.sensor.protocol.proxy") as owproxy:
+        owproxy.return_value.dir.return_value = dir_return_value
+        owproxy.return_value.read.side_effect = read_side_effect
+
+        assert await async_setup_component(hass, SENSOR_DOMAIN, MOCK_CONFIG)
+        await hass.async_block_till_done()
+
+    assert len(entity_registry.entities) == len(expected_sensors)
+
+    for expected_sensor in expected_sensors:
+        entity_id = expected_sensor["entity_id"]
+        registry_entry = entity_registry.entities.get(entity_id)
+        assert registry_entry is not None
+        assert registry_entry.unique_id == expected_sensor["unique_id"]
+        assert registry_entry.unit_of_measurement == expected_sensor["unit"]
+        state = hass.states.get(entity_id)
+        assert state.state == expected_sensor["result"]
diff --git a/tests/components/onewire/test_entity_sysbus.py b/tests/components/onewire/test_entity_sysbus.py
index 4667ab641e7..8a233315ab2 100644
--- a/tests/components/onewire/test_entity_sysbus.py
+++ b/tests/components/onewire/test_entity_sysbus.py
@@ -1,129 +1,121 @@
-"""Tests for 1-Wire temperature sensor (device family 10, 22, 28, 3B, 42) connected on SysBus."""
-from datetime import datetime, timedelta
+"""Tests for 1-Wire devices connected on SysBus."""
 from unittest.mock import PropertyMock, patch
 
-from pi1wire import (
-    InvalidCRCException,
-    NotFoundSensorException,
-    UnsupportResponseException,
-)
+from pi1wire import InvalidCRCException, UnsupportResponseException
+import pytest
 
-from homeassistant.components.onewire.const import (
-    DEFAULT_OWSERVER_PORT,
-    DEFAULT_SYSBUS_MOUNT_DIR,
-    DOMAIN,
-)
+from homeassistant.components.onewire.const import DEFAULT_SYSBUS_MOUNT_DIR, DOMAIN
 from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
 from homeassistant.const import TEMP_CELSIUS
 from homeassistant.setup import async_setup_component
 
-from tests.common import async_fire_time_changed, mock_registry
+from tests.common import mock_registry
 
-MOCK_DEVICE_ID = "28-111111111111"
-MOCK_DEVICE_NAME = "My DS18B20"
-MOCK_ENTITY_ID = "sensor.my_ds18b20_temperature"
-
-
-async def test_onewiredirect_setup_valid_device(hass):
+MOCK_CONFIG = {
+    "sensor": {
+        "platform": DOMAIN,
+        "mount_dir": DEFAULT_SYSBUS_MOUNT_DIR,
+        "names": {
+            "10-111111111111": "My DS18B20",
+        },
+    }
+}
+
+MOCK_DEVICE_SENSORS = {
+    "00-111111111111": {"sensors": []},
+    "10-111111111111": {
+        "sensors": [
+            {
+                "entity_id": "sensor.my_ds18b20_temperature",
+                "unique_id": "/sys/bus/w1/devices/10-111111111111/w1_slave",
+                "injected_value": 25.123,
+                "result": "25.1",
+                "unit": TEMP_CELSIUS,
+            },
+        ]
+    },
+    "1D-111111111111": {"sensors": []},
+    "22-111111111111": {
+        "sensors": [
+            {
+                "entity_id": "sensor.22_111111111111_temperature",
+                "unique_id": "/sys/bus/w1/devices/22-111111111111/w1_slave",
+                "injected_value": FileNotFoundError,
+                "result": "unknown",
+                "unit": TEMP_CELSIUS,
+            },
+        ]
+    },
+    "28-111111111111": {
+        "sensors": [
+            {
+                "entity_id": "sensor.28_111111111111_temperature",
+                "unique_id": "/sys/bus/w1/devices/28-111111111111/w1_slave",
+                "injected_value": InvalidCRCException,
+                "result": "unknown",
+                "unit": TEMP_CELSIUS,
+            },
+        ]
+    },
+    "3B-111111111111": {
+        "sensors": [
+            {
+                "entity_id": "sensor.3b_111111111111_temperature",
+                "unique_id": "/sys/bus/w1/devices/3B-111111111111/w1_slave",
+                "injected_value": 29.993,
+                "result": "30.0",
+                "unit": TEMP_CELSIUS,
+            },
+        ]
+    },
+    "42-111111111111": {
+        "sensors": [
+            {
+                "entity_id": "sensor.42_111111111111_temperature",
+                "unique_id": "/sys/bus/w1/devices/42-111111111111/w1_slave",
+                "injected_value": UnsupportResponseException,
+                "result": "unknown",
+                "unit": TEMP_CELSIUS,
+            },
+        ]
+    },
+    "EF-111111111111": {
+        "sensors": [],
+    },
+    "EF-111111111112": {
+        "sensors": [],
+    },
+}
+
+
+@pytest.mark.parametrize("device_id", MOCK_DEVICE_SENSORS.keys())
+async def test_onewiredirect_setup_valid_device(hass, device_id):
     """Test that sysbus config entry works correctly."""
     entity_registry = mock_registry(hass)
-    config = {
-        "sensor": {
-            "platform": DOMAIN,
-            "mount_dir": DEFAULT_SYSBUS_MOUNT_DIR,
-            "port": DEFAULT_OWSERVER_PORT,
-            "names": {
-                MOCK_DEVICE_ID: MOCK_DEVICE_NAME,
-            },
-        }
-    }
+
+    read_side_effect = []
+    expected_sensors = MOCK_DEVICE_SENSORS[device_id]["sensors"]
+    for expected_sensor in expected_sensors:
+        read_side_effect.append(expected_sensor["injected_value"])
 
     with patch(
         "homeassistant.components.onewire.sensor.Pi1Wire"
     ) as mock_pi1wire, patch("pi1wire.OneWire") as mock_owsensor:
         type(mock_owsensor).mac_address = PropertyMock(
-            return_value=MOCK_DEVICE_ID.replace("-", "")
+            return_value=device_id.replace("-", "")
         )
-        mock_owsensor.get_temperature.side_effect = [
-            25.123,
-            FileNotFoundError,
-            25.223,
-            InvalidCRCException,
-            25.323,
-            NotFoundSensorException,
-            25.423,
-            UnsupportResponseException,
-            25.523,
-        ]
+        mock_owsensor.get_temperature.side_effect = read_side_effect
         mock_pi1wire.return_value.find_all_sensors.return_value = [mock_owsensor]
-        assert await async_setup_component(hass, SENSOR_DOMAIN, config)
-        await hass.async_block_till_done()
-
-        assert len(entity_registry.entities) == 1
-        registry_entry = entity_registry.entities.get(MOCK_ENTITY_ID)
-        assert registry_entry is not None
-        assert (
-            registry_entry.unique_id == f"/sys/bus/w1/devices/{MOCK_DEVICE_ID}/w1_slave"
-        )
-        assert registry_entry.unit_of_measurement == TEMP_CELSIUS
-
-        # 25.123
-        current_time = datetime.now()
-        state = hass.states.get(MOCK_ENTITY_ID)
-        assert state.state == "25.1"
-
-        # FileNotFoundError
-        current_time = current_time + timedelta(minutes=2)
-        async_fire_time_changed(hass, current_time)
-        await hass.async_block_till_done()
-        state = hass.states.get(MOCK_ENTITY_ID)
-        assert state.state == "unknown"
-
-        # 25.223
-        current_time = current_time + timedelta(minutes=2)
-        async_fire_time_changed(hass, current_time)
-        await hass.async_block_till_done()
-        state = hass.states.get(MOCK_ENTITY_ID)
-        assert state.state == "25.2"
-
-        # InvalidCRCException
-        current_time = current_time + timedelta(minutes=2)
-        async_fire_time_changed(hass, current_time)
+        assert await async_setup_component(hass, SENSOR_DOMAIN, MOCK_CONFIG)
         await hass.async_block_till_done()
-        state = hass.states.get(MOCK_ENTITY_ID)
-        assert state.state == "unknown"
 
-        # 25.323
-        current_time = current_time + timedelta(minutes=2)
-        async_fire_time_changed(hass, current_time)
-        await hass.async_block_till_done()
-        state = hass.states.get(MOCK_ENTITY_ID)
-        assert state.state == "25.3"
-
-        # NotFoundSensorException
-        current_time = current_time + timedelta(minutes=2)
-        async_fire_time_changed(hass, current_time)
-        await hass.async_block_till_done()
-        state = hass.states.get(MOCK_ENTITY_ID)
-        assert state.state == "unknown"
-
-        # 25.423
-        current_time = current_time + timedelta(minutes=2)
-        async_fire_time_changed(hass, current_time)
-        await hass.async_block_till_done()
-        state = hass.states.get(MOCK_ENTITY_ID)
-        assert state.state == "25.4"
+    assert len(entity_registry.entities) == len(expected_sensors)
 
-        # UnsupportResponseException
-        current_time = current_time + timedelta(minutes=2)
-        async_fire_time_changed(hass, current_time)
-        await hass.async_block_till_done()
-        state = hass.states.get(MOCK_ENTITY_ID)
-        assert state.state == "unknown"
-
-        # 25.523
-        current_time = current_time + timedelta(minutes=2)
-        async_fire_time_changed(hass, current_time)
-        await hass.async_block_till_done()
-        state = hass.states.get(MOCK_ENTITY_ID)
-        assert state.state == "25.5"
+    for expected_sensor in expected_sensors:
+        entity_id = expected_sensor["entity_id"]
+        registry_entry = entity_registry.entities.get(entity_id)
+        assert registry_entry is not None
+        assert registry_entry.unique_id == expected_sensor["unique_id"]
+        assert registry_entry.unit_of_measurement == expected_sensor["unit"]
+        state = hass.states.get(entity_id)
+        assert state.state == expected_sensor["result"]
diff --git a/tests/components/onewire/test_sensor.py b/tests/components/onewire/test_sensor.py
new file mode 100644
index 00000000000..0aa02a9906d
--- /dev/null
+++ b/tests/components/onewire/test_sensor.py
@@ -0,0 +1,43 @@
+"""Tests for 1-Wire sensor platform."""
+from homeassistant.components.onewire.const import DEFAULT_SYSBUS_MOUNT_DIR
+import homeassistant.components.sensor as sensor
+from homeassistant.setup import async_setup_component
+
+from tests.common import assert_setup_component
+
+
+async def test_setup_minimum(hass):
+    """Test setup with minimum configuration."""
+    config = {"sensor": {"platform": "onewire"}}
+    with assert_setup_component(1, "sensor"):
+        assert await async_setup_component(hass, sensor.DOMAIN, config)
+    await hass.async_block_till_done()
+
+
+async def test_setup_sysbus(hass):
+    """Test setup with SysBus configuration."""
+    config = {
+        "sensor": {
+            "platform": "onewire",
+            "mount_dir": DEFAULT_SYSBUS_MOUNT_DIR,
+        }
+    }
+    with assert_setup_component(1, "sensor"):
+        assert await async_setup_component(hass, sensor.DOMAIN, config)
+    await hass.async_block_till_done()
+
+
+async def test_setup_owserver(hass):
+    """Test setup with OWServer configuration."""
+    config = {"sensor": {"platform": "onewire", "host": "localhost"}}
+    with assert_setup_component(1, "sensor"):
+        assert await async_setup_component(hass, sensor.DOMAIN, config)
+    await hass.async_block_till_done()
+
+
+async def test_setup_owserver_with_port(hass):
+    """Test setup with OWServer configuration."""
+    config = {"sensor": {"platform": "onewire", "host": "localhost", "port": "1234"}}
+    with assert_setup_component(1, "sensor"):
+        assert await async_setup_component(hass, sensor.DOMAIN, config)
+    await hass.async_block_till_done()
-- 
GitLab