Files
StarPilot/openpilot/starpilot/galaxy/tests/test_favorites.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

129 lines
6.4 KiB
Python

import http.client
import json
from pathlib import Path
import tempfile
import threading
import unittest
from openpilot.common.params import Params
from openpilot.starpilot.favorites.actions import BOOKMARK, SET_SPEED
from openpilot.starpilot.favorites.owner import FAVORITE_SLOTS_PARAM, default_slots
from openpilot.starpilot.galaxy.access import GalaxyAccessOwner
from openpilot.starpilot.galaxy.favorites import FavoritesGateway
from openpilot.starpilot.galaxy.server import make_server
from openpilot.starpilot.galaxy.settings import AuthorityContext
class Context:
def sample(self):
return AuthorityContext(False, None, None)
class TestFavoritesHttp(unittest.TestCase):
def setUp(self):
directory = tempfile.TemporaryDirectory()
self.addCleanup(directory.cleanup)
self.params = Params(str(Path(directory.name) / "params"))
self.gateway = FavoritesGateway(self.params, Context())
self.server = make_server(port=0, owner=GalaxyAccessOwner(Path(directory.name) / "access"), favorites=self.gateway)
self.thread = threading.Thread(target=self.server.serve_forever, kwargs={"poll_interval": .01}, daemon=True)
self.thread.start()
self.addCleanup(self.stop)
def stop(self):
self.server.shutdown()
self.thread.join(timeout=2)
self.server.server_close()
def request(self, path="/api/favorites/slots", payload=None, cookie="", *, origin=None, raw=None):
connection = http.client.HTTPConnection("127.0.0.1", self.server.server_port, timeout=3)
headers = {"Cookie": cookie}
if payload is not None or raw is not None:
headers.update({"Content-Type": "application/json", "Origin": origin or f"http://127.0.0.1:{self.server.server_port}"})
try:
connection.request("POST" if payload is not None or raw is not None else "GET", path,
body=raw if raw is not None else json.dumps(payload) if payload is not None else None, headers=headers)
response = connection.getresponse()
return response.status, json.loads(response.read()), dict(response.getheaders())
finally:
connection.close()
def login(self):
code, _, headers = self.request("/api/auth/session")
self.assertEqual(code, 200)
return headers["Set-Cookie"].split(";", 1)[0]
def test_config_allowed_without_parked_authority_but_does_not_invoke_control(self):
self.assertEqual(self.request()[0], 401)
cookie = self.login()
code, data, _ = self.request(cookie=cookie)
self.assertEqual(code, 200)
self.assertTrue(data["editable"])
self.assertEqual(data["slots"], default_slots())
self.assertIn(BOOKMARK, {option["key"] for option in data["options"]})
self.assertFalse(next(option["available"] for option in data["options"] if option["key"] == BOOKMARK))
slots = data["slots"]
slots[0] = {"enabled": True, "show_onroad": True, "key": BOOKMARK, "label": "Mark"}
payload = {"revision": data["revision"], "slots": slots}
code, saved, _ = self.request(payload=payload, cookie=cookie)
self.assertEqual(code, 200)
self.assertEqual(saved["slots"], slots)
favorite_path = Path(self.params.get_param_path(FAVORITE_SLOTS_PARAM))
self.assertEqual(list(favorite_path.parent.iterdir()), [favorite_path])
self.assertEqual(self.request(payload=payload, cookie=cookie)[0], 409)
self.assertEqual(self.request("/api/favorites/action", {"key": BOOKMARK}, cookie)[0], 405)
def test_write_requires_session_origin_unique_fields_and_registered_control(self):
cookie = self.login()
_, data, _ = self.request(cookie=cookie)
payload = {"revision": data["revision"], "slots": data["slots"]}
self.assertEqual(self.request(payload=payload)[0], 401)
self.assertEqual(self.request(payload=payload, cookie=cookie, origin="http://example.com")[0], 403)
self.assertEqual(self.request(cookie=cookie, raw='{"revision":"a","revision":"b","slots":[]}')[0], 400)
payload["slots"][0]["key"] = "ArbitraryParam"
self.assertEqual(self.request(payload=payload, cookie=cookie)[0], 400)
self.assertFalse(Path(self.params.get_param_path(FAVORITE_SLOTS_PARAM)).exists())
def test_set_speed_preserved_for_device_execution_without_http_invocation(self):
slots = default_slots()
slots[1] = {"enabled": True, "show_onroad": True, "key": SET_SPEED, "label": "45 mph", "value": 45}
Path(self.params.get_param_path(FAVORITE_SLOTS_PARAM)).write_text(json.dumps(slots))
cookie = self.login()
code, data, _ = self.request(cookie=cookie)
self.assertEqual(code, 200)
self.assertEqual(data["slots"], slots)
self.assertFalse(data["states"][1]["available"])
self.assertIn("Use the qualified control on the device", data["states"][1]["reason"])
data["slots"][0] = {"enabled": True, "show_onroad": True, "key": BOOKMARK, "label": "Bookmark"}
self.assertEqual(self.request(payload={"revision": data["revision"], "slots": data["slots"]}, cookie=cookie)[0], 200)
self.assertEqual(json.loads(Path(self.params.get_param_path(FAVORITE_SLOTS_PARAM)).read_text())[1], slots[1])
def test_speed_edit_persists_and_preserves_other_slots_with_revision_protection(self):
slots = default_slots()
slots[0] = {"enabled": True, "show_onroad": False, "key": SET_SPEED, "label": "Cruise", "value": 30}
slots[1] = {"enabled": True, "show_onroad": True, "key": BOOKMARK, "label": "Mark"}
slots[2] = {"enabled": False, "show_onroad": False, "key": "OldUnsupportedSetting", "label": "Keep me"}
path = Path(self.params.get_param_path(FAVORITE_SLOTS_PARAM))
path.write_text(json.dumps(slots))
cookie = self.login()
_, data, _ = self.request(cookie=cookie)
data["slots"][0]["value"] = 45
payload = {"revision": data["revision"], "slots": data["slots"]}
code, saved, _ = self.request(payload=payload, cookie=cookie)
self.assertEqual(code, 200)
expected = [{**slots[0], "value": 45}, *slots[1:]]
self.assertEqual(saved["slots"], expected)
self.assertEqual(json.loads(path.read_text()), expected)
code, reloaded, _ = self.request(cookie=cookie)
self.assertEqual(code, 200)
self.assertEqual(reloaded["slots"], expected)
self.assertNotEqual(reloaded["revision"], payload["revision"])
payload["slots"][0]["value"] = 60
self.assertEqual(self.request(payload=payload, cookie=cookie)[0], 409)
self.assertEqual(json.loads(path.read_text()), expected)
self.assertEqual(self.request("/api/favorites/action", {"key": SET_SPEED}, cookie)[0], 405)
if __name__ == "__main__":
unittest.main()