Files
StarPilot/openpilot/starpilot/bluetooth/tests/test_native_owner.py
T
Danny a531afd02a Bluetooth: Coordinate companion pairing and Android Auto ownership
Expose pairing readiness and coordinate adapter hand-offs with Android Auto.
Show app pairings separately, support individual removal, and improve pairing
controls on both comma screens. Add Galaxy companion endpoints and web asset
manifests, preserve the plain camera look, and fix headless speed-limit shapes.
2026-10-22 15:37:20 -07:00

77 lines
3.8 KiB
Python

from unittest.mock import Mock, patch
import pytest
from openpilot.starpilot.bluetooth.native_owner import NativeBluetoothOwner
from openpilot.starpilot.bluetooth.owner import AdapterBusyElsewhere, BluetoothOwner, BluetoothRejected
@pytest.mark.parametrize('operation, fields', [
('scan', {}), ('stop_scan', {}), ('pair', {'address': 'AA:BB:CC:DD:EE:FF', 'session': ('large', 'one')}),
('connect', {'address': 'AA:BB:CC:DD:EE:FF'}), ('disconnect', {'address': 'AA:BB:CC:DD:EE:FF'}),
('forget', {'address': 'AA:BB:CC:DD:EE:FF'}), ('power', {'enabled': False}),
])
def test_busy_native_owner_delegates_all_controls(operation, fields):
owner = NativeBluetoothOwner(lambda: True)
call = Mock(return_value=(200, {'devices': []}))
with patch.object(BluetoothOwner, 'request', side_effect=AdapterBusyElsewhere('AA owns radio')), \
patch('openpilot.starpilot.galaxy_bluetooth.phone_ui.galaxy_call', return_value=call) as factory:
assert owner.request(operation, **fields) == {'devices': []}
payload = {'operation': operation, **{k: v for k, v in fields.items() if k != 'session'}}
call.assert_called_once_with('POST', '/api/bluetooth/action', payload)
owner.snapshot(session=('large', 'one'))
call.assert_called_with('GET', '/api/bluetooth/status')
factory.assert_called_once()
def test_pairing_poll_response_and_close_use_the_same_galaxy_session():
owner = NativeBluetoothOwner(lambda: True)
call = Mock(return_value=(200, {'devices': [], 'pairing': {'state': 'pairing'}}))
with patch.object(BluetoothOwner, 'request', side_effect=AdapterBusyElsewhere('busy')) as local, \
patch('openpilot.starpilot.galaxy_bluetooth.phone_ui.galaxy_call', return_value=call) as factory:
owner.request('pair', address='AA:BB:CC:DD:EE:FF', session=('large', 'one'))
owner.snapshot(session=('large', 'one'))
owner.request('pairing_response', prompt_id='a' * 32, accepted=True, value='', session=('large', 'one'))
call.assert_called_with('POST', '/api/bluetooth/action',
{'operation': 'pairing_response', 'promptId': 'a' * 32, 'accepted': True, 'value': ''})
owner.close()
call.assert_called_with('POST', '/api/bluetooth/action', {'operation': 'cancel_pair'})
local.assert_called_once()
factory.assert_called_once()
assert owner.galaxy is None
@pytest.mark.parametrize('error', [BluetoothRejected('park first', code='park_required'),
BluetoothRejected('Pairing is in progress', code='busy')])
def test_local_denials_never_switch_to_galaxy(error):
owner = NativeBluetoothOwner(lambda: True)
with patch.object(BluetoothOwner, 'request', side_effect=error), \
patch('openpilot.starpilot.galaxy_bluetooth.phone_ui.galaxy_call') as factory:
with pytest.raises(BluetoothRejected):
owner.request('scan')
factory.assert_not_called()
assert owner.galaxy is None
def test_delegated_page_open_never_scans():
owner = NativeBluetoothOwner(lambda: True)
owner.galaxy = Mock(return_value=(200, {'devices': []}))
assert owner.request('scan', delegate=False) == {'devices': []}
owner.galaxy.assert_called_once_with('GET', '/api/bluetooth/status')
def test_denials_do_not_retry_through_a_different_owner():
owner = NativeBluetoothOwner(lambda: True)
with patch.object(BluetoothOwner, 'request', side_effect=BluetoothRejected('park first', code='park_required')), \
patch('openpilot.starpilot.galaxy_bluetooth.phone_ui.galaxy_call') as factory:
with pytest.raises(BluetoothRejected, match='park first'):
owner.request('power', enabled=False)
factory.assert_not_called()
call = Mock(return_value=(409, {'code': 'busy'}))
owner.galaxy = call
with patch.object(BluetoothOwner, 'request') as local:
with pytest.raises(BluetoothRejected):
owner.request('scan')
local.assert_not_called()
call.assert_called_once()