FrameBridge 0.3.1 community source release
This commit is contained in:
commit
b23819fff1
1354 files changed
+343716
No files matched your search
@@ -0,0 +1,127 @@
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from framebridge_expanded.real_screen import screen_rpc as rpc
|
||||
|
||||
|
||||
class ScreenRpcTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.directory = Path(self.temp.name)
|
||||
self.counter = 0
|
||||
self.binding = {'backend': 'visible-tmux', 'pane_id': '%1', 'node': 'TEST', 'uid': os.getuid()}
|
||||
self.actions = []
|
||||
self.patches = [
|
||||
patch.object(rpc, 'snapshot_binding', return_value=self.binding),
|
||||
patch.object(rpc, 'capture_screen', side_effect=self.capture),
|
||||
patch.object(rpc, 'bind_screen', side_effect=lambda m: {**m, 'binding': self.binding}),
|
||||
patch.object(rpc, 'read_chunk', side_effect=self.chunk),
|
||||
patch.object(rpc, 'act', side_effect=self.action),
|
||||
patch.object(rpc.time, 'sleep'),
|
||||
]
|
||||
for p in self.patches:
|
||||
p.start()
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
def capture(self, directory):
|
||||
self.counter += 1
|
||||
capture_id = f'{self.counter:032x}'
|
||||
data = b'actual-pixel-test-fixture-' + capture_id.encode()
|
||||
path = directory / (capture_id + '.png')
|
||||
path.write_bytes(data)
|
||||
return {'capture_id': capture_id, 'source': 'linux-framebuffer', 'screen_type': 'tty',
|
||||
'node': 'TEST', 'uid': os.getuid(), 'width': 1920, 'height': 1080,
|
||||
'path': str(path), 'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest(),
|
||||
'active_vt': 'tty1', 'synthetic': False, 'pixel_source_verified': True}
|
||||
|
||||
def chunk(self, directory, capture_id, index, **kwargs):
|
||||
data = (directory / (capture_id + '.png')).read_bytes()
|
||||
digest = hashlib.sha256(data).hexdigest()
|
||||
if digest != kwargs['expected_sha256']:
|
||||
raise rpc.CaptureError('CAPTURE_SHA256_MISMATCH')
|
||||
return {'capture_id': capture_id, 'index': index, 'bytes': len(data), 'sha256': digest,
|
||||
'total_chunks': 1, 'encoding': 'base64', 'data': base64.b64encode(data).decode(), 'more': False}
|
||||
|
||||
def action(self, manifest, kind, **kwargs):
|
||||
self.actions.append((manifest['capture_id'], kind, kwargs))
|
||||
return {'performed': True}
|
||||
|
||||
def observe(self, **kwargs):
|
||||
return rpc.dispatch({'op': 'observe', **kwargs}, self.directory)['result']
|
||||
|
||||
def test_observation_uses_capture_pixels_not_terminal_text(self):
|
||||
m = self.observe(session_id='hidden_terminal')
|
||||
self.assertTrue(m['full_screen'] and m['authentic_pixels'])
|
||||
self.assertFalse(m['requested_session_visible'])
|
||||
self.assertNotIn('path', m)
|
||||
self.assertTrue(m['control_available'])
|
||||
self.assertEqual(m['width'], 1920)
|
||||
|
||||
def test_hidden_terminal_id_cannot_receive_input_for_visible_image(self):
|
||||
m = self.observe()
|
||||
with self.assertRaisesRegex(rpc.ScreenRpcError, 'SESSION_NOT_VISIBLE'):
|
||||
rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'session_id': 'hidden_terminal', 'key': 'ENTER'}, self.directory)
|
||||
self.assertEqual(self.actions, [])
|
||||
|
||||
def test_input_returns_new_observation_and_cannot_replay_old_capture(self):
|
||||
m = self.observe()
|
||||
after = rpc.dispatch({'op': 'text', 'capture_id': m['capture_id'], 'session_id': m['session_id'], 'text': 'visible marker'}, self.directory)['result']
|
||||
self.assertNotEqual(after['capture_id'], m['capture_id'])
|
||||
self.assertEqual(len(self.actions), 1)
|
||||
with self.assertRaisesRegex(rpc.ScreenRpcError, 'CAPTURE_ALREADY_USED'):
|
||||
rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'key': 'ENTER'}, self.directory)
|
||||
self.assertEqual(len(self.actions), 1)
|
||||
|
||||
def test_unknown_send_result_does_not_replay_input(self):
|
||||
m = self.observe()
|
||||
with patch.object(rpc, 'act', side_effect=rpc.ControlError('SCREEN_BINDING_CHANGED')):
|
||||
with self.assertRaisesRegex(rpc.ScreenRpcError, 'SCREEN_BINDING_CHANGED'):
|
||||
rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'key': 'ENTER'}, self.directory)
|
||||
with self.assertRaisesRegex(rpc.ScreenRpcError, 'CAPTURE_ALREADY_USED'):
|
||||
rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'key': 'ENTER'}, self.directory)
|
||||
|
||||
def test_target_change_during_capture_returns_no_bound_image(self):
|
||||
with patch.object(rpc, 'bind_screen', side_effect=lambda m: {**m, 'binding': {**self.binding, 'pane_id': '%0'}}):
|
||||
with self.assertRaisesRegex(rpc.ScreenRpcError, 'VISIBLE_TARGET_CHANGED_DURING_CAPTURE'):
|
||||
self.observe()
|
||||
self.assertEqual(list(self.directory.glob('*.bound.json')), [])
|
||||
|
||||
def test_sent_input_is_reported_if_following_capture_fails(self):
|
||||
m = self.observe()
|
||||
with patch.object(rpc, 'capture_screen', side_effect=rpc.CaptureError('ACTIVE_VT_CHANGED')):
|
||||
with self.assertRaises(rpc.ScreenRpcError) as caught:
|
||||
rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'key': 'ENTER'}, self.directory)
|
||||
self.assertEqual(caught.exception.code, 'INPUT_SENT_OBSERVE_FAILED')
|
||||
self.assertTrue(caught.exception.details['performed'])
|
||||
self.assertEqual(len(self.actions), 1)
|
||||
|
||||
def test_view_can_succeed_without_safe_input_target(self):
|
||||
with patch.object(rpc, 'snapshot_binding', side_effect=rpc.ControlError('SCREEN_VISIBLE_TMUX_NOT_FOUND')):
|
||||
m = self.observe()
|
||||
self.assertFalse(m['control_available'])
|
||||
with self.assertRaisesRegex(rpc.ScreenRpcError, 'VISIBLE_INPUT_UNAVAILABLE'):
|
||||
rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'key': 'ENTER'}, self.directory)
|
||||
self.assertEqual(self.actions, [])
|
||||
|
||||
def test_changed_png_cannot_drive_input(self):
|
||||
m = self.observe()
|
||||
(self.directory / (m['capture_id'] + '.png')).write_bytes(b'different pixels')
|
||||
with self.assertRaisesRegex(rpc.CaptureError, 'CAPTURE_SHA256_MISMATCH'):
|
||||
rpc.dispatch({'op': 'key', 'capture_id': m['capture_id'], 'key': 'ENTER'}, self.directory)
|
||||
self.assertEqual(self.actions, [])
|
||||
|
||||
def test_read_requires_expected_image_hash(self):
|
||||
m = self.observe()
|
||||
with self.assertRaisesRegex(rpc.ScreenRpcError, 'CAPTURE_SHA256_MISMATCH'):
|
||||
rpc.dispatch({'op': 'read_chunk', 'capture_id': m['capture_id'], 'index': 0, 'expected_sha256': '0' * 64}, self.directory)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user