Add plumbing for passing credentials to devices (#507)

* Add plumbing for passing credentials as far as discovery

* Pass credentials to Smart devices

* Rename authentication exception

* Fix tests failure due to test_json_output leaving echo as nop

* Fix test_credentials test

* Do not print credentials, fix echo function bug and improve get type parameter

* Add device class constructor test

* Add comment for echo handling and move assignment
This commit is contained in:
sdb9696
2023-09-13 14:46:38 +01:00
committed by GitHub
parent f7c22f0a0c
commit 7bb4a456a2
13 changed files with 258 additions and 41 deletions

View File

@@ -1,13 +1,26 @@
import json
import sys
import asyncclick as click
import pytest
from asyncclick.testing import CliRunner
from kasa import SmartDevice
from kasa.cli import alias, brightness, cli, emeter, raw_command, state, sysinfo, toggle
from kasa import SmartDevice, TPLinkSmartHomeProtocol
from kasa.cli import (
TYPE_TO_CLASS,
alias,
brightness,
cli,
emeter,
raw_command,
state,
sysinfo,
toggle,
)
from kasa.discover import Discover
from .conftest import handle_turn_on, turn_on
from .newfakes import FakeTransportProtocol
async def test_sysinfo(dev):
@@ -121,3 +134,70 @@ async def test_json_output(dev: SmartDevice, mocker):
res = await runner.invoke(cli, ["--json", "state"], obj=dev)
assert res.exit_code == 0
assert json.loads(res.output) == dev.internal_state
async def test_credentials(discovery_data: dict, mocker):
"""Test credentials are passed correctly from cli to device."""
# As this is testing the device constructor need to explicitly wire in
# the FakeTransportProtocol
ftp = FakeTransportProtocol(discovery_data)
mocker.patch.object(TPLinkSmartHomeProtocol, "query", ftp.query)
# Patch state to echo username and password
pass_dev = click.make_pass_decorator(SmartDevice)
@pass_dev
async def _state(dev: SmartDevice):
if dev.credentials:
click.echo(
f"Username:{dev.credentials.username} Password:{dev.credentials.password}"
)
mocker.patch("kasa.cli.state", new=_state)
# Get the type string parameter from the discovery_info
for cli_device_type in {
i
for i in TYPE_TO_CLASS
if TYPE_TO_CLASS[i] == Discover._get_device_class(discovery_data)
}:
break
runner = CliRunner()
res = await runner.invoke(
cli,
[
"--host",
"127.0.0.1",
"--type",
cli_device_type,
"--username",
"foo",
"--password",
"bar",
],
)
assert res.exit_code == 0
assert res.output == "Username:foo Password:bar\n"
@pytest.mark.parametrize("auth_param", ["--username", "--password"])
async def test_invalid_credential_params(auth_param):
runner = CliRunner()
# Test for handling only one of username or passowrd supplied.
res = await runner.invoke(
cli,
[
"--host",
"127.0.0.1",
"--type",
"plug",
auth_param,
"foo",
],
)
assert res.exit_code == 0
assert (
res.output == "Using authentication requires both --username and --password\n"
)

View File

@@ -1,14 +1,26 @@
import inspect
from datetime import datetime
from unittest.mock import patch
import pytest # type: ignore # https://github.com/pytest-dev/pytest/issues/3342
from kasa import SmartDeviceException
import kasa
from kasa import Credentials, SmartDevice, SmartDeviceException
from kasa.smartstrip import SmartStripPlug
from .conftest import handle_turn_on, has_emeter, no_emeter, turn_on
from .newfakes import PLUG_SCHEMA, TZ_SCHEMA, FakeTransportProtocol
# List of all SmartXXX classes including the SmartDevice base class
smart_device_classes = [
dc
for (mn, dc) in inspect.getmembers(
kasa,
lambda member: inspect.isclass(member)
and (member == SmartDevice or issubclass(member, SmartDevice)),
)
]
async def test_state_info(dev):
assert isinstance(dev.state_information, dict)
@@ -150,3 +162,15 @@ async def test_features(dev):
assert dev.features == set(sysinfo["feature"].split(":"))
else:
assert dev.features == set()
@pytest.mark.parametrize("device_class", smart_device_classes)
def test_device_class_ctors(device_class):
"""Make sure constructor api not broken for new and existing SmartDevices."""
host = "127.0.0.2"
port = 1234
credentials = Credentials("foo", "bar")
dev = device_class(host, port=port, credentials=credentials)
assert dev.host == host
assert dev.port == port
assert dev.credentials == credentials