Skip to content

Commit d4a4d45

Browse files
committed
finalize session with GET /index.cgi after login following HAR
1 parent 0507e91 commit d4a4d45

File tree

4 files changed

+44
-1
lines changed

4 files changed

+44
-1
lines changed

airos/base.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,21 @@ async def _request_json(
275275
request_headers["Sec-Fetch-Dest"] = "empty"
276276
request_headers["Sec-Fetch-Mode"] = "cors"
277277
request_headers["Sec-Fetch-Site"] = "same-origin"
278+
if url.startswith(self._login_urls["v6_login"]):
279+
request_headers["Referrer"] = f"{self.base_url}/login.cgi"
280+
request_headers["Origin"] = self.base_url
281+
request_headers["Accept"] = (
282+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
283+
)
284+
request_headers["User-Agent"] = (
285+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"
286+
)
287+
request_headers["Sec-Fetch-Dest"] = "document"
288+
request_headers["Sec-Fetch-Mode"] = "navigate"
289+
request_headers["Sec-Fetch-Site"] = "same-origin"
290+
request_headers["Sec-Fetch-User"] = "?1"
291+
request_headers["Cache-Control"] = "no-cache"
292+
request_headers["Pragma"] = "no-cache"
278293

279294
try:
280295
if (
@@ -304,9 +319,14 @@ async def _request_json(
304319
_LOGGER.error(
305320
"TESTv%s - Response code: %s", self.api_version, response.status
306321
)
322+
_LOGGER.error(
323+
"TESTv%s - Response headers: %s",
324+
self.api_version,
325+
dict(response.headers),
326+
)
307327

308328
# v6 responds with a 302 redirect and empty body
309-
if url != self._login_urls["v6_login"]:
329+
if not url.startswith(self._login_urls["v6_login"]):
310330
self.api_version = 6
311331
response.raise_for_status()
312332

@@ -416,6 +436,21 @@ async def login(self) -> None:
416436
raise
417437
else:
418438
_LOGGER.error("TESTv%s - returning from simple multipart", self.api_version)
439+
# Finalize session by visiting /index.cgi
440+
_LOGGER.error(
441+
"TESTv%s - Finalizing session with GET to /index.cgi", self.api_version
442+
)
443+
with contextlib.suppress(Exception):
444+
await self._request_json(
445+
"GET",
446+
f"{self.base_url}/index.cgi",
447+
headers={
448+
"Referer": f"{self.base_url}/login.cgi",
449+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
450+
},
451+
authenticated=True,
452+
allow_redirects=True,
453+
)
419454
return # Success
420455

421456
async def status(self) -> AirOSDataModel:

tests/test_airos6.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ async def test_status_invalid_json_response(airos6_device: AirOS6) -> None:
7979
mock_status_response.__aenter__.return_value = mock_status_response
8080
mock_status_response.text = AsyncMock(return_value="This is not JSON")
8181
mock_status_response.status = 200
82+
mock_status_response.headers = {"Content-Type": "text/html"}
8283

8384
with (
8485
patch.object(
@@ -100,6 +101,7 @@ async def test_status_missing_interface_key_data(airos6_device: AirOS6) -> None:
100101
return_value=json.dumps({"system": {}})
101102
) # Missing 'interfaces'
102103
mock_status_response.status = 200
104+
mock_status_response.headers = {"Content-Type": "text/html"}
103105

104106
with (
105107
patch.object(

tests/test_airos8.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ async def test_status_invalid_json_response(airos8_device: AirOS8) -> None:
8080
mock_status_response.__aenter__.return_value = mock_status_response
8181
mock_status_response.text = AsyncMock(return_value="This is not JSON")
8282
mock_status_response.status = 200
83+
mock_status_response.headers = {"Content-Type": "text/html"}
8384

8485
with (
8586
patch.object(
@@ -101,6 +102,7 @@ async def test_status_missing_interface_key_data(airos8_device: AirOS8) -> None:
101102
return_value=json.dumps({"system": {}})
102103
) # Missing 'interfaces'
103104
mock_status_response.status = 200
105+
mock_status_response.headers = {"Content-Type": "text/html"}
104106

105107
with (
106108
patch.object(

tests/test_airos_request.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ async def test_request_json_success(
4242
expected_response_data = {"key": "value"}
4343
mock_response = AsyncMock()
4444
mock_response.status = 200
45+
mock_response.headers = {"Content-Type": "text/html"}
4546
mock_response.text = AsyncMock(return_value='{"key": "value"}')
4647
mock_response.raise_for_status = MagicMock()
4748

@@ -87,6 +88,7 @@ async def test_request_json_http_error(
8788
"""Test handling of a non-200 HTTP status code."""
8889
mock_response = AsyncMock()
8990
mock_response.status = 401
91+
mock_response.headers = {"Content-Type": "text/html"}
9092
mock_response.raise_for_status = MagicMock(
9193
side_effect=aiohttp.ClientResponseError(
9294
request_info=MagicMock(), history=(), status=401, message="Unauthorized"
@@ -114,6 +116,7 @@ async def test_request_json_non_json_response(
114116
"""Test handling of a response that is not valid JSON."""
115117
mock_response = AsyncMock()
116118
mock_response.status = 200
119+
mock_response.headers = {"Content-Type": "text/html"}
117120
mock_response.text = AsyncMock(return_value="NOT-A-JSON-STRING")
118121
mock_response.raise_for_status = MagicMock()
119122
mock_session.request.return_value.__aenter__.return_value = mock_response
@@ -136,6 +139,7 @@ async def test_request_json_with_params_and_data(
136139
"""Test request with parameters and data."""
137140
mock_response = AsyncMock()
138141
mock_response.status = 200
142+
mock_response.headers = {"Content-Type": "text/html"}
139143
mock_response.text = AsyncMock(return_value="{}")
140144
mock_response.raise_for_status = MagicMock()
141145

0 commit comments

Comments
 (0)