@@ -988,6 +988,163 @@ async def test_refresh_device_states(self, client: OverkizClient):
988988 with patch .object (aiohttp .ClientSession , "post" , return_value = resp ):
989989 await client .refresh_device_states ("rts://2025-8464-6867/16756006" )
990990
991+ # --- Local API specific tests ---
992+ # The local gateway (KizOs) behaves differently from the cloud API
993+ # in several cases. These tests verify the client raises proper errors
994+ # instead of crashing when called via the local API.
995+
996+ @pytest .mark .asyncio
997+ async def test_local_get_current_execution_empty_list (
998+ self , local_client : OverkizClient
999+ ):
1000+ """Local gateway returns [] for non-existent exec_id (cloud returns {})."""
1001+ resp = MockResponse ("[]" )
1002+
1003+ with patch .object (aiohttp .ClientSession , "get" , return_value = resp ):
1004+ result = await local_client .get_current_execution (
1005+ "00000000-0000-0000-0000-000000000000"
1006+ )
1007+ assert result is None
1008+
1009+ @pytest .mark .asyncio
1010+ async def test_local_get_state_no_such_device (self , local_client : OverkizClient ):
1011+ """Local gateway raises NoSuchDeviceError for unknown device URLs."""
1012+ resp = MockResponse (
1013+ '{"error":"No such device : \\ "io://0000-0000-0000/12345678\\ "","errorCode":"NO_SUCH_DEVICE"}' ,
1014+ status = 400 ,
1015+ )
1016+
1017+ with (
1018+ patch .object (aiohttp .ClientSession , "get" , return_value = resp ),
1019+ pytest .raises (exceptions .NoSuchDeviceError ),
1020+ ):
1021+ await local_client .get_state ("io://0000-0000-0000/12345678" )
1022+
1023+ @pytest .mark .asyncio
1024+ async def test_local_get_device_definition_no_such_device (
1025+ self , local_client : OverkizClient
1026+ ):
1027+ """Local gateway raises NoSuchDeviceError for unknown device definition lookups."""
1028+ resp = MockResponse (
1029+ '{"error":"No such device : \\ "io://0000-0000-0000/12345678\\ "","errorCode":"NO_SUCH_DEVICE"}' ,
1030+ status = 400 ,
1031+ )
1032+
1033+ with (
1034+ patch .object (aiohttp .ClientSession , "get" , return_value = resp ),
1035+ pytest .raises (exceptions .NoSuchDeviceError ),
1036+ ):
1037+ await local_client .get_device_definition ("io://0000-0000-0000/12345678" )
1038+
1039+ @pytest .mark .asyncio
1040+ async def test_local_get_setup_option_unknown_object (
1041+ self , local_client : OverkizClient
1042+ ):
1043+ """Local gateway raises UnknownObjectError for non-existent options (cloud returns {})."""
1044+ resp = MockResponse (
1045+ '{"error":"Unknown object.","errorCode":"UNSPECIFIED_ERROR"}' ,
1046+ status = 400 ,
1047+ )
1048+
1049+ with (
1050+ patch .object (aiohttp .ClientSession , "get" , return_value = resp ),
1051+ pytest .raises (exceptions .UnknownObjectError ),
1052+ ):
1053+ await local_client .get_setup_option ("nonExistentOption" )
1054+
1055+ @pytest .mark .asyncio
1056+ async def test_local_refresh_device_states_unknown_object (
1057+ self , local_client : OverkizClient
1058+ ):
1059+ """Local gateway raises UnknownObjectError for unknown device refresh."""
1060+ resp = MockResponse (
1061+ '{"error":"Unknown object.","errorCode":"UNSPECIFIED_ERROR"}' ,
1062+ status = 400 ,
1063+ )
1064+
1065+ with (
1066+ patch .object (aiohttp .ClientSession , "post" , return_value = resp ),
1067+ pytest .raises (exceptions .UnknownObjectError ),
1068+ ):
1069+ await local_client .refresh_device_states ("io://0000-0000-0000/12345678" )
1070+
1071+ @pytest .mark .asyncio
1072+ async def test_local_get_reference_controllable_unknown_object (
1073+ self , local_client : OverkizClient
1074+ ):
1075+ """Local gateway raises UnknownObjectError for unknown controllable names."""
1076+ resp = MockResponse (
1077+ '{"error":"Unknown object.","errorCode":"UNSPECIFIED_ERROR"}' ,
1078+ status = 400 ,
1079+ )
1080+
1081+ with (
1082+ patch .object (aiohttp .ClientSession , "get" , return_value = resp ),
1083+ pytest .raises (exceptions .UnknownObjectError ),
1084+ ):
1085+ await local_client .get_reference_controllable ("io:NonExistentControllable" )
1086+
1087+ @pytest .mark .asyncio
1088+ async def test_local_cancel_execution_succeeds_on_unknown_id (
1089+ self , local_client : OverkizClient
1090+ ):
1091+ """Local gateway returns 200 with [] for cancel on unknown exec_id (idempotent)."""
1092+ resp = MockResponse ("[]" , status = 200 )
1093+
1094+ with patch .object (aiohttp .ClientSession , "delete" , return_value = resp ):
1095+ await local_client .cancel_execution ("00000000-0000-0000-0000-000000000000" )
1096+
1097+ @pytest .mark .asyncio
1098+ async def test_local_execute_action_group_rts_close (
1099+ self , local_client : OverkizClient
1100+ ):
1101+ """Verify executing an RTS command via the local API."""
1102+ action = Action (
1103+ "rts://2025-8464-6867/16756006" ,
1104+ [Command (name = "close" )],
1105+ )
1106+ resp = MockResponse ('{"execId": "45e52d27-3c08-4fd5-87f2-03d650b67f4b"}' )
1107+
1108+ with patch .object (aiohttp .ClientSession , "post" ) as mock_post :
1109+ mock_post .return_value = resp
1110+ exec_id = await local_client .execute_action_group ([action ])
1111+
1112+ assert exec_id == "45e52d27-3c08-4fd5-87f2-03d650b67f4b"
1113+
1114+ @pytest .mark .asyncio
1115+ async def test_local_no_registered_event_listener (
1116+ self , local_client : OverkizClient
1117+ ):
1118+ """Local gateway raises NoRegisteredEventListenerError for unregistered fetch."""
1119+ resp = MockResponse (
1120+ '{"error":"\\ "No registered event listener.\\ "","errorCode":"UNSPECIFIED_ERROR"}' ,
1121+ status = 400 ,
1122+ )
1123+
1124+ with (
1125+ patch .object (aiohttp .ClientSession , "post" , return_value = resp ),
1126+ pytest .raises (exceptions .NoRegisteredEventListenerError ),
1127+ ):
1128+ await check_response (resp )
1129+
1130+ @pytest .mark .asyncio
1131+ async def test_local_schedule_persisted_action_group_unknown_object (
1132+ self , local_client : OverkizClient
1133+ ):
1134+ """Local gateway raises UnknownObjectError when scheduling a non-existent action group."""
1135+ resp = MockResponse (
1136+ '{"error":"Unknown object.","errorCode":"UNSPECIFIED_ERROR"}' ,
1137+ status = 400 ,
1138+ )
1139+
1140+ with (
1141+ patch .object (aiohttp .ClientSession , "post" , return_value = resp ),
1142+ pytest .raises (exceptions .UnknownObjectError ),
1143+ ):
1144+ await local_client .schedule_persisted_action_group (
1145+ "00000000-0000-0000-0000-000000000000" , 9999999999
1146+ )
1147+
9911148
9921149class MockResponse :
9931150 """Simple stand-in for aiohttp responses used in tests."""
0 commit comments