Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions airos/airos6.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ def _derived_wireless_data(

return derived

async def login(self) -> None:
"""Login to airOS v6 devices."""
await self._login_v6()

async def update_check(self, force: bool = False) -> dict[str, Any]:
"""Check for firmware updates. Not supported on AirOS6."""
raise AirOSNotSupportedError("Firmware update check not supported on AirOS6.")
Expand Down
1 change: 1 addition & 0 deletions airos/model_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"LiteAP AC": "LAP-120", # Shortened name for airMAX Lite Access Point AC, Issue 137
"LiteAP GPS": "LAP-GPS", # Shortened name for airMAX Lite Access Point GPS
"LiteBeam 5AC 23": "LBE-5AC-23",
"NanoStation loco M2": "LocoM2", # XW firmware version 6 - note the reversed names
"NanoStation loco M5": "LocoM5", # XM firmware version 6 - note the reversed names
"NanoStation loco M900": "LocoM900", # XM firmware version 6, 900 MHz variant
}
Comment thread
CoMPaTech marked this conversation as resolved.
Expand Down
5 changes: 5 additions & 0 deletions tests/test_model_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ def test_get_sku_by_devmodel_case_insensitivity(self):
sku = self.mapper.get_sku_by_devmodel("nanostation 5ac loco")
assert sku == "Loco5AC"

def test_get_sku_by_devmodel_airos6_reversed_loco_m2(self):
"""Test the reversed loco M2 model name reported by airOS 6."""
sku = self.mapper.get_sku_by_devmodel("NanoStation loco M2 ")
assert sku == "LocoM2"

def test_get_sku_by_devmodel_not_found_raises_keyerror(self):
"""Test to raise KeyError when no match (exact or partial) is found."""
with pytest.raises(
Expand Down
14 changes: 8 additions & 6 deletions tests/test_stations6.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ async def test_ap_object(
# Create an async mock that can return different values for different calls
mock_request_json = AsyncMock(
side_effect=[
{}, # First call for login()
fixture_data, # Second call for status()
fixture_data, # status()
]
)

with (
# Patch the internal method, not the session object
patch.object(airos6_device, "_request_json", new=mock_request_json),
patch.object(airos6_device, "login", new=AsyncMock()),
# You need to manually set the connected state since login() is mocked
patch.object(airos6_device, "connected", True),
):
Expand All @@ -70,7 +70,7 @@ async def test_ap_object(


@pytest.mark.asyncio
async def test_login_v6_flow() -> None:
async def test_login_v6_flow(base_url: str) -> None:
"""Test AirOS v6 XM login flow with manual cookie handling."""

# Create a mock session
Expand All @@ -93,7 +93,7 @@ async def test_login_v6_flow() -> None:
get_index_response = MagicMock()
get_index_response.__aenter__.return_value = get_index_response
get_index_response.status = 200
get_index_response.url = "http://192.168.1.3/index.cgi"
get_index_response.url = f"{base_url}/index.cgi"

# Set side effects for session.request
session.request.side_effect = [
Expand All @@ -104,16 +104,18 @@ async def test_login_v6_flow() -> None:

# Create device
airos6_device = AirOS6(
host="http://192.168.1.3",
host=base_url,
username="ubnt",
password="ubnt",
session=session,
)

await airos6_device._login_v6() # noqa: SLF001
await airos6_device.login()

# Assertions
assert airos6_device.connected is True
assert airos6_device.api_version == 6
assert airos6_device._auth_cookie == "AIROS_ABC123=xyz789" # noqa: SLF001
assert session.request.call_count == 3
called_urls = [call.args[1] for call in session.request.call_args_list]
assert f"{base_url}/api/auth" not in called_urls