blob: 71ab79df9ceef21c51e4f9340472f0c35082379e [file]
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
import json
import unittest
from argparse import Namespace
from unittest.mock import MagicMock, patch
from apache_polaris.cli.api_client_builder import ApiClientBuilder, BuilderConfig
from apache_polaris.cli.exceptions import CliError, CLI_ERROR_EXIT_CODE
def _builder_options() -> Namespace:
return Namespace(
base_url="http://localhost:8181",
catalog_url=None,
host=None,
port=None,
scheme=None,
proxy=None,
access_token=None,
client_id="test-id",
client_secret="test-secret",
realm=None,
header=None,
profile=None,
)
class TestGetToken(unittest.TestCase):
def _call_get_token(self, response_body: str) -> str:
builder = ApiClientBuilder(_builder_options(), direct_authentication=True)
mock_response = MagicMock()
mock_response.response.data = response_body
with patch(
"apache_polaris.cli.api_client_builder.ApiClient"
) as mock_api_client:
mock_api_client.return_value.call_api.return_value = mock_response
return builder._get_token()
def test_success_returns_access_token(self) -> None:
token = self._call_get_token(json.dumps({"access_token": "abc123"}))
self.assertEqual(token, "abc123")
def test_oauth_error_with_description(self) -> None:
response_body = json.dumps(
{
"error": "invalid_client",
"error_description": "Client authentication failed",
}
)
with self.assertRaises(CliError) as cm:
self._call_get_token(response_body)
self.assertEqual(cm.exception.exit_code, CLI_ERROR_EXIT_CODE)
message = str(cm.exception)
self.assertIn("Failed to get access token", message)
self.assertIn("invalid_client", message)
self.assertIn("Client authentication failed", message)
def test_oauth_error_without_description(self) -> None:
response_body = json.dumps({"error": "invalid_grant"})
with self.assertRaises(CliError) as cm:
self._call_get_token(response_body)
self.assertEqual(cm.exception.exit_code, CLI_ERROR_EXIT_CODE)
message = str(cm.exception)
self.assertIn("Failed to get access token", message)
self.assertIn("invalid_grant", message)
def test_missing_token_and_oauth_error_fields(self) -> None:
with self.assertRaises(CliError) as cm:
self._call_get_token(json.dumps({}))
self.assertEqual(cm.exception.exit_code, CLI_ERROR_EXIT_CODE)
self.assertEqual(str(cm.exception), "Failed to get access token")
def test_invalid_json_response(self) -> None:
with self.assertRaises(json.JSONDecodeError):
self._call_get_token("not-json")
@patch("apache_polaris.cli.api_client_builder.json.loads")
def test_response_body_parsed_once(self, mock_loads: MagicMock) -> None:
mock_loads.return_value = {"access_token": "abc123"}
builder = ApiClientBuilder(_builder_options(), direct_authentication=True)
mock_response = MagicMock()
mock_response.response.data = '{"access_token": "abc123"}'
with patch(
"apache_polaris.cli.api_client_builder.ApiClient"
) as mock_api_client:
mock_api_client.return_value.call_api.return_value = mock_response
token = builder._get_token()
self.assertEqual(token, "abc123")
mock_loads.assert_called_once_with('{"access_token": "abc123"}')
class TestBuilderConfigCatalogUrl(unittest.TestCase):
def _options(self, *, catalog_url: str | None = None) -> Namespace:
return Namespace(
base_url="http://localhost:8181",
catalog_url=catalog_url,
host=None,
port=None,
scheme=None,
proxy=None,
access_token=None,
client_id=None,
client_secret=None,
realm=None,
header=None,
profile=None,
)
def test_explicit_catalog_url_when_provided(self) -> None:
opts = self._options(catalog_url="http://localhost:8181/server1")
cfg = BuilderConfig(opts)
self.assertEqual(cfg.explicit_catalog_url, "http://localhost:8181/server1")
self.assertEqual(cfg.catalog_url, "http://localhost:8181/server1")
def test_catalog_url_falls_back_when_not_provided(self) -> None:
opts = self._options(catalog_url=None)
cfg = BuilderConfig(opts)
self.assertIsNone(cfg.explicit_catalog_url)
self.assertEqual(cfg.catalog_url, "http://localhost:8181/api/catalog")
def test_explicit_catalog_url_set_on_client(self) -> None:
opts = self._options(catalog_url="http://localhost:8181/server1")
opts.client_id = "id"
opts.client_secret = "secret"
builder = ApiClientBuilder(opts, direct_authentication=True)
with patch("apache_polaris.cli.api_client_builder.ApiClient") as mock_api:
mock_client = MagicMock()
mock_api.return_value = mock_client
builder.get_api_client()
self.assertTrue(hasattr(mock_client.configuration, "_polaris_catalog_base"))
self.assertEqual(
mock_client.configuration._polaris_catalog_base,
"http://localhost:8181/server1",
)
def test_no_explicit_when_not_set(self) -> None:
opts = self._options(catalog_url=None)
opts.client_id = "id"
opts.client_secret = "secret"
builder = ApiClientBuilder(opts, direct_authentication=True)
with patch("apache_polaris.cli.api_client_builder.ApiClient") as mock_api:
# Use plain object so we can assert attr was never set
mock_config = type("obj", (object,), {})()
mock_client = MagicMock()
mock_client.configuration = mock_config
mock_api.return_value = mock_client
builder.get_api_client()
self.assertFalse(hasattr(mock_config, "_polaris_catalog_base"))
class TestBuilderConfigBaseUrl(unittest.TestCase):
def _options(
self,
*,
base_url: str | None = None,
host: str | None = None,
port: int | None = None,
scheme: str | None = None,
profile: str | None = None,
) -> Namespace:
return Namespace(
base_url=base_url,
catalog_url=None,
host=host,
port=port,
scheme=scheme,
proxy=None,
access_token=None,
client_id=None,
client_secret=None,
realm=None,
header=None,
profile=profile,
)
def test_default_scheme_is_http(self) -> None:
cfg = BuilderConfig(self._options(host="localhost", port=8181))
self.assertEqual(cfg.base_url, "http://localhost:8181")
def test_base_url_includes_scheme(self) -> None:
cfg = BuilderConfig(
self._options(host="localhost", port=443, scheme="https")
)
self.assertEqual(cfg.base_url, "https://localhost:443")
def test_scheme_from_profile(self) -> None:
opts = self._options(profile="prod")
with patch(
"apache_polaris.cli.api_client_builder.load_profiles",
return_value={
"prod": {
"host": "localhost",
"port": 443,
"scheme": "https",
}
},
):
cfg = BuilderConfig(opts)
self.assertEqual(cfg.base_url, "https://localhost:443")
def test_option_scheme_overrides_profile(self) -> None:
opts = self._options(profile="prod", scheme="http")
with patch(
"apache_polaris.cli.api_client_builder.load_profiles",
return_value={
"prod": {
"host": "localhost",
"port": 443,
"scheme": "https",
}
},
):
cfg = BuilderConfig(opts)
self.assertEqual(cfg.base_url, "http://localhost:443")
def test_base_url_conflicts_with_scheme(self) -> None:
cfg = BuilderConfig(
self._options(base_url="https://localhost", scheme="https")
)
with self.assertRaises(CliError) as cm:
_ = cfg.base_url
message = str(cm.exception)
self.assertIn("--base-url", message)
self.assertIn("--scheme", message)
def test_base_url_is_preserved(self) -> None:
cfg = BuilderConfig(self._options(base_url="https://localhost:443"))
self.assertEqual(cfg.base_url, "https://localhost:443")
if __name__ == "__main__":
unittest.main()