From e4e89becc63309489555c49c885264b469ee2311 Mon Sep 17 00:00:00 2001 From: Thomas Hollstegge Date: Mon, 4 May 2020 02:27:19 +0200 Subject: [PATCH] Return emulated hue id for update requests (#35139) * Return emulated hue id for update requests * Emulated Hue: Rename function argument to match its content * Use HTTP status consts throughout the test * Use HTTP status consts in UPnP test --- .../components/emulated_hue/hue_api.py | 10 +- tests/components/emulated_hue/test_hue_api.py | 138 +++++++++++------- tests/components/emulated_hue/test_upnp.py | 7 +- 3 files changed, 98 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 9637b0fb371..d7830b7b699 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -506,7 +506,9 @@ class HueOneLightChangeView(HomeAssistantView): # Create success responses for all received keys json_response = [ - create_hue_success_response(entity_id, HUE_API_STATE_ON, parsed[STATE_ON]) + create_hue_success_response( + entity_number, HUE_API_STATE_ON, parsed[STATE_ON] + ) ] for (key, val) in ( @@ -517,7 +519,7 @@ class HueOneLightChangeView(HomeAssistantView): ): if parsed[key] is not None: json_response.append( - create_hue_success_response(entity_id, val, parsed[key]) + create_hue_success_response(entity_number, val, parsed[key]) ) return self.json(json_response) @@ -710,9 +712,9 @@ def entity_to_json(config, entity): return retval -def create_hue_success_response(entity_id, attr, value): +def create_hue_success_response(entity_number, attr, value): """Create a success response for an attribute set on a light.""" - success_key = f"/lights/{entity_id}/state/{attr}" + success_key = f"/lights/{entity_number}/state/{attr}" return {"success": {success_key: value}} diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 67baed7878e..2171bac8c3f 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -34,6 +34,8 @@ from homeassistant.components.emulated_hue.hue_api import ( from homeassistant.const import ( ATTR_ENTITY_ID, HTTP_NOT_FOUND, + HTTP_OK, + HTTP_UNAUTHORIZED, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, @@ -54,6 +56,26 @@ BRIDGE_SERVER_PORT = get_test_instance_port() BRIDGE_URL_BASE = f"http://127.0.0.1:{BRIDGE_SERVER_PORT}" + "{}" JSON_HEADERS = {CONTENT_TYPE: const.CONTENT_TYPE_JSON} +ENTITY_IDS_BY_NUMBER = { + "1": "light.ceiling_lights", + "2": "light.bed_light", + "3": "script.set_kitchen_light", + "4": "light.kitchen_lights", + "5": "media_player.living_room", + "6": "media_player.bedroom", + "7": "media_player.walkman", + "8": "media_player.lounge_room", + "9": "fan.living_room_fan", + "10": "fan.ceiling_fan", + "11": "cover.living_room_window", + "12": "climate.hvac", + "13": "climate.heatpump", + "14": "climate.ecobee", + "15": "light.no_brightness", +} + +ENTITY_NUMBERS_BY_ID = {v: k for k, v in ENTITY_IDS_BY_NUMBER.items()} + @pytest.fixture def hass_hue(loop, hass): @@ -144,7 +166,6 @@ def hue_client(loop, hass_hue, aiohttp_client): config = Config( None, { - emulated_hue.CONF_TYPE: emulated_hue.TYPE_ALEXA, emulated_hue.CONF_ENTITIES: { "light.bed_light": {emulated_hue.CONF_ENTITY_HIDDEN: True}, # Kitchen light is explicitly excluded from being exposed @@ -162,6 +183,7 @@ def hue_client(loop, hass_hue, aiohttp_client): }, }, ) + config.numbers = ENTITY_IDS_BY_NUMBER HueUsernameView().register(web_app, web_app.router) HueAllLightsStateView(config).register(web_app, web_app.router) @@ -177,7 +199,7 @@ async def test_discover_lights(hue_client): """Test the discovery of lights.""" result = await hue_client.get("/api/username/lights") - assert result.status == 200 + assert result.status == HTTP_OK assert "application/json" in result.headers["content-type"] result_json = await result.json() @@ -204,7 +226,7 @@ async def test_discover_lights(hue_client): async def test_light_without_brightness_supported(hass_hue, hue_client): """Test that light without brightness is supported.""" light_without_brightness_json = await perform_get_light_state( - hue_client, "light.no_brightness", 200 + hue_client, "light.no_brightness", HTTP_OK ) assert light_without_brightness_json["state"][HUE_API_STATE_ON] is True @@ -223,7 +245,7 @@ async def test_light_without_brightness_can_be_turned_off(hass_hue, hue_client): ) no_brightness_result_json = await no_brightness_result.json() - assert no_brightness_result.status == 200 + assert no_brightness_result.status == HTTP_OK assert "application/json" in no_brightness_result.headers["content-type"] assert len(no_brightness_result_json) == 1 @@ -255,7 +277,7 @@ async def test_light_without_brightness_can_be_turned_on(hass_hue, hue_client): no_brightness_result_json = await no_brightness_result.json() - assert no_brightness_result.status == 200 + assert no_brightness_result.status == HTTP_OK assert "application/json" in no_brightness_result.headers["content-type"] assert len(no_brightness_result_json) == 1 @@ -283,7 +305,7 @@ async def test_reachable_for_state(hass_hue, hue_client, state, is_reachable): hass_hue.states.async_set(entity_id, state) - state_json = await perform_get_light_state(hue_client, entity_id, 200) + state_json = await perform_get_light_state(hue_client, entity_id, HTTP_OK) assert state_json["state"]["reachable"] == is_reachable, state_json @@ -292,7 +314,7 @@ async def test_discover_full_state(hue_client): """Test the discovery of full state.""" result = await hue_client.get(f"/api/{HUE_API_USERNAME}") - assert result.status == 200 + assert result.status == HTTP_OK assert "application/json" in result.headers["content-type"] result_json = await result.json() @@ -344,7 +366,9 @@ async def test_get_light_state(hass_hue, hue_client): blocking=True, ) - office_json = await perform_get_light_state(hue_client, "light.ceiling_lights", 200) + office_json = await perform_get_light_state( + hue_client, "light.ceiling_lights", HTTP_OK + ) assert office_json["state"][HUE_API_STATE_ON] is True assert office_json["state"][HUE_API_STATE_BRI] == 127 @@ -354,13 +378,18 @@ async def test_get_light_state(hass_hue, hue_client): # Check all lights view result = await hue_client.get("/api/username/lights") - assert result.status == 200 + assert result.status == HTTP_OK assert "application/json" in result.headers["content-type"] result_json = await result.json() - assert "light.ceiling_lights" in result_json - assert result_json["light.ceiling_lights"]["state"][HUE_API_STATE_BRI] == 127 + assert ENTITY_NUMBERS_BY_ID["light.ceiling_lights"] in result_json + assert ( + result_json[ENTITY_NUMBERS_BY_ID["light.ceiling_lights"]]["state"][ + HUE_API_STATE_BRI + ] + == 127 + ) # Turn office light off await hass_hue.services.async_call( @@ -370,7 +399,9 @@ async def test_get_light_state(hass_hue, hue_client): blocking=True, ) - office_json = await perform_get_light_state(hue_client, "light.ceiling_lights", 200) + office_json = await perform_get_light_state( + hue_client, "light.ceiling_lights", HTTP_OK + ) assert office_json["state"][HUE_API_STATE_ON] is False # Removed assert HUE_API_STATE_BRI == 0 as Hue API states bri must be 1..254 @@ -378,10 +409,10 @@ async def test_get_light_state(hass_hue, hue_client): assert office_json["state"][HUE_API_STATE_SAT] == 0 # Make sure bedroom light isn't accessible - await perform_get_light_state(hue_client, "light.bed_light", 401) + await perform_get_light_state(hue_client, "light.bed_light", HTTP_UNAUTHORIZED) # Make sure kitchen light isn't accessible - await perform_get_light_state(hue_client, "light.kitchen_lights", 401) + await perform_get_light_state(hue_client, "light.kitchen_lights", HTTP_UNAUTHORIZED) async def test_put_light_state(hass, hass_hue, hue_client): @@ -432,7 +463,7 @@ async def test_put_light_state(hass, hass_hue, hue_client): # go through api to get the state back ceiling_json = await perform_get_light_state( - hue_client, "light.ceiling_lights", 200 + hue_client, "light.ceiling_lights", HTTP_OK ) assert ceiling_json["state"][HUE_API_STATE_BRI] == 123 assert ceiling_json["state"][HUE_API_STATE_HUE] == 4369 @@ -451,7 +482,7 @@ async def test_put_light_state(hass, hass_hue, hue_client): # go through api to get the state back ceiling_json = await perform_get_light_state( - hue_client, "light.ceiling_lights", 200 + hue_client, "light.ceiling_lights", HTTP_OK ) assert ceiling_json["state"][HUE_API_STATE_BRI] == 254 assert ceiling_json["state"][HUE_API_STATE_HUE] == 4369 @@ -464,7 +495,7 @@ async def test_put_light_state(hass, hass_hue, hue_client): ceiling_result_json = await ceiling_result.json() - assert ceiling_result.status == 200 + assert ceiling_result.status == HTTP_OK assert "application/json" in ceiling_result.headers["content-type"] assert len(ceiling_result_json) == 1 @@ -473,7 +504,7 @@ async def test_put_light_state(hass, hass_hue, hue_client): ceiling_lights = hass_hue.states.get("light.ceiling_lights") assert ceiling_lights.state == STATE_OFF ceiling_json = await perform_get_light_state( - hue_client, "light.ceiling_lights", 200 + hue_client, "light.ceiling_lights", HTTP_OK ) # Removed assert HUE_API_STATE_BRI == 0 as Hue API states bri must be 1..254 assert ceiling_json["state"][HUE_API_STATE_HUE] == 0 @@ -483,13 +514,13 @@ async def test_put_light_state(hass, hass_hue, hue_client): bedroom_result = await perform_put_light_state( hass_hue, hue_client, "light.bed_light", True ) - assert bedroom_result.status == 401 + assert bedroom_result.status == HTTP_UNAUTHORIZED # Make sure we can't change the kitchen light state kitchen_result = await perform_put_light_state( - hass_hue, hue_client, "light.kitchen_light", True + hass_hue, hue_client, "light.kitchen_lights", True ) - assert kitchen_result.status == HTTP_NOT_FOUND + assert kitchen_result.status == HTTP_UNAUTHORIZED async def test_put_light_state_script(hass, hass_hue, hue_client): @@ -512,7 +543,7 @@ async def test_put_light_state_script(hass, hass_hue, hue_client): script_result_json = await script_result.json() - assert script_result.status == 200 + assert script_result.status == HTTP_OK assert len(script_result_json) == 2 kitchen_light = hass_hue.states.get("light.kitchen_lights") @@ -535,7 +566,7 @@ async def test_put_light_state_climate_set_temperature(hass_hue, hue_client): hvac_result_json = await hvac_result.json() - assert hvac_result.status == 200 + assert hvac_result.status == HTTP_OK assert len(hvac_result_json) == 2 hvac = hass_hue.states.get("climate.hvac") @@ -546,7 +577,7 @@ async def test_put_light_state_climate_set_temperature(hass_hue, hue_client): ecobee_result = await perform_put_light_state( hass_hue, hue_client, "climate.ecobee", True ) - assert ecobee_result.status == 401 + assert ecobee_result.status == HTTP_UNAUTHORIZED async def test_put_light_state_media_player(hass_hue, hue_client): @@ -569,7 +600,7 @@ async def test_put_light_state_media_player(hass_hue, hue_client): mp_result_json = await mp_result.json() - assert mp_result.status == 200 + assert mp_result.status == HTTP_OK assert len(mp_result_json) == 2 walkman = hass_hue.states.get("media_player.walkman") @@ -604,7 +635,7 @@ async def test_close_cover(hass_hue, hue_client): hass_hue, hue_client, cover_id, True, 100 ) - assert cover_result.status == 200 + assert cover_result.status == HTTP_OK assert "application/json" in cover_result.headers["content-type"] for _ in range(7): @@ -624,6 +655,7 @@ async def test_close_cover(hass_hue, hue_client): async def test_set_position_cover(hass_hue, hue_client): """Test setting position cover .""" cover_id = "cover.living_room_window" + cover_number = ENTITY_NUMBERS_BY_ID[cover_id] # Turn the office light off first await hass_hue.services.async_call( cover.DOMAIN, @@ -651,19 +683,14 @@ async def test_set_position_cover(hass_hue, hue_client): hass_hue, hue_client, cover_id, False, brightness ) - assert cover_result.status == 200 + assert cover_result.status == HTTP_OK assert "application/json" in cover_result.headers["content-type"] cover_result_json = await cover_result.json() assert len(cover_result_json) == 2 - assert True, cover_result_json[0]["success"][ - "/lights/cover.living_room_window/state/on" - ] - assert ( - cover_result_json[1]["success"]["/lights/cover.living_room_window/state/bri"] - == level - ) + assert True, cover_result_json[0]["success"][f"/lights/{cover_number}/state/on"] + assert cover_result_json[1]["success"][f"/lights/{cover_number}/state/bri"] == level for _ in range(100): future = dt_util.utcnow() + timedelta(seconds=1) @@ -696,7 +723,7 @@ async def test_put_light_state_fan(hass_hue, hue_client): fan_result_json = await fan_result.json() - assert fan_result.status == 200 + assert fan_result.status == HTTP_OK assert len(fan_result_json) == 2 living_room_fan = hass_hue.states.get("fan.living_room_fan") @@ -707,6 +734,7 @@ async def test_put_light_state_fan(hass_hue, hue_client): # pylint: disable=invalid-name async def test_put_with_form_urlencoded_content_type(hass_hue, hue_client): """Test the form with urlencoded content.""" + entity_number = ENTITY_NUMBERS_BY_ID["light.ceiling_lights"] # Needed for Alexa await perform_put_test_on_ceiling_lights( hass_hue, hue_client, "application/x-www-form-urlencoded" @@ -715,7 +743,7 @@ async def test_put_with_form_urlencoded_content_type(hass_hue, hue_client): # Make sure we fail gracefully when we can't parse the data data = {"key1": "value1", "key2": "value2"} result = await hue_client.put( - "/api/username/lights/light.ceiling_lights/state", + f"/api/username/lights/{entity_number}/state", headers={"content-type": "application/x-www-form-urlencoded"}, data=data, ) @@ -725,22 +753,26 @@ async def test_put_with_form_urlencoded_content_type(hass_hue, hue_client): async def test_entity_not_found(hue_client): """Test for entity which are not found.""" - result = await hue_client.get("/api/username/lights/not.existant_entity") + result = await hue_client.get("/api/username/lights/98") assert result.status == HTTP_NOT_FOUND - result = await hue_client.put("/api/username/lights/not.existant_entity/state") + result = await hue_client.put("/api/username/lights/98/state") assert result.status == HTTP_NOT_FOUND async def test_allowed_methods(hue_client): """Test the allowed methods.""" - result = await hue_client.get("/api/username/lights/light.ceiling_lights/state") + result = await hue_client.get( + "/api/username/lights/ENTITY_NUMBERS_BY_ID[light.ceiling_lights]/state" + ) assert result.status == 405 - result = await hue_client.put("/api/username/lights/light.ceiling_lights") + result = await hue_client.put( + "/api/username/lights/ENTITY_NUMBERS_BY_ID[light.ceiling_lights]" + ) assert result.status == 405 @@ -753,7 +785,9 @@ async def test_proper_put_state_request(hue_client): """Test the request to set the state.""" # Test proper on value parsing result = await hue_client.put( - "/api/username/lights/{}/state".format("light.ceiling_lights"), + "/api/username/lights/{}/state".format( + ENTITY_NUMBERS_BY_ID["light.ceiling_lights"] + ), data=json.dumps({HUE_API_STATE_ON: 1234}), ) @@ -761,7 +795,9 @@ async def test_proper_put_state_request(hue_client): # Test proper brightness value parsing result = await hue_client.put( - "/api/username/lights/{}/state".format("light.ceiling_lights"), + "/api/username/lights/{}/state".format( + ENTITY_NUMBERS_BY_ID["light.ceiling_lights"] + ), data=json.dumps({HUE_API_STATE_ON: True, HUE_API_STATE_BRI: "Hello world!"}), ) @@ -773,7 +809,7 @@ async def test_get_empty_groups_state(hue_client): # Test proper on value parsing result = await hue_client.get("/api/username/groups") - assert result.status == 200 + assert result.status == HTTP_OK result_json = await result.json() @@ -801,7 +837,7 @@ async def perform_put_test_on_ceiling_lights( hass_hue, hue_client, "light.ceiling_lights", True, 56, content_type ) - assert office_result.status == 200 + assert office_result.status == HTTP_OK assert "application/json" in office_result.headers["content-type"] office_result_json = await office_result.json() @@ -816,11 +852,12 @@ async def perform_put_test_on_ceiling_lights( async def perform_get_light_state(client, entity_id, expected_status): """Test the getting of a light state.""" - result = await client.get(f"/api/username/lights/{entity_id}") + entity_number = ENTITY_NUMBERS_BY_ID[entity_id] + result = await client.get(f"/api/username/lights/{entity_number}") assert result.status == expected_status - if expected_status == 200: + if expected_status == HTTP_OK: assert "application/json" in result.headers["content-type"] return await result.json() @@ -850,8 +887,9 @@ async def perform_put_light_state( if saturation is not None: data[HUE_API_STATE_SAT] = saturation + entity_number = ENTITY_NUMBERS_BY_ID[entity_id] result = await client.put( - f"/api/username/lights/{entity_id}/state", + f"/api/username/lights/{entity_number}/state", headers=req_headers, data=json.dumps(data).encode(), ) @@ -878,12 +916,12 @@ async def test_external_ip_blocked(hue_client): ): for getUrl in getUrls: result = await hue_client.get(getUrl) - assert result.status == 401 + assert result.status == HTTP_UNAUTHORIZED for postUrl in postUrls: result = await hue_client.post(postUrl) - assert result.status == 401 + assert result.status == HTTP_UNAUTHORIZED for putUrl in putUrls: result = await hue_client.put(putUrl) - assert result.status == 401 + assert result.status == HTTP_UNAUTHORIZED diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py index 2557ecac2dd..110ecf868e6 100644 --- a/tests/components/emulated_hue/test_upnp.py +++ b/tests/components/emulated_hue/test_upnp.py @@ -8,6 +8,7 @@ import requests from homeassistant import const, setup from homeassistant.components import emulated_hue +from homeassistant.const import HTTP_OK from tests.async_mock import patch from tests.common import get_test_home_assistant, get_test_instance_port @@ -51,7 +52,7 @@ class TestEmulatedHue(unittest.TestCase): """Test the description.""" result = requests.get(BRIDGE_URL_BASE.format("/description.xml"), timeout=5) - assert result.status_code == 200 + assert result.status_code == HTTP_OK assert "text/xml" in result.headers["content-type"] # Make sure the XML is parsable @@ -68,7 +69,7 @@ class TestEmulatedHue(unittest.TestCase): BRIDGE_URL_BASE.format("/api"), data=json.dumps(request_json), timeout=5 ) - assert result.status_code == 200 + assert result.status_code == HTTP_OK assert "application/json" in result.headers["content-type"] resp_json = result.json() @@ -87,7 +88,7 @@ class TestEmulatedHue(unittest.TestCase): timeout=5, ) - assert result.status_code == 200 + assert result.status_code == HTTP_OK assert "application/json" in result.headers["content-type"] resp_json = result.json()