121 lines
5.6 KiB
Python
121 lines
5.6 KiB
Python
"""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):
|
|
# Do not fabricate optional API methods such as log_group on lookup.
|
|
self.owner = Mock(spec=[
|
|
'entity_exists', 'get_state', 'log', 'log_info', 'log_warning',
|
|
'log_error', 'fire_event', 'call_service',
|
|
])
|
|
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()
|