blob: 4fe90df472c41dcd46062dea0941c099075f6ade [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 io
from unittest.mock import patch, MagicMock
from cli_test_utils import CLITestBase
from apache_polaris.cli.command.repl import ReplCommand, PolarisRepl
from apache_polaris.cli.constants import Commands
from apache_polaris.cli.exceptions import CliError
from apache_polaris.sdk.management.exceptions import ApiException
class TestReplCommand(CLITestBase):
@patch("apache_polaris.cli.options.parser.Parser.parse")
@patch("apache_polaris.cli.command.Command.from_options")
def test_repl_executes_command(
self, mock_from_options: MagicMock, mock_parse: MagicMock
) -> None:
mock_client = self.build_mock_client()
mock_command = MagicMock()
mock_from_options.return_value = mock_command
repl = PolarisRepl(mock_client)
self.assertEqual(repl.prompt, "polaris@localhost:8080> ")
repl.default("catalogs list")
mock_parse.assert_called_with(["catalogs", "list"])
mock_from_options.assert_called()
mock_command.execute.assert_called_with(mock_client)
@patch(
"apache_polaris.cli.options.parser.Parser.parse",
side_effect=RuntimeError("boom"),
)
def test_repl_handles_unexpected_exception(
self, _mock_parse: MagicMock
) -> None:
repl = PolarisRepl(self.build_mock_client())
with patch("sys.stderr", new_callable=io.StringIO) as mock_stderr:
repl.default("bad command")
output = mock_stderr.getvalue()
self.assertIn("An unexpected error occurred", output)
self.assertIn("boom", output)
@patch(
"apache_polaris.cli.options.parser.Parser.parse",
side_effect=CliError("bad usage"),
)
def test_repl_handles_cli_error(self, _mock_parse: MagicMock) -> None:
repl = PolarisRepl(self.build_mock_client())
with patch("sys.stderr", new_callable=io.StringIO) as mock_stderr:
repl.default("bad usage")
output = mock_stderr.getvalue()
self.assertIn("bad usage", output)
self.assertNotIn("unexpected", output)
@patch(
"apache_polaris.cli.options.parser.Parser.parse",
side_effect=NotImplementedError("nope"),
)
def test_repl_handles_not_implemented(self, _mock_parse: MagicMock) -> None:
repl = PolarisRepl(self.build_mock_client())
with patch("sys.stderr", new_callable=io.StringIO) as mock_stderr:
repl.default("something")
output = mock_stderr.getvalue()
self.assertIn("Internal error", output)
self.assertIn("nope", output)
@patch("apache_polaris.cli.polaris_cli.PolarisCli.print_api_exception")
@patch("apache_polaris.cli.options.parser.Parser.parse")
def test_repl_handles_api_exception(
self, mock_parse: MagicMock, mock_print_api_exc: MagicMock
) -> None:
api_exc = ApiException(status=500, reason="server error")
mock_parse.side_effect = api_exc
repl = PolarisRepl(self.build_mock_client())
repl.default("catalogs list")
mock_print_api_exc.assert_called_once_with(api_exc)
@patch(
"apache_polaris.cli.options.parser.Parser.parse", side_effect=SystemExit(2)
)
def test_repl_swallows_system_exit(self, _mock_parse: MagicMock) -> None:
repl = PolarisRepl(self.build_mock_client())
repl.default("--help")
@patch("apache_polaris.cli.command.Command.from_options")
@patch("apache_polaris.cli.options.parser.Parser.parse")
def test_repl_in_repl_is_guarded(
self, mock_parse: MagicMock, mock_from_options: MagicMock
) -> None:
fake_options = MagicMock()
fake_options.command = Commands.REPL
mock_parse.return_value = fake_options
repl = PolarisRepl(self.build_mock_client())
with patch("sys.stderr", new_callable=io.StringIO) as mock_stderr:
repl.default("repl")
mock_from_options.assert_not_called()
self.assertIn("Already in REPL session", mock_stderr.getvalue())
@patch(
"apache_polaris.cli.options.parser.Parser.parse",
side_effect=KeyboardInterrupt(),
)
def test_repl_handles_keyboard_interrupt_per_command(
self, _mock_parse: MagicMock
) -> None:
repl = PolarisRepl(self.build_mock_client())
with patch("sys.stderr", new_callable=io.StringIO) as mock_stderr:
repl.default("catalogs list")
self.assertIn("Session interrupted", mock_stderr.getvalue())
def test_repl_empty_line_is_no_op(self) -> None:
repl = PolarisRepl(self.build_mock_client())
self.assertIsNone(repl.default(""))
self.assertIsNone(repl.default(" "))
@patch("apache_polaris.cli.command.repl.PolarisRepl")
def test_repl_command_execute(self, mock_repl_class: MagicMock) -> None:
mock_client = self.build_mock_client()
mock_repl_instance = mock_repl_class.return_value
# Test with catalog
ReplCommand(profile="test-profile", catalog="test-catalog").execute(mock_client)
mock_repl_class.assert_called_with(
mock_client, profile="test-profile", catalog="test-catalog"
)
# Test without catalog
ReplCommand(profile="test-profile").execute(mock_client)
mock_repl_class.assert_called_with(mock_client, profile="test-profile", catalog=None)
self.assertEqual(mock_repl_instance.cmdloop.call_count, 2)
def test_repl_prompt_resolution(self) -> None:
mock_client = self.build_mock_client()
# Profile takes precedence; otherwise show the api client's host:port.
repl_with_profile = PolarisRepl(mock_client, profile="prod")
self.assertEqual(repl_with_profile.prompt, "polaris@prod> ")
repl_with_catalog = PolarisRepl(mock_client, catalog="test-catalog")
self.assertEqual(repl_with_catalog.prompt, "polaris@localhost:8080/test-catalog> ")
mock_client.api_client.configuration.host = (
"http://resolved-host:9000/api/management/v1"
)
repl_default = PolarisRepl(mock_client)
self.assertEqual(repl_default.prompt, "polaris@resolved-host:9000> ")
@patch("apache_polaris.cli.options.parser.Parser.parse")
@patch("apache_polaris.cli.command.Command.from_options")
def test_repl_catalog_injestion(self, mock_from_options: MagicMock, mock_parse: MagicMock) -> None:
mock_client = self.build_mock_client()
repl = PolarisRepl(mock_client, catalog="test-catalog")
fake_options = MagicMock()
fake_options.catalog = None
mock_parse.return_value = fake_options
repl.default("namespaces list")
self.assertEqual(fake_options.catalog, "test-catalog")
mock_from_options.assert_called_with(fake_options)
def test_emptyline_returns_false(self) -> None:
repl = PolarisRepl(self.build_mock_client())
self.assertFalse(repl.emptyline())
def test_help_lists_polaris_commands(self) -> None:
repl = PolarisRepl(self.build_mock_client())
with patch("sys.stdout", new_callable=io.StringIO) as mock_stdout:
repl.do_help("")
out = mock_stdout.getvalue()
self.assertIn("catalogs", out)
self.assertIn("principals", out)
self.assertIn("repl", out)
self.assertIn("exit", out)
def test_help_for_command_delegates_to_argparse(self) -> None:
repl = PolarisRepl(self.build_mock_client())
with patch("sys.stdout", new_callable=io.StringIO) as mock_stdout:
repl.do_help("principals")
self.assertIn("usage: polaris principals", mock_stdout.getvalue())