"""Check volume routing with the real condition parser, without AppDaemon.""" import ast import sys import unittest from pathlib import Path from types import SimpleNamespace from unittest.mock import Mock import yaml TOOLBOX = Path(__file__).resolve().parents[1] sys.path.insert(0, str(TOOLBOX)) import smartcondition from expressionparser import ParsingException def load_classes(path, namespace): nodes = [node for node in ast.parse(path.read_text()).body if isinstance(node, ast.ClassDef)] exec(compile(ast.Module(body=nodes, type_ignores=[]), str(path), 'exec'), namespace) return namespace handlers = load_classes(TOOLBOX / 'eventhandler.py', {}) classes = load_classes(TOOLBOX / 'eventsforwarder.py', { 'hass': SimpleNamespace(Hass=type('Hass', (), {})), 'LoggerInterface': type('LoggerInterface', (), {}), 'SmartCondition': smartcondition, 'EventHandler': handlers['EventHandler'], 'ParsingException': ParsingException, }) class ForwarderTests(unittest.TestCase): def setUp(self): self.owner = Mock() self.states = {'media_player.bravia_theatre': 'off', 'binary_sensor.is_tv_on': 'off'} self.owner.entity_exists.side_effect = lambda entity: entity in self.states self.owner.get_state.side_effect = lambda entity, **kwargs: self.states.get(entity) config = yaml.safe_load((TOOLBOX.parents[0] / 'global/homecinema.yaml').read_text()) self.blocks = config['homecinema_volume']['forwarders'] def test_priorities_and_repeated_commands(self): for direction, block in self.blocks.items(): senders = [classes['EventSender'](self.owner, value) for value in block['events_to_send'].values()] callers = [classes['ServiceCaller'](self.owner, value) for value in block['services_to_call'].values()] for bravia in ('on', 'playing', 'paused', 'idle', 'off', 'standby', 'unknown', 'unavailable'): for tv in ('on', 'off'): with self.subTest(direction=direction, bravia=bravia, tv=tv): self.states.update({'media_player.bravia_theatre': bravia, 'binary_sensor.is_tv_on': tv}) self.owner.fire_event.reset_mock() self.owner.call_service.reset_mock() for _ in range(2): for sender in senders: sender.send_event('button', {}) for caller in callers: caller.call_service('button', {}) if bravia in ('on', 'playing', 'paused', 'idle'): target = 'media_player.bravia_theatre' elif tv == 'on': self.assertEqual(self.owner.fire_event.call_count, 2) self.owner.fire_event.assert_called_with(f'the_frame_{direction}') self.owner.call_service.assert_not_called() continue else: target = 'media_player.living_room_sonos' self.owner.fire_event.assert_not_called() self.assertEqual(self.owner.call_service.call_count, 2) self.owner.call_service.assert_called_with( f'media_player/{direction}', entity_id=target) def test_unconditional_service_keeps_static_data(self): caller = classes['ServiceCaller'](self.owner, { 'service': 'media_player/volume_up', 'data': {'entity_id': 'media_player.test'}}) caller.call_service('button', {'entity_id': 'ignored'}) self.owner.call_service.assert_called_once_with( 'media_player/volume_up', entity_id='media_player.test') class SamsungVolumeTests(unittest.TestCase): def setUp(self): errors = SimpleNamespace(ConnectionFailure=ConnectionError, ResponseError=RuntimeError) samsung = load_classes(TOOLBOX.parent / 'samsungtheframe.py', { 'SmartObject': type('SmartObject', (), {}), 'tv_exceptions': errors, 'websocket': SimpleNamespace(WebSocketException=ValueError), }) self.tv_app = samsung['SamsungTheFrame']() self.tv_app.is_on_sensor = 'binary_sensor.is_tv_on' self.tv_app.get_state = Mock(return_value='on') self.connection = Mock() self.tv_app.connect = Mock(return_value=self.connection) self.tv_app.log = Mock() def test_volume_keys_and_connection_cleanup(self): for direction, key in [('up', 'KEY_VOLUP'), ('down', 'KEY_VOLDOWN')]: self.connection.reset_mock() self.tv_app.on_volume_event('button', {}, direction) self.connection.send_key.assert_called_once_with(key) self.connection.close.assert_called_once_with() def test_art_mode_does_not_send_volume(self): self.tv_app.get_state.return_value = 'off' self.tv_app.on_volume_event('button', {}, 'up') self.tv_app.connect.assert_not_called() def test_failure_is_logged_and_connection_closed(self): self.connection.send_key.side_effect = ConnectionError('Disconnected') self.tv_app.on_volume_event('button', {}, 'up') self.tv_app.log.assert_called_once() self.connection.close.assert_called_once_with() if __name__ == '__main__': unittest.main()