Files
saphid--frame-control/tests/e2e/test_framey.py
T

122 lines
6.0 KiB
Python

"""Verified Decky-store-shaped install flow through the real HTTP server and fake Frame SSH."""
import hashlib
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import io
import json
import os
import shlex
import subprocess
import threading
import zipfile
from harness import FrameTestCase, ROOT, Server, api, finished, ok, require, ssh
require() # Skip the module outside the disposable fake Frame, like its peers.
def archive(version):
raw = io.BytesIO()
with zipfile.ZipFile(raw, 'w') as bundle:
bundle.writestr('sample-panel/plugin.json', json.dumps({'name':'Sample Panel','api_version':2,'flags':[]}))
bundle.writestr('sample-panel/dist/index.js',
f"export default () => ({{content: SP_REACT.createElement('p', null, 'Sample {version}')}});")
bundle.writestr('sample-panel/README.txt', ''.join(hashlib.sha256(f'{version}-{n}'.encode()).hexdigest() for n in range(35)))
return raw.getvalue()
ARTIFACTS = {hashlib.sha256(a).hexdigest(): a for a in (archive('1.0.0'), archive('1.1.0'))}
HASHES = list(ARTIFACTS)
class StoreFixture(BaseHTTPRequestHandler):
release = 0
def log_message(self, *_):
pass
def do_GET(self):
if self.path == '/plugins':
body = json.dumps([{'id':123,'name':'Sample Panel','author':'Fixture',
'description':'A harmless panel','tags':[],
'versions':[{'name':['1.0.0','1.1.0'][self.release],
'hash':HASHES[self.release]}]}]).encode()
content_type = 'application/json'
elif self.path.startswith('/versions/') and self.path.endswith('.zip'):
body = ARTIFACTS.get(self.path.split('/')[-1][:-4])
content_type = 'application/zip'
else:
body = None
self.send_response(200 if body is not None else 404)
self.send_header('Content-Type', content_type if body is not None else 'text/plain')
self.end_headers()
self.wfile.write(body or b'not found')
class FrameyFlow(FrameTestCase):
@classmethod
def setUpClass(cls):
require()
StoreFixture.release = 0
cls.fixture = ThreadingHTTPServer(('127.0.0.1', 0), StoreFixture)
cls.thread = threading.Thread(target=cls.fixture.serve_forever, daemon=True)
cls.thread.start()
port = cls.fixture.server_port
cls.previous = {key:os.environ.get(key) for key in ('FRAMEY_STORE_URL','FRAMEY_CDN_URL')}
os.environ['FRAMEY_STORE_URL'] = f'http://127.0.0.1:{port}/plugins'
os.environ['FRAMEY_CDN_URL'] = f'http://127.0.0.1:{port}/versions/'
Server.stop()
super().setUpClass()
@classmethod
def tearDownClass(cls):
Server.stop()
cls.fixture.shutdown()
for key, value in cls.previous.items():
if value is None: os.environ.pop(key, None)
else: os.environ[key] = value
def backend(self):
code = '''import json,pathlib
h=pathlib.Path.home(); b=h/'.local/share/frame-control/decky/.frame-control'; b.mkdir(parents=True,exist_ok=True)
(b/'receipt.json').write_text(json.dumps({'managedBy':'Frame Control','version':'v3.3.0'}))
(b.parent/'plugins').mkdir(exist_ok=True)
u=h/'.config/systemd/user/frame-control-decky.service'; u.parent.mkdir(parents=True,exist_ok=True); u.write_text('[Service]')'''
ssh('python3 -c ' + shlex.quote(code))
ssh('mkdir -p ~/.local/share/frame-control/headset/bundle/ui')
source = (ROOT/'ui/frame_steam.py').read_bytes()
copy = subprocess.run(['ssh','frame','cat > ~/.local/share/frame-control/headset/bundle/ui/frame_steam.py'],
input=source, capture_output=True, timeout=20)
self.assertEqual(copy.returncode, 0, copy.stderr)
def test_store_install_update_remove_and_guards(self):
self.backend()
status, _, _ = api('GET', '/api/decky/store', headers={'X-Frame-UI':''})
self.assertEqual(status, 403)
catalogue = ok('GET', '/api/decky/store')['plugins']
self.assertEqual((catalogue[0]['version'], catalogue[0]['sha256']), ('1.0.0', HASHES[0]))
status, _, _ = api('POST', '/api/decky/plugin', {'action':'install','id':123})
self.assertEqual(status, 400) # Third-party code needs explicit acknowledgement.
job = finished(ok('POST', '/api/decky/plugin',
{'action':'install','id':123,'acknowledged':True}))
self.assertFalse(job.get('error'), job)
self.assertEqual(ok('GET', '/api/decky')['plugins'][0]['version'], '1.0.0')
self.assertIn('Sample 1.0.0', ssh('cat ~/.local/share/frame-control/decky/plugins/sample-panel/dist/index.js'))
status, _, _ = api('POST', '/api/framey/method', {'id':123,'method':'get_setting','args':[]},
headers={'X-Frame-UI':''})
self.assertEqual(status, 403)
status, _, _ = api('POST', '/api/framey/method', {'id':123,'method':'x;alert(1)','args':[]})
self.assertEqual(status, 400)
self.assertEqual(ok('POST', '/api/framey/method',
{'id':123,'method':'get_setting','args':['theme','dark']})['result'], 'dark')
self.assertTrue(ok('POST', '/api/framey/method',
{'id':123,'method':'set_setting','args':['theme','light']})['result'])
StoreFixture.release = 1
job = finished(ok('POST', '/api/decky/plugin',
{'action':'update','id':123,'acknowledged':True}))
self.assertFalse(job.get('error'), job)
self.assertEqual(ok('GET', '/api/decky')['plugins'][0]['version'], '1.1.0')
job = finished(ok('POST', '/api/decky/plugin',
{'action':'remove','id':123,'acknowledged':True}))
self.assertFalse(job.get('error'), job)
self.assertEqual(ok('GET', '/api/decky')['plugins'], [])
self.assertEqual(ssh('test -e ~/.local/share/frame-control/decky/plugins/sample-panel && echo yes || echo no').strip(), 'no')