"""The tests for the nexbus sensor component.""" from copy import deepcopy from unittest.mock import patch import pytest import homeassistant.components.nextbus.sensor as nextbus import homeassistant.components.sensor as sensor from tests.common import assert_setup_component, async_setup_component VALID_AGENCY = "sf-muni" VALID_ROUTE = "F" VALID_STOP = "5650" VALID_AGENCY_TITLE = "San Francisco Muni" VALID_ROUTE_TITLE = "F-Market & Wharves" VALID_STOP_TITLE = "Market St & 7th St" SENSOR_ID_SHORT = "sensor.sf_muni_f" CONFIG_BASIC = { "sensor": { "platform": "nextbus", "agency": VALID_AGENCY, "route": VALID_ROUTE, "stop": VALID_STOP, } } CONFIG_INVALID_MISSING = {"sensor": {"platform": "nextbus"}} BASIC_RESULTS = { "predictions": { "agencyTitle": VALID_AGENCY_TITLE, "routeTitle": VALID_ROUTE_TITLE, "stopTitle": VALID_STOP_TITLE, "direction": { "title": "Outbound", "prediction": [ {"minutes": "1", "epochTime": "1553807371000"}, {"minutes": "2", "epochTime": "1553807372000"}, {"minutes": "3", "epochTime": "1553807373000"}, ], }, } } async def assert_setup_sensor(hass, config, count=1): """Set up the sensor and assert it's been created.""" with assert_setup_component(count): assert await async_setup_component(hass, sensor.DOMAIN, config) @pytest.fixture def mock_nextbus(): """Create a mock py_nextbus module.""" with patch( "homeassistant.components.nextbus.sensor.NextBusClient" ) as NextBusClient: yield NextBusClient @pytest.fixture def mock_nextbus_predictions(mock_nextbus): """Create a mock of NextBusClient predictions.""" instance = mock_nextbus.return_value instance.get_predictions_for_multi_stops.return_value = BASIC_RESULTS yield instance.get_predictions_for_multi_stops @pytest.fixture def mock_nextbus_lists(mock_nextbus): """Mock all list functions in nextbus to test validate logic.""" instance = mock_nextbus.return_value instance.get_agency_list.return_value = { "agency": [{"tag": "sf-muni", "title": "San Francisco Muni"}] } instance.get_route_list.return_value = { "route": [{"tag": "F", "title": "F - Market & Wharves"}] } instance.get_route_config.return_value = { "route": {"stop": [{"tag": "5650", "title": "Market St & 7th St"}]} } async def test_valid_config(hass, mock_nextbus, mock_nextbus_lists): """Test that sensor is set up properly with valid config.""" await assert_setup_sensor(hass, CONFIG_BASIC) async def test_invalid_config(hass, mock_nextbus, mock_nextbus_lists): """Checks that component is not setup when missing information.""" await assert_setup_sensor(hass, CONFIG_INVALID_MISSING, count=0) async def test_validate_tags(hass, mock_nextbus, mock_nextbus_lists): """Test that additional validation against the API is successful.""" # with self.subTest('Valid everything'): assert nextbus.validate_tags(mock_nextbus(), VALID_AGENCY, VALID_ROUTE, VALID_STOP) # with self.subTest('Invalid agency'): assert not nextbus.validate_tags( mock_nextbus(), "not-valid", VALID_ROUTE, VALID_STOP ) # with self.subTest('Invalid route'): assert not nextbus.validate_tags(mock_nextbus(), VALID_AGENCY, "0", VALID_STOP) # with self.subTest('Invalid stop'): assert not nextbus.validate_tags(mock_nextbus(), VALID_AGENCY, VALID_ROUTE, 0) async def test_verify_valid_state( hass, mock_nextbus, mock_nextbus_lists, mock_nextbus_predictions ): """Verify all attributes are set from a valid response.""" await assert_setup_sensor(hass, CONFIG_BASIC) mock_nextbus_predictions.assert_called_once_with( [{"stop_tag": VALID_STOP, "route_tag": VALID_ROUTE}], VALID_AGENCY ) state = hass.states.get(SENSOR_ID_SHORT) assert state is not None assert state.state == "2019-03-28T21:09:31+00:00" assert state.attributes["agency"] == VALID_AGENCY_TITLE assert state.attributes["route"] == VALID_ROUTE_TITLE assert state.attributes["stop"] == VALID_STOP_TITLE assert state.attributes["direction"] == "Outbound" assert state.attributes["upcoming"] == "1, 2, 3" async def test_message_dict( hass, mock_nextbus, mock_nextbus_lists, mock_nextbus_predictions ): """Verify that a single dict message is rendered correctly.""" mock_nextbus_predictions.return_value = { "predictions": { "agencyTitle": VALID_AGENCY_TITLE, "routeTitle": VALID_ROUTE_TITLE, "stopTitle": VALID_STOP_TITLE, "message": {"text": "Message"}, "direction": { "title": "Outbound", "prediction": [ {"minutes": "1", "epochTime": "1553807371000"}, {"minutes": "2", "epochTime": "1553807372000"}, {"minutes": "3", "epochTime": "1553807373000"}, ], }, } } await assert_setup_sensor(hass, CONFIG_BASIC) state = hass.states.get(SENSOR_ID_SHORT) assert state is not None assert state.attributes["message"] == "Message" async def test_message_list( hass, mock_nextbus, mock_nextbus_lists, mock_nextbus_predictions ): """Verify that a list of messages are rendered correctly.""" mock_nextbus_predictions.return_value = { "predictions": { "agencyTitle": VALID_AGENCY_TITLE, "routeTitle": VALID_ROUTE_TITLE, "stopTitle": VALID_STOP_TITLE, "message": [{"text": "Message 1"}, {"text": "Message 2"}], "direction": { "title": "Outbound", "prediction": [ {"minutes": "1", "epochTime": "1553807371000"}, {"minutes": "2", "epochTime": "1553807372000"}, {"minutes": "3", "epochTime": "1553807373000"}, ], }, } } await assert_setup_sensor(hass, CONFIG_BASIC) state = hass.states.get(SENSOR_ID_SHORT) assert state is not None assert state.attributes["message"] == "Message 1 -- Message 2" async def test_direction_list( hass, mock_nextbus, mock_nextbus_lists, mock_nextbus_predictions ): """Verify that a list of messages are rendered correctly.""" mock_nextbus_predictions.return_value = { "predictions": { "agencyTitle": VALID_AGENCY_TITLE, "routeTitle": VALID_ROUTE_TITLE, "stopTitle": VALID_STOP_TITLE, "message": [{"text": "Message 1"}, {"text": "Message 2"}], "direction": [ { "title": "Outbound", "prediction": [ {"minutes": "1", "epochTime": "1553807371000"}, {"minutes": "2", "epochTime": "1553807372000"}, {"minutes": "3", "epochTime": "1553807373000"}, ], }, { "title": "Outbound 2", "prediction": {"minutes": "0", "epochTime": "1553807374000"}, }, ], } } await assert_setup_sensor(hass, CONFIG_BASIC) state = hass.states.get(SENSOR_ID_SHORT) assert state is not None assert state.state == "2019-03-28T21:09:31+00:00" assert state.attributes["agency"] == VALID_AGENCY_TITLE assert state.attributes["route"] == VALID_ROUTE_TITLE assert state.attributes["stop"] == VALID_STOP_TITLE assert state.attributes["direction"] == "Outbound, Outbound 2" assert state.attributes["upcoming"] == "0, 1, 2, 3" async def test_custom_name( hass, mock_nextbus, mock_nextbus_lists, mock_nextbus_predictions ): """Verify that a custom name can be set via config.""" config = deepcopy(CONFIG_BASIC) config["sensor"]["name"] = "Custom Name" await assert_setup_sensor(hass, config) state = hass.states.get("sensor.custom_name") assert state is not None async def test_no_predictions( hass, mock_nextbus, mock_nextbus_predictions, mock_nextbus_lists ): """Verify there are no exceptions when no predictions are returned.""" mock_nextbus_predictions.return_value = {} await assert_setup_sensor(hass, CONFIG_BASIC) state = hass.states.get(SENSOR_ID_SHORT) assert state is not None assert state.state == "unknown" async def test_verify_no_upcoming( hass, mock_nextbus, mock_nextbus_lists, mock_nextbus_predictions ): """Verify attributes are set despite no upcoming times.""" mock_nextbus_predictions.return_value = { "predictions": { "agencyTitle": VALID_AGENCY_TITLE, "routeTitle": VALID_ROUTE_TITLE, "stopTitle": VALID_STOP_TITLE, "direction": {"title": "Outbound", "prediction": []}, } } await assert_setup_sensor(hass, CONFIG_BASIC) state = hass.states.get(SENSOR_ID_SHORT) assert state is not None assert state.state == "unknown" assert state.attributes["upcoming"] == "No upcoming predictions"