Files
StarPilot/openpilot/starpilot/ui/tests/test_galaxy_connection.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

220 lines
11 KiB
Python

"""Local Galaxy stays passwordless, including when credential storage is unavailable."""
import tempfile
import threading
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import Mock, patch
from openpilot.starpilot.galaxy.access import AccessStatus, GalaxyAccessOwner
from openpilot.starpilot.galaxy.remote import RemotePairing
from openpilot.starpilot.galaxy.server import make_server
import pyray as rl
from openpilot.starpilot.ui import galaxy_access
from openpilot.starpilot.ui.galaxy_access import GalaxyAccessFlow, GalaxyConnectionPage, GalaxyConnectionView, RemoteStatus, connection_url
from openpilot.system.ui.widgets import Widget
class TestGalaxyConnection(unittest.TestCase):
def test_native_client_uses_local_authenticated_pairing_routes(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
pairing = RemotePairing(root / "pairing")
parked = [True]
server = make_server(port=0, owner=GalaxyAccessOwner(root / "credentials"),
remote_pairing=pairing, parked=lambda: parked[0])
worker = threading.Thread(target=server.serve_forever, kwargs={"poll_interval": .01}, daemon=True)
worker.start()
try:
with patch.object(galaxy_access, "LOCAL_PORT", server.server_port), \
patch.object(galaxy_access, "LOCAL_ORIGIN", f"http://127.0.0.1:{server.server_port}"):
self.assertFalse(galaxy_access._remote_operation("status").paired)
paired = galaxy_access._remote_operation("pair", " password123 ")
self.assertTrue(paired.paired)
self.assertTrue(galaxy_access.REMOTE_URL.fullmatch(paired.url))
record = pairing.read()
assert record is not None
self.assertEqual(record["slug"], paired.url.rsplit("/", 1)[1])
self.assertTrue(galaxy_access._remote_operation("status").paired)
parked[0] = False
self.assertIn("Turn off the vehicle", galaxy_access._remote_operation("unpair").error)
self.assertIsNotNone(pairing.read())
parked[0] = True
self.assertFalse(galaxy_access._remote_operation("unpair").paired)
self.assertIsNone(pairing.read())
finally:
server.shutdown()
worker.join(timeout=2)
server.server_close()
def test_only_numeric_lan_ipv4_becomes_a_browser_url(self):
self.assertEqual(connection_url("192.168.1.111"), "http://192.168.1.111:8082/#/")
self.assertEqual(connection_url("10.0.0.1"), "http://10.0.0.1:8082/#/")
for address in ("", "localhost", "127.0.0.1", "0.0.0.0", "169.254.1.1", "224.0.0.1", "::1",
"192.168.1.111/?password=secret"):
with self.subTest(address=address):
self.assertIsNone(connection_url(address))
def test_native_wifi_address_updates_without_credentials(self):
with tempfile.TemporaryDirectory() as directory:
flow = GalaxyAccessFlow(GalaxyAccessOwner(Path(directory)), lambda: True)
wifi = SimpleNamespace(ipv4_address="192.168.1.111", stop=Mock())
with patch("openpilot.starpilot.ui.galaxy_access.WifiManager", return_value=wifi) as provider, \
patch("openpilot.starpilot.ui.galaxy_access.time.monotonic", side_effect=[1.0, 1.5, 2.1]):
self.assertEqual(flow._url(), "http://192.168.1.111:8082/#/")
wifi.ipv4_address = "192.168.1.112"
self.assertEqual(flow._url(), "http://192.168.1.111:8082/#/")
self.assertEqual(flow._url(), "http://192.168.1.112:8082/#/")
provider.assert_called_once_with()
def test_opening_connection_does_not_remove_configured_access(self):
with tempfile.TemporaryDirectory() as directory:
owner = GalaxyAccessOwner(Path(directory))
self.assertTrue(owner.configure("secure-password", lambda: True))
flow = GalaxyAccessFlow(owner, lambda: True)
with patch("openpilot.starpilot.ui.galaxy_access.GalaxyConnectionView", return_value="connection page"), \
patch("openpilot.starpilot.ui.galaxy_access.gui_app.push_widget") as pushed:
flow.open_large()
pushed.assert_called_once_with("connection page")
self.assertEqual(owner.status().status, AccessStatus.CONFIGURED_LOCAL)
def test_both_pages_show_local_access_even_when_driving_without_reading_credentials(self):
owner = Mock()
owner.status.side_effect = RuntimeError("credential storage unavailable")
parked = Mock(return_value=True)
flow = GalaxyAccessFlow(owner, parked)
for method, page in ((flow.open_large, "GalaxyConnectionView"), (flow.open_compact, "GalaxyConnectionPage")):
with self.subTest(page=page), patch(f"openpilot.starpilot.ui.galaxy_access.{page}", return_value="connection page"), \
patch("openpilot.starpilot.ui.galaxy_access.gui_app.push_widget") as pushed:
parked.return_value = True
method()
pushed.assert_called_once_with("connection page")
pushed.reset_mock()
parked.return_value = False
method()
pushed.assert_called_once_with("connection page")
owner.assert_not_called()
self.assertEqual(owner.mock_calls, [])
def test_compact_button_available_without_credential_storage(self):
class Button(Widget):
def __init__(self, text, value, icon):
super().__init__()
self.text, self.value = text, value
def _render(self, rect):
pass
owner = Mock()
owner.status.side_effect = RuntimeError("credential storage unavailable")
parked = Mock(return_value=True)
flow = GalaxyAccessFlow(owner, parked)
with patch("openpilot.selfdrive.ui.mici.widgets.button.BigButton", Button), \
patch("openpilot.starpilot.ui.galaxy_access.gui_app.texture"):
button = flow.compact_button()
button._update_state()
self.assertTrue(button.enabled)
self.assertEqual(button.value, "pair your comma")
parked.return_value = False
self.assertTrue(button.enabled)
self.assertEqual(owner.mock_calls, [])
def test_pairing_requires_parked_and_single_flight(self):
parked = Mock(return_value=False)
flow = GalaxyAccessFlow(Mock(), parked)
self.assertFalse(flow.pair("password123"))
self.assertFalse(flow.unpair())
parked.return_value = True
self.assertFalse(flow.pair("1234567"))
self.assertIn("at least 8", flow.remote.error)
with patch.object(flow, "_remote_worker", Mock()) as worker:
self.assertTrue(flow.pair("password123"))
self.assertFalse(flow.unpair())
worker.submit.assert_called_once()
flow.close()
def test_status_error_keeps_last_pairing_metadata(self):
flow = GalaxyAccessFlow(Mock(), lambda: True)
url = "https://galaxy.firestar.link/Abcdef1234567890"
flow.remote = RemoteStatus(True, url, True)
from concurrent.futures import Future
pending = Future()
pending.set_result(RemoteStatus(error="Local Galaxy is unavailable"))
flow._remote_future = pending
flow._remote_next_check = float("inf")
self.assertEqual(flow.update_remote(), RemoteStatus(True, url, True, error="Local Galaxy is unavailable"))
def test_large_page_shows_passwordless_address_and_qr_without_credential_storage(self):
class Button(Widget):
def __init__(self, text, callback, **kwargs):
super().__init__()
self.text = text
def _render(self, rect):
pass
owner = Mock()
owner.status.side_effect = RuntimeError("credential storage unavailable")
flow = GalaxyAccessFlow(owner, lambda: True)
url = "http://192.168.1.111:8082/#/"
with patch("openpilot.system.ui.widgets.button.Button", Button), \
patch.object(flow, "_url", return_value=url), \
patch.object(flow, "update_remote", return_value=RemoteStatus()), \
patch("openpilot.starpilot.ui.galaxy_access.make_texture", return_value=None) as qr, \
patch("openpilot.starpilot.ui.galaxy_access.gui_app.font"), \
patch.object(rl, "draw_rectangle_rec"), patch.object(rl, "draw_text_ex") as draw:
view = GalaxyConnectionView(flow)
view._update_state()
view._render(rl.Rectangle(0, 0, 2160, 1080))
qr.assert_called_once_with(url)
texts = [call.args[1] for call in draw.call_args_list]
self.assertIn(url, texts)
self.assertIn("No password is needed on this network.", texts)
self.assertLess(texts.index("Choose a password, then pair your comma with Galaxy."), texts.index("Local network"))
self.assertEqual((view._close.text, view._pair.text, view._unpair.text),
("Close", "Set password & pair", "Unpair remote Galaxy"))
self.assertEqual(owner.mock_calls, [])
def test_compact_page_has_local_and_remote_views_without_credential_access(self):
class Scroller:
def __init__(self):
self._scroller = Mock()
def _update_state(self):
pass
owner = Mock()
owner.status.side_effect = RuntimeError("credential storage unavailable")
flow = GalaxyAccessFlow(owner, lambda: True)
url = "http://192.168.1.111:8082/#/"
qr_widgets = [Mock(_texture=None, _url=""), Mock(_texture=None, _url="")]
with patch("openpilot.system.ui.widgets.scroller.NavScroller", Scroller), \
patch("openpilot.selfdrive.ui.mici.widgets.qr.QR", side_effect=qr_widgets), \
patch("openpilot.selfdrive.ui.mici.widgets.button.GreyBigButton") as button, \
patch("openpilot.selfdrive.ui.mici.widgets.button.BigButton"), \
patch("openpilot.selfdrive.ui.mici.widgets.dialog.BigInputDialog", return_value="masked prompt") as prompt, \
patch("openpilot.starpilot.ui.galaxy_access.gui_app.texture"), \
patch.object(flow, "_url", return_value=url), \
patch.object(flow, "update_remote", return_value=RemoteStatus()):
page = GalaxyConnectionPage(flow)
page._update_state()
self.assertEqual(qr_widgets[0]._url, url)
qr_widgets[0]._generate_qr_code.assert_called_once_with()
qr_widgets[0].set_visible.assert_called_once_with(True)
qr_widgets[1].set_visible.assert_called_once_with(False)
self.assertEqual(button.call_args_list[0].args, ("local Galaxy • no password", "connect to Wi-Fi"))
self.assertEqual([call.args for call in button.call_args_list], [
("local Galaxy • no password", "connect to Wi-Fi"),
("remote Galaxy", "checking pairing"),
("remote connection", "")])
self.assertEqual(page._scroller.add_widgets.call_args.args[0],
[page._pair, page._remote_qr, page._remote_address, page._remote_state, page._unpair, page._qr, page._address])
with patch("openpilot.starpilot.ui.galaxy_access.gui_app.push_widget") as pushed:
page._enter_password()
self.assertIs(prompt.call_args.kwargs["password_mode"], True)
self.assertEqual(prompt.call_args.kwargs["minimum_length"], 8)
pushed.assert_called_once_with("masked prompt")
self.assertEqual(owner.mock_calls, [])