simplify the app using typer
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from collections.abc import Sequence
|
||||
from unittest.mock import patch
|
||||
|
||||
from click.testing import Result
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from chromy.cli import app
|
||||
|
||||
|
||||
class CliTests(unittest.TestCase):
|
||||
def test_list_collections_and_alias(self) -> None:
|
||||
for command in ("list-collections", "lc"):
|
||||
with patch(
|
||||
"chromy.handlers.list_collections.list_collections",
|
||||
return_value=[],
|
||||
):
|
||||
result = _invoke([command])
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
self.assertEqual(result.stdout, "No collections found.\n")
|
||||
|
||||
def test_create_collection_and_alias(self) -> None:
|
||||
for command in ("create-collection", "cc"):
|
||||
with patch(
|
||||
"chromy.handlers.create_collection.create_collection",
|
||||
return_value="notes",
|
||||
) as create_collection:
|
||||
result = _invoke([command, "notes"])
|
||||
|
||||
create_collection.assert_called_once_with("notes")
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
self.assertEqual(result.stdout, "Created collection 'notes'.\n")
|
||||
|
||||
def test_delete_collection_and_alias(self) -> None:
|
||||
for command in ("delete-collection", "dc"):
|
||||
with patch(
|
||||
"chromy.handlers.delete_collection.delete_collection",
|
||||
) as delete_collection:
|
||||
result = _invoke([command, "notes"])
|
||||
|
||||
delete_collection.assert_called_once_with("notes")
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
self.assertEqual(result.stdout, "Deleted collection 'notes'.\n")
|
||||
|
||||
def test_count_and_alias(self) -> None:
|
||||
for command in ("count", "co"):
|
||||
with patch(
|
||||
"chromy.handlers.count_collection.count_collection",
|
||||
return_value=7,
|
||||
) as count_collection:
|
||||
result = _invoke([command, "notes"])
|
||||
|
||||
count_collection.assert_called_once_with("notes")
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
self.assertEqual(result.stdout, "7\n")
|
||||
|
||||
def test_add_data_and_alias(self) -> None:
|
||||
for command in ("add-data", "ad"):
|
||||
with patch(
|
||||
"chromy.handlers.add_data.ingest_file",
|
||||
return_value=3,
|
||||
) as ingest_file:
|
||||
result = _invoke([command, "notes", "romeo_and_juliet.txt"])
|
||||
|
||||
ingest_file.assert_called_once_with("notes", "romeo_and_juliet.txt")
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
self.assertEqual(result.stdout, "Added 3 records to collection 'notes'.\n")
|
||||
|
||||
def test_query_and_alias(self) -> None:
|
||||
query_result = {"ids": [["1"]], "documents": [["hello"]]}
|
||||
|
||||
for command in ("query", "q"):
|
||||
with (
|
||||
patch(
|
||||
"chromy.handlers.query.run_query", return_value=query_result
|
||||
) as run,
|
||||
patch(
|
||||
"chromy.handlers.query.format_query_result",
|
||||
return_value=["Query results:", "1"],
|
||||
) as format_result,
|
||||
):
|
||||
result = _invoke([command, "notes", "Where is Romeo?"])
|
||||
|
||||
run.assert_called_once_with("notes", "Where is Romeo?")
|
||||
format_result.assert_called_once_with(query_result)
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
self.assertEqual(result.stdout, "Query results:\n1\n")
|
||||
|
||||
def test_delete_records_and_alias(self) -> None:
|
||||
for command in ("delete", "del"):
|
||||
with patch(
|
||||
"chromy.handlers.delete_collection.delete_data",
|
||||
return_value=2,
|
||||
) as delete_data:
|
||||
result = _invoke(
|
||||
[command, "notes", "--where", " file_name = play.txt "],
|
||||
)
|
||||
|
||||
delete_data.assert_called_once_with("notes", {"file_name": "play.txt"})
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
self.assertEqual(
|
||||
result.stdout,
|
||||
"Deleted 2 record(s) from collection 'notes' "
|
||||
"where file_name=play.txt.\n",
|
||||
)
|
||||
|
||||
def test_invalid_delete_filter_keeps_user_facing_error(self) -> None:
|
||||
result = _invoke(["delete", "notes", "--where", "file_name"])
|
||||
|
||||
self.assertEqual(result.exit_code, 1)
|
||||
self.assertEqual(
|
||||
result.stdout,
|
||||
"Invalid --where value. Expected <condition>=<value>.\n",
|
||||
)
|
||||
|
||||
def test_delete_requires_where_option(self) -> None:
|
||||
result = _invoke(["delete", "notes"])
|
||||
|
||||
self.assertNotEqual(result.exit_code, 0)
|
||||
self.assertIn("Missing option", result.output)
|
||||
|
||||
|
||||
def _invoke(arguments: Sequence[str]) -> Result:
|
||||
return CliRunner().invoke(app, list(arguments))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,98 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import unittest
|
||||
from argparse import Namespace
|
||||
from collections.abc import Sequence
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
from chromy.cli_app import build_command_input, execute_command
|
||||
from chromy.cli_parser import build_parser
|
||||
from chromy.command_inputs import (
|
||||
AddDataInput,
|
||||
CountCollectionInput,
|
||||
CreateCollectionInput,
|
||||
DeleteCollectionInput,
|
||||
DeleteRecordsInput,
|
||||
ListCollectionsInput,
|
||||
QueryInput,
|
||||
)
|
||||
|
||||
|
||||
class BuildCommandInputTests(unittest.TestCase):
|
||||
def test_parser_converts_list_collections_and_alias(self) -> None:
|
||||
self.assertEqual(_parse_input(["list-collections"]), ListCollectionsInput())
|
||||
self.assertEqual(_parse_input(["lc"]), ListCollectionsInput())
|
||||
|
||||
def test_parser_converts_create_collection_and_alias(self) -> None:
|
||||
expected = CreateCollectionInput(collection="notes")
|
||||
|
||||
self.assertEqual(_parse_input(["create-collection", "notes"]), expected)
|
||||
self.assertEqual(_parse_input(["cc", "notes"]), expected)
|
||||
|
||||
def test_parser_converts_delete_collection_and_alias(self) -> None:
|
||||
expected = DeleteCollectionInput(collection="notes")
|
||||
|
||||
self.assertEqual(_parse_input(["delete-collection", "notes"]), expected)
|
||||
self.assertEqual(_parse_input(["dc", "notes"]), expected)
|
||||
|
||||
def test_parser_converts_count_and_alias(self) -> None:
|
||||
expected = CountCollectionInput(collection="notes")
|
||||
|
||||
self.assertEqual(_parse_input(["count", "notes"]), expected)
|
||||
self.assertEqual(_parse_input(["co", "notes"]), expected)
|
||||
|
||||
def test_parser_converts_add_data_and_alias(self) -> None:
|
||||
expected = AddDataInput(collection="notes", file="romeo_and_juliet.txt")
|
||||
|
||||
self.assertEqual(
|
||||
_parse_input(["add-data", "notes", "romeo_and_juliet.txt"]),
|
||||
expected,
|
||||
)
|
||||
self.assertEqual(
|
||||
_parse_input(["ad", "notes", "romeo_and_juliet.txt"]),
|
||||
expected,
|
||||
)
|
||||
|
||||
def test_parser_converts_query_and_alias(self) -> None:
|
||||
expected = QueryInput(collection="notes", query_text="Where is Romeo?")
|
||||
|
||||
self.assertEqual(
|
||||
_parse_input(["query", "notes", "Where is Romeo?"]),
|
||||
expected,
|
||||
)
|
||||
self.assertEqual(_parse_input(["q", "notes", "Where is Romeo?"]), expected)
|
||||
|
||||
def test_parser_converts_delete_records_and_alias(self) -> None:
|
||||
expected = DeleteRecordsInput(collection="notes", where="file_name=play.txt")
|
||||
|
||||
self.assertEqual(
|
||||
_parse_input(["delete", "notes", "--where", "file_name=play.txt"]),
|
||||
expected,
|
||||
)
|
||||
self.assertEqual(
|
||||
_parse_input(["del", "notes", "--where", "file_name=play.txt"]),
|
||||
expected,
|
||||
)
|
||||
|
||||
def test_invalid_delete_filter_keeps_user_facing_error(self) -> None:
|
||||
args = Namespace(command="delete", collection="notes", where="file_name")
|
||||
output = io.StringIO()
|
||||
|
||||
with redirect_stdout(output):
|
||||
exit_code = execute_command(args)
|
||||
|
||||
self.assertEqual(exit_code, 1)
|
||||
self.assertEqual(
|
||||
output.getvalue().strip(),
|
||||
"Invalid --where value. Expected <condition>=<value>.",
|
||||
)
|
||||
self.assertFalse(hasattr(args, "error_message"))
|
||||
|
||||
|
||||
def _parse_input(argv: Sequence[str]) -> object:
|
||||
return build_command_input(build_parser().parse_args(argv))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+13
-23
@@ -7,15 +7,6 @@ from contextlib import redirect_stdout
|
||||
from typing import TypeVar
|
||||
from unittest.mock import patch
|
||||
|
||||
from chromy.command_inputs import (
|
||||
AddDataInput,
|
||||
CountCollectionInput,
|
||||
CreateCollectionInput,
|
||||
DeleteCollectionInput,
|
||||
DeleteRecordsInput,
|
||||
ListCollectionsInput,
|
||||
QueryInput,
|
||||
)
|
||||
from chromy.handlers.add_data import handle_add_data
|
||||
from chromy.handlers.count_collection import handle_count_collection
|
||||
from chromy.handlers.create_collection import handle_create_collection
|
||||
@@ -36,7 +27,6 @@ class HandlerTests(unittest.TestCase):
|
||||
):
|
||||
exit_code, output = _capture_output(
|
||||
handle_list_collections,
|
||||
ListCollectionsInput(),
|
||||
)
|
||||
|
||||
self.assertEqual(exit_code, 0)
|
||||
@@ -49,7 +39,6 @@ class HandlerTests(unittest.TestCase):
|
||||
):
|
||||
exit_code, output = _capture_output(
|
||||
handle_list_collections,
|
||||
ListCollectionsInput(),
|
||||
)
|
||||
|
||||
self.assertEqual(exit_code, 0)
|
||||
@@ -62,7 +51,7 @@ class HandlerTests(unittest.TestCase):
|
||||
) as create_collection:
|
||||
exit_code, output = _capture_output(
|
||||
handle_create_collection,
|
||||
CreateCollectionInput(collection="notes"),
|
||||
"notes",
|
||||
)
|
||||
|
||||
create_collection.assert_called_once_with("notes")
|
||||
@@ -73,7 +62,7 @@ class HandlerTests(unittest.TestCase):
|
||||
with patch("chromy.handlers.delete_collection.delete_collection") as delete:
|
||||
exit_code, output = _capture_output(
|
||||
handle_delete_collection,
|
||||
DeleteCollectionInput(collection="notes"),
|
||||
"notes",
|
||||
)
|
||||
|
||||
delete.assert_called_once_with("notes")
|
||||
@@ -87,7 +76,7 @@ class HandlerTests(unittest.TestCase):
|
||||
) as count:
|
||||
exit_code, output = _capture_output(
|
||||
handle_count_collection,
|
||||
CountCollectionInput(collection="notes"),
|
||||
"notes",
|
||||
)
|
||||
|
||||
count.assert_called_once_with("notes")
|
||||
@@ -101,7 +90,8 @@ class HandlerTests(unittest.TestCase):
|
||||
) as ingest_file:
|
||||
exit_code, output = _capture_output(
|
||||
handle_add_data,
|
||||
AddDataInput(collection="notes", file="romeo_and_juliet.txt"),
|
||||
"notes",
|
||||
"romeo_and_juliet.txt",
|
||||
)
|
||||
|
||||
ingest_file.assert_called_once_with("notes", "romeo_and_juliet.txt")
|
||||
@@ -119,7 +109,8 @@ class HandlerTests(unittest.TestCase):
|
||||
):
|
||||
exit_code, output = _capture_output(
|
||||
handle_query,
|
||||
QueryInput(collection="notes", query_text="hello"),
|
||||
"notes",
|
||||
"hello",
|
||||
)
|
||||
|
||||
run.assert_called_once_with("notes", "hello")
|
||||
@@ -134,7 +125,8 @@ class HandlerTests(unittest.TestCase):
|
||||
) as delete_data:
|
||||
exit_code, output = _capture_output(
|
||||
handle_delete_records,
|
||||
DeleteRecordsInput(collection="notes", where=" file_name = play.txt "),
|
||||
"notes",
|
||||
" file_name = play.txt ",
|
||||
)
|
||||
|
||||
delete_data.assert_called_once_with("notes", {"file_name": "play.txt"})
|
||||
@@ -149,19 +141,17 @@ class HandlerTests(unittest.TestCase):
|
||||
ValueError,
|
||||
"Invalid --where value. Expected <condition>=<value>.",
|
||||
):
|
||||
handle_delete_records(
|
||||
DeleteRecordsInput(collection="notes", where="file_name")
|
||||
)
|
||||
handle_delete_records("notes", "file_name")
|
||||
|
||||
|
||||
def _capture_output(
|
||||
handler: Callable[[CommandT], int],
|
||||
command: CommandT,
|
||||
handler: Callable[..., int],
|
||||
*arguments: CommandT,
|
||||
) -> tuple[int, str]:
|
||||
output = io.StringIO()
|
||||
|
||||
with redirect_stdout(output):
|
||||
exit_code = handler(command)
|
||||
exit_code = handler(*arguments)
|
||||
|
||||
return exit_code, output.getvalue()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user