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

126 lines
6.2 KiB
Python

import http.client
import json
from pathlib import Path
import threading
from openpilot.starpilot.galaxy.access import GalaxyAccessOwner
from openpilot.starpilot.galaxy.server import make_server
from opendbc.car.structs import car
from copy import deepcopy
from dataclasses import replace
import unittest
from openpilot.starpilot.galaxy.toggle_backup import ToggleBackup
from openpilot.starpilot.galaxy.onroad_layout import OnroadLayoutOwner
from openpilot.starpilot.galaxy.settings import SettingsChanged
from openpilot.starpilot.galaxy.tests import test_settings as fixtures
class ToggleBackupTest(unittest.TestCase):
def setUp(self):
self.fixture = fixtures.SettingsGatewayTest()
self.fixture.setUp()
self.addCleanup(self.fixture.doCleanups)
cp = car.CarParams.new_message(carFingerprint='TOYOTA_COROLLA_TSS2', brand='toyota',
openpilotLongitudinalControl=True, pcmCruise=False, notCar=False, dashcamOnly=False, passive=False,
steerControlType=car.CarParams.SteerControlType.torque,
transmissionType=car.CarParams.TransmissionType.automatic)
self.fixture.context.value = replace(self.fixture.context.value, cp=cp, cp_raw=cp.to_bytes())
self.gateway = self.fixture.gateway
self.layout = OnroadLayoutOwner(self.fixture.params, lambda: self.fixture.context.value.parked)
self.owner = ToggleBackup(self.gateway, self.layout)
self.identity = self.fixture.session, self.fixture.generation
def restore(self, backup, authorized=lambda: True):
return self.owner.restore(backup, self.identity, authorized=authorized)
def test_export_and_roundtrip_preserve_settings_and_exclude_secrets(self):
self.fixture.params.put('GithubSshKeys', 'private', block=True)
backup = self.owner.export()
self.assertGreater(len(backup['settings']), 20)
self.assertIsNotNone(backup['layout'])
self.assertNotIn('GithubSshKeys', [entry['key'] for entry in backup['settings']])
result = self.restore(backup)
self.assertTrue(result['complete'])
self.assertEqual(result['restored'], 0)
self.assertGreater(result['unchanged'], 20)
self.assertEqual(self.fixture.params.get('GithubSshKeys'), 'private')
def test_restore_uses_existing_owner_and_validates_before_writes(self):
self.fixture.params.put_bool('AlwaysAllowUploads', False, block=True)
backup = self.owner.export()
saved = next(entry for entry in backup['settings'] if entry['key'] == 'AlwaysAllowUploads')
self.assertEqual(saved['value'], 'Off')
self.fixture.params.put_bool('AlwaysAllowUploads', True, block=True)
invalid = deepcopy(backup)
next(entry for entry in invalid['settings'] if entry['key'] == 'AlwaysAllowUploads')['value'] = 'invalid'
with self.assertRaises(SettingsChanged):
self.restore(invalid)
self.assertTrue(self.fixture.params.get_bool('AlwaysAllowUploads'))
result = self.restore(backup)
self.assertTrue(result['complete'])
self.assertEqual(result['restored'], 1)
self.assertFalse(self.fixture.params.get_bool('AlwaysAllowUploads'))
def test_parked_session_units_and_duplicate_guards(self):
backup = self.owner.export()
with self.assertRaises(SettingsChanged):
self.restore(backup, authorized=lambda: False)
self.fixture.context.value = replace(self.fixture.context.value, parked=False)
with self.assertRaises(SettingsChanged):
self.restore(backup)
self.fixture.context.value = replace(self.fixture.context.value, parked=True, metric=True)
with self.assertRaises(ValueError):
self.restore(backup)
self.fixture.context.value = replace(self.fixture.context.value, metric=False)
backup['settings'].append(backup['settings'][0])
with self.assertRaises(ValueError):
self.restore(backup)
def test_unknown_operational_keys_are_skipped_and_layout_is_validated(self):
backup = self.owner.export()
backup['settings'].append({'page': 'data', 'key': 'DoReboot', 'value': 'On'})
result = self.restore(backup)
self.assertTrue(result['complete'])
self.assertIn('DoReboot', result['skipped'])
self.assertFalse(self.fixture.params.get_bool('DoReboot'))
backup['layout'] = {'version': 999}
with self.assertRaises(ValueError):
self.restore(backup)
def test_authenticated_http_export_restore_and_origin_guard(self):
access = GalaxyAccessOwner(Path(self.fixture.params.get_param_path('Version')).parent.parent / 'access')
self.assertTrue(access.configure('password123', lambda: True))
server = make_server(port=0, owner=access, settings=self.gateway, layouts=self.layout)
worker = threading.Thread(target=server.serve_forever, kwargs={'poll_interval': .01}, daemon=True)
worker.start()
def stop():
server.shutdown()
worker.join(timeout=2)
server.server_close()
self.addCleanup(stop)
def request(path, payload=None, cookie='', origin=None):
connection = http.client.HTTPConnection('127.0.0.1', server.server_port, timeout=10)
headers = {'Cookie': cookie}
if payload is not None:
headers.update({'Content-Type':'application/json', 'Origin':origin or f'http://127.0.0.1:{server.server_port}'})
try:
connection.request('POST' if payload is not None else 'GET', path,
body=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()
self.assertEqual(request('/api/settings/backup')[0], 401)
status, _, headers = request('/api/auth/login', {'password':'password123'})
self.assertEqual(status, 200)
cookie = headers['Set-Cookie'].split(';',1)[0]
status, backup, _ = request('/api/settings/backup', cookie=cookie)
self.assertEqual(status, 200)
self.assertEqual(backup['format'], 'galaxy-toggles')
self.assertEqual(request('/api/settings/restore', backup, cookie, 'http://foreign.test')[0], 403)
status, result, _ = request('/api/settings/restore', backup, cookie)
self.assertEqual(status, 200)
self.assertTrue(result['complete'])
self.fixture.context.value = replace(self.fixture.context.value, parked=False)
self.assertEqual(request('/api/settings/restore', backup, cookie)[0], 409)