mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-06 10:44:04 +00:00
Follow main package structure for tests (#1317)
* Transport tests under tests/transports/ * Protocol tests under tests/protocols/ * IOT tests under iot/ * Plus some minor cleanups, most code changes are related to splitting up smart & iot tests
This commit is contained in:
201
tests/iot/modules/test_emeter.py
Normal file
201
tests/iot/modules/test_emeter.py
Normal file
@@ -0,0 +1,201 @@
|
||||
import datetime
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from voluptuous import (
|
||||
All,
|
||||
Any,
|
||||
Coerce,
|
||||
Range,
|
||||
Schema,
|
||||
)
|
||||
|
||||
from kasa import Device, DeviceType, EmeterStatus, Module
|
||||
from kasa.interfaces.energy import Energy
|
||||
from kasa.iot import IotStrip
|
||||
from kasa.iot.modules.emeter import Emeter
|
||||
from tests.conftest import has_emeter_iot, no_emeter_iot
|
||||
|
||||
CURRENT_CONSUMPTION_SCHEMA = Schema(
|
||||
Any(
|
||||
{
|
||||
"voltage_mv": Any(All(float, Range(min=0, max=300000)), int, None),
|
||||
"power_mw": Any(Coerce(float), None),
|
||||
"current_ma": Any(All(float), int, None),
|
||||
"energy_wh": Any(Coerce(float), None),
|
||||
"total_wh": Any(Coerce(float), None),
|
||||
"voltage": Any(All(float, Range(min=0, max=300)), None),
|
||||
"power": Any(Coerce(float), None),
|
||||
"current": Any(All(float), None),
|
||||
"total": Any(Coerce(float), None),
|
||||
"energy": Any(Coerce(float), None),
|
||||
"slot_id": Any(Coerce(int), None),
|
||||
},
|
||||
None,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@no_emeter_iot
|
||||
async def test_no_emeter(dev):
|
||||
assert not dev.has_emeter
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
await dev.get_emeter_realtime()
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
await dev.get_emeter_daily()
|
||||
with pytest.raises(AttributeError):
|
||||
await dev.get_emeter_monthly()
|
||||
with pytest.raises(AttributeError):
|
||||
await dev.erase_emeter_stats()
|
||||
|
||||
|
||||
@has_emeter_iot
|
||||
async def test_get_emeter_realtime(dev):
|
||||
emeter = dev.modules[Module.Energy]
|
||||
|
||||
current_emeter = await emeter.get_status()
|
||||
# Check realtime query gets the same value as status property
|
||||
# iot _query_helper strips out the error code from module responses.
|
||||
# but it's not stripped out of the _modular_update queries.
|
||||
assert current_emeter == {k: v for k, v in emeter.status.items() if k != "err_code"}
|
||||
CURRENT_CONSUMPTION_SCHEMA(current_emeter)
|
||||
|
||||
|
||||
@has_emeter_iot
|
||||
@pytest.mark.requires_dummy
|
||||
async def test_get_emeter_daily(dev):
|
||||
emeter = dev.modules[Module.Energy]
|
||||
|
||||
assert await emeter.get_daily_stats(year=1900, month=1) == {}
|
||||
|
||||
d = await emeter.get_daily_stats()
|
||||
assert len(d) > 0
|
||||
|
||||
k, v = d.popitem()
|
||||
assert isinstance(k, int)
|
||||
assert isinstance(v, float)
|
||||
|
||||
# Test kwh (energy, energy_wh)
|
||||
d = await emeter.get_daily_stats(kwh=False)
|
||||
k2, v2 = d.popitem()
|
||||
assert v * 1000 == v2
|
||||
|
||||
|
||||
@has_emeter_iot
|
||||
@pytest.mark.requires_dummy
|
||||
async def test_get_emeter_monthly(dev):
|
||||
emeter = dev.modules[Module.Energy]
|
||||
|
||||
assert await emeter.get_monthly_stats(year=1900) == {}
|
||||
|
||||
d = await emeter.get_monthly_stats()
|
||||
assert len(d) > 0
|
||||
|
||||
k, v = d.popitem()
|
||||
assert isinstance(k, int)
|
||||
assert isinstance(v, float)
|
||||
|
||||
# Test kwh (energy, energy_wh)
|
||||
d = await emeter.get_monthly_stats(kwh=False)
|
||||
k2, v2 = d.popitem()
|
||||
assert v * 1000 == v2
|
||||
|
||||
|
||||
@has_emeter_iot
|
||||
async def test_emeter_status(dev):
|
||||
emeter = dev.modules[Module.Energy]
|
||||
|
||||
d = await emeter.get_status()
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
assert d["foo"]
|
||||
|
||||
assert d["power_mw"] == d["power"] * 1000
|
||||
# bulbs have only power according to tplink simulator.
|
||||
if (
|
||||
dev.device_type is not DeviceType.Bulb
|
||||
and dev.device_type is not DeviceType.LightStrip
|
||||
):
|
||||
assert d["voltage_mv"] == d["voltage"] * 1000
|
||||
|
||||
assert d["current_ma"] == d["current"] * 1000
|
||||
assert d["total_wh"] == d["total"] * 1000
|
||||
|
||||
|
||||
@pytest.mark.skip("not clearing your stats..")
|
||||
@has_emeter_iot
|
||||
async def test_erase_emeter_stats(dev):
|
||||
emeter = dev.modules[Module.Energy]
|
||||
|
||||
await emeter.erase_emeter()
|
||||
|
||||
|
||||
@has_emeter_iot
|
||||
async def test_current_consumption(dev):
|
||||
emeter = dev.modules[Module.Energy]
|
||||
x = emeter.current_consumption
|
||||
assert isinstance(x, float)
|
||||
assert x >= 0.0
|
||||
|
||||
|
||||
async def test_emeterstatus_missing_current():
|
||||
"""KL125 does not report 'current' for emeter."""
|
||||
regular = EmeterStatus(
|
||||
{"err_code": 0, "power_mw": 0, "total_wh": 13, "current_ma": 123}
|
||||
)
|
||||
assert regular["current"] == 0.123
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
regular["invalid_key"]
|
||||
|
||||
missing_current = EmeterStatus({"err_code": 0, "power_mw": 0, "total_wh": 13})
|
||||
assert missing_current["current"] is None
|
||||
|
||||
|
||||
async def test_emeter_daily():
|
||||
"""Test fetching the emeter for today.
|
||||
|
||||
This test uses inline data since the fixtures
|
||||
will not have data for the current day.
|
||||
"""
|
||||
emeter_data = {
|
||||
"get_daystat": {
|
||||
"day_list": [{"day": 1, "energy_wh": 8, "month": 1, "year": 2023}],
|
||||
"err_code": 0,
|
||||
}
|
||||
}
|
||||
|
||||
class MockEmeter(Emeter):
|
||||
@property
|
||||
def data(self):
|
||||
return emeter_data
|
||||
|
||||
emeter = MockEmeter(Mock(), "emeter")
|
||||
now = datetime.datetime.now()
|
||||
emeter_data["get_daystat"]["day_list"].append(
|
||||
{"day": now.day, "energy_wh": 500, "month": now.month, "year": now.year}
|
||||
)
|
||||
assert emeter.consumption_today == 0.500
|
||||
|
||||
|
||||
@has_emeter_iot
|
||||
async def test_supported(dev: Device):
|
||||
energy_module = dev.modules.get(Module.Energy)
|
||||
assert energy_module
|
||||
|
||||
info = (
|
||||
dev._last_update
|
||||
if not isinstance(dev, IotStrip)
|
||||
else dev.children[0].internal_state
|
||||
)
|
||||
emeter = info[energy_module._module]["get_realtime"]
|
||||
has_total = "total" in emeter or "total_wh" in emeter
|
||||
has_voltage_current = "voltage" in emeter or "voltage_mv" in emeter
|
||||
assert energy_module.supports(Energy.ModuleFeature.CONSUMPTION_TOTAL) is has_total
|
||||
assert (
|
||||
energy_module.supports(Energy.ModuleFeature.VOLTAGE_CURRENT)
|
||||
is has_voltage_current
|
||||
)
|
||||
assert energy_module.supports(Energy.ModuleFeature.PERIODIC_STATS) is True
|
86
tests/iot/modules/test_usage.py
Normal file
86
tests/iot/modules/test_usage.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import datetime
|
||||
from unittest.mock import Mock
|
||||
|
||||
from kasa.iot.modules import Usage
|
||||
|
||||
|
||||
def test_usage_convert_stat_data():
|
||||
usage = Usage(None, module="usage")
|
||||
|
||||
test_data = []
|
||||
assert usage._convert_stat_data(test_data, "day") == {}
|
||||
|
||||
test_data = [
|
||||
{"year": 2016, "month": 5, "day": 2, "time": 20},
|
||||
{"year": 2016, "month": 5, "day": 4, "time": 30},
|
||||
]
|
||||
d = usage._convert_stat_data(test_data, "day")
|
||||
assert len(d) == len(test_data)
|
||||
assert isinstance(d, dict)
|
||||
k, v = d.popitem()
|
||||
assert isinstance(k, int)
|
||||
assert isinstance(v, int)
|
||||
assert k == 4
|
||||
assert v == 30
|
||||
|
||||
|
||||
def test_usage_today():
|
||||
"""Test fetching the usage for today.
|
||||
|
||||
This test uses inline data since the fixtures
|
||||
will not have data for the current day.
|
||||
"""
|
||||
emeter_data = {
|
||||
"get_daystat": {
|
||||
"day_list": [],
|
||||
"err_code": 0,
|
||||
}
|
||||
}
|
||||
|
||||
class MockUsage(Usage):
|
||||
@property
|
||||
def data(self):
|
||||
return emeter_data
|
||||
|
||||
usage = MockUsage(Mock(), "usage")
|
||||
assert usage.usage_today is None
|
||||
now = datetime.datetime.now()
|
||||
emeter_data["get_daystat"]["day_list"].extend(
|
||||
[
|
||||
{"day": now.day - 1, "time": 200, "month": now.month - 1, "year": now.year},
|
||||
{"day": now.day, "time": 500, "month": now.month, "year": now.year},
|
||||
{"day": now.day + 1, "time": 100, "month": now.month + 1, "year": now.year},
|
||||
]
|
||||
)
|
||||
assert usage.usage_today == 500
|
||||
|
||||
|
||||
def test_usage_this_month():
|
||||
"""Test fetching the usage for this month.
|
||||
|
||||
This test uses inline data since the fixtures
|
||||
will not have data for the current month.
|
||||
"""
|
||||
emeter_data = {
|
||||
"get_monthstat": {
|
||||
"month_list": [],
|
||||
"err_code": 0,
|
||||
}
|
||||
}
|
||||
|
||||
class MockUsage(Usage):
|
||||
@property
|
||||
def data(self):
|
||||
return emeter_data
|
||||
|
||||
usage = MockUsage(Mock(), "usage")
|
||||
assert usage.usage_this_month is None
|
||||
now = datetime.datetime.now()
|
||||
emeter_data["get_monthstat"]["month_list"].extend(
|
||||
[
|
||||
{"time": 200, "month": now.month - 1, "year": now.year},
|
||||
{"time": 500, "month": now.month, "year": now.year},
|
||||
{"time": 100, "month": now.month + 1, "year": now.year},
|
||||
]
|
||||
)
|
||||
assert usage.usage_this_month == 500
|
Reference in New Issue
Block a user