Switch from TPLinkSmartHomeProtocol to IotProtocol/XorTransport (#710)

* Switch from TPLinkSmartHomeProtocol to IotProtocol/XorTransport

* Add test

* Update docs

* Fix ruff deleting deprecated import
This commit is contained in:
Steven B
2024-01-26 09:11:31 +00:00
committed by GitHub
parent c318303255
commit 0d0f56414c
15 changed files with 172 additions and 355 deletions

View File

@@ -20,9 +20,9 @@ from kasa import (
SmartLightStrip,
SmartPlug,
SmartStrip,
TPLinkSmartHomeProtocol,
)
from kasa.tapo import TapoBulb, TapoDevice, TapoPlug
from kasa.xortransport import XorEncryption
from .newfakes import FakeSmartProtocol, FakeTransportProtocol
@@ -478,7 +478,7 @@ def discovery_mock(all_fixture_data, mocker):
device_type = sys_info.get("mic_type") or sys_info.get("type")
encrypt_type = "XOR"
login_version = None
datagram = TPLinkSmartHomeProtocol.encrypt(json_dumps(discovery_data))[4:]
datagram = XorEncryption.encrypt(json_dumps(discovery_data))[4:]
dm = _DiscoveryMock(
"127.0.0.123",
9999,
@@ -517,7 +517,6 @@ def discovery_mock(all_fixture_data, mocker):
mocker.patch("kasa.IotProtocol.query", side_effect=_query)
mocker.patch("kasa.SmartProtocol.query", side_effect=_query)
mocker.patch("kasa.TPLinkSmartHomeProtocol.query", side_effect=_query)
yield dm

View File

@@ -19,8 +19,10 @@ from voluptuous import (
from ..credentials import Credentials
from ..deviceconfig import DeviceConfig
from ..exceptions import SmartDeviceException
from ..protocol import BaseTransport, TPLinkSmartHomeProtocol, _XorTransport
from ..iotprotocol import IotProtocol
from ..protocol import BaseTransport
from ..smartprotocol import SmartProtocol
from ..xortransport import XorTransport
_LOGGER = logging.getLogger(__name__)
@@ -381,10 +383,10 @@ class FakeSmartTransport(BaseTransport):
pass
class FakeTransportProtocol(TPLinkSmartHomeProtocol):
class FakeTransportProtocol(IotProtocol):
def __init__(self, info):
super().__init__(
transport=_XorTransport(
transport=XorTransport(
config=DeviceConfig("127.0.0.123"),
)
)

View File

@@ -11,7 +11,6 @@ from kasa import (
EmeterStatus,
SmartDevice,
SmartDeviceException,
TPLinkSmartHomeProtocol,
UnsupportedDeviceException,
)
from kasa.cli import (

View File

@@ -54,7 +54,6 @@ async def test_connect(
mocker.patch("kasa.IotProtocol.query", return_value=all_fixture_data)
mocker.patch("kasa.SmartProtocol.query", return_value=all_fixture_data)
mocker.patch("kasa.TPLinkSmartHomeProtocol.query", return_value=all_fixture_data)
config = DeviceConfig(
host=host, credentials=Credentials("foor", "bar"), connection_type=ctype
@@ -87,7 +86,6 @@ async def test_connect_custom_port(all_fixture_data: dict, mocker, custom_port):
default_port = 80 if "discovery_result" in all_fixture_data else 9999
ctype, _ = _get_connection_type_device_class(all_fixture_data)
mocker.patch("kasa.TPLinkSmartHomeProtocol.query", return_value=all_fixture_data)
mocker.patch("kasa.IotProtocol.query", return_value=all_fixture_data)
mocker.patch("kasa.SmartProtocol.query", return_value=all_fixture_data)
dev = await connect(config=config)
@@ -102,7 +100,6 @@ async def test_connect_logs_connect_time(
ctype, _ = _get_connection_type_device_class(all_fixture_data)
mocker.patch("kasa.IotProtocol.query", return_value=all_fixture_data)
mocker.patch("kasa.SmartProtocol.query", return_value=all_fixture_data)
mocker.patch("kasa.TPLinkSmartHomeProtocol.query", return_value=all_fixture_data)
host = "127.0.0.1"
config = DeviceConfig(
@@ -118,7 +115,6 @@ async def test_connect_logs_connect_time(
async def test_connect_query_fails(all_fixture_data: dict, mocker):
"""Make sure that connect fails when query fails."""
host = "127.0.0.1"
mocker.patch("kasa.TPLinkSmartHomeProtocol.query", side_effect=SmartDeviceException)
mocker.patch("kasa.IotProtocol.query", side_effect=SmartDeviceException)
mocker.patch("kasa.SmartProtocol.query", side_effect=SmartDeviceException)
@@ -138,7 +134,6 @@ async def test_connect_http_client(all_fixture_data, mocker):
mocker.patch("kasa.IotProtocol.query", return_value=all_fixture_data)
mocker.patch("kasa.SmartProtocol.query", return_value=all_fixture_data)
mocker.patch("kasa.TPLinkSmartHomeProtocol.query", return_value=all_fixture_data)
http_client = aiohttp.ClientSession()

View File

@@ -15,7 +15,6 @@ from kasa import (
Discover,
SmartDevice,
SmartDeviceException,
TPLinkSmartHomeProtocol,
protocol,
)
from kasa.deviceconfig import (
@@ -26,6 +25,7 @@ from kasa.deviceconfig import (
)
from kasa.discover import DiscoveryResult, _DiscoverProtocol, json_dumps
from kasa.exceptions import AuthenticationException, UnsupportedDeviceException
from kasa.xortransport import XorEncryption
from .conftest import bulb, bulb_iot, dimmer, lightstrip, new_discovery, plug, strip
@@ -189,7 +189,7 @@ async def test_discover_invalid_info(msg, data, mocker):
def mock_discover(self):
self.datagram_received(
protocol.TPLinkSmartHomeProtocol.encrypt(json_dumps(data))[4:], (host, 9999)
XorEncryption.encrypt(json_dumps(data))[4:], (host, 9999)
)
mocker.patch.object(_DiscoverProtocol, "do_discover", mock_discover)
@@ -212,7 +212,7 @@ async def test_discover_datagram_received(mocker, discovery_data):
"""Verify that datagram received fills discovered_devices."""
proto = _DiscoverProtocol()
mocker.patch.object(protocol.TPLinkSmartHomeProtocol, "decrypt")
mocker.patch.object(XorEncryption, "decrypt")
addr = "127.0.0.1"
port = 20002 if "result" in discovery_data else 9999
@@ -238,8 +238,8 @@ async def test_discover_invalid_responses(msg, data, mocker):
"""Verify that we don't crash whole discovery if some devices in the network are sending unexpected data."""
proto = _DiscoverProtocol()
mocker.patch("kasa.discover.json_loads", return_value=data)
mocker.patch.object(protocol.TPLinkSmartHomeProtocol, "encrypt")
mocker.patch.object(protocol.TPLinkSmartHomeProtocol, "decrypt")
mocker.patch.object(XorEncryption, "encrypt")
mocker.patch.object(XorEncryption, "decrypt")
proto.datagram_received(data, ("127.0.0.1", 9999))
assert len(proto.discovered_devices) == 0
@@ -375,9 +375,7 @@ class FakeDatagramTransport(asyncio.DatagramTransport):
self.do_not_reply_count = do_not_reply_count
self.send_count = 0
if port == 9999:
self.datagram = TPLinkSmartHomeProtocol.encrypt(
json_dumps(LEGACY_DISCOVER_DATA)
)[4:]
self.datagram = XorEncryption.encrypt(json_dumps(LEGACY_DISCOVER_DATA))[4:]
elif port == 20002:
discovery_data = UNSUPPORTED if unsupported else AUTHENTICATION_DATA_KLAP
self.datagram = (

View File

@@ -15,13 +15,11 @@ from ..aestransport import AesTransport
from ..credentials import Credentials
from ..deviceconfig import DeviceConfig
from ..exceptions import SmartDeviceException
from ..iotprotocol import IotProtocol
from ..iotprotocol import IotProtocol, _deprecated_TPLinkSmartHomeProtocol
from ..klaptransport import KlapTransport, KlapTransportV2
from ..protocol import (
BaseProtocol,
BaseTransport,
TPLinkSmartHomeProtocol,
_XorTransport,
)
from ..xortransport import XorEncryption, XorTransport
@@ -29,10 +27,10 @@ from ..xortransport import XorEncryption, XorTransport
@pytest.mark.parametrize(
"protocol_class, transport_class",
[
(TPLinkSmartHomeProtocol, _XorTransport),
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
(IotProtocol, XorTransport),
],
ids=("TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
ids=("_deprecated_TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
)
@pytest.mark.parametrize("retry_count", [1, 3, 5])
async def test_protocol_retries(mocker, retry_count, protocol_class, transport_class):
@@ -59,10 +57,10 @@ async def test_protocol_retries(mocker, retry_count, protocol_class, transport_c
@pytest.mark.parametrize(
"protocol_class, transport_class",
[
(TPLinkSmartHomeProtocol, _XorTransport),
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
(IotProtocol, XorTransport),
],
ids=("TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
ids=("_deprecated_TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
)
async def test_protocol_no_retry_on_unreachable(
mocker, protocol_class, transport_class
@@ -83,10 +81,10 @@ async def test_protocol_no_retry_on_unreachable(
@pytest.mark.parametrize(
"protocol_class, transport_class",
[
(TPLinkSmartHomeProtocol, _XorTransport),
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
(IotProtocol, XorTransport),
],
ids=("TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
ids=("_deprecated_TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
)
async def test_protocol_no_retry_connection_refused(
mocker, protocol_class, transport_class
@@ -107,10 +105,10 @@ async def test_protocol_no_retry_connection_refused(
@pytest.mark.parametrize(
"protocol_class, transport_class",
[
(TPLinkSmartHomeProtocol, _XorTransport),
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
(IotProtocol, XorTransport),
],
ids=("TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
ids=("_deprecated_TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
)
async def test_protocol_retry_recoverable_error(
mocker, protocol_class, transport_class
@@ -131,10 +129,14 @@ async def test_protocol_retry_recoverable_error(
@pytest.mark.parametrize(
"protocol_class, transport_class, encryption_class",
[
(TPLinkSmartHomeProtocol, _XorTransport, TPLinkSmartHomeProtocol),
(
_deprecated_TPLinkSmartHomeProtocol,
XorTransport,
_deprecated_TPLinkSmartHomeProtocol,
),
(IotProtocol, XorTransport, XorEncryption),
],
ids=("TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
ids=("_deprecated_TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
)
@pytest.mark.parametrize("retry_count", [1, 3, 5])
async def test_protocol_reconnect(
@@ -177,10 +179,14 @@ async def test_protocol_reconnect(
@pytest.mark.parametrize(
"protocol_class, transport_class, encryption_class",
[
(TPLinkSmartHomeProtocol, _XorTransport, TPLinkSmartHomeProtocol),
(
_deprecated_TPLinkSmartHomeProtocol,
XorTransport,
_deprecated_TPLinkSmartHomeProtocol,
),
(IotProtocol, XorTransport, XorEncryption),
],
ids=("TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
ids=("_deprecated_TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
)
async def test_protocol_handles_cancellation_during_write(
mocker, protocol_class, transport_class, encryption_class
@@ -227,10 +233,14 @@ async def test_protocol_handles_cancellation_during_write(
@pytest.mark.parametrize(
"protocol_class, transport_class, encryption_class",
[
(TPLinkSmartHomeProtocol, _XorTransport, TPLinkSmartHomeProtocol),
(
_deprecated_TPLinkSmartHomeProtocol,
XorTransport,
_deprecated_TPLinkSmartHomeProtocol,
),
(IotProtocol, XorTransport, XorEncryption),
],
ids=("TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
ids=("_deprecated_TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
)
async def test_protocol_handles_cancellation_during_connection(
mocker, protocol_class, transport_class, encryption_class
@@ -275,10 +285,14 @@ async def test_protocol_handles_cancellation_during_connection(
@pytest.mark.parametrize(
"protocol_class, transport_class, encryption_class",
[
(TPLinkSmartHomeProtocol, _XorTransport, TPLinkSmartHomeProtocol),
(
_deprecated_TPLinkSmartHomeProtocol,
XorTransport,
_deprecated_TPLinkSmartHomeProtocol,
),
(IotProtocol, XorTransport, XorEncryption),
],
ids=("TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
ids=("_deprecated_TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
)
@pytest.mark.parametrize("log_level", [logging.WARNING, logging.DEBUG])
async def test_protocol_logging(
@@ -318,10 +332,14 @@ async def test_protocol_logging(
@pytest.mark.parametrize(
"protocol_class, transport_class, encryption_class",
[
(TPLinkSmartHomeProtocol, _XorTransport, TPLinkSmartHomeProtocol),
(
_deprecated_TPLinkSmartHomeProtocol,
XorTransport,
_deprecated_TPLinkSmartHomeProtocol,
),
(IotProtocol, XorTransport, XorEncryption),
],
ids=("TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
ids=("_deprecated_TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
)
@pytest.mark.parametrize("custom_port", [123, None])
async def test_protocol_custom_port(
@@ -358,11 +376,11 @@ async def test_protocol_custom_port(
@pytest.mark.parametrize(
"encrypt_class",
[TPLinkSmartHomeProtocol, XorEncryption],
[_deprecated_TPLinkSmartHomeProtocol, XorEncryption],
)
@pytest.mark.parametrize(
"decrypt_class",
[TPLinkSmartHomeProtocol, XorEncryption],
[_deprecated_TPLinkSmartHomeProtocol, XorEncryption],
)
def test_encrypt(encrypt_class, decrypt_class):
d = json.dumps({"foo": 1, "bar": 2})
@@ -374,7 +392,7 @@ def test_encrypt(encrypt_class, decrypt_class):
@pytest.mark.parametrize(
"encrypt_class",
[TPLinkSmartHomeProtocol, XorEncryption],
[_deprecated_TPLinkSmartHomeProtocol, XorEncryption],
)
def test_encrypt_unicode(encrypt_class):
d = "{'snowman': '\u2603'}"
@@ -411,7 +429,7 @@ def test_encrypt_unicode(encrypt_class):
@pytest.mark.parametrize(
"decrypt_class",
[TPLinkSmartHomeProtocol, XorEncryption],
[_deprecated_TPLinkSmartHomeProtocol, XorEncryption],
)
def test_decrypt_unicode(decrypt_class):
e = bytes(
@@ -451,7 +469,11 @@ def _get_subclasses(of_class):
importlib.import_module("." + modname, package="kasa")
module = sys.modules["kasa." + modname]
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, of_class):
if (
inspect.isclass(obj)
and issubclass(obj, of_class)
and name != "_deprecated_TPLinkSmartHomeProtocol"
):
subclasses.add((name, obj))
return subclasses
@@ -491,7 +513,7 @@ def test_transport_init_signature(class_name_obj):
@pytest.mark.parametrize(
"transport_class",
[AesTransport, KlapTransport, KlapTransportV2, _XorTransport, XorTransport],
[AesTransport, KlapTransport, KlapTransportV2, XorTransport, XorTransport],
)
async def test_transport_credentials_hash(mocker, transport_class):
host = "127.0.0.1"
@@ -519,10 +541,10 @@ async def test_transport_credentials_hash(mocker, transport_class):
@pytest.mark.parametrize(
"protocol_class, transport_class",
[
(TPLinkSmartHomeProtocol, _XorTransport),
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
(IotProtocol, XorTransport),
],
ids=("TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
ids=("_deprecated_TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
)
async def test_protocol_will_retry_on_connect(
mocker, protocol_class, transport_class, error, retry_expectation
@@ -551,10 +573,10 @@ async def test_protocol_will_retry_on_connect(
@pytest.mark.parametrize(
"protocol_class, transport_class",
[
(TPLinkSmartHomeProtocol, _XorTransport),
(_deprecated_TPLinkSmartHomeProtocol, XorTransport),
(IotProtocol, XorTransport),
],
ids=("TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
ids=("_deprecated_TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
)
async def test_protocol_will_retry_on_write(
mocker, protocol_class, transport_class, error, retry_expectation
@@ -580,3 +602,16 @@ async def test_protocol_will_retry_on_write(
expected_call_count = retry_count + 1 if retry_expectation else 1
assert conn.call_count == expected_call_count
assert write_mock.call_count == expected_call_count
def test_deprecated_protocol():
with pytest.deprecated_call():
from kasa import TPLinkSmartHomeProtocol
with pytest.raises(
SmartDeviceException, match="host or transport must be supplied"
):
proto = TPLinkSmartHomeProtocol()
host = "127.0.0.1"
proto = TPLinkSmartHomeProtocol(host=host)
assert proto.config.host == host