Files
soundtower-controller/tests/test_discovery.py
T

70 lines
2.9 KiB
Python

"""Discovery fixtures include the owner's actual unnamed FE40 advertisement."""
import asyncio
import queue
import sys
import types
import unittest
from unittest.mock import AsyncMock, patch
from controller.discovery import ScanWorker, Speaker, scan_speakers, selected_address
def advertisement(name, address, uuids=()):
return (
types.SimpleNamespace(name=name, address=address),
types.SimpleNamespace(local_name=name, service_uuids=list(uuids)),
)
class DiscoveryTests(unittest.TestCase):
def test_unnamed_model_advertisement_and_named_speaker_are_selectable(self):
found = {
"unnamed": advertisement(
None, "12:34:56:78:9A:BC", ["0000fe40-0000-1000-8000-00805f9b34fb"]
),
"named": advertisement("[AV] MX-T40", "12:34:56:78:9A:BD"),
"unrelated": advertisement(
"Headphones", "12:34:56:78:9A:BE", ["0000ffa0-0000-1000-8000-00805f9b34fb"]
),
}
discover = AsyncMock(return_value=found)
with patch.dict(
sys.modules,
{"bleak": types.SimpleNamespace(BleakScanner=types.SimpleNamespace(discover=discover))},
):
speakers = asyncio.run(scan_speakers(timeout=3))
self.assertEqual(len(speakers), 2)
self.assertEqual(
{speaker.address for speaker in speakers}, {"12:34:56:78:9A:BC", "12:34:56:78:9A:BD"}
)
unnamed = next(speaker for speaker in speakers if speaker.address.endswith("BC"))
self.assertEqual(unnamed.name, "MX-T40 (model advertisement)")
self.assertIn(unnamed.address, unnamed.label)
discover.assert_awaited_once_with(timeout=3, return_adv=True)
def test_choice_resolves_to_address_and_preserves_manual_input(self):
speaker = Speaker("12:34:56:78:9A:BC", "MX-T40")
self.assertEqual(selected_address(speaker.label, [speaker]), speaker.address)
self.assertEqual(selected_address(" 12:34:56:78:9A:BD ", [speaker]), "12:34:56:78:9A:BD")
def test_identical_names_remain_distinguishable(self):
speakers = [Speaker("12:34:56:78:9A:BC", "MX-T40"), Speaker("12:34:56:78:9A:BD", "MX-T40")]
self.assertNotEqual(speakers[0].label, speakers[1].label)
self.assertEqual(selected_address(speakers[1].label, speakers), speakers[1].address)
def test_scan_error_is_delivered_to_gui_queue(self):
events = queue.Queue()
with patch(
"controller.discovery.scan_speakers",
AsyncMock(side_effect=RuntimeError("Bluetooth is off")),
):
ScanWorker(events).run()
self.assertEqual(events.get_nowait(), ("scan_error", "Bluetooth is off"))
def test_empty_scan_has_a_result_event(self):
events = queue.Queue()
with patch("controller.discovery.scan_speakers", AsyncMock(return_value=[])):
ScanWorker(events).run()
self.assertEqual(events.get_nowait(), ("scan_results", []))