From 82759c6ef4d2c225563f9baa91494fc8b6375642 Mon Sep 17 00:00:00 2001 From: tbaur Date: Sat, 10 Oct 2026 13:20:20 -0700 Subject: [PATCH] chore: tidy test fixtures and doc examples --- README.md | 2 +- controller.py | 4 +- docs/API_VERIFICATION.md | 10 +-- docs/README-DETAILED.md | 64 ++++++++-------- main.py | 1 - smoke_test.py | 14 ++-- tests/test_cli_commands.py | 10 +-- tests/test_cli_coverage.py | 44 +++++------ tests/test_cli_multi_device.py | 30 ++++---- tests/test_controller.py | 12 +-- tests/test_controller_discovery.py | 4 +- tests/test_controller_new_features.py | 26 +++---- tests/test_controller_sync.py | 104 +++++++++++++------------- tests/test_controller_xml.py | 2 +- 14 files changed, 163 insertions(+), 164 deletions(-) diff --git a/README.md b/README.md index 2670d19..ea1a076 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ bluos-controller discover # Find devices (mDNS + CI zones) bluos-controller status # Show all device status bluos-controller status --scan # Force rediscovery, then status bluos-controller volume 25 # Set volume -bluos-controller play "Kitchen" # Play on specific device +bluos-controller play "Player A" # Play on specific device bluos-controller --help # Full command reference ``` diff --git a/controller.py b/controller.py index 4941470..686c1a6 100644 --- a/controller.py +++ b/controller.py @@ -63,7 +63,7 @@ def parse_bluos_host(value: Optional[str]) -> str: - """Extract an IP/host from BluOS endpoint values like ``172.16.0.1`` or ``172.16.0.1:11000``.""" + """Extract an IP/host from BluOS endpoint values like ``192.168.1.20`` or ``192.168.1.20:11000``.""" if not value: return "" ip, _port = parse_endpoint(value.strip()) @@ -83,7 +83,7 @@ def parse_sync_status_root(root: ET.Element) -> tuple[str, str, List[str]]: BluOS may expose values as root attributes or child elements. Legacy clients assumed ``master`` was an attribute; current firmware uses ````. - Multi-zone CI players use ``ip:port`` ids (e.g. ``172.16.0.1:11010``). + Multi-zone CI players use ``ip:port`` ids (e.g. ``192.168.1.20:11010``). """ master_attr = root.attrib.get("master") or "" master_elem = root.find("master") diff --git a/docs/API_VERIFICATION.md b/docs/API_VERIFICATION.md index 6559b9a..55dd896 100644 --- a/docs/API_VERIFICATION.md +++ b/docs/API_VERIFICATION.md @@ -40,20 +40,20 @@ Select an input with `GET /Play?inputTypeIndex=-` where type is `analog ### Primary (group leader) ```xml - - + + ``` ### Slave (group follower) ```xml - - 172.16.10.174 + + 192.168.1.32 ``` -Values may include a port suffix (`172.16.10.174:11000` or `172.16.10.144:11010`). Clients should preserve the port for API calls and sync membership; bare IPs default to `11000`. +Values may include a port suffix (`192.168.1.32:11000` or `192.168.1.30:11010`). Clients should preserve the port for API calls and sync membership; bare IPs default to `11000`. ### CI secondary zones diff --git a/docs/README-DETAILED.md b/docs/README-DETAILED.md index da20125..fae0870 100644 --- a/docs/README-DETAILED.md +++ b/docs/README-DETAILED.md @@ -52,7 +52,7 @@ Current line: multi-zone discovery (`ip:port`, `_musc` + `_musp` for CI secondar **Multi-Device Support:** - Control all devices simultaneously (default) - Target specific devices by name -- Pattern matching for multiple devices (e.g., "Room" matches "Living Room", "Bedroom") +- Pattern matching for multiple devices (e.g., "Zone" matches "Zone One", "Zone Two") **Volume Operations:** - Set absolute volume (0-100%) @@ -123,18 +123,18 @@ Current line: multi-zone discovery (`ip:port`, `_musc` + `_musp` for CI secondar **Example output (`sync list`):** ```text -Group: Living Room Speakers + 1 slave - Primary: Living Room Speakers (172.16.10.174) - Slave: Kitchen Speakers (172.16.10.88) +Group: Zone One + 1 slave + Primary: Zone One (192.168.1.32) + Slave: Player A (192.168.1.20) Standalone: - Patio Speakers (172.16.10.90) + Player B (192.168.1.23) ``` **Example output (`status`, grouped primary with 7 slaves):** ```text -[1] Living Room Speakers +[1] Zone One Status: ★ PRIMARY | Vol: 10% | Source: Tidal Sync: 6 slaves ♫ Track: Hayling @@ -218,7 +218,7 @@ Standalone: **Universal Device Targeting:** - All commands support operating on all devices (default behavior) - Target specific devices by exact or partial name match -- Pattern matching for bulk operations (e.g., "Room" matches multiple devices) +- Pattern matching for bulk operations (e.g., "Zone" matches multiple devices) - Case-insensitive device name matching - Clear feedback showing which devices are being controlled @@ -325,8 +325,8 @@ bluos-controller keychain delete After installation, use the `bluos-controller` command: **Note**: Most commands support controlling all devices (default) or specific devices by name. Device names can be: -- Exact match: `"Living Room"` -- Partial match: `"Room"` matches "Living Room", "Bedroom", etc. +- Exact match: `"Zone One"` +- Partial match: `"Zone"` matches "Zone One", "Zone Two", etc. - Case-insensitive matching ### Discover Devices @@ -342,7 +342,7 @@ bluos-controller discover bluos-controller status # Filter by name -bluos-controller status "Living Room" +bluos-controller status "Zone One" # Force fresh scan bluos-controller status --scan @@ -372,7 +372,7 @@ bluos-controller volume unmute bluos-controller volume reset # Target specific device -bluos-controller volume 25 "Living Room" +bluos-controller volume 25 "Zone One" ``` ### Playback Control @@ -389,11 +389,11 @@ bluos-controller previous bluos-controller toggle # Specific device -bluos-controller play "Living Room" -bluos-controller pause "Kitchen" +bluos-controller play "Zone One" +bluos-controller pause "Player A" # Pattern matching (multiple devices) -bluos-controller play "Room" # Matches "Living Room", "Bedroom", etc. +bluos-controller play "Zone" # Matches "Zone One", "Zone Two", etc. ``` ### Queue Management @@ -405,17 +405,17 @@ Queue commands support all devices or specific devices: bluos-controller queue # Show queue for specific device -bluos-controller queue "Living Room" +bluos-controller queue "Zone One" # Clear queue on all devices bluos-controller queue clear # Clear queue on specific device -bluos-controller queue clear "Kitchen" +bluos-controller queue clear "Player A" # Move track in queue (all or specific) bluos-controller queue move 3 1 # All devices -bluos-controller queue move 3 1 "Living Room" # Specific device +bluos-controller queue move 3 1 "Zone One" # Specific device ``` ### Input Source Selection @@ -427,14 +427,14 @@ Input commands support all devices or specific devices: bluos-controller inputs # List inputs for specific device -bluos-controller inputs "Living Room" +bluos-controller inputs "Zone One" # Set input on all devices bluos-controller inputs "" "Bluetooth" # Set input on specific device -bluos-controller inputs "Living Room" "Bluetooth" -bluos-controller inputs "Kitchen" "Optical 1" +bluos-controller inputs "Zone One" "Bluetooth" +bluos-controller inputs "Player A" "Optical 1" ``` ### Bluetooth Mode Control @@ -446,16 +446,16 @@ Bluetooth commands support all devices or specific devices: bluos-controller bluetooth # Show mode for specific device -bluos-controller bluetooth "Living Room" +bluos-controller bluetooth "Zone One" # Set mode on all devices bluos-controller bluetooth "" auto # Set mode on specific device -bluos-controller bluetooth "Living Room" manual -bluos-controller bluetooth "Kitchen" auto -bluos-controller bluetooth "Bedroom" guest -bluos-controller bluetooth "Office" disable +bluos-controller bluetooth "Zone One" manual +bluos-controller bluetooth "Player A" auto +bluos-controller bluetooth "Zone Two" guest +bluos-controller bluetooth "Player C" disable ``` ### Preset Management @@ -467,13 +467,13 @@ Preset commands support all devices or specific devices: bluos-controller presets # List presets for specific device -bluos-controller presets "Living Room" +bluos-controller presets "Zone One" # Play preset on all devices bluos-controller presets "" 1 # Play preset on specific device -bluos-controller presets "Living Room" 1 +bluos-controller presets "Zone One" 1 ``` ### Sync Group Management @@ -481,8 +481,8 @@ bluos-controller presets "Living Room" 1 Runtime BluOS groups use the **primary** player to attach or detach slaves. ```bash -# Create sync group (Living Room primary, Kitchen + Bedroom slaves) -bluos-controller sync create "Living Room Speakers" "Kitchen Speakers,Bedroom Speakers" +# Create sync group (Zone One primary, Player A + Zone Two slaves) +bluos-controller sync create "Zone One" "Player A,Zone Two" # Break all runtime groups bluos-controller sync break @@ -491,7 +491,7 @@ bluos-controller sync break bluos-controller sync list --scan # Break groups involving a specific player (primary or slave) -bluos-controller sync break "Kitchen Speakers" +bluos-controller sync break "Player A" # List sync groups and standalone players bluos-controller sync list @@ -537,13 +537,13 @@ Reboot commands support all devices or specific devices: bluos-controller reboot # Hard reboot specific device -bluos-controller reboot "Living Room" +bluos-controller reboot "Zone One" # Soft reboot all devices bluos-controller reboot --soft # Soft reboot specific device -bluos-controller reboot --soft "Kitchen" +bluos-controller reboot --soft "Player A" ``` ## Debug Mode & Logging diff --git a/main.py b/main.py index e20c659..92dc1dd 100755 --- a/main.py +++ b/main.py @@ -55,7 +55,6 @@ def run_tests_and_update_docs() -> None: else: # If not found, try common locations possible_roots = [ - Path.home() / "github" / "tbaur" / "bluos-controller", Path.home() / "github" / "bluos-controller", Path.home() / ".config" / "bluos-controller", Path(__file__).parent, diff --git a/smoke_test.py b/smoke_test.py index 1daf3fd..a863e05 100755 --- a/smoke_test.py +++ b/smoke_test.py @@ -59,7 +59,7 @@ def setup_mock_controller(self) -> BluOSController: # Mock device info mock_status1 = PlayerStatus( ip='192.168.1.100', - name='Living Room', + name='Zone One', model='Node', brand='BluOS', status='online', @@ -73,7 +73,7 @@ def setup_mock_controller(self) -> BluOSController: mock_status2 = PlayerStatus( ip='192.168.1.101', - name='Bedroom', + name='Zone Two', model='Pulse', brand='BluOS', status='online', @@ -134,7 +134,7 @@ def test_status_filtered(self) -> None: cli = BluOSCLI(controller) with patch('sys.stdout', new=StringIO()) as fake_out: - cli.status(pattern='Living', json_mode=False) + cli.status(pattern='One', json_mode=False) output = fake_out.getvalue() assert len(output) > 0 @@ -463,8 +463,8 @@ def test_sync_create(self) -> None: args = Mock() args.action = 'create' - args.master = 'Living Room' - args.slaves = 'Bedroom' + args.master = 'Zone One' + args.slaves = 'Zone Two' args.target = None with patch('sys.stdout', new=StringIO()) as fake_out: @@ -481,7 +481,7 @@ def test_sync_break(self) -> None: args.action = 'break' args.master = None args.slaves = None - args.target = 'Living Room' + args.target = 'Zone One' with patch('sys.stdout', new=StringIO()) as fake_out: cli.sync(args) @@ -534,7 +534,7 @@ def test_diagnose(self) -> None: cli = BluOSCLI(controller) with patch('sys.stdout', new=StringIO()) as fake_out: - cli.diagnose('Living Room') + cli.diagnose('Zone One') output = fake_out.getvalue() assert len(output) > 0 diff --git a/tests/test_cli_commands.py b/tests/test_cli_commands.py index d9ab189..620d69f 100644 --- a/tests/test_cli_commands.py +++ b/tests/test_cli_commands.py @@ -31,8 +31,8 @@ def cli(self, controller): def mock_devices(self): """Create mock device statuses.""" return [ - PlayerStatus(ip="192.168.1.100", name="Living Room", volume=50, state="play"), - PlayerStatus(ip="192.168.1.101", name="Kitchen", volume=30, state="pause"), + PlayerStatus(ip="192.168.1.100", name="Zone One", volume=50, state="play"), + PlayerStatus(ip="192.168.1.101", name="Player A", volume=30, state="pause"), ] def test_discover(self, cli): @@ -113,7 +113,7 @@ def test_status_pattern(self, cli, mock_devices): with patch.object(cli.ctl, 'sync_unifi', return_value="SUCCESS:2"): with patch('builtins.print'): - cli.status(pattern="Living", json_mode=False) + cli.status(pattern="One", json_mode=False) def test_volume_list(self, cli, mock_devices): """Test volume command listing volumes.""" @@ -192,7 +192,7 @@ def test_soft_reboot(self, mock_input, cli, mock_devices): def test_diagnose(self, mock_arp, cli, mock_devices): """Test diagnose command.""" mock_arp.return_value = "192.168.1.100 aa:bb:cc:dd:ee:ff" - target = "Living Room" + target = "Zone One" with patch.object(cli.ctl, 'get_device_info') as mock_info: mock_info.return_value = mock_devices[0] @@ -212,7 +212,7 @@ def test_print_status_header(self, cli): """Test status header printing.""" with patch('builtins.print'): cli._print_status_header(None, "SUCCESS:5") - cli._print_status_header("Room", "SUCCESS:5") + cli._print_status_header("Zone", "SUCCESS:5") def test_format_connection_string(self, cli): """Test connection string formatting.""" diff --git a/tests/test_cli_coverage.py b/tests/test_cli_coverage.py index 01eb8b0..44eb3bb 100644 --- a/tests/test_cli_coverage.py +++ b/tests/test_cli_coverage.py @@ -215,12 +215,12 @@ def test_sync_break(self, cli): primary = PlayerStatus( ip="192.168.1.100", - name="Living Room", + name="Zone One", slaves=["192.168.1.101:11000"], ) slave = PlayerStatus( ip="192.168.1.101", - name="Kitchen", + name="Player A", master="192.168.1.100:11000", ) @@ -244,7 +244,7 @@ def test_sync_break(self, cli): 'get_device_info', return_value=PlayerStatus( ip="192.168.1.100", - name="Living Room", + name="Zone One", slaves=[], ), ): @@ -261,19 +261,19 @@ def test_sync_break_with_master_arg(self, cli): """Test sync break accepts device name in master positional arg.""" primary = PlayerStatus( ip="192.168.1.100", - name="Living Room", + name="Zone One", slaves=["192.168.1.101:11000"], ) slave = PlayerStatus( ip="192.168.1.101", - name="Kitchen", + name="Player A", master="192.168.1.100:11000", ) args = MagicMock() args.action = 'break' args.target = None - args.master = 'Kitchen' + args.master = 'Player A' with patch.object(cli, '_get_matching_devices', side_effect=[[primary, slave], [slave]]): with patch.object(cli.ctl, 'remove_sync_slave', return_value=True) as mock_remove: @@ -283,7 +283,7 @@ def test_sync_break_with_master_arg(self, cli): 'get_device_info', return_value=PlayerStatus( ip="192.168.1.100", - name="Living Room", + name="Zone One", slaves=[], ), ): @@ -295,17 +295,17 @@ def test_sync_break_with_master_arg(self, cli): def test_sync_enable_groups_all_others(self, cli): """sync enable adds every other player under the primary.""" - primary = PlayerStatus(ip="192.168.1.100", name="Living Room Speakers") - kitchen = PlayerStatus(ip="192.168.1.101", name="Kitchen Speakers") - patio = PlayerStatus(ip="192.168.1.102", name="Patio Speakers") + primary = PlayerStatus(ip="192.168.1.100", name="Zone One") + player_a = PlayerStatus(ip="192.168.1.101", name="Player A") + player_b = PlayerStatus(ip="192.168.1.102", name="Player B") args = MagicMock() args.action = 'enable' - args.master = 'Living Room Speakers' + args.master = 'Zone One' args.slaves = None args.target = None - with patch.object(cli, '_get_matching_devices', return_value=[primary, kitchen, patio]): + with patch.object(cli, '_get_matching_devices', return_value=[primary, player_a, player_b]): with patch.object(cli.ctl, 'add_sync_slave', return_value=True) as mock_add: with patch('builtins.print'): cli.sync(args) @@ -331,9 +331,9 @@ def test_format_status_row_aligns_source(self, cli): def test_format_sync_line_primary_shows_count_only(self, cli): """Primary sync line shows slave count, not names.""" primary = PlayerStatus( - ip="172.16.10.174", - name="Living Room Speakers", - slaves=["172.16.10.88", "172.16.10.86", "172.16.10.48"], + ip="192.168.1.32", + name="Zone One", + slaves=["192.168.1.20", "192.168.1.22", "192.168.1.21"], ) line = cli._format_sync_line(primary, [primary]) assert line == "Sync: 3 slaves" @@ -341,13 +341,13 @@ def test_format_sync_line_primary_shows_count_only(self, cli): def test_format_sync_line_for_slave_uses_name(self, cli): """Slaves should reference the primary by name, not IP.""" devices = [ - PlayerStatus(ip="172.16.10.174", name="Living Room Speakers"), - PlayerStatus(ip="172.16.10.88", name="Kitchen Speakers", master="172.16.10.174"), + PlayerStatus(ip="192.168.1.32", name="Zone One"), + PlayerStatus(ip="192.168.1.20", name="Player A", master="192.168.1.32"), ] line = cli._format_sync_line(devices[1], devices) assert "Following" in line - assert "Living Room Speakers" in line - assert "172.16.10.174" not in line + assert "Zone One" in line + assert "192.168.1.32" not in line def test_sync_break_no_match(self, cli): """Test sync break with no matching devices.""" @@ -381,12 +381,12 @@ def test_sync_list(self, cli): mock_future1 = MagicMock() mock_future1.result.return_value = PlayerStatus( ip="192.168.1.100", - name="Living Room", - group="Living Room+Kitchen", + name="Zone One", + group="Zone One+Player A", slaves=["192.168.1.101"], ) mock_future2 = MagicMock() - mock_future2.result.return_value = PlayerStatus(ip="192.168.1.101", name="Kitchen", master="192.168.1.100") + mock_future2.result.return_value = PlayerStatus(ip="192.168.1.101", name="Player A", master="192.168.1.100") with patch('cli.as_completed', return_value=[mock_future1, mock_future2]): with patch('cli.ThreadPoolExecutor') as mock_executor_class: diff --git a/tests/test_cli_multi_device.py b/tests/test_cli_multi_device.py index cc7a65e..ab9bf7b 100644 --- a/tests/test_cli_multi_device.py +++ b/tests/test_cli_multi_device.py @@ -30,9 +30,9 @@ def cli(self, controller): def mock_devices(self): """Create mock device statuses.""" return [ - PlayerStatus(ip="192.168.1.100", name="Living Room"), - PlayerStatus(ip="192.168.1.101", name="Kitchen"), - PlayerStatus(ip="192.168.1.102", name="Bedroom"), + PlayerStatus(ip="192.168.1.100", name="Zone One"), + PlayerStatus(ip="192.168.1.101", name="Player A"), + PlayerStatus(ip="192.168.1.102", name="Zone Two"), ] def test_get_matching_devices_all(self, cli, mock_devices): @@ -41,32 +41,32 @@ def test_get_matching_devices_all(self, cli, mock_devices): mock_info.side_effect = mock_devices devices = cli._get_matching_devices(None) assert len(devices) == 3 - assert all(d.name in ["Living Room", "Kitchen", "Bedroom"] for d in devices) + assert all(d.name in ["Zone One", "Player A", "Zone Two"] for d in devices) def test_get_matching_devices_specific(self, cli, mock_devices): """Test getting specific device by exact name.""" with patch.object(cli.ctl, 'get_device_info') as mock_info: mock_info.side_effect = mock_devices - devices = cli._get_matching_devices("Living Room") + devices = cli._get_matching_devices("Zone One") assert len(devices) == 1 - assert devices[0].name == "Living Room" + assert devices[0].name == "Zone One" def test_get_matching_devices_pattern(self, cli, mock_devices): """Test getting devices by pattern match.""" with patch.object(cli.ctl, 'get_device_info') as mock_info: mock_info.side_effect = mock_devices - devices = cli._get_matching_devices("Room") + devices = cli._get_matching_devices("Zone") assert len(devices) == 2 device_names = [d.name for d in devices] - assert "Living Room" in device_names - assert "Bedroom" in device_names - assert "Kitchen" not in device_names + assert "Zone One" in device_names + assert "Zone Two" in device_names + assert "Player A" not in device_names def test_get_matching_devices_no_match(self, cli, mock_devices): """Test getting devices with no match.""" with patch.object(cli.ctl, 'get_device_info') as mock_info: mock_info.side_effect = mock_devices - devices = cli._get_matching_devices("Office") + devices = cli._get_matching_devices("Player C") assert len(devices) == 0 @patch('cli.Network') @@ -84,8 +84,8 @@ def test_play_all_devices(self, mock_network, cli, mock_devices): def test_play_specific_device(self, mock_network, cli, mock_devices): """Test play command on specific device.""" mock_network.get.return_value = b"OK" - args = Namespace(target="Living Room") - matching = [d for d in mock_devices if d.name == "Living Room"] + args = Namespace(target="Zone One") + matching = [d for d in mock_devices if d.name == "Zone One"] with patch.object(cli, '_get_matching_devices', return_value=matching): with patch.object(cli.ctl, 'play', return_value=True) as mock_play: @@ -145,8 +145,8 @@ def test_presets_play_all_devices(self, mock_network, cli, mock_devices): @patch('cli.Network') def test_reboot_with_target(self, mock_network, cli, mock_devices): """Test reboot with device target.""" - matching = [d for d in mock_devices if d.name == "Living Room"] - args = Namespace(target="Living Room") + matching = [d for d in mock_devices if d.name == "Zone One"] + args = Namespace(target="Zone One") with patch.object(cli, '_get_matching_devices', return_value=matching): with patch('builtins.input', return_value='y'): diff --git a/tests/test_controller.py b/tests/test_controller.py index d5827c5..f6bc7b7 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -208,7 +208,7 @@ def test_sync_unifi_wired_uses_switch_port_rates( ): """Wired clients prefer switch port_table rates over broken STA rates.""" mock_exists.return_value = False - controller.ips = ['172.16.10.101'] + controller.ips = ['192.168.1.35'] controller.config.data = { 'UNIFI_ENABLED': 'true', 'UNIFI_CONTROLLER': 'controller.local', @@ -219,7 +219,7 @@ def test_sync_unifi_wired_uses_switch_port_rates( sta_response = { 'data': [{ - 'ip': '172.16.10.101', + 'ip': '192.168.1.35', 'is_wired': True, 'last_uplink_name': 'sw3-01', 'sw_mac': '11:22:33:44:55:66', @@ -254,7 +254,7 @@ def test_sync_unifi_wired_uses_switch_port_rates( result = controller.sync_unifi() assert result == "SUCCESS:1" - wired = controller.unifi_map['172.16.10.101'] + wired = controller.unifi_map['192.168.1.35'] assert wired.is_wired is True assert wired.rate_source == "switch-port" assert wired.down_rate == 500_000 @@ -272,7 +272,7 @@ def test_sync_unifi_wired_falls_back_to_client_rates( ): """If switch stats are missing, keep wired STA rates as fallback.""" mock_exists.return_value = False - controller.ips = ['172.16.10.101'] + controller.ips = ['192.168.1.35'] controller.config.data = { 'UNIFI_ENABLED': 'true', 'UNIFI_CONTROLLER': 'controller.local', @@ -282,7 +282,7 @@ def test_sync_unifi_wired_falls_back_to_client_rates( } sta_response = { 'data': [{ - 'ip': '172.16.10.101', + 'ip': '192.168.1.35', 'is_wired': True, 'last_uplink_name': 'sw3-01', 'sw_mac': '11:22:33:44:55:66', @@ -303,7 +303,7 @@ def test_sync_unifi_wired_falls_back_to_client_rates( result = controller.sync_unifi() assert result == "SUCCESS:1" - wired = controller.unifi_map['172.16.10.101'] + wired = controller.unifi_map['192.168.1.35'] assert wired.rate_source == "client" assert wired.down_rate == 50 assert wired.up_rate == 25 diff --git a/tests/test_controller_discovery.py b/tests/test_controller_discovery.py index 42b3a11..b186bd0 100644 --- a/tests/test_controller_discovery.py +++ b/tests/test_controller_discovery.py @@ -147,8 +147,8 @@ def test_discover_mdns_keeps_srv_ports(self, controller): """musc + musp SRV ports become distinct endpoints.""" def fake_dns_sd(service, timeout): if service == '_musp._tcp': - return "Kitchen._musp._tcp SRV 0 0 11010 speaker.local.\n" - return "Living._musc._tcp SRV 0 0 11000 speaker.local.\n" + return "PlayerA._musp._tcp SRV 0 0 11010 speaker.local.\n" + return "ZoneOne._musc._tcp SRV 0 0 11000 speaker.local.\n" with patch.object(controller, '_run_dns_sd', side_effect=fake_dns_sd): with patch.object( diff --git a/tests/test_controller_new_features.py b/tests/test_controller_new_features.py index d21be3b..623d8a4 100644 --- a/tests/test_controller_new_features.py +++ b/tests/test_controller_new_features.py @@ -301,17 +301,17 @@ def test_remove_sync_slave_rejects_error_xml(self, mock_network, controller): mock_network.get.side_effect = [None, None, err, err] controller.ips = [] result = controller.remove_sync_slave( - "172.16.10.174:11000", "172.16.10.166:11000" + "192.168.1.32:11000", "192.168.1.31:11000" ) assert result is False @patch('controller.Network') def test_remove_sync_slave_reparents_orphan(self, mock_network, controller): """Orphaned reconnecting slave is cleared via a live donor primary.""" - controller.ips = ["172.16.10.144:11000", "172.16.10.166:11000"] - add_ok = b'' - remove_ok = b"" - standalone = b"" + controller.ips = ["192.168.1.30:11000", "192.168.1.31:11000"] + add_ok = b'' + remove_ok = b"" + standalone = b"" mock_network.get.side_effect = [ None, # RemoveSlave on dead master None, # legacy on master @@ -322,26 +322,26 @@ def test_remove_sync_slave_reparents_orphan(self, mock_network, controller): standalone, # verify ungrouped ] result = controller.remove_sync_slave( - "172.16.10.174:11000", "172.16.10.166:11000" + "192.168.1.32:11000", "192.168.1.31:11000" ) assert result is True urls = [c.args[0] for c in mock_network.get.call_args_list] - assert any("172.16.10.144:11000/AddSlave" in u for u in urls) - assert any("172.16.10.144:11000/RemoveSlave" in u for u in urls) + assert any("192.168.1.30:11000/AddSlave" in u for u in urls) + assert any("192.168.1.30:11000/RemoveSlave" in u for u in urls) @patch('controller.Network') def test_player_is_ungrouped_states(self, mock_network, controller): """Ungrouped check distinguishes standalone, grouped, and unreachable.""" mock_network.get.return_value = b"" - assert controller._player_is_ungrouped("172.16.10.166:11000") is True + assert controller._player_is_ungrouped("192.168.1.31:11000") is True mock_network.get.return_value = ( - b"172.16.10.174" + b"192.168.1.32" ) - assert controller._player_is_ungrouped("172.16.10.166:11000") is False + assert controller._player_is_ungrouped("192.168.1.31:11000") is False mock_network.get.return_value = None - assert controller._player_is_ungrouped("172.16.10.166:11000") is None + assert controller._player_is_ungrouped("192.168.1.31:11000") is None @patch('controller.Network') def test_remove_sync_slave_unreachable_not_success(self, mock_network, controller): @@ -355,7 +355,7 @@ def test_remove_sync_slave_unreachable_not_success(self, mock_network, controlle with patch.object(controller, '_wait_until_ungrouped', return_value=False): with patch.object(controller, '_ungroup_via_reparent', return_value=False): result = controller.remove_sync_slave( - "172.16.10.174:11000", "172.16.10.166:11000" + "192.168.1.32:11000", "192.168.1.31:11000" ) assert result is False diff --git a/tests/test_controller_sync.py b/tests/test_controller_sync.py index a018614..aacf721 100644 --- a/tests/test_controller_sync.py +++ b/tests/test_controller_sync.py @@ -18,10 +18,10 @@ class TestParseBluosHost: """Test BluOS host/IP parsing.""" def test_plain_ip(self): - assert parse_bluos_host("172.16.10.174") == "172.16.10.174" + assert parse_bluos_host("192.168.1.32") == "192.168.1.32" def test_ip_with_port(self): - assert parse_bluos_host("172.16.10.174:11000") == "172.16.10.174" + assert parse_bluos_host("192.168.1.32:11000") == "192.168.1.32" def test_empty(self): assert parse_bluos_host("") == "" @@ -32,10 +32,10 @@ class TestParseBluosEndpoint: """Test BluOS endpoint normalization.""" def test_plain_ip_defaults_port(self): - assert parse_bluos_endpoint("172.16.10.174") == "172.16.10.174:11000" + assert parse_bluos_endpoint("192.168.1.32") == "192.168.1.32:11000" def test_ip_with_port(self): - assert parse_bluos_endpoint("172.16.10.174:11010") == "172.16.10.174:11010" + assert parse_bluos_endpoint("192.168.1.32:11010") == "192.168.1.32:11010" def test_empty(self): assert parse_bluos_endpoint("") == "" @@ -47,32 +47,32 @@ class TestParseSyncStatusRoot: def test_primary_with_group_and_slaves(self): root = ET.fromstring( - b'' - b'' + b'' + b'' b"" ) master, group, slaves = parse_sync_status_root(root) assert master == "" - assert group == "Living Room Speakers+Kitchen Speakers" - assert slaves == ["172.16.10.88:11000"] + assert group == "Zone One+Player A" + assert slaves == ["192.168.1.20:11000"] def test_ci_secondary_slave_port(self): root = ET.fromstring( - b'' - b'' + b'' + b'' b"" ) _master, _group, slaves = parse_sync_status_root(root) - assert slaves == ["172.16.10.144:11010"] + assert slaves == ["192.168.1.30:11010"] def test_slave_with_master_child_element(self): root = ET.fromstring( - b'' - b'172.16.10.174' + b'' + b'192.168.1.32' b"" ) master, group, slaves = parse_sync_status_root(root) - assert master == "172.16.10.174:11000" + assert master == "192.168.1.32:11000" assert group == "" assert slaves == [] @@ -95,38 +95,38 @@ def controller(self): def test_break_all_from_primary(self, controller): devices = [ PlayerStatus( - ip="172.16.10.174", - name="Living Room Speakers", - group="Living Room Speakers+Kitchen Speakers", - slaves=["172.16.10.88:11000"], + ip="192.168.1.32", + name="Zone One", + group="Zone One+Player A", + slaves=["192.168.1.20:11000"], ), PlayerStatus( - ip="172.16.10.88", - name="Kitchen Speakers", - master="172.16.10.174:11000", + ip="192.168.1.20", + name="Player A", + master="192.168.1.32:11000", ), ] operations = controller.collect_sync_break_operations(devices) assert operations == [ ( - "172.16.10.174:11000", - "172.16.10.88:11000", - "Kitchen Speakers from Living Room Speakers", + "192.168.1.32:11000", + "192.168.1.20:11000", + "Player A from Zone One", ), ] def test_break_single_slave_target(self, controller): devices = [ PlayerStatus( - ip="172.16.10.174", - name="Living Room Speakers", - slaves=["172.16.10.88:11000"], + ip="192.168.1.32", + name="Zone One", + slaves=["192.168.1.20:11000"], ), PlayerStatus( - ip="172.16.10.88", - name="Kitchen Speakers", - master="172.16.10.174:11000", + ip="192.168.1.20", + name="Player A", + master="192.168.1.32:11000", ), ] target = [devices[1]] @@ -134,23 +134,23 @@ def test_break_single_slave_target(self, controller): operations = controller.collect_sync_break_operations(devices, target) assert operations == [ ( - "172.16.10.174:11000", - "172.16.10.88:11000", - "Kitchen Speakers from Living Room Speakers", + "192.168.1.32:11000", + "192.168.1.20:11000", + "Player A from Zone One", ), ] def test_break_primary_target(self, controller): devices = [ PlayerStatus( - ip="172.16.10.174", - name="Living Room Speakers", - slaves=["172.16.10.88:11000"], + ip="192.168.1.32", + name="Zone One", + slaves=["192.168.1.20:11000"], ), PlayerStatus( - ip="172.16.10.88", - name="Kitchen Speakers", - master="172.16.10.174:11000", + ip="192.168.1.20", + name="Player A", + master="192.168.1.32:11000", ), ] target = [devices[0]] @@ -158,40 +158,40 @@ def test_break_primary_target(self, controller): operations = controller.collect_sync_break_operations(devices, target) assert operations == [ ( - "172.16.10.174:11000", - "172.16.10.88:11000", - "Kitchen Speakers from Living Room Speakers", + "192.168.1.32:11000", + "192.168.1.20:11000", + "Player A from Zone One", ), ] @patch("controller.Network.get") def test_get_device_info_reads_master_child(self, mock_network, controller): sync_xml = ( - b'' - b"172.16.10.174:11000" + b'' + b"192.168.1.32:11000" b"" ) status_xml = b"20play" mock_network.side_effect = [sync_xml, status_xml, None] - status = controller.get_device_info("172.16.10.88") - assert status.master == "172.16.10.174:11000" + status = controller.get_device_info("192.168.1.20") + assert status.master == "192.168.1.32:11000" assert status.group == "" assert status.slaves == [] assert status.port == 11000 - assert status.endpoint == "172.16.10.88:11000" + assert status.endpoint == "192.168.1.20:11000" @patch("controller.Network.get") def test_get_device_info_ci_secondary_port(self, mock_network, controller): sync_xml = ( - b'' + b'' ) status_xml = b"49stream" mock_network.side_effect = [sync_xml, status_xml, None] - status = controller.get_device_info("172.16.10.144:11010") - assert status.name == "Kitchen Speakers" + status = controller.get_device_info("192.168.1.30:11010") + assert status.name == "Player A" assert status.port == 11010 - assert status.endpoint == "172.16.10.144:11010" + assert status.endpoint == "192.168.1.30:11010" assert status.volume == 49 diff --git a/tests/test_controller_xml.py b/tests/test_controller_xml.py index 52a6268..47f7a9b 100644 --- a/tests/test_controller_xml.py +++ b/tests/test_controller_xml.py @@ -163,7 +163,7 @@ def test_get_device_info_with_master(self, mock_network, controller): def test_synced_slave_prefers_syncstatus_volume(self, mock_network, controller): """Synced /Status volume is group level — prefer SyncStatus attribute.""" sync_xml = ( - b'' + b'' b'192.168.1.174' b'' )