From bea9b7c6195729b4332a8ad6c2db4825fb96123a Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Wed, 4 Dec 2024 21:14:48 +0100 Subject: [PATCH 1/2] cli: print model, https, and lv for discover list --- kasa/cli/discover.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/kasa/cli/discover.py b/kasa/cli/discover.py index 377d75e8f..9452a4224 100644 --- a/kasa/cli/discover.py +++ b/kasa/cli/discover.py @@ -85,8 +85,9 @@ async def list(ctx): async def print_discovered(dev: Device): cparams = dev.config.connection_type infostr = ( - f"{dev.host:<15} {cparams.device_family.value:<20} " - f"{cparams.encryption_type.value:<7}" + f"{dev.host:<15} {dev.model:<9} {cparams.device_family.value:<20} " + f"{cparams.encryption_type.value:<7} {cparams.https:<5} " + f"{cparams.login_version or '-':<3}" ) async with sem: try: @@ -100,7 +101,10 @@ async def print_unsupported(unsupported_exception: UnsupportedDeviceError): if host := unsupported_exception.host: echo(f"{host:<15} UNSUPPORTED DEVICE") - echo(f"{'HOST':<15} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} {'ALIAS'}") + echo( + f"{'HOST':<15} {'MODEL':<9} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} " + f"{'HTTPS':<5} {'LV':<3} {'ALIAS'}" + ) return await _discover(ctx, print_discovered, print_unsupported, do_echo=False) From c6760244269a734483760351406ff17e5993da9e Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:27:16 +0000 Subject: [PATCH 2/2] Fix tests and handle timeouts and unexpected exceptions --- kasa/cli/discover.py | 4 ++++ tests/discovery_fixtures.py | 16 ++++++++++++- tests/test_cli.py | 47 ++++++++++++++++++++++++++++++------- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/kasa/cli/discover.py b/kasa/cli/discover.py index bef46e137..2470434b7 100644 --- a/kasa/cli/discover.py +++ b/kasa/cli/discover.py @@ -132,6 +132,10 @@ async def print_discovered(dev: Device): await dev.update() except AuthenticationError: echo(f"{infostr} - Authentication failed") + except TimeoutError: + echo(f"{infostr} - Timed out") + except Exception as ex: + echo(f"{infostr} - Error: {ex}") else: echo(f"{infostr} {dev.alias}") diff --git a/tests/discovery_fixtures.py b/tests/discovery_fixtures.py index 87541effe..eb843f1a0 100644 --- a/tests/discovery_fixtures.py +++ b/tests/discovery_fixtures.py @@ -160,6 +160,17 @@ class _DiscoveryMock: login_version: int | None = None port_override: int | None = None + @property + def model(self) -> str: + dd = self.discovery_data + model_region = ( + dd["result"]["device_model"] + if self.discovery_port == 20002 + else dd["system"]["get_sysinfo"]["model"] + ) + model, _, _ = model_region.partition("(") + return model + @property def _datagram(self) -> bytes: if self.default_port == 9999: @@ -178,7 +189,10 @@ def _datagram(self) -> bytes: "encrypt_type", discovery_result.get("encrypt_info", {}).get("sym_schm") ) - login_version = discovery_result["mgt_encrypt_schm"].get("lv") + if not (login_version := discovery_result["mgt_encrypt_schm"].get("lv")) and ( + et := discovery_result.get("encrypt_type") + ): + login_version = max([int(i) for i in et]) https = discovery_result["mgt_encrypt_schm"]["is_support_https"] dm = _DiscoveryMock( ip, diff --git a/tests/test_cli.py b/tests/test_cli.py index 42f6e12b0..3621ef203 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -122,8 +122,15 @@ async def test_list_devices(discovery_mock, runner): catch_exceptions=False, ) assert res.exit_code == 0 - header = f"{'HOST':<15} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} {'ALIAS'}" - row = f"{discovery_mock.ip:<15} {discovery_mock.device_type:<20} {discovery_mock.encrypt_type:<7}" + header = ( + f"{'HOST':<15} {'MODEL':<9} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} " + f"{'HTTPS':<5} {'LV':<3} {'ALIAS'}" + ) + row = ( + f"{discovery_mock.ip:<15} {discovery_mock.model:<9} {discovery_mock.device_type:<20} " + f"{discovery_mock.encrypt_type:<7} {discovery_mock.https:<5} " + f"{discovery_mock.login_version or '-':<3}" + ) assert header in res.output assert row in res.output @@ -158,14 +165,26 @@ async def test_discover_raw(discovery_mock, runner, mocker): redact_spy.assert_called() +@pytest.mark.parametrize( + ("exception", "expected"), + [ + pytest.param( + AuthenticationError("Failed to authenticate"), + "Authentication failed", + id="auth", + ), + pytest.param(TimeoutError(), "Timed out", id="timeout"), + pytest.param(Exception("Foobar"), "Error: Foobar", id="other-error"), + ], +) @new_discovery -async def test_list_auth_failed(discovery_mock, mocker, runner): +async def test_list_update_failed(discovery_mock, mocker, runner, exception, expected): """Test that device update is called on main.""" device_class = Discover._get_device_class(discovery_mock.discovery_data) mocker.patch.object( device_class, "update", - side_effect=AuthenticationError("Failed to authenticate"), + side_effect=exception, ) res = await runner.invoke( cli, @@ -173,10 +192,17 @@ async def test_list_auth_failed(discovery_mock, mocker, runner): catch_exceptions=False, ) assert res.exit_code == 0 - header = f"{'HOST':<15} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} {'ALIAS'}" - row = f"{discovery_mock.ip:<15} {discovery_mock.device_type:<20} {discovery_mock.encrypt_type:<7} - Authentication failed" - assert header in res.output - assert row in res.output + header = ( + f"{'HOST':<15} {'MODEL':<9} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} " + f"{'HTTPS':<5} {'LV':<3} {'ALIAS'}" + ) + row = ( + f"{discovery_mock.ip:<15} {discovery_mock.model:<9} {discovery_mock.device_type:<20} " + f"{discovery_mock.encrypt_type:<7} {discovery_mock.https:<5} " + f"{discovery_mock.login_version or '-':<3} - {expected}" + ) + assert header in res.output.replace("\n", "") + assert row in res.output.replace("\n", "") async def test_list_unsupported(unsupported_device_info, runner): @@ -187,7 +213,10 @@ async def test_list_unsupported(unsupported_device_info, runner): catch_exceptions=False, ) assert res.exit_code == 0 - header = f"{'HOST':<15} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} {'ALIAS'}" + header = ( + f"{'HOST':<15} {'MODEL':<9} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} " + f"{'HTTPS':<5} {'LV':<3} {'ALIAS'}" + ) row = f"{'127.0.0.1':<15} UNSUPPORTED DEVICE" assert header in res.output assert row in res.output