mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
80fbb4f6db
Register vehicle controls, planner modes, UI actions and Galaxy services after their providers exist. Wire the shared runtime entrypoints and cover their combined behavior. Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> Co-authored-by: Daniel Koepping <elkoled@gmail.com> Co-authored-by: Danny <dannyirwin31@yahoo.com> Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com> Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com> Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com> Co-authored-by: Kacper Rączy <gfw.kra@gmail.com> Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu> Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com> Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Co-authored-by: stef <19478336+stefpi@users.noreply.github.com> Co-authored-by: whoisdomi <dcantu83@gmail.com> Co-authored-by: Intelli <contact@intelli.software> Co-authored-by: haraschax <6804392+haraschax@users.noreply.github.com> Co-authored-by: prabhaavp <prabhaav@msu.edu>
116 lines
6.4 KiB
Python
116 lines
6.4 KiB
Python
import base64
|
|
import hashlib
|
|
import http.client
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import threading
|
|
import unittest
|
|
|
|
from openpilot.common.params import Params
|
|
from openpilot.starpilot.galaxy.access import GalaxyAccessOwner
|
|
from openpilot.starpilot.galaxy.remote import RemotePairing
|
|
from openpilot.starpilot.galaxy.server import make_remote_server, make_server
|
|
from openpilot.starpilot.galaxy.software_operations import SoftwareOperations
|
|
from openpilot.starpilot.galaxy.software_status import SoftwareStatus
|
|
from openpilot.starpilot.galaxy.tests.test_software_operations import FakeProcess
|
|
|
|
|
|
class SoftwareHttpTest(unittest.TestCase):
|
|
def setUp(self):
|
|
temporary = tempfile.TemporaryDirectory()
|
|
self.addCleanup(temporary.cleanup)
|
|
root = Path(temporary.name)
|
|
installed = root / 'installed'
|
|
installed.mkdir()
|
|
subprocess.run(['git', '-C', str(installed), '-c', 'init.defaultBranch=main', 'init', '-q'], check=True)
|
|
subprocess.run(['git', '-C', str(installed), '-c', 'core.hooksPath=/dev/null', '-c', 'user.name=Test',
|
|
'-c', 'user.email=test@example.com', 'commit', '--allow-empty', '-qm', 'Software history test'], check=True)
|
|
commit = subprocess.check_output(['git', '-C', str(installed), 'rev-parse', 'HEAD'], text=True).strip()
|
|
self.params = Params(str(root / 'params'))
|
|
for key, value in {'Version': 'test', 'GitBranch': 'main', 'GitCommit': commit,
|
|
'UpdaterState': 'idle', 'UpdaterTargetBranch': 'main', 'UpdaterAvailableBranches': 'main'}.items():
|
|
self.params.put(key, value, block=True)
|
|
self.params.put_bool('IsOffroad', True, block=True)
|
|
self.params.put('UpdaterCurrentReleaseNotes', b'<h1>Release notes</h1><li>Useful changes</li>', block=True)
|
|
self.process = FakeProcess()
|
|
self.parked = True
|
|
self.operations = SoftwareOperations(self.params, parked=lambda: self.parked, process=self.process,
|
|
installed=installed, finalized=root / 'finalized')
|
|
self.access = GalaxyAccessOwner(root / 'access')
|
|
self.pairing = RemotePairing(root / 'pairing')
|
|
self.local = make_server(port=0, owner=self.access, remote_pairing=self.pairing,
|
|
software=SoftwareStatus(self.params), software_operations=self.operations)
|
|
self.remote = make_remote_server(self.local, port=0)
|
|
self.servers = []
|
|
for server in (self.local, self.remote):
|
|
thread = threading.Thread(target=server.serve_forever, kwargs={'poll_interval': .01}, daemon=True)
|
|
thread.start()
|
|
self.servers.append((server, thread))
|
|
self.addCleanup(self.close)
|
|
self.local_cookie = self.request('/api/auth/session')[2]['Set-Cookie'].split(';', 1)[0]
|
|
self.access.configure('password123', lambda: True)
|
|
self.slug = self.pairing.pair(hashlib.sha256(b'password123').hexdigest())
|
|
record = self.pairing.read()
|
|
assert record is not None
|
|
token = base64.urlsafe_b64encode(json.dumps({self.slug: record['session']}).encode()).decode().rstrip('=')
|
|
self.remote_cookie = 'galaxy_session=' + token
|
|
|
|
def close(self):
|
|
for server, thread in reversed(self.servers):
|
|
server.shutdown()
|
|
thread.join(timeout=2)
|
|
server.server_close()
|
|
|
|
def request(self, path, *, payload=None, remote=False, cookie=None, origin=None):
|
|
server = self.remote if remote else self.local
|
|
host = f'{self.slug}.devices.local' if remote else f'127.0.0.1:{server.server_port}'
|
|
headers = {'Host': host}
|
|
if cookie is not None:
|
|
headers['Cookie'] = cookie
|
|
if payload is not None:
|
|
headers.update({'Content-Type': 'application/json', 'Origin': origin or
|
|
('https://galaxy.firestar.link' if remote else f'http://{host}')})
|
|
connection = http.client.HTTPConnection('127.0.0.1', server.server_port, timeout=5)
|
|
try:
|
|
connection.request('POST' if payload is not None else 'GET', path,
|
|
json.dumps(payload) if payload is not None else None, headers)
|
|
response = connection.getresponse()
|
|
return response.status, json.loads(response.read()), dict(response.getheaders())
|
|
finally:
|
|
connection.close()
|
|
|
|
def test_local_and_remote_history_and_immediate_preference_roundtrip(self):
|
|
for remote in (False, True):
|
|
with self.subTest(remote=remote):
|
|
cookie = self.remote_cookie if remote else self.local_cookie
|
|
status, light, _ = self.request('/api/software/status?history=0', remote=remote, cookie=cookie)
|
|
self.assertEqual(status, 200)
|
|
self.assertNotIn('history', light['operations'])
|
|
status, value, _ = self.request('/api/software/status', remote=remote, cookie=cookie)
|
|
self.assertEqual(light['operations'], {key: entry for key, entry in value['operations'].items() if key != 'history'})
|
|
self.assertEqual(status, 200)
|
|
view = value['operations']
|
|
self.assertEqual(view['history']['installed'][0]['subject'], 'Software history test')
|
|
self.assertEqual(view['history']['currentReleaseNotes'], 'Release notes\n\nUseful changes')
|
|
expected = view['automaticDownloads']
|
|
payload = {'action': 'preferences', 'automaticDownloads': not expected, 'expectedAutomaticDownloads': expected}
|
|
status, saved, _ = self.request('/api/software/action', payload=payload, remote=remote, cookie=cookie)
|
|
self.assertEqual(status, 200)
|
|
self.assertIs(saved['operations']['automaticDownloads'], not expected)
|
|
self.assertIsNone(saved['operations']['request'])
|
|
self.assertEqual(self.request('/api/software/action', payload=payload, remote=remote, cookie=cookie)[0], 409)
|
|
self.assertEqual(self.process.sent, [])
|
|
|
|
def test_remote_auth_origin_and_parked_rules_cannot_be_bypassed(self):
|
|
payload = {'action': 'preferences', 'automaticDownloads': False, 'expectedAutomaticDownloads': True}
|
|
self.assertEqual(self.request('/api/software/status', remote=True)[0], 401)
|
|
self.assertEqual(self.request('/api/software/status', remote=True, cookie=self.local_cookie)[0], 401)
|
|
self.assertEqual(self.request('/api/software/action', payload=payload, remote=True,
|
|
cookie=self.remote_cookie, origin='https://wrong.example')[0], 403)
|
|
self.parked = False
|
|
self.assertEqual(self.request('/api/software/action', payload=payload, remote=True, cookie=self.remote_cookie)[0], 409)
|
|
self.assertFalse(Path(self.params.get_param_path('UpdaterAutomaticDownloads')).exists())
|
|
self.assertEqual(self.process.sent, [])
|