Files
StarPilot/openpilot/starpilot/galaxy/tests/test_diagnostic_http.py
T
firestar5683 80fbb4f6db Feature registration
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>
2026-10-14 20:02:39 -05:00

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()