diff --git a/src/api/hive_api.py b/src/api/hive_api.py index 0bf1ba76..e88aba07 100644 --- a/src/api/hive_api.py +++ b/src/api/hive_api.py @@ -18,7 +18,10 @@ # json.JSONDecodeError subclass. _REQUEST_ERRORS = (OSError, RuntimeError, json.JSONDecodeError) -_SSO_ASSIGNMENT = re.compile(r'window\.(\w+)\s*=\s*"([^"]*)"') +# The SSO bootstrap page embeds its config as `window. = ` +# assignments. The value quoting changed from double to single quotes on +# 2026-10-20, so accept either (and require the closing quote to match). +_SSO_ASSIGNMENT = re.compile(r"""window\.(\w+)\s*=\s*(["'])(.*?)\2""") class HiveApi: @@ -118,7 +121,10 @@ def get_login_info(self): "get_login_info - Login info response status: %s", data.status_code ) script_text = PyQuery(data.content)("script:first").text() - sso_values = dict(_SSO_ASSIGNMENT.findall(script_text)) + sso_values = { + key: value + for key, _quote, value in _SSO_ASSIGNMENT.findall(script_text) + } login_data = { "UPID": sso_values["HiveSSOPoolId"], diff --git a/tests/unit/test_hive_api.py b/tests/unit/test_hive_api.py index 4511c104..debba3a5 100644 --- a/tests/unit/test_hive_api.py +++ b/tests/unit/test_hive_api.py @@ -206,6 +206,43 @@ def test_successful_parse_returns_login_data(self): # REGION mirrors UPID assert result["REGION"] == "eu-west-1_abc" + def test_single_quoted_values_are_parsed(self): + """Since 2026-10-20 the SSO page uses single quotes and spaces around =.""" + api = _make_api() + html_content = ( + b"" + ) + mock_resp = MagicMock() + mock_resp.content = html_content + mock_resp.status_code = 200 + + with patch("apyhiveapi.api.hive_api.requests.get", return_value=mock_resp): + result = api.get_login_info() + + assert result == { + "UPID": "eu-west-1_abc", + "CLIID": "client123", + "REGION": "eu-west-1_abc", + } + + def test_mismatched_quotes_are_not_parsed(self): + """A value must be closed by the same quote character it was opened with.""" + api = _make_api() + html_content = b"" + mock_resp = MagicMock() + mock_resp.content = html_content + mock_resp.status_code = 200 + + with patch("apyhiveapi.api.hive_api.requests.get", return_value=mock_resp): + result = api.get_login_info() + + assert result is None + def test_os_error_calls_error_and_raises_connection_error(self): api = _make_api() with (