Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RSPEED-399] Fix query read from stdin #83

Merged
merged 1 commit into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
22 changes: 18 additions & 4 deletions command_line_assistant/commands/query.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Module to handle the query command."""

from argparse import Namespace
from typing import Optional

from command_line_assistant.dbus.constants import QUERY_IDENTIFIER
from command_line_assistant.dbus.exceptions import (
Expand Down Expand Up @@ -84,13 +85,15 @@
class QueryCommand(BaseCLICommand):
"""Class that represents the query command."""

def __init__(self, query_string: str) -> None:
def __init__(self, query_string: str, stdin: Optional[str]) -> None:
"""Constructor of the class.

Args:
query_string (str): The query provided by the user.
stdin (Optional[str]): The user redirect input from stdin
"""
self._query = query_string
self._stdin = stdin

self._spinner_renderer: SpinnerRenderer = _initialize_spinner_renderer()
self._text_renderer: TextRenderer = _initialize_text_renderer()
Expand All @@ -106,9 +109,16 @@
int: Status code of the execution
"""
proxy = QUERY_IDENTIFIER.get_proxy()
input_query = Message()
input_query.message = self._query

query = self._query
if self._stdin:
# If query is provided, the message becomes "{query} {stdin}",
# otherwise, to avoid submitting `None` as part of the query, let's
# default to submit only the stidn.
query = f"{query} {self._stdin}" if query else self._stdin

Check warning on line 118 in command_line_assistant/commands/query.py

View check run for this annotation

Codecov / codecov/patch

command_line_assistant/commands/query.py#L118

Added line #L118 was not covered by tests

input_query = Message()
input_query.message = query
output = "Nothing to see here..."

try:
Expand Down Expand Up @@ -159,4 +169,8 @@
Returns:
QueryCommand: Return an instance of class
"""
return QueryCommand(args.query_string)
# We may not always have the stdin argument in the namespace.
if "stdin" in args:
return QueryCommand(args.query_string, args.stdin)

return QueryCommand(args.query_string, None)
16 changes: 11 additions & 5 deletions command_line_assistant/initialize.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""Main module for the cli."""

import sys
from argparse import Namespace

from command_line_assistant.commands import history, query, record
from command_line_assistant.utils.cli import (
add_default_command,
create_argument_parser,
read_stdin,
)


Expand All @@ -17,15 +19,19 @@ def initialize() -> int:
"""
parser, commands_parser = create_argument_parser()

# TODO: add autodetection of BaseCLICommand classes in the future so we can just drop
# new subcommand python modules into the directory and then loop and call `register_subcommand()`
# on each one.
# TODO: add autodetection of BaseCLICommand classes in the future so we can
# just drop new subcommand python modules into the directory and then loop
# and call `register_subcommand()` on each one.
query.register_subcommand(commands_parser) # type: ignore
history.register_subcommand(commands_parser) # type: ignore
record.register_subcommand(commands_parser) # type: ignore

args = add_default_command(sys.argv)
args = parser.parse_args(args)
stdin = read_stdin()
args = add_default_command(stdin, sys.argv)

# Small workaround to include the stdin in the namespace object in case it exists.
namespace = Namespace(stdin=stdin) if stdin else Namespace()
args = parser.parse_args(args, namespace=namespace)

if not hasattr(args, "func"):
parser.print_help()
Expand Down
9 changes: 5 additions & 4 deletions command_line_assistant/utils/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@ def run(self) -> int:
"""Entrypoint method for all CLI commands."""


def add_default_command(argv: list[str]):
def add_default_command(stdin: Optional[str], argv: list[str]):
"""Add the default command when none is given

