"""The tests for the nexbus sensor component.""" from copy import deepcopy import pytest import homeassistant.components.sensor as sensor import homeassistant.components.nextbus.sensor as nextbus from tests.common import (assert_setup_component, async_setup_component, MockDependency) 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 MockDependency('py_nextbus') as py_nextbus: yield py_nextbus @pytest.fixture def mock_nextbus_predictions(mock_nextbus): """Create a mock of NextBusClient predictions.""" instance = mock_nextbus.NextBusClient.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.NextBusClient.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.""" client = mock_nextbus.NextBusClient() # with self.subTest('Valid everything'): assert nextbus.validate_tags( client, VALID_AGENCY, VALID_ROUTE, VALID_STOP, ) # with self.subTest('Invalid agency'): assert not nextbus.validate_tags( client, 'not-valid', VALID_ROUTE, VALID_STOP, ) # with self.subTest('Invalid route'): assert not nextbus.validate_tags( client, VALID_AGENCY, '0', VALID_STOP, ) # with self.subTest('Invalid stop'): assert not nextbus.validate_tags( client, 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': int(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': '4', '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'] == '1, 2, 3, 4' 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'