mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-06 10:44:04 +00:00
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:
@@ -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"
|
||||
)
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user