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

178 lines
8.1 KiB
Python

"""Actual authenticated HTTP dispatch to the single parked map owner."""
import http.client
import json
from pathlib import Path
import tempfile
import threading
import unittest
from openpilot.starpilot.galaxy.access import GalaxyAccessOwner
from openpilot.starpilot.galaxy.map_operations import MapOperations
from openpilot.starpilot.galaxy.server import make_server
class MapOperationsHTTPTest(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.owner = GalaxyAccessOwner(Path(temporary.name) / 'access')
self.calls = []
self.reply = lambda command: {'version': 1, 'ok': True, 'result': {'schemaVersion': 1, 'state': 'idle'}}
def request_owner(command):
self.calls.append(command)
return self.reply(command)
self.server = make_server(port=0, owner=self.owner, map_operations=MapOperations(request_owner))
self.thread = threading.Thread(target=self.server.serve_forever, kwargs={'poll_interval': 0.01}, daemon=True)
self.thread.start()
self.addCleanup(self.stop)
self.cookie = None
def stop(self):
self.server.shutdown()
self.thread.join(2)
self.server.server_close()
def request(self, path, *, payload=None, raw=None, headers=None):
# Exercise the password-backed forwarded path.
request_headers = {'Forwarded': 'for=203.0.113.8'}
if self.cookie:
request_headers['Cookie'] = self.cookie
method = 'GET'
if payload is not None or raw is not None:
method = 'POST'
request_headers.update({'Content-Type': 'application/json',
'Origin': f'http://127.0.0.1:{self.server.server_port}'})
raw = raw if raw is not None else json.dumps(payload)
request_headers.update(headers or {})
connection = http.client.HTTPConnection('127.0.0.1', self.server.server_port, timeout=3)
try:
connection.request(method, path, body=raw, headers=request_headers)
response = connection.getresponse()
return response.status, json.loads(response.read()), dict(response.getheaders())
finally:
connection.close()
def login(self):
self.assertTrue(self.owner.configure('password123', lambda: True))
status, _, headers = self.request('/api/auth/login', payload={'password': 'password123'})
self.assertEqual(status, 200)
self.cookie = headers['Set-Cookie'].split(';', 1)[0]
@staticmethod
def start_payload():
return {'regionToken': 'us_state.Iowa', 'maxTransferBytes': 1 << 30,
'maxNewDiskBytes': 2 << 30, 'expectedCurrentGeneration': ''}
def test_setup_is_authenticated_read_only_owner_projection(self):
self.login()
self.reply = lambda command: {'version': 1, 'ok': True, 'result': {
'schemaVersion': 1, 'packageReady': False, 'packageState': 'invalid_package',
'snapshotReady': True, 'selectedGeneration': 'a' * 64, 'parked': True,
'freeDiskBytes': 2 << 30, 'maxTransferBytes': 8 << 30, 'maxNewDiskBytes': 16 << 30}}
status, body, _ = self.request('/api/maps/setup')
self.assertEqual(status, 200)
self.assertEqual(body['packageState'], 'invalid_package')
self.assertEqual(body['selectedGeneration'], 'a' * 64)
self.assertEqual(self.calls, [{'version': 1, 'op': 'setup'}])
def test_session_and_same_origin_precede_every_owner_call(self):
routes = (('/api/maps/setup', None), ('/api/maps/catalog', None), ('/api/maps/operation', None),
('/api/maps/start', self.start_payload()), ('/api/maps/cancel', {'operationId': 'a' * 32 + ':1'}))
for path, payload in routes:
self.assertEqual(self.request(path, payload=payload)[0], 503)
self.login()
cookie, self.cookie = self.cookie, None
for path, payload in routes:
self.assertEqual(self.request(path, payload=payload)[0], 401)
self.cookie = cookie
self.assertEqual(self.request('/api/maps/start', payload=self.start_payload(),
headers={'Origin': 'http://foreign.example'})[0], 403)
self.assertEqual(self.calls, [])
for path, payload in routes:
status, body, headers = self.request(path, payload=payload)
self.assertEqual(status, 200)
self.assertEqual(body['state'], 'idle')
self.assertEqual(headers['Cache-Control'], 'no-store')
self.assertEqual([item['op'] for item in self.calls], ['setup', 'catalog', 'status', 'start', 'cancel'])
self.assertEqual(next(item for item in self.calls if item['op'] == 'start'), {'version': 1, 'op': 'start', **self.start_payload()})
def test_invalid_actions_never_reach_owner(self):
self.login()
payload = self.start_payload()
invalid = [[], {**payload, 'url': 'https://foreign.example'}, {**payload, 'maxTransferBytes': True},
{**payload, 'maxNewDiskBytes': 0}, {**payload, 'maxTransferBytes': 9 << 30},
{**payload, 'expectedCurrentGeneration': '../current'},
{**payload, 'expectedCurrentGeneration': None}]
for value in invalid:
self.assertEqual(self.request('/api/maps/start', payload=value)[0], 400)
self.assertEqual(self.request('/api/maps/start', raw='{"regionToken":"a","regionToken":"b"}')[0], 400)
self.assertEqual(self.request('/api/maps/cancel', payload={'operationId': '../all'})[0], 400)
self.assertEqual(self.request('/api/maps/cancel', payload={'operationId': 'op', 'all': True})[0], 400)
self.assertEqual(self.calls, [])
def test_owner_refusals_remain_distinct_from_acceptance(self):
self.login()
for code, expected in (('not_parked', 409), ('busy', 409), ('selection_changed', 409),
('invalid_region', 400), ('package_unavailable', 503)):
self.reply = lambda command, code=code: {'version': 1, 'ok': False, 'error': {'code': code, 'message': 'private detail'}}
status, body, _ = self.request('/api/maps/start', payload=self.start_payload())
self.assertEqual(status, expected)
self.assertEqual(body['code'], code)
self.assertNotIn('private detail', json.dumps(body))
self.reply = lambda command: {'version': 1, 'ok': True, 'result': ['invalid']}
self.assertEqual(self.request('/api/maps/catalog')[0], 503)
def test_revoke_during_read_discards_owner_result(self):
self.login()
entered, release = threading.Event(), threading.Event()
def paused(command):
entered.set()
release.wait(2)
return {'version': 1, 'ok': True, 'result': {'selectedGeneration': 'a' * 64}}
self.reply = paused
responses = []
worker = threading.Thread(target=lambda: responses.append(self.request('/api/maps/operation')))
worker.start()
try:
self.assertTrue(entered.wait(1))
self.assertEqual(self.request('/api/auth/logout', payload={})[0], 200)
finally:
release.set()
worker.join(2)
self.assertEqual(responses[0][0], 401)
self.assertNotIn('selectedGeneration', responses[0][1])
self.assertEqual(self.request('/api/maps/start', payload=self.start_payload())[0], 401)
self.assertEqual(len(self.calls), 1)
def test_logout_waits_for_authorized_dispatch_then_blocks_later_start(self):
self.login()
entered, release, logged_out = threading.Event(), threading.Event(), threading.Event()
def paused(command):
entered.set()
release.wait(2)
return {'version': 1, 'ok': True, 'result': {'schemaVersion': 1, 'state': 'transferring'}}
self.reply = paused
starts, logouts = [], []
starter = threading.Thread(target=lambda: starts.append(self.request('/api/maps/start', payload=self.start_payload())))
def logout():
logouts.append(self.request('/api/auth/logout', payload={}))
logged_out.set()
quitter = threading.Thread(target=logout)
starter.start()
try:
self.assertTrue(entered.wait(1))
quitter.start()
self.assertFalse(logged_out.wait(0.05))
finally:
release.set()
starter.join(2)
if quitter.ident is not None:
quitter.join(2)
self.assertEqual(logouts[0][0], 200)
self.assertIn(starts[0][0], (200, 401)) # Revocation may win before the response is sent.
self.assertEqual(self.request('/api/maps/start', payload=self.start_payload())[0], 401)
self.assertEqual(len(self.calls), 1)