Args:
argv: THe arguments passed from the stdin.
stdin (str): The input string coming from stdin
argv (list[str]): List of arguments from CLI
"""
args = argv[1:]

# Early exit if we don't have any argv
if not args:
# Early exit if we don't have any argv or stdin
if not args and not stdin:
return args

subcommand = _subcommand_used(argv)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ enabled = true
file = "~/.local/share/command-line-assistant/command-line-assistant_history.json"

[backend]
endpoint = "https://rlsapi-rhel-lightspeed--runtime-int.apps.int.spoke.preprod.us-east-1.aws.paas.redat.com"
# endpoint = "http://localhost:8080"
endpoint = "http://localhost:8080"

[backend.auth]
cert_file = "data/development/certificate/fake-certificate.pem"
Expand Down
2 changes: 1 addition & 1 deletion data/release/xdg/history.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"history": []}
{"history": [],"metadata": {"last_updated": "2024-12-30T12:04:36.298732Z", "version": "0.1.0", "entry_count": 0, "size_bytes": 0}}
35 changes: 0 additions & 35 deletions tests/commands/test_commands.py

This file was deleted.

55 changes: 25 additions & 30 deletions tests/commands/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def mock_dbus_service(mock_proxy):
def test_query_command_initialization():
"""Test QueryCommand initialization"""
query = "test query"
command = QueryCommand(query)
command = QueryCommand(query, None)
assert command._query == query


Expand All @@ -45,6 +45,7 @@ def test_query_command_initialization():
[
("how to list files?", "Use the ls command"),
("what is linux?", "Linux is an operating system"),
("test!@#$%^&*()_+ query", "response with special chars !@#%"),
],
)
def test_query_command_run(mock_dbus_service, test_input, expected_output, capsys):
Expand All @@ -55,7 +56,7 @@ def test_query_command_run(mock_dbus_service, test_input, expected_output, capsy
mock_dbus_service.RetrieveAnswer = lambda: Message.to_structure(mock_output)

# Create and run command
command = QueryCommand(test_input)
command = QueryCommand(test_input, None)
command.run()

# Verify ProcessQuery was called with correct input
Expand All @@ -77,7 +78,7 @@ def test_query_command_empty_response(mock_dbus_service, capsys):
mock_output.message = ""
mock_dbus_service.RetrieveAnswer = lambda: Message.to_structure(mock_output)

command = QueryCommand("test query")
command = QueryCommand("test query", None)
command.run()

captured = capsys.readouterr()
Expand All @@ -93,7 +94,7 @@ def test_query_command_empty_response(mock_dbus_service, capsys):
)
def test_query_command_invalid_inputs(mock_dbus_service, test_args):
"""Test QueryCommand with invalid inputs"""
command = QueryCommand(test_args)
command = QueryCommand(test_args, None)
command.run()
# Verify DBus calls still happen even with invalid input
mock_dbus_service.ProcessQuery.assert_called_once()
Expand All @@ -114,16 +115,32 @@ def test_register_subcommand():
assert hasattr(args, "func")


def test_command_factory():
@pytest.mark.parametrize(
("query_string", "stdin"),
(
(
"test query",
None,
),
(None, "stdin"),
("test query", "test stdin"),
),
)
def test_command_factory(query_string, stdin):
"""Test _command_factory function"""

from command_line_assistant.commands.query import _command_factory

args = Namespace(query_string="test query")
args = (
Namespace(query_string=query_string, stdin=stdin)
if stdin
else Namespace(query_string=query_string)
)
command = _command_factory(args)

assert isinstance(command, QueryCommand)
assert command._query == "test query"
assert command._query == query_string
assert command._stdin == stdin


@pytest.mark.parametrize(
Expand All @@ -148,31 +165,9 @@ def test_dbus_error_handling(exception, expected, mock_dbus_service, capsys):
# Make ProcessQuery raise a DBus error
mock_dbus_service.ProcessQuery.side_effect = exception

command = QueryCommand("test query")
command = QueryCommand("test query", None)
command.run()

# Verify error message in stdout
captured = capsys.readouterr()
assert expected in captured.out.strip()


def test_query_with_special_characters(mock_dbus_service, capsys):
"""Test query containing special characters"""
special_query = "test!@#$%^&*()_+ query"
expected_response = "response with special chars !@#$"

mock_output = Message()
mock_output.message = expected_response
mock_dbus_service.RetrieveAnswer = lambda: Message.to_structure(mock_output)

command = QueryCommand(special_query)
command.run()

expected_input = Message()
expected_input.message = special_query
mock_dbus_service.ProcessQuery.assert_called_once_with(
Message.to_structure(expected_input)
)

captured = capsys.readouterr()
assert expected_response in captured.out.strip()
30 changes: 26 additions & 4 deletions tests/test_initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,38 @@ def run(self): # type: ignore

def test_initialize_with_no_args(capsys):
"""Test initialize with no arguments - should print help and return 1"""
with patch("sys.argv", ["c"]):
with (
patch("sys.argv", ["c"]),
patch("command_line_assistant.initialize.read_stdin", lambda: None),
):
result = initialize()
captured = capsys.readouterr()

assert result == 1
assert "usage:" in captured.out


def test_initialize_with_query_command():
@pytest.mark.parametrize(
("argv", "stdin"),
(
(
["c", "query", "test", "query"],
None,
),
(["c"], "test from stdin"),
(["c", "what is this?"], "error in line 1"),
),
)
def test_initialize_with_query_command(argv, stdin):
"""Test initialize with query command"""
mock_command = Mock(return_value=MockCommand())

with (
patch("sys.argv", ["c", "query", "test query"]),
patch("sys.argv", argv),
patch("command_line_assistant.commands.query.register_subcommand"),
patch("command_line_assistant.commands.history.register_subcommand"),
patch("command_line_assistant.commands.record.register_subcommand"),
patch("command_line_assistant.initialize.read_stdin", lambda: stdin),
patch("argparse.ArgumentParser.parse_args") as mock_parse,
):
mock_parse.return_value.func = mock_command
Expand All @@ -48,6 +63,7 @@ def test_initialize_with_history_command():
patch("command_line_assistant.commands.query.register_subcommand"),
patch("command_line_assistant.commands.history.register_subcommand"),
patch("command_line_assistant.commands.record.register_subcommand"),
patch("command_line_assistant.initialize.read_stdin", lambda: None),
patch("argparse.ArgumentParser.parse_args") as mock_parse,
):
mock_parse.return_value.func = mock_command
Expand All @@ -66,6 +82,7 @@ def test_initialize_with_record_command():
patch("command_line_assistant.commands.query.register_subcommand"),
patch("command_line_assistant.commands.history.register_subcommand"),
patch("command_line_assistant.commands.record.register_subcommand"),
patch("command_line_assistant.initialize.read_stdin", lambda: None),
patch("argparse.ArgumentParser.parse_args") as mock_parse,
):
mock_parse.return_value.func = mock_command
Expand All @@ -79,6 +96,7 @@ def test_initialize_with_version():
"""Test initialize with --version flag"""
with (
patch("sys.argv", ["c", "--version"]),
patch("command_line_assistant.initialize.read_stdin", lambda: None),
patch("argparse.ArgumentParser.exit") as mock_exit,
):
initialize()
Expand All @@ -87,7 +105,10 @@ def test_initialize_with_version():

def test_initialize_with_help(capsys):
"""Test initialize with --help flag"""
with patch("sys.argv", ["c", "--help"]):
with (
patch("sys.argv", ["c", "--help"]),
patch("command_line_assistant.initialize.read_stdin", lambda: None),
):
with pytest.raises(SystemExit):
initialize()

Expand All @@ -113,6 +134,7 @@ def test_initialize_command_selection(argv, expected_command):

with (
patch("sys.argv", argv),
patch("command_line_assistant.initialize.read_stdin", lambda: None),
patch("command_line_assistant.commands.query.register_subcommand"),
patch("command_line_assistant.commands.history.register_subcommand"),
patch("command_line_assistant.commands.record.register_subcommand"),
Expand Down
Loading
Loading