77created during tests.
88"""
99
10- import hashlib
1110import sys
1211import os
1312from unittest .mock import patch , MagicMock
1413
14+ from utils .crypto_utils import string_to_fake_mac
15+
1516import pytest
1617
1718# ---------------------------------------------------------------------------
@@ -59,19 +60,12 @@ def _make_host_entry(mac="AA:BB:CC:DD:EE:FF", ip="192.168.1.10",
5960@pytest .fixture
6061def mock_fritz_hosts ():
6162 """
62- Patches fritzconnection.lib.fritzhosts in sys.modules so that
63- fritzbox.get_connected_devices() uses a controllable FritzHosts mock.
64- Yields the FritzHosts *instance* (what FritzHosts(fc) returns).
63+ Patches fritzbox.FritzHosts so that get_connected_devices() uses a
64+ controllable mock. Yields the FritzHosts *instance* (what FritzHosts(fc)
65+ returns).
6566 """
6667 hosts_instance = MagicMock ()
67- fritz_hosts_module = MagicMock ()
68- fritz_hosts_module .FritzHosts = MagicMock (return_value = hosts_instance )
69-
70- with patch .dict ("sys.modules" , {
71- "fritzconnection" : MagicMock (),
72- "fritzconnection.lib" : MagicMock (),
73- "fritzconnection.lib.fritzhosts" : fritz_hosts_module ,
74- }):
68+ with patch ("fritzbox.FritzHosts" , return_value = hosts_instance ):
7569 yield hosts_instance
7670
7771
@@ -229,12 +223,15 @@ def test_returns_device_dict(self):
229223 assert device ["active_status" ] == "Active"
230224 assert device ["interface_type" ] == "Access Point"
231225 assert device ["ip_address" ] == ""
226+ # MAC must match string_to_fake_mac output (fa:ce: prefix)
227+ assert device ["mac_address" ].startswith ("fa:ce:" )
232228
233229 def test_guest_mac_has_locally_administered_bit (self ):
234- """First byte must be 0x02 — locally-administered, unicast."""
230+ """The locally-administered bit (0x02) must be set in the first byte.
231+ string_to_fake_mac uses the 'fa:ce:' prefix; 0xFA & 0x02 == 0x02."""
235232 device = fritzbox .create_guest_wifi_device (self ._fc_with_mac ("AA:BB:CC:DD:EE:FF" ))
236233 first_byte = int (device ["mac_address" ].split (":" )[0 ], 16 )
237- assert first_byte == 0x02
234+ assert first_byte & 0x02 != 0
238235
239236 def test_guest_mac_format_is_valid (self ):
240237 """MAC must be 6 colon-separated lowercase hex pairs."""
@@ -258,11 +255,11 @@ def test_different_fritzbox_macs_produce_different_guest_macs(self):
258255 assert mac_a != mac_b
259256
260257 def test_no_fritzbox_mac_uses_fallback (self ):
261- """When DeviceInfo returns no MAC, fall back to 02:00:00:00:00:01 ."""
258+ """When DeviceInfo returns no MAC, fall back to a sentinel-derived MAC ."""
262259 fc = MagicMock ()
263260 fc .call_action .return_value = {"NewMACAddress" : "" }
264261 device = fritzbox .create_guest_wifi_device (fc )
265- assert device ["mac_address" ] == "02:00:00:00:00:01"
262+ assert device ["mac_address" ] == string_to_fake_mac ( "FRITZBOX_GUEST" )
266263
267264 def test_device_info_exception_returns_none (self ):
268265 """If DeviceInfo call raises, create_guest_wifi_device must return None."""
@@ -274,12 +271,11 @@ def test_device_info_exception_returns_none(self):
274271 def test_known_mac_produces_known_guest_mac (self ):
275272 """
276273 Regression anchor: for a fixed Fritz!Box MAC, the expected guest MAC
277- is precomputed here independently. If the hashing logic in
278- fritzbox.py changes, this test fails immediately .
274+ is derived via string_to_fake_mac(normalize_mac(...)). If the hashing
275+ logic in fritzbox.py or string_to_fake_mac changes, this test fails.
279276 """
280- fritzbox_mac = "aa:bb:cc:dd:ee:ff" # normalize_mac output of "AA:BB:CC:DD:EE:FF"
281- digest = hashlib .md5 (f"GUEST:{ fritzbox_mac } " .encode ()).digest ()
282- expected = "02:" + ":" .join (f"{ b :02x} " for b in digest [:5 ])
277+ fritzbox_mac = normalize_mac ("AA:BB:CC:DD:EE:FF" )
278+ expected = string_to_fake_mac (fritzbox_mac )
283279
284280 device = fritzbox .create_guest_wifi_device (self ._fc_with_mac ("AA:BB:CC:DD:EE:FF" ))
285281 assert device ["mac_address" ] == expected
@@ -296,10 +292,8 @@ def test_successful_connection(self):
296292 fc_instance .modelname = "FRITZ!Box 7590"
297293 fc_instance .system_version = "7.57"
298294 fc_class = MagicMock (return_value = fc_instance )
299- fc_module = MagicMock ()
300- fc_module .FritzConnection = fc_class
301295
302- with patch . dict ( "sys.modules " , { "fritzconnection" : fc_module } ):
296+ with patch ( "fritzbox.FritzConnection " , fc_class ):
303297 result = fritzbox .get_fritzbox_connection ("fritz.box" , 49443 , "admin" , "pass" , True )
304298
305299 assert result is fc_instance
@@ -308,16 +302,13 @@ def test_successful_connection(self):
308302 )
309303
310304 def test_import_error_returns_none (self ):
311- with patch . dict ( "sys.modules " , { "fritzconnection" : None } ):
305+ with patch ( "fritzbox.FritzConnection " , side_effect = ImportError ( "fritzconnection not found" ) ):
312306 result = fritzbox .get_fritzbox_connection ("fritz.box" , 49443 , "admin" , "pass" , True )
313307
314308 assert result is None
315309
316310 def test_connection_exception_returns_none (self ):
317- fc_module = MagicMock ()
318- fc_module .FritzConnection .side_effect = Exception ("Connection refused" )
319-
320- with patch .dict ("sys.modules" , {"fritzconnection" : fc_module }):
311+ with patch ("fritzbox.FritzConnection" , side_effect = Exception ("Connection refused" )):
321312 result = fritzbox .get_fritzbox_connection ("fritz.box" , 49443 , "admin" , "pass" , True )
322313
323314 assert result is None
0 commit comments