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>
122 lines
5.8 KiB
Python
122 lines
5.8 KiB
Python
from unittest.mock import Mock
|
|
"""Authenticated diagnostics over the same local and remote HTTP handler."""
|
|
|
|
import http.client
|
|
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
import threading
|
|
from types import SimpleNamespace
|
|
import unittest
|
|
|
|
from openpilot.starpilot.galaxy.access import GalaxyAccessOwner
|
|
from openpilot.starpilot.galaxy.server import make_server
|
|
from openpilot.starpilot.galaxy.settings import AuthorityContext, SettingsGateway
|
|
|
|
|
|
class DiagnosticHTTPTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.access = GalaxyAccessOwner(Path(self.temp.name))
|
|
self.access.configure('test-password-123', lambda: True)
|
|
self.calls = []
|
|
self.addresses = SimpleNamespace(snapshot=lambda: self.record('address', {'available': True, 'addresses': [
|
|
{'interface': 'wlan0', 'label': '192.168.4.2 (wlan0)', 'url': 'http://192.168.4.2:8082/'}], 'reason': ''}))
|
|
self.console = SimpleNamespace(snapshot=lambda: self.record('console', {'available': True, 'text': 'actual console',
|
|
'truncated': False, 'pane': 'comma:0.0', 'reason': ''}))
|
|
self.settings = SimpleNamespace(diagnostics=lambda: self.record('settings', {'schemaVersion': 1, 'sections': []}))
|
|
self.server = make_server(port=0, owner=self.access, parked=lambda: True, local_access=self.addresses,
|
|
tmux_live=self.console, settings=self.settings)
|
|
self.thread = threading.Thread(target=self.server.serve_forever, kwargs={'poll_interval': .01}, daemon=True)
|
|
self.thread.start()
|
|
self.cookie = ''
|
|
|
|
def tearDown(self):
|
|
self.server.shutdown()
|
|
self.thread.join(2)
|
|
self.server.server_close()
|
|
self.temp.cleanup()
|
|
|
|
def record(self, name, value):
|
|
self.calls.append(name)
|
|
return value
|
|
|
|
def request(self, path, *, method='GET', payload=None, remote=False, cookie=None, origin=None):
|
|
headers = {'Origin': origin or f'http://127.0.0.1:{self.server.server_port}', 'Content-Type': 'application/json'}
|
|
if cookie is not None or self.cookie:
|
|
headers['Cookie'] = self.cookie if cookie is None else cookie
|
|
if remote:
|
|
headers['Forwarded'] = 'for=203.0.113.8'
|
|
connection = http.client.HTTPConnection('127.0.0.1', self.server.server_port, timeout=4)
|
|
try:
|
|
connection.request(method, path, None if payload is None else json.dumps(payload), headers)
|
|
response = connection.getresponse()
|
|
token = response.getheader('Set-Cookie')
|
|
return response.status, json.loads(response.read()), token.split(';')[0] if token else ''
|
|
finally:
|
|
connection.close()
|
|
|
|
def test_real_http_routes_authentication_and_no_mutations(self):
|
|
for path in ('/api/local-access', '/api/tmux/live', '/api/troubleshoot'):
|
|
self.assertEqual(self.request(path, remote=True)[0], 401)
|
|
self.assertEqual(self.calls, [])
|
|
status, _, self.cookie = self.request('/api/auth/session')
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(self.request('/api/local-access')[1]['addresses'][0]['url'], 'http://192.168.4.2:8082/')
|
|
self.assertEqual(self.request('/api/tmux/live')[1]['text'], 'actual console')
|
|
self.assertEqual(self.request('/api/troubleshoot')[1]['sections'], [])
|
|
before = len(self.calls)
|
|
self.assertEqual(self.request('/api/tmux/live', origin='https://example.org')[0], 403)
|
|
self.assertNotEqual(self.request('/api/tmux/live', method='POST', payload={})[0], 200)
|
|
self.assertEqual(len(self.calls), before)
|
|
|
|
def test_authenticated_forwarded_transport_uses_same_tools(self):
|
|
status, _, self.cookie = self.request('/api/auth/login', method='POST', payload={'password': 'test-password-123'}, remote=True)
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(self.request('/api/tmux/live', remote=True)[1]['text'], 'actual console')
|
|
self.assertEqual(self.request('/api/local-access', remote=True)[0], 200)
|
|
self.assertEqual(self.request('/api/troubleshoot', remote=True)[0], 200)
|
|
|
|
def test_revocation_during_console_read_discards_output(self):
|
|
_, _, self.cookie = self.request('/api/auth/login', method='POST', payload={'password': 'test-password-123'}, remote=True)
|
|
entered, release = threading.Event(), threading.Event()
|
|
def wait():
|
|
entered.set()
|
|
release.wait(2)
|
|
return {'available': True, 'text': 'private console'}
|
|
self.console.snapshot = wait
|
|
response = []
|
|
thread = threading.Thread(target=lambda: response.append(self.request('/api/tmux/live', remote=True)))
|
|
thread.start()
|
|
self.assertTrue(entered.wait(2))
|
|
self.assertEqual(self.request('/api/auth/logout', method='POST', payload={}, remote=True)[0], 200)
|
|
release.set()
|
|
thread.join(3)
|
|
self.assertEqual(response[0][0], 401)
|
|
self.assertNotIn('private console', json.dumps(response))
|
|
|
|
def test_diagnostics_projects_existing_owners_without_raw_params(self):
|
|
context = AuthorityContext(True, SimpleNamespace(carFingerprint='TEST_CAR', brand='test',
|
|
openpilotLongitudinalControl=True, steerControlType='torque'), b'cp', False)
|
|
class NoRawParams:
|
|
def __getattr__(self, name):
|
|
raise AssertionError(f'Raw Params access: {name}')
|
|
gateway = SettingsGateway(NoRawParams(), Mock(sample=lambda: context))
|
|
pages = []
|
|
def state(page, ctx):
|
|
pages.append(page)
|
|
self.assertIs(ctx, context)
|
|
return SimpleNamespace(title=page, rows=[SimpleNamespace(label='Visible preference', value='On', source=b'private')])
|
|
gateway._state = Mock(side_effect=state)
|
|
result = gateway.diagnostics()
|
|
self.assertEqual(len(pages), 8)
|
|
self.assertEqual(result['vehicle']['fingerprint'], 'TEST_CAR')
|
|
self.assertEqual(result['sections'][0]['rows'], [{'label': 'Visible preference', 'value': 'On'}])
|
|
self.assertNotIn('private', json.dumps(result))
|
|
self.assertEqual(gateway.views, {})
|
|
self.assertEqual(gateway.intents, {})
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|