async def test_is_on(bridge: Bridge): """Test the is_on method returns device state.""" bridge.leap.send_unsolicited( Response( CommuniqueType="ReadResponse", Header=ResponseHeader( MessageBodyType="OneZoneStatus", StatusCode=ResponseStatus(200, "OK"), Url="/zone/1/status", ), Body={"ZoneStatus": { "Level": 50, "Zone": { "href": "/zone/1" } }}, )) assert bridge.target.is_on("2") is True bridge.leap.send_unsolicited( Response( CommuniqueType="ReadResponse", Header=ResponseHeader( MessageBodyType="OneZoneStatus", StatusCode=ResponseStatus(200, "OK"), Url="/zone/1/status", ), Body={"ZoneStatus": { "Level": 0, "Zone": { "href": "/zone/1" } }}, )) assert bridge.target.is_on("2") is False
async def test_occupancy_no_bodies(bridge_uninit: Bridge): """Test the that the bridge initializes even if no occupancy status is returned.""" bridge = bridge_uninit # unconfirmed: user says sometimes they get back a response where the body is None. # It's unclear if there is some other indication via StatusCode or MessageBodyType # that the body is missing. # See #61 bridge.occupancy_group_list_result = Response( Header=ResponseHeader( StatusCode=ResponseStatus(code=200, message="OK"), Url="/occupancygroup", MessageBodyType="MultipleOccupancyGroupDefinition", ), CommuniqueType="ReadResponse", Body=None, ) bridge.occupancy_group_subscription_data_result = Response( Header=ResponseHeader( StatusCode=ResponseStatus(code=200, message="OK"), Url="/occupancygroup/status", MessageBodyType="MultipleOccupancyGroupStatus", ), CommuniqueType="SubscribeResponse", Body=None, ) await bridge.initialize() assert bridge.target.occupancy_groups == {}
async def test_subscribe_tagged_404(pipe: Pipe): """Test subscribing to a topic that does not exist.""" def _handler(_: Response): pass task = asyncio.create_task(pipe.leap.subscribe("/test", _handler)) received = json.loads((await pipe.test_reader.readline()).decode("utf-8")) tag = received.get("Header", {}).pop("ClientTag", None) response_obj = { "CommuniqueType": "SubscribeResponse", "Header": { "ClientTag": tag, "StatusCode": "404 Not Found", "Url": "/test" }, } response_bytes = f"{json.dumps(response_obj)}\r\n".encode("utf-8") pipe.test_writer.write(response_bytes) result, _ = await task assert result == Response( Header=ResponseHeader(StatusCode=ResponseStatus(404, "Not Found"), Url="/test"), CommuniqueType="SubscribeResponse", Body=None, ) # The subscription should not be registered. assert {} == pipe.leap._tagged_subscriptions # pylint: disable=protected-access
async def test_occupancy_group_status_change_notification(bridge: Bridge): """Test that occupancy status changes send notifications.""" notified = False def notify(): nonlocal notified notified = True bridge.target.add_occupancy_subscriber("2", notify) bridge.leap.send_unsolicited( Response( CommuniqueType="ReadResponse", Header=ResponseHeader( MessageBodyType="MultipleOccupancyGroupStatus", StatusCode=ResponseStatus(200, "OK"), Url="/occupancygroup/status", ), Body={ "OccupancyGroupStatuses": [{ "href": "/occupancygroup/2/status", "OccupancyGroup": { "href": "/occupancygroup/2" }, "OccupancyStatus": "Unoccupied", }] }, )) assert notified
async def test_notifications(bridge: Bridge): """Test notifications are sent to subscribers.""" notified = False def callback(): nonlocal notified notified = True bridge.target.add_subscriber("2", callback) bridge.leap.send_unsolicited( Response( CommuniqueType="ReadResponse", Header=ResponseHeader( MessageBodyType="OneZoneStatus", StatusCode=ResponseStatus(200, "OK"), Url="/zone/1/status", ), Body={"ZoneStatus": { "Level": 100, "Zone": { "href": "/zone/1" } }}, )) await asyncio.wait_for(bridge.leap.requests.join(), 10) assert notified
async def test_call(pipe: Pipe): """Test basic call and response.""" task = asyncio.create_task(pipe.leap.request("ReadRequest", "/test")) received = json.loads((await pipe.test_reader.readline()).decode("utf-8")) # message should contain ClientTag tag = received.get("Header", {}).pop("ClientTag", None) assert tag assert isinstance(tag, str) assert received == { "CommuniqueType": "ReadRequest", "Header": { "Url": "/test" } } response_obj = { "CommuniqueType": "ReadResponse", "Header": { "ClientTag": tag, "StatusCode": "200 OK", "Url": "/test" }, "Body": { "ok": True }, } response_bytes = f"{json.dumps(response_obj)}\r\n".encode("utf-8") pipe.test_writer.write(response_bytes) result = await task assert result == Response( Header=ResponseHeader(StatusCode=ResponseStatus(200, "OK"), Url="/test"), CommuniqueType="ReadResponse", Body={"ok": True}, )
async def test_occupancy_group_status_change(bridge: Bridge): """Test that the status is updated when occupancy changes.""" bridge.leap.send_unsolicited( Response( CommuniqueType="ReadResponse", Header=ResponseHeader( MessageBodyType="MultipleOccupancyGroupStatus", StatusCode=ResponseStatus(200, "OK"), Url="/occupancygroup/status", ), Body={ "OccupancyGroupStatuses": [{ "href": "/occupancygroup/2/status", "OccupancyGroup": { "href": "/occupancygroup/2" }, "OccupancyStatus": "Unoccupied", }] }, )) new_status = bridge.target.occupancy_groups["2"]["status"] assert new_status == OCCUPANCY_GROUP_UNOCCUPIED
async def test_raise_cover(bridge: Bridge, event_loop): """Test that raising a cover produces the right commands.""" devices = bridge.target.get_devices() task = event_loop.create_task(bridge.target.raise_cover("7")) command, response = await bridge.leap.requests.get() assert command == Request( communique_type="CreateRequest", url="/zone/6/commandprocessor", body={"Command": {"CommandType": "Raise"}}, ) # the real response probably contains more data response.set_result( Response( CommuniqueType="CreateResponse", Header=ResponseHeader( StatusCode=ResponseStatus(201, "Created"), Url="/zone/6/commandprocessor", ), ), ) bridge.leap.requests.task_done() await task assert devices["7"]["current_state"] == 100
async def test_subscribe_tagged(pipe: Pipe): """ Test subscribing to a topic and receiving responses. Unlike with unsolicited subscriptions, when the client sends a SubscribeRequest, the server sends back all events related to that subscription with the same tag value. """ handler_message = None handler_called = asyncio.Event() def handler(response): nonlocal handler_message handler_message = response handler_called.set() task = asyncio.create_task(pipe.leap.subscribe("/test", handler)) received = json.loads((await pipe.test_reader.readline()).decode("utf-8")) # message should contain ClientTag tag = received.get("Header", {}).pop("ClientTag", None) assert tag assert isinstance(tag, str) assert received == { "CommuniqueType": "SubscribeRequest", "Header": { "Url": "/test" }, } response_obj = { "CommuniqueType": "SubscribeResponse", "Header": { "ClientTag": tag, "StatusCode": "200 OK", "Url": "/test" }, "Body": { "ok": True }, } response_bytes = f"{json.dumps(response_obj)}\r\n".encode("utf-8") pipe.test_writer.write(response_bytes) result, received_tag = await task assert result == Response( Header=ResponseHeader(StatusCode=ResponseStatus(200, "OK"), Url="/test"), CommuniqueType="SubscribeResponse", Body={"ok": True}, ) assert received_tag == tag # Now that the client has subscribed, send an event to the handler. response_obj = { "CommuniqueType": "ReadResponse", "Header": { "ClientTag": tag, "StatusCode": "200 OK", "Url": "/test" }, "Body": { "ok": True }, } response_bytes = f"{json.dumps(response_obj)}\r\n".encode("utf-8") pipe.test_writer.write(response_bytes) await asyncio.wait_for(handler_called.wait(), 1.0) assert handler_message == Response( Header=ResponseHeader(StatusCode=ResponseStatus(200, "OK"), Url="/test"), CommuniqueType="ReadResponse", Body={"ok": True}, )
async def test_set_value(bridge: Bridge, event_loop): """Test that setting values produces the right commands.""" task = event_loop.create_task(bridge.target.set_value("2", 50)) command, response = await bridge.leap.requests.get() assert command == Request( communique_type="CreateRequest", url="/zone/1/commandprocessor", body={ "Command": { "CommandType": "GoToLevel", "Parameter": [{ "Type": "Level", "Value": 50 }], } }, ) response.set_result( Response( CommuniqueType="CreateResponse", Header=ResponseHeader( MessageBodyType="OneZoneStatus", StatusCode=ResponseStatus(201, "Created"), Url="/zone/1/commandprocessor", ), Body={ "ZoneStatus": { "href": "/zone/1/status", "Level": 50, "Zone": { "href": "/zone/1" }, } }, )) bridge.leap.requests.task_done() await task task = event_loop.create_task(bridge.target.turn_on("2")) command, response = await bridge.leap.requests.get() assert command == Request( communique_type="CreateRequest", url="/zone/1/commandprocessor", body={ "Command": { "CommandType": "GoToLevel", "Parameter": [{ "Type": "Level", "Value": 100 }], } }, ) response.set_result( Response( CommuniqueType="CreateResponse", Header=ResponseHeader( MessageBodyType="OneZoneStatus", StatusCode=ResponseStatus(201, "Created"), Url="/zone/1/commandprocessor", ), Body={ "ZoneStatus": { "href": "/zone/1/status", "Level": 100, "Zone": { "href": "/zone/1" }, } }, ), ) bridge.leap.requests.task_done() await task task = event_loop.create_task(bridge.target.turn_off("2")) command, response = await bridge.leap.requests.get() assert command == Request( communique_type="CreateRequest", url="/zone/1/commandprocessor", body={ "Command": { "CommandType": "GoToLevel", "Parameter": [{ "Type": "Level", "Value": 0 }], } }, ) response.set_result( Response( CommuniqueType="CreateResponse", Header=ResponseHeader( MessageBodyType="OneZoneStatus", StatusCode=ResponseStatus(201, "Created"), Url="/zone/1/commandprocessor", ), Body={ "ZoneStatus": { "href": "/zone/1/status", "Level": 0, "Zone": { "href": "/zone/1" }, } }, ), ) bridge.leap.requests.task_done() await task
async def test_device_list(bridge: Bridge): """Test methods getting devices.""" devices = bridge.target.get_devices() expected_devices = { "1": { "device_id": "1", "name": "Smart Bridge", "type": "SmartBridge", "zone": None, "current_state": -1, "fan_speed": None, "model": "L-BDG2-WH", "serial": 1234, }, "2": { "device_id": "2", "name": "Hallway_Lights", "type": "WallDimmer", "zone": "1", "model": "PD-6WCL-XX", "serial": 2345, "current_state": 0, "fan_speed": None, }, "3": { "device_id": "3", "name": "Hallway_Fan", "type": "CasetaFanSpeedController", "zone": "2", "model": "PD-FSQN-XX", "serial": 3456, "current_state": 0, "fan_speed": None, }, "4": { "device_id": "4", "name": "Living Room_Occupancy Sensor", "type": "RPSOccupancySensor", "model": "LRF2-XXXXB-P-XX", "serial": 4567, "current_state": -1, "fan_speed": None, "zone": None, }, "5": { "device_id": "5", "name": "Master Bathroom_Occupancy Sensor Door", "type": "RPSOccupancySensor", "model": "PD-VSENS-XX", "serial": 5678, "current_state": -1, "fan_speed": None, "zone": None, }, "6": { "device_id": "6", "name": "Master Bathroom_Occupancy Sensor Tub", "type": "RPSOccupancySensor", "model": "PD-OSENS-XX", "serial": 6789, "current_state": -1, "fan_speed": None, "zone": None, }, "7": { "device_id": "7", "name": "Living Room_Living Shade 3", "type": "QsWirelessShade", "model": "QSYC-J-RCVR", "serial": 1234, "current_state": 0, "fan_speed": None, "zone": "6", }, } assert devices == expected_devices bridge.leap.send_unsolicited( Response( CommuniqueType="ReadResponse", Header=ResponseHeader( MessageBodyType="OneZoneStatus", StatusCode=ResponseStatus(200, "OK"), Url="/zone/1/status", ), Body={"ZoneStatus": { "Level": 100, "Zone": { "href": "/zone/1" } }}, )) bridge.leap.send_unsolicited( Response( CommuniqueType="ReadResponse", Header=ResponseHeader( MessageBodyType="OneZoneStatus", StatusCode=ResponseStatus(200, "OK"), Url="/zone/2/status", ), Body={ "ZoneStatus": { "FanSpeed": "Medium", "Zone": { "href": "/zone/2" } } }, )) devices = bridge.target.get_devices() assert devices["2"]["current_state"] == 100 assert devices["2"]["fan_speed"] is None assert devices["3"]["current_state"] == -1 assert devices["3"]["fan_speed"] == FAN_MEDIUM devices = bridge.target.get_devices_by_domain("light") assert len(devices) == 1 assert devices[0]["device_id"] == "2" devices = bridge.target.get_devices_by_type("WallDimmer") assert len(devices) == 1 assert devices[0]["device_id"] == "2" devices = bridge.target.get_devices_by_types(("SmartBridge", "WallDimmer")) assert len(devices) == 2 device = bridge.target.get_device_by_id("2") assert device["device_id"] == "2" devices = bridge.target.get_devices_by_domain("fan") assert len(devices) == 1 assert devices[0]["device_id"] == "3" devices = bridge.target.get_devices_by_type("CasetaFanSpeedController") assert len(devices) == 1 assert devices[0]["device_id"] == "3"
async def _accept_connection(self, leap, wait): """Accept a connection from SmartBridge (implementation).""" # First message should be read request on /device request, response = await wait(leap.requests.get()) assert request == Request(communique_type="ReadRequest", url="/device") response.set_result(response_from_json_file("devices.json")) leap.requests.task_done() # Second message should be read request on /virtualbutton request, response = await wait(leap.requests.get()) assert request == Request(communique_type="ReadRequest", url="/virtualbutton") response.set_result(response_from_json_file("scenes.json")) leap.requests.task_done() # Third message should be read request on /areas request, response = await wait(leap.requests.get()) assert request == Request(communique_type="ReadRequest", url="/area") response.set_result(response_from_json_file("areas.json")) leap.requests.task_done() # Fourth message should be read request on /occupancygroup request, response = await wait(leap.requests.get()) assert request == Request(communique_type="ReadRequest", url="/occupancygroup") response.set_result(response_from_json_file("occupancygroups.json")) leap.requests.task_done() # Fifth message should be subscribe request on /occupancygroup/status request, response = await wait(leap.requests.get()) assert request == Request(communique_type="SubscribeRequest", url="/occupancygroup/status") response.set_result( response_from_json_file("occupancygroupsubscribe.json")) leap.requests.task_done() # Finally, we should check the zone status on each zone requested_zones = [] for _ in range(0, 3): request, response = await wait(leap.requests.get()) logging.info("Read %s", request) assert request.communique_type == "ReadRequest" requested_zones.append(request.url) response.set_result( Response( CommuniqueType="ReadResponse", Header=ResponseHeader( MessageBodyType="OneZoneStatus", StatusCode=ResponseStatus(200, "OK"), Url=request.url, ), Body={ "ZoneStatus": { "href": request.url, "Level": 0, "Zone": { "href": request.url.replace("/status", "") }, "StatusAccuracy": "Good", } }, )) leap.requests.task_done() requested_zones.sort() assert requested_zones == [ "/zone/1/status", "/zone/2/status", "/zone/6/status" ]