mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-09 20:24:02 +00:00
Replace custom deviceconfig serialization with mashumaru (#1274)
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import sys
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
@@ -21,6 +22,13 @@ from .fixtureinfo import fixture_info # noqa: F401
|
||||
turn_on = pytest.mark.parametrize("turn_on", [True, False])
|
||||
|
||||
|
||||
def load_fixture(foldername, filename):
|
||||
"""Load a fixture."""
|
||||
path = Path(Path(__file__).parent / "fixtures" / foldername / filename)
|
||||
with path.open() as fdp:
|
||||
return fdp.read()
|
||||
|
||||
|
||||
async def handle_turn_on(dev, turn_on):
|
||||
if turn_on:
|
||||
await dev.turn_on()
|
||||
|
10
tests/fixtures/serialization/deviceconfig_camera-aes-https.json
vendored
Normal file
10
tests/fixtures/serialization/deviceconfig_camera-aes-https.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"host": "127.0.0.1",
|
||||
"timeout": 5,
|
||||
"connection_type": {
|
||||
"device_family": "SMART.IPCAMERA",
|
||||
"encryption_type": "AES",
|
||||
"https": true
|
||||
},
|
||||
"uses_http": false
|
||||
}
|
11
tests/fixtures/serialization/deviceconfig_plug-klap.json
vendored
Normal file
11
tests/fixtures/serialization/deviceconfig_plug-klap.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"host": "127.0.0.1",
|
||||
"timeout": 5,
|
||||
"connection_type": {
|
||||
"device_family": "SMART.TAPOPLUG",
|
||||
"encryption_type": "KLAP",
|
||||
"https": false,
|
||||
"login_version": 2
|
||||
},
|
||||
"uses_http": false
|
||||
}
|
10
tests/fixtures/serialization/deviceconfig_plug-xor.json
vendored
Normal file
10
tests/fixtures/serialization/deviceconfig_plug-xor.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"host": "127.0.0.1",
|
||||
"timeout": 5,
|
||||
"connection_type": {
|
||||
"device_family": "IOT.SMARTPLUGSWITCH",
|
||||
"encryption_type": "XOR",
|
||||
"https": false
|
||||
},
|
||||
"uses_http": false
|
||||
}
|
@@ -1,35 +1,97 @@
|
||||
import json
|
||||
from dataclasses import replace
|
||||
from json import dumps as json_dumps
|
||||
from json import loads as json_loads
|
||||
|
||||
import aiohttp
|
||||
import pytest
|
||||
from mashumaro import MissingField
|
||||
|
||||
from kasa.credentials import Credentials
|
||||
from kasa.deviceconfig import (
|
||||
DeviceConfig,
|
||||
DeviceConnectionParameters,
|
||||
DeviceEncryptionType,
|
||||
DeviceFamily,
|
||||
)
|
||||
|
||||
from .conftest import load_fixture
|
||||
|
||||
PLUG_XOR_CONFIG = DeviceConfig(host="127.0.0.1")
|
||||
PLUG_KLAP_CONFIG = DeviceConfig(
|
||||
host="127.0.0.1",
|
||||
connection_type=DeviceConnectionParameters(
|
||||
DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Klap, login_version=2
|
||||
),
|
||||
)
|
||||
CAMERA_AES_CONFIG = DeviceConfig(
|
||||
host="127.0.0.1",
|
||||
connection_type=DeviceConnectionParameters(
|
||||
DeviceFamily.SmartIpCamera, DeviceEncryptionType.Aes, https=True
|
||||
),
|
||||
)
|
||||
from kasa.exceptions import KasaException
|
||||
|
||||
|
||||
async def test_serialization():
|
||||
"""Test device config serialization."""
|
||||
config = DeviceConfig(host="Foo", http_client=aiohttp.ClientSession())
|
||||
config_dict = config.to_dict()
|
||||
config_json = json_dumps(config_dict)
|
||||
config2_dict = json_loads(config_json)
|
||||
config2 = DeviceConfig.from_dict(config2_dict)
|
||||
assert config == config2
|
||||
assert config.to_dict_control_credentials() == config.to_dict()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("input_value", "expected_msg"),
|
||||
("fixture_name", "expected_value"),
|
||||
[
|
||||
({"Foo": "Bar"}, "Cannot create dataclass from dict, unknown key: Foo"),
|
||||
("foobar", "Invalid device config data: foobar"),
|
||||
("deviceconfig_plug-xor.json", PLUG_XOR_CONFIG),
|
||||
("deviceconfig_plug-klap.json", PLUG_KLAP_CONFIG),
|
||||
("deviceconfig_camera-aes-https.json", CAMERA_AES_CONFIG),
|
||||
],
|
||||
ids=lambda arg: arg.split("_")[-1] if isinstance(arg, str) else "",
|
||||
)
|
||||
async def test_deserialization(fixture_name: str, expected_value: DeviceConfig):
|
||||
"""Test device config deserialization."""
|
||||
dict_val = json.loads(load_fixture("serialization", fixture_name))
|
||||
config = DeviceConfig.from_dict(dict_val)
|
||||
assert config == expected_value
|
||||
assert expected_value.to_dict() == dict_val
|
||||
|
||||
|
||||
async def test_serialization_http_client():
|
||||
"""Test that the http client does not try to serialize."""
|
||||
dict_val = json.loads(load_fixture("serialization", "deviceconfig_plug-klap.json"))
|
||||
|
||||
config = replace(PLUG_KLAP_CONFIG, http_client=object())
|
||||
assert config.http_client
|
||||
|
||||
assert config.to_dict() == dict_val
|
||||
|
||||
|
||||
async def test_conn_param_no_https():
|
||||
"""Test no https in connection param defaults to False."""
|
||||
dict_val = {
|
||||
"device_family": "SMART.TAPOPLUG",
|
||||
"encryption_type": "KLAP",
|
||||
"login_version": 2,
|
||||
}
|
||||
param = DeviceConnectionParameters.from_dict(dict_val)
|
||||
assert param.https is False
|
||||
assert param.to_dict() == {**dict_val, "https": False}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("input_value", "expected_error"),
|
||||
[
|
||||
({"Foo": "Bar"}, MissingField),
|
||||
("foobar", ValueError),
|
||||
],
|
||||
ids=["invalid-dict", "not-dict"],
|
||||
)
|
||||
def test_deserialization_errors(input_value, expected_msg):
|
||||
with pytest.raises(KasaException, match=expected_msg):
|
||||
def test_deserialization_errors(input_value, expected_error):
|
||||
with pytest.raises(expected_error):
|
||||
DeviceConfig.from_dict(input_value)
|
||||
|
||||
|
||||
@@ -39,7 +101,7 @@ async def test_credentials_hash():
|
||||
http_client=aiohttp.ClientSession(),
|
||||
credentials=Credentials("foo", "bar"),
|
||||
)
|
||||
config_dict = config.to_dict(credentials_hash="credhash")
|
||||
config_dict = config.to_dict_control_credentials(credentials_hash="credhash")
|
||||
config_json = json_dumps(config_dict)
|
||||
config2_dict = json_loads(config_json)
|
||||
config2 = DeviceConfig.from_dict(config2_dict)
|
||||
@@ -53,7 +115,7 @@ async def test_blank_credentials_hash():
|
||||
http_client=aiohttp.ClientSession(),
|
||||
credentials=Credentials("foo", "bar"),
|
||||
)
|
||||
config_dict = config.to_dict(credentials_hash="")
|
||||
config_dict = config.to_dict_control_credentials(credentials_hash="")
|
||||
config_json = json_dumps(config_dict)
|
||||
config2_dict = json_loads(config_json)
|
||||
config2 = DeviceConfig.from_dict(config2_dict)
|
||||
@@ -67,7 +129,7 @@ async def test_exclude_credentials():
|
||||
http_client=aiohttp.ClientSession(),
|
||||
credentials=Credentials("foo", "bar"),
|
||||
)
|
||||
config_dict = config.to_dict(exclude_credentials=True)
|
||||
config_dict = config.to_dict_control_credentials(exclude_credentials=True)
|
||||
config_json = json_dumps(config_dict)
|
||||
config2_dict = json_loads(config_json)
|
||||
config2 = DeviceConfig.from_dict(config2_dict)
|
||||
|
Reference in New Issue
Block a user