Skip to main content
Glama
test_api.py8.35 kB
import os import sys import uuid import datetime import math import pytest sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))) from src.st.device import DeviceItem from src.api import Command, Location, _bucket_time, _aggregate_values noRoomId = uuid.UUID("00000000-0000-0000-0000-000000000000") room1Id = uuid.UUID("00000000-0000-0000-0000-000000000001") def _make_location(): loc = object.__new__(Location) loc.location_id = "loc1" loc.rooms = {room1Id: "Room 1"} loc.device_ids = set() return loc def test_get_status_none(): assert Location.get_status(None) == ("?", None, None, None) assert Location.get_status({}) == ("?", None, None, None) def test_get_status_valid(): status = { "supportedValues": [], "temperature": {"value": 20, "unit": "C", "timestamp": "t"}, } assert Location.get_status(status) == ("temperature", 20, "C", "t") def test_get_devices_url(monkeypatch): loc = _make_location() captured = {} def fake_get(url): captured["url"] = url devices: list[DeviceItem] = [] return devices loc._get_devices = fake_get res = loc.get_devices( capability="motionSensor", capabilities_mode="or", include_restricted=True, room_id=room1Id, include_status=True, category="Light", connection_type="LAN", ) assert res == [] expected_url = ( f"devices?locationId=loc1" "&capability=motionSensor" "&category=Light" "&capabilitiesMode=or" "&includeRestricted=true" f"&roomId={room1Id}" "&includeStatus=true" "&type=LAN" ) assert captured["url"] == expected_url # pyright: ignore def test_get_devices_invalid(monkeypatch): loc = _make_location() loc._get_devices = lambda url: [] with pytest.raises(ValueError): loc.get_devices(capability="unknown") # type: ignore with pytest.raises(ValueError): loc.get_devices(room_id=noRoomId) def test_device_commands(monkeypatch): loc = _make_location() valid = uuid.UUID("11111111-1111-1111-1111-111111111111") loc.device_ids = {valid} captured = {} def fake_post(device_id, commands): captured["device_id"] = device_id captured["commands"] = commands return {"status": "ok"} loc._device_commands = fake_post cmds = [Command(component="main", capability="switch", command="on", arguments=[])] res = loc.device_commands(valid, cmds) assert res == {"status": "ok"} assert captured["device_id"] == valid assert captured["commands"] == cmds def test_validate_device_id(): loc = _make_location() valid = uuid.UUID("11111111-1111-1111-1111-111111111111") loc.device_ids = {valid} assert loc.validate_device_id(valid) == valid with pytest.raises(ValueError): loc.validate_device_id("not-a-uuid") # type: ignore with pytest.raises(ValueError): loc.validate_device_id(uuid.UUID("22222222-2222-2222-2222-222222222222")) def test_bucket_time(): ts = datetime.datetime(2025, 6, 15, 12, 34, 56) assert _bucket_time(ts, "realtime") == ts assert _bucket_time(ts, "5min") == ts.replace(minute=30, second=0, microsecond=0) assert _bucket_time(ts, "hourly") == ts.replace(minute=0, second=0, microsecond=0) assert _bucket_time(ts, "daily") == ts.replace(hour=0, minute=0, second=0, microsecond=0) with pytest.raises(ValueError): _bucket_time(ts, "bogus") # type: ignore def test_aggregate_values(): values = [1.0, 2.0, 3.0] assert _aggregate_values(values, "sum") == 6.0 assert _aggregate_values(values, "avg") == 2.0 assert _aggregate_values(values, "min") == 1.0 assert _aggregate_values(values, "max") == 3.0 assert math.isnan(_aggregate_values([], "avg")) with pytest.raises(ValueError): _aggregate_values(values, "bogus") # type: ignore def test_room_history_raw(monkeypatch): loc = _make_location() loc.get_devices_short = lambda **kwargs: [ # type: ignore {"deviceId": "dev1"}, {"deviceId": "dev2"}, ] base = datetime.datetime(2025, 1, 1, 12, 0, 0) events = { "dev1": [{"time": base, "value": 1}], "dev2": [{"time": base + datetime.timedelta(minutes=3), "value": 2}], } def fake_event_history(device_id, *args, **kwargs): return events[device_id] loc.event_history = fake_event_history # type: ignore res = loc.room_history( room_id=room1Id, attribute="temperature", start_ms=0, end_ms=0 ) assert res == [ {"time": base, "value": 1.0}, {"time": base + datetime.timedelta(minutes=3), "value": 2.0}, ] def test_calc_epoch_range(monkeypatch): import pytz loc = _make_location() loc.timezone = pytz.UTC fake_now = datetime.datetime(2025, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc) class FakeDateTime(datetime.datetime): @classmethod def now(cls, tz=None): return fake_now import src.api as api_mod monkeypatch.setattr(api_mod, "datetime", FakeDateTime) start_ms, end_ms = loc._calc_epoch_range("P1D") assert start_ms == int((fake_now - datetime.timedelta(days=1)).timestamp() * 1000) assert end_ms == int(fake_now.timestamp() * 1000) def test_history_calls_event(monkeypatch): import pytz loc = _make_location() loc.timezone = pytz.UTC captured = {} def fake_event_history(*, device_id=None, attribute=None, limit=None, paging_after_epoch=None, paging_before_epoch=None, **kwargs): captured["device_id"] = device_id captured["paging_after_epoch"] = paging_after_epoch captured["paging_before_epoch"] = paging_before_epoch return [] loc.event_history = fake_event_history # type: ignore fake_now = datetime.datetime(2025, 1, 2, 0, 0, 0, tzinfo=datetime.timezone.utc) class FakeDateTime(datetime.datetime): @classmethod def now(cls, tz=None): return fake_now import src.api as api_mod monkeypatch.setattr(api_mod, "datetime", FakeDateTime) dev_id = uuid.UUID("11111111-1111-1111-1111-111111111111") loc.history(device_id=dev_id, attribute="temperature", delta_start="P1D") expected_start = int((fake_now - datetime.timedelta(days=1)).timestamp() * 1000) expected_end = int(fake_now.timestamp() * 1000) assert captured["device_id"] == dev_id assert captured["paging_after_epoch"] == expected_start assert captured["paging_before_epoch"] == expected_end def test_event_history_capability_filter(monkeypatch): loc = _make_location() base_time = datetime.datetime(2025, 1, 1, 12, 0, 0) events_data = { "items": [ { "deviceId": "11111111-1111-1111-1111-111111111111", "deviceName": "Device1", "locationId": "22222222-2222-2222-2222-222222222222", "locationName": "Home", "time": base_time.isoformat() + "Z", "text": "on", "component": "main", "componentLabel": "main", "capability": "switch", "attribute": "switch", "value": "on", "epoch": 1, "hash": 1, }, { "deviceId": "33333333-3333-3333-3333-333333333333", "deviceName": "Device2", "locationId": "22222222-2222-2222-2222-222222222222", "locationName": "Home", "time": (base_time + datetime.timedelta(minutes=1)).isoformat() + "Z", "text": "motion", "component": "main", "componentLabel": "main", "capability": "motionSensor", "attribute": "motion", "value": "active", "epoch": 2, "hash": 2, }, ] } class FakeSession: def get_json(self, url): return events_data loc.session = FakeSession() # type: ignore result = loc.event_history(capability={"switch"}) assert len(result) == 1 assert result[0]["capability"] == "switch"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/veonua/smartthings-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server