From de3ebf238d28d48ffe104e59bc7f69a139794dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lesaint?= Date: Sat, 27 Apr 2024 09:48:45 +0200 Subject: [PATCH 1/4] PYL-26 begin refactor to extract print() and input() from pylms.py --- src/pylms/pylms.py | 231 +++++++++++++++++----------- tests/pylms/pylms_protected_test.py | 87 +++++++---- tests/pylms/pymls_test.py | 153 ++++++++---------- 3 files changed, 259 insertions(+), 212 deletions(-) diff --git a/src/pylms/pylms.py b/src/pylms/pylms.py index 67907eb..a033cd1 100644 --- a/src/pylms/pylms.py +++ b/src/pylms/pylms.py @@ -2,59 +2,155 @@ from pylms.core import Person, PersonIdGenerator from pylms.core import relationship_definitions, RelationshipDefinition, Relationship from pylms.core import resolve_persons +from abc import abstractmethod, ABC class ExitPyLMS(BaseException): pass -def _input_or_exit_pylms(): - try: - return input() - except KeyboardInterrupt: - raise ExitPyLMS() +class IOs(ABC): + @abstractmethod + def show_person(self, person: Person) -> None: + pass + @abstractmethod + def list_persons(self, persons: list[Person]) -> None: + pass -def list_persons() -> None: - persons = storage.read_persons() - if persons: - relationships = storage.read_relationships(persons) - resolved_persons = resolve_persons(persons, relationships) + @abstractmethod + def select_person(self, persons: list[Person]) -> Person | None: + pass - for person, rs in sorted(resolved_persons, key=lambda t: t[0].person_id): - _print_person(person) - for r in rs: - other = r.right if r.left == person else r.left - print(f" -> {r.repr_for(person)} de ({other.person_id}) {other}") - else: - print("No Person registered yet.") - - -def _print_person(person): - created = person.created - print( - f"({person.person_id})", - person, - f"({created.year}-{created.month}-{created.day} {created.hour}-{created.minute}-{created.second})", - ) + @abstractmethod + def get_person_details(self) -> tuple[str, str | None]: + pass + + +class EventListener(ABC): + @abstractmethod + def creating_person(self, person: Person) -> None: + pass + + @abstractmethod + def deleting_person(self, person_to_delete: Person) -> None: + pass + + @abstractmethod + def creating_link(self, rl_definition: RelationshipDefinition, person_left: Person, person_right: Person) -> None: + pass + + +class CLI(IOs, EventListener): + def creating_person(self, person: Person) -> None: + print(f"Create Person {person}.") + def show_person(self, person: Person) -> None: + created = person.created + print( + f"({person.person_id})", + person, + f"({created.year}-{created.month}-{created.day} {created.hour}-{created.minute}-{created.second})", + ) -def _interactive_person_id(valid_ids: list[int]) -> int: - if not valid_ids: - raise ValueError("valid_ids can not be empty.") + def list_persons(self, resolved_persons: list[(Person, list[Relationship])]) -> None: + if resolved_persons: + for person, rs in sorted(resolved_persons, key=lambda t: t[0].person_id): + ios.show_person(person) + for r in rs: + other = r.right if r.left == person else r.left + print(f" -> {r.repr_for(person)} de ({other.person_id}) {other}") + else: + print("No Person registered yet.") - while True: - n = _input_or_exit_pylms() + def _interactive_hit_enter(self): + while True: + s = self._input_or_exit_pylms() + if len(s) == 0: + return + + print("Just hit ENTER") + continue + + def _input_or_exit_pylms(self): try: - res = int(n) - if res not in valid_ids: - print("Not a valid id.") + return input() + except KeyboardInterrupt: + raise ExitPyLMS() + + def _interactive_person_id(self, valid_ids: list[int]) -> int: + if not valid_ids: + raise ValueError("valid_ids can not be empty.") + + while True: + n = self._input_or_exit_pylms() + + try: + res = int(n) + if res not in valid_ids: + print("Not a valid id.") + continue + + return res + except ValueError: + print("Not an integer.") + + def select_person(self, persons: list[Person]) -> Person | None: + print("Input id of person to update:") + for person in sorted(persons, key=lambda p: p.person_id): + self.show_person(person) + print("CTRL+C to exit") + + person_id = self._interactive_person_id([person.person_id for person in persons]) + for person in persons: + if person.person_id == person_id: + return person + + # should not happen + raise RuntimeError(f"id {person_id} does not exist in list of Persons") + + def get_person_details(self) -> tuple[str, str | None]: + while True: + text = self._input_or_exit_pylms() + + words = text.split(" ") + if len(words) > 2: + print("Too many words.") continue - return res - except ValueError: - print("Not an integer.") + if len(words) == 1: + return words[0], None + return words[0], words[1] + + def deleting_person(self, person_to_delete: Person) -> None: + print("Hit ENTER to delete:") + self.show_person(person_to_delete) + print("CTRL+C to exit") + self._interactive_hit_enter() + + def creating_link(self, rl_definition: RelationshipDefinition, person_left: Person, person_right: Person) -> None: + print(f'Hit ENTER to link as "{rl_definition.name}":') + self.show_person(person_left) + self.show_person(person_right) + print("CTRL+C to exit") + self._interactive_hit_enter() + + +_cli: CLI = CLI() +ios: IOs = _cli +events: EventListener = _cli + + +def list_persons() -> None: + persons = storage.read_persons() + + resolved_persons = [] + if persons: + relationships = storage.read_relationships(persons) + resolved_persons = resolve_persons(persons, relationships) + + ios.list_persons(resolved_persons) def _interactive_select_person(pattern: str) -> Person | None: @@ -66,32 +162,7 @@ def _interactive_select_person(pattern: str) -> Person | None: if len(persons) == 1: return persons[0] - print("Input id of person to update:") - for person in sorted(persons, key=lambda p: p.person_id): - _print_person(person) - print("CTRL+C to exit") - - person_id = _interactive_person_id([person.person_id for person in persons]) - for person in persons: - if person.person_id == person_id: - return person - - # should not happen - raise RuntimeError(f"id {person_id} does not exist in list of Persons") - - -def _interactive_person_details() -> tuple[str, str | None]: - while True: - text = _input_or_exit_pylms() - - words = text.split(" ") - if len(words) > 2: - print("Too many words.") - continue - - if len(words) == 1: - return words[0], None - return words[0], words[1] + return ios.select_person(persons) def update_person(pattern: str) -> None: @@ -100,12 +171,12 @@ def update_person(pattern: str) -> None: return print("Input new first name and last name to update:") - _print_person(person_to_update) + ios.show_person(person_to_update) print("CTRL+C to exit") firstname: str lastname: str - firstname, lastname = _interactive_person_details() + firstname, lastname = ios.get_person_details() persons = storage.read_persons() for person in persons: @@ -117,7 +188,7 @@ def update_person(pattern: str) -> None: def search_person(pattern: str) -> None: for person in _search_person(pattern): - _print_person(person) + ios.show_person(person) def _search_person(pattern: str) -> list[Person]: @@ -143,7 +214,7 @@ def store_person(firstname: str) -> None: persons = storage.read_persons() id_generator = PersonIdGenerator(persons) person = Person(person_id=id_generator.next_person_id(), firstname=firstname) - print(f"Create Person {person}.") + events.creating_person(person) storage.store_persons([person] + persons) @@ -151,31 +222,16 @@ def store_person(firstname: str, lastname: str = None) -> None: persons = storage.read_persons() id_generator = PersonIdGenerator(persons) person = Person(person_id=id_generator.next_person_id(), firstname=firstname, lastname=lastname) - print(f"Create Person {person}.") + events.creating_person(person) storage.store_persons([person] + persons) -def _interactive_hit_enter(): - while True: - s = _input_or_exit_pylms() - - if len(s) == 0: - return - - print("Just hit ENTER") - continue - - def delete_person(pattern: str) -> None: person_to_delete: Person = _interactive_select_person(pattern) if not person_to_delete: return - print("Hit ENTER to delete:") - _print_person(person_to_delete) - print("CTRL+C to exit") - - _interactive_hit_enter() + events.deleting_person(person_to_delete) persons = storage.read_persons() persons.remove(person_to_delete) @@ -241,12 +297,7 @@ def link_persons(natural_language_link_request: str) -> None: if person_left is None or person_right is None: return - print(f'Hit ENTER to link as "{link_request.definition.name}":') - _print_person(person_left) - _print_person(person_right) - print("CTRL+C to exit") - - _interactive_hit_enter() + events.creating_link(link_request.definition, person_left, person_right) persons = storage.read_persons() relationships = storage.read_relationships(persons) diff --git a/tests/pylms/pylms_protected_test.py b/tests/pylms/pylms_protected_test.py index f99f0d1..21a8d3c 100644 --- a/tests/pylms/pylms_protected_test.py +++ b/tests/pylms/pylms_protected_test.py @@ -1,10 +1,13 @@ from pylms.core import Person from pylms.core import RelationshipDefinition -from pylms.pylms import _search_person, _interactive_person_id, _interactive_select_person, _interactive_hit_enter +from pylms.pylms import CLI +from pylms.pylms import _search_person, _interactive_select_person from pylms.pylms import _parse_nl_link_request from pytest import raises, mark from unittest.mock import patch, call +under_test = CLI() + @patch("pylms.pylms.storage") def test_search_person(mock_storage): @@ -22,18 +25,60 @@ def test_search_person(mock_storage): assert _search_person("foo") == [] +class Test_list_persons: + + @patch("builtins.print") + def test_no_persons(self, mock_print): + under_test.list_persons([]) + + mock_print.assert_called_with("No Person registered yet.") + assert mock_print.call_count == 1 + + @patch("builtins.print") + @patch("pylms.pylms.ios.show_person") + def test_one_person_no_relationship(self, mock_show_person, mock_print): + person = Person(3, "John", "Doe") + + under_test.list_persons([(person, [])]) + + assert mock_show_person.call_count == 1 + mock_show_person.assert_called_once_with(person) + assert mock_print.call_count == 0 + + @patch("builtins.print") + @patch("pylms.pylms.ios.show_person") + def test_persons_no_relationship(self, mock_show_person, mock_print): + persons = [ + Person(3, "Bob"), + Person(1, "Seb", "King"), + Person(2, "Mario", "Bros"), + ] + + under_test.list_persons([(person, []) for person in persons]) + + assert mock_show_person.call_count == 3 + mock_show_person.assert_has_calls( + [ + call(persons[1]), + call(persons[2]), + call(persons[0]), + ] + ) + assert mock_print.call_count == 0 + + class Test_interactive_person_id: def test_sanity_check(self): with raises(ValueError, match="valid_ids can not be empty."): - _interactive_person_id([]) + under_test._interactive_person_id([]) @patch("builtins.input") @mark.parametrize("valid_ids", [[1], [2, 1], [23, 7, 1, 2]]) def test_straight_correct_input(self, mock_input, valid_ids): mock_input.return_value = "1" - res = _interactive_person_id([2, 1]) + res = under_test._interactive_person_id([2, 1]) assert res == 1 @@ -66,45 +111,23 @@ def test_one_match(self, mock_search_person, mock_print): assert mock_search_person.call_count == 1 assert mock_print.call_count == 0 - @patch("builtins.print") - @patch("pylms.pylms._interactive_person_id") - @patch("pylms.pylms._print_person") + @patch("pylms.pylms.ios.select_person") @patch("pylms.pylms._search_person") - def test_several_matches(self, mock_search_person, mock_print_person, mock_interactive_person_id, mock_print): + def test_several_matches(self, mock_search_person, mock_select_person): persons = [ Person(3, "Bob"), Person(1, "Seb"), ] mock_search_person.return_value = persons - mock_interactive_person_id.return_value = 3 + mock_select_person.return_value = persons[0] res = _interactive_select_person("p") assert res == persons[0] mock_search_person.assert_called_once_with("p") assert mock_search_person.call_count == 1 - assert mock_print.call_count == 2 - mock_print.assert_has_calls([call("Input id of person to update:"), call("CTRL+C to exit")]) - assert mock_print_person.call_count == 2 - mock_print_person.assert_has_calls([call(persons[1]), call(persons[0])]) - assert mock_interactive_person_id.call_count == 1 - mock_interactive_person_id.assert_called_once_with([3, 1]) - - @patch("builtins.print") - @patch("pylms.pylms._interactive_person_id") - @patch("pylms.pylms._search_person") - def test_interactive_raise_runtime_error_if_interactive_person_id_returns_crap( - self, mock_search_person, mock_interactive_person_id, mock_print - ): - persons = [ - Person(3, "Bob"), - Person(1, "Seb"), - ] - mock_search_person.return_value = persons - mock_interactive_person_id.return_value = 123 - - with raises(RuntimeError, match="id 123 does not exist in list of Persons"): - _interactive_select_person("p") + assert mock_select_person.call_count == 1 + mock_select_person.assert_called_once_with(persons) class Test_interactive_hit_enter: @@ -113,7 +136,7 @@ class Test_interactive_hit_enter: def test_interactive_hit_enter_straight_correct_input(self, mock_input): mock_input.return_value = "" - _interactive_hit_enter() + under_test._interactive_hit_enter() mock_input.assert_called_once_with() assert mock_input.call_count == 1 @@ -122,7 +145,7 @@ def test_interactive_hit_enter_straight_correct_input(self, mock_input): def test_interactive_hit_enter_incorrect_inputs(self, mock_input): mock_input.side_effect = ["q", "12", "ENTER", ""] - _interactive_hit_enter() + under_test._interactive_hit_enter() mock_input.assert_has_calls([call(), call(), call(), call()]) assert mock_input.call_count == 4 diff --git a/tests/pylms/pymls_test.py b/tests/pylms/pymls_test.py index 998fe75..8d8f844 100644 --- a/tests/pylms/pymls_test.py +++ b/tests/pylms/pymls_test.py @@ -77,79 +77,68 @@ def test_only_firstname_and_add_to_existing_persons(self, mock_store_persons, mo assert mock_print.call_count == 1 -@patch("builtins.print") -@patch("pylms.pylms.storage.read_persons") -@patch("pylms.pylms.storage.store_persons") -def test_list_persons_empty_storage(mock_store_persons, mock_read_persons, mock_print): - mock_read_persons.return_value = [] - - list_persons() - - mock_read_persons.assert_called_with() - assert mock_read_persons.call_count == 1 - assert mock_store_persons.call_count == 0 - mock_print.assert_called_with("No Person registered yet.") - assert mock_print.call_count == 1 - - -@patch("builtins.print") -@patch("pylms.pylms.storage.read_relationships", return_value=[]) -@patch("pylms.pylms.storage.read_persons") -@patch("pylms.pylms.storage.store_persons") -def test_list_persons_persons_but_no_relationships( - mock_store_persons, mock_read_persons, mock_read_relationships, mock_print -): - t1 = datetime(2024, 4, 5, 12, 41, 58) - t2 = datetime(2024, 9, 18, 21, 8, 8) - persons = [ - Person(3, "Bob", created=t1), - Person(1, "Seb", "King", t1), - Person(2, "Mario", "Bros", t2), - ] - - mock_read_persons.return_value = persons - - list_persons() - - mock_read_persons.assert_called_with() - assert mock_read_persons.call_count == 1 - assert mock_store_persons.call_count == 0 - mock_print.assert_has_calls( - [ - call("(1)", persons[1], "(2024-4-5 12-41-58)"), - call("(2)", persons[2], "(2024-9-18 21-8-8)"), - call("(3)", persons[0], "(2024-4-5 12-41-58)"), +class Test_list_persons: + @patch("pylms.pylms.ios.list_persons") + @patch("pylms.pylms.storage.read_relationships") + @patch("pylms.pylms.storage.read_persons") + def test_empty_storage(self, mock_read_persons, mock_read_relationships, mock_list_persons): + mock_read_persons.return_value = [] + + list_persons() + + mock_read_persons.assert_called_once_with() + assert mock_read_relationships.call_count == 0 + mock_list_persons.assert_called_once_with([]) + + @patch("pylms.pylms.ios.list_persons") + @patch("pylms.pylms.storage.read_relationships", return_value=[]) + @patch("pylms.pylms.storage.read_persons") + def test_persons_but_no_relationships( + self, mock_read_persons, mock_read_relationships, mock_list_persons, + ): + persons = [ + Person(3, "Bob"), + Person(1, "Seb", "King"), + Person(2, "Mario", "Bros"), ] - ) - assert mock_print.call_count == 3 + mock_read_persons.return_value = persons -@patch("builtins.print") -@patch("pylms.pylms._search_person") -def test_search_person_no_result(mock_search_person, mock_print): - mock_search_person.return_value = [] + list_persons() - search_person("p") + mock_read_persons.assert_called_once_with() + mock_read_relationships.assert_called_once_with(persons) + mock_list_persons.assert_called_once_with( + [(person, []) for person in persons] + ) - mock_search_person.assert_called_once_with("p") - assert mock_print.call_count == 0 +class Test_search_person: + @patch("builtins.print") + @patch("pylms.pylms._search_person") + def test_search_person_no_result(self, mock_search_person, mock_print): + mock_search_person.return_value = [] -@patch("pylms.pylms._print_person") -@patch("pylms.pylms._search_person") -def test_search_person_results(mock_search_person, mock_print_persons): - persons = [ - Person(3, "Bob"), - Person(1, "Seb"), - Person(2, "MarioEb"), - ] - mock_search_person.return_value = persons + search_person("p") - search_person("p") + mock_search_person.assert_called_once_with("p") + assert mock_print.call_count == 0 - mock_search_person.assert_called_once_with("p") - assert mock_print_persons.call_count == 3 - mock_print_persons.assert_has_calls([call(person) for person in persons]) + @patch("pylms.pylms.ios.show_person") + @patch("pylms.pylms._search_person") + def test_search_person_results(self, mock_search_person, mock_show_person): + persons = [ + Person(3, "Bob"), + Person(1, "Seb"), + Person(2, "MarioEb"), + ] + mock_search_person.return_value = persons + + search_person("p") + + mock_search_person.assert_called_once_with("p") + assert mock_show_person.call_count == 3 + mock_show_person.assert_has_calls([call(person) for person in persons]) class Test_update_person: @@ -165,7 +154,7 @@ def test_no_person_selected(self, mock_select, mock_storage): assert mock_storage.store_persons.call_count == 0 @patch("pylms.pylms.storage") - @patch("pylms.pylms._interactive_person_details") + @patch("pylms.pylms.ios.get_person_details") @patch("pylms.pylms._interactive_select_person") def test_single_person(self, mock_select, mock_person_details, mock_storage): person = Person(1, "foo", "bar") @@ -176,12 +165,10 @@ def test_single_person(self, mock_select, mock_person_details, mock_storage): update_person("p") mock_select.assert_called_once_with("p") - assert mock_storage.read_persons.call_count == 1 - assert mock_storage.store_persons.call_count == 1 mock_storage.store_persons.assert_called_once_with([Person(1, "donut", "acme")]) @patch("pylms.pylms.storage") - @patch("pylms.pylms._interactive_person_details") + @patch("pylms.pylms.ios.get_person_details") @patch("pylms.pylms._interactive_select_person") def test_out_of_several(self, mock_select, mock_person_details, mock_storage): person1 = Person(1, "foo", "bar") @@ -194,19 +181,15 @@ def test_out_of_several(self, mock_select, mock_person_details, mock_storage): update_person("p") mock_select.assert_called_once_with("p") - assert mock_storage.read_persons.call_count == 1 - assert mock_storage.store_persons.call_count == 1 mock_storage.store_persons.assert_called_once_with([person3, person1, Person(2, "donut", "acme")]) class Test_delete_person: - @patch("builtins.print") @patch("pylms.pylms.storage") - @patch("pylms.pylms._interactive_hit_enter") - @patch("pylms.pylms._print_person") + @patch("pylms.pylms.events.deleting_person") @patch("pylms.pylms._interactive_select_person") - def test_delete_person(self, mock_select, mock_print_person, mock_hit_enter, mock_storage, mock_print): + def test_delete_person(self, mock_select, mock_deleting_person, mock_storage): person1 = Person(1, "foo", "bar") person2 = Person(2, "foo", "bar") person3 = Person(3, "foo", "bar") @@ -216,34 +199,25 @@ def test_delete_person(self, mock_select, mock_print_person, mock_hit_enter, moc delete_person("p") mock_select.assert_called_once_with("p") - assert mock_select.call_count == 1 - mock_print.assert_has_calls([call("Hit ENTER to delete:"), call("CTRL+C to exit")]) - assert mock_print.call_count == 2 - mock_print_person.assert_called_once_with(person3) - assert mock_print_person.call_count == 1 - mock_hit_enter.assert_called_once_with() - assert mock_hit_enter.call_count == 1 + mock_deleting_person.assert_called_once_with(person3) mock_storage.read_persons.assert_called_once_with() - assert mock_storage.read_persons.call_count == 1 mock_storage.store_persons.assert_called_once_with([person2, person1]) - assert mock_storage.store_persons.call_count == 1 @patch("builtins.print") @patch("pylms.pylms.storage") - @patch("pylms.pylms._interactive_hit_enter") - @patch("pylms.pylms._print_person") + @patch("pylms.pylms.events.deleting_person") + @patch("pylms.pylms.ios.show_person") @patch("pylms.pylms._interactive_select_person") def test_no_person_selected_to_delete( - self, mock_select, mock_print_person, mock_hit_enter, mock_storage, mock_print + self, mock_select, mock_show_person, mock_creating_person, mock_storage, mock_print ): mock_select.return_value = None delete_person("p") mock_select.assert_called_once_with("p") - assert mock_select.call_count == 1 - assert mock_print_person.call_count == 0 - assert mock_hit_enter.call_count == 0 + assert mock_show_person.call_count == 0 + assert mock_creating_person.call_count == 0 assert mock_storage.call_count == 0 assert mock_print.call_count == 0 @@ -261,4 +235,3 @@ def test_no_relationship_definition_match(self, mock_parse_link_request): assert link_persons(request) is None mock_parse_link_request.assert_called_once_with(request) - assert mock_parse_link_request.call_count == 1 From 1752c43b3a8fa656b4433b9bf382082de8806e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lesaint?= Date: Sat, 27 Apr 2024 14:40:55 +0200 Subject: [PATCH 2/4] PYL-26 rename to _get_person_details and add update_person to IOs --- src/pylms/pylms.py | 32 +++++++----- tests/pylms/pylms_protected_test.py | 78 ++++++++++++++++++++++++++++- tests/pylms/pymls_test.py | 23 +++++---- 3 files changed, 109 insertions(+), 24 deletions(-) diff --git a/src/pylms/pylms.py b/src/pylms/pylms.py index a033cd1..9ea86c8 100644 --- a/src/pylms/pylms.py +++ b/src/pylms/pylms.py @@ -23,7 +23,7 @@ def select_person(self, persons: list[Person]) -> Person | None: pass @abstractmethod - def get_person_details(self) -> tuple[str, str | None]: + def update_person(self, person_to_update: Person) -> Person: pass @@ -56,7 +56,7 @@ def show_person(self, person: Person) -> None: def list_persons(self, resolved_persons: list[(Person, list[Relationship])]) -> None: if resolved_persons: for person, rs in sorted(resolved_persons, key=lambda t: t[0].person_id): - ios.show_person(person) + self.show_person(person) for r in rs: other = r.right if r.left == person else r.left print(f" -> {r.repr_for(person)} de ({other.person_id}) {other}") @@ -110,7 +110,21 @@ def select_person(self, persons: list[Person]) -> Person | None: # should not happen raise RuntimeError(f"id {person_id} does not exist in list of Persons") - def get_person_details(self) -> tuple[str, str | None]: + def update_person(self, person_to_update: Person) -> Person: + print("Input new first name and last name to update:") + self.show_person(person_to_update) + print("CTRL+C to exit") + + firstname: str + lastname: str + firstname, lastname = self._get_person_details() + + person_to_update.firstname = firstname + person_to_update.lastname = lastname + + return person_to_update + + def _get_person_details(self) -> tuple[str, str | None]: while True: text = self._input_or_exit_pylms() @@ -170,19 +184,13 @@ def update_person(pattern: str) -> None: if not person_to_update: return - print("Input new first name and last name to update:") - ios.show_person(person_to_update) - print("CTRL+C to exit") - - firstname: str - lastname: str - firstname, lastname = ios.get_person_details() + updated_person = ios.update_person(person_to_update) persons = storage.read_persons() for person in persons: if person.person_id == person_to_update.person_id: - person.firstname = firstname - person.lastname = lastname + person.firstname = updated_person.firstname + person.lastname = updated_person.lastname storage.store_persons(persons) diff --git a/tests/pylms/pylms_protected_test.py b/tests/pylms/pylms_protected_test.py index 21a8d3c..c0ae6d2 100644 --- a/tests/pylms/pylms_protected_test.py +++ b/tests/pylms/pylms_protected_test.py @@ -35,7 +35,7 @@ def test_no_persons(self, mock_print): assert mock_print.call_count == 1 @patch("builtins.print") - @patch("pylms.pylms.ios.show_person") + @patch.object(under_test, "show_person") def test_one_person_no_relationship(self, mock_show_person, mock_print): person = Person(3, "John", "Doe") @@ -46,7 +46,7 @@ def test_one_person_no_relationship(self, mock_show_person, mock_print): assert mock_print.call_count == 0 @patch("builtins.print") - @patch("pylms.pylms.ios.show_person") + @patch.object(under_test, "show_person") def test_persons_no_relationship(self, mock_show_person, mock_print): persons = [ Person(3, "Bob"), @@ -198,3 +198,77 @@ def test_select_the_right_definition_from_alias(self, alias, expected_definition @mark.parametrize("nl_request", ["aaa acme", "aaa", " aaa ", "fooaaa", "foo aaa"]) def test_return_none_if_missing_person_patterns(self, nl_request): assert _parse_nl_link_request(nl_request) is None + + +class Test_update_person: + @patch("builtins.print") + @patch.object(under_test, "_get_person_details") + @patch.object(under_test, "show_person") + def test_top_level(self, mock_show_person, mock_get_person_details, mock_print): + person = Person(1, "John", "Doe") + mock_get_person_details.return_value = ("foo", "bar") + + res = under_test.update_person(person) + + assert res.firstname == "foo" + assert res.lastname == "bar" + + mock_show_person.assert_called_once_with(person) + mock_get_person_details.assert_called_once_with() + mock_print.assert_has_calls( + [ + call("Input new first name and last name to update:"), + call("CTRL+C to exit"), + ] + ) + + @patch("builtins.print") + @patch("builtins.input") + @patch.object(under_test, "show_person") + def test_get_person_details_one_word(self, mock_show_person, mock_input, mock_print): + person = Person(1, "John", "Doe") + + mock_input.return_value = "foo" + + res = under_test.update_person(person) + + assert res.firstname == "foo" + assert res.lastname is None + mock_input.assert_called_once_with() + assert mock_print.call_count == 2 + + @patch("builtins.print") + @patch("builtins.input") + @patch.object(under_test, "show_person") + def test_get_person_details_two_words(self, mock_show_person, mock_input, mock_print): + person = Person(1, "John", "Doe") + + mock_input.return_value = "foo bar" + + res = under_test.update_person(person) + + assert res.firstname == "foo" + assert res.lastname == "bar" + mock_input.assert_called_once_with() + assert mock_print.call_count == 2 + + @patch("builtins.print") + @patch("builtins.input") + @patch.object(under_test, "show_person") + def test_get_person_three_words(self, mock_show_person, mock_input, mock_print): + person = Person(1, "John", "Doe") + + mock_input.side_effect = ["foo bar acme", "foo bar"] + + res = under_test.update_person(person) + + assert res.firstname == "foo" + assert res.lastname == "bar" + assert mock_input.call_count == 2 + mock_print.has_calls( + [ + call("Input new first name and last name to update:"), + call("CTRL+C to exit"), + call("Too many words."), + ] + ) diff --git a/tests/pylms/pymls_test.py b/tests/pylms/pymls_test.py index 8d8f844..6c7b58a 100644 --- a/tests/pylms/pymls_test.py +++ b/tests/pylms/pymls_test.py @@ -94,7 +94,10 @@ def test_empty_storage(self, mock_read_persons, mock_read_relationships, mock_li @patch("pylms.pylms.storage.read_relationships", return_value=[]) @patch("pylms.pylms.storage.read_persons") def test_persons_but_no_relationships( - self, mock_read_persons, mock_read_relationships, mock_list_persons, + self, + mock_read_persons, + mock_read_relationships, + mock_list_persons, ): persons = [ Person(3, "Bob"), @@ -108,9 +111,7 @@ def test_persons_but_no_relationships( mock_read_persons.assert_called_once_with() mock_read_relationships.assert_called_once_with(persons) - mock_list_persons.assert_called_once_with( - [(person, []) for person in persons] - ) + mock_list_persons.assert_called_once_with([(person, []) for person in persons]) class Test_search_person: @@ -154,33 +155,35 @@ def test_no_person_selected(self, mock_select, mock_storage): assert mock_storage.store_persons.call_count == 0 @patch("pylms.pylms.storage") - @patch("pylms.pylms.ios.get_person_details") + @patch("pylms.pylms.ios.update_person") @patch("pylms.pylms._interactive_select_person") - def test_single_person(self, mock_select, mock_person_details, mock_storage): + def test_single_person(self, mock_select, mock_update_person, mock_storage): person = Person(1, "foo", "bar") mock_select.return_value = person - mock_person_details.return_value = ("donut", "acme") + mock_update_person.return_value = Person(234, "donut", "acme") mock_storage.read_persons.return_value = [person] update_person("p") mock_select.assert_called_once_with("p") + mock_update_person.assert_called_once_with(person) mock_storage.store_persons.assert_called_once_with([Person(1, "donut", "acme")]) @patch("pylms.pylms.storage") - @patch("pylms.pylms.ios.get_person_details") + @patch("pylms.pylms.ios.update_person") @patch("pylms.pylms._interactive_select_person") - def test_out_of_several(self, mock_select, mock_person_details, mock_storage): + def test_out_of_several(self, mock_select, mock_update_person, mock_storage): person1 = Person(1, "foo", "bar") person2 = Person(2, "foo", "bar") person3 = Person(3, "foo", "bar") mock_select.return_value = person2 - mock_person_details.return_value = ("donut", "acme") + mock_update_person.return_value = Person(888, "donut", "acme") mock_storage.read_persons.return_value = [person3, person1, person2] update_person("p") mock_select.assert_called_once_with("p") + mock_update_person.assert_called_once_with(person2) mock_storage.store_persons.assert_called_once_with([person3, person1, Person(2, "donut", "acme")]) From 51e988613a18b9c9ec95804b3fbf0f54fc08ba3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lesaint?= Date: Sat, 27 Apr 2024 14:58:06 +0200 Subject: [PATCH 3/4] PYL-26 use logger and remove remaining print calls in pylms module --- src/pylms/__main__.py | 2 ++ src/pylms/pylms.py | 10 ++++++---- tests/pylms/pylms_protected_test.py | 3 +-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/pylms/__main__.py b/src/pylms/__main__.py index c123e49..c524b7d 100644 --- a/src/pylms/__main__.py +++ b/src/pylms/__main__.py @@ -1,10 +1,12 @@ #!/bin/env python3 +import logging from sys import argv from pylms.pylms import list_persons, store_person, update_person, delete_person, link_persons, ExitPyLMS def main() -> None: + logging.basicConfig(level=logging.INFO) try: _read_and_execute_commands() except ExitPyLMS: diff --git a/src/pylms/pylms.py b/src/pylms/pylms.py index 9ea86c8..628722a 100644 --- a/src/pylms/pylms.py +++ b/src/pylms/pylms.py @@ -3,6 +3,9 @@ from pylms.core import relationship_definitions, RelationshipDefinition, Relationship from pylms.core import resolve_persons from abc import abstractmethod, ABC +import logging + +logger = logging.getLogger(__name__) class ExitPyLMS(BaseException): @@ -170,7 +173,6 @@ def list_persons() -> None: def _interactive_select_person(pattern: str) -> Person | None: persons = _search_person(pattern) if not persons: - print("No match.") return if len(persons) == 1: @@ -279,7 +281,7 @@ def _parse_nl_link_request(natural_language_link_request: str) -> LinkRequest | person_patterns = list(filter(lambda s: len(s) > 0, map(str.strip, natural_language_link_request.split(alias)))) patterns_count = len(person_patterns) if patterns_count != 2: - print(f"Unsupported link request: wrong number of person patterns ({patterns_count})") + logger.error(f"Unsupported link request: wrong number of person patterns ({patterns_count})") return None return LinkRequest( @@ -299,9 +301,9 @@ def link_persons(natural_language_link_request: str) -> None: person_right = _interactive_select_person(link_request.right_person_pattern) if person_left is None: - print(f'No match for "{link_request.left_person_pattern}".') + logger.info(f'No match for "{link_request.left_person_pattern}".') if person_right is None: - print(f'No match for "{link_request.right_person_pattern}".') + logger.info(f'No match for "{link_request.right_person_pattern}".') if person_left is None or person_right is None: return diff --git a/tests/pylms/pylms_protected_test.py b/tests/pylms/pylms_protected_test.py index c0ae6d2..bfa6306 100644 --- a/tests/pylms/pylms_protected_test.py +++ b/tests/pylms/pylms_protected_test.py @@ -95,8 +95,7 @@ def test_no_match(self, mock_search_person, mock_print): assert res is None mock_search_person.assert_called_once_with("p") assert mock_search_person.call_count == 1 - mock_print.assert_called_once_with("No match.") - assert mock_print.call_count == 1 + assert mock_print.call_count == 0 @patch("builtins.print") @patch("pylms.pylms._search_person") From f3c0e3bf2787722c23955eced431bafe651ec9cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lesaint?= Date: Sat, 27 Apr 2024 15:22:07 +0200 Subject: [PATCH 4/4] PYL-26 finalize refactor and move cli related to module cli --- src/pylms/__main__.py | 5 + src/pylms/cli.py | 111 +++++++++++++++++++ src/pylms/pylms.py | 115 +------------------- tests/pylms/cli_test.py | 159 ++++++++++++++++++++++++++++ tests/pylms/pylms_protected_test.py | 158 +-------------------------- 5 files changed, 278 insertions(+), 270 deletions(-) create mode 100644 src/pylms/cli.py create mode 100644 tests/pylms/cli_test.py diff --git a/src/pylms/__main__.py b/src/pylms/__main__.py index c524b7d..9a748ee 100644 --- a/src/pylms/__main__.py +++ b/src/pylms/__main__.py @@ -2,10 +2,15 @@ import logging from sys import argv +import pylms.pylms from pylms.pylms import list_persons, store_person, update_person, delete_person, link_persons, ExitPyLMS +from pylms.cli import CLI def main() -> None: + _cli = CLI() + pylms.pylms.ios = _cli + pylms.pylms.events = _cli logging.basicConfig(level=logging.INFO) try: _read_and_execute_commands() diff --git a/src/pylms/cli.py b/src/pylms/cli.py new file mode 100644 index 0000000..5d6ccf2 --- /dev/null +++ b/src/pylms/cli.py @@ -0,0 +1,111 @@ +from pylms.pylms import IOs, EventListener, ExitPyLMS +from pylms.pylms import Person, Relationship, RelationshipDefinition + + +class CLI(IOs, EventListener): + def creating_person(self, person: Person) -> None: + print(f"Create Person {person}.") + + def show_person(self, person: Person) -> None: + created = person.created + print( + f"({person.person_id})", + person, + f"({created.year}-{created.month}-{created.day} {created.hour}-{created.minute}-{created.second})", + ) + + def list_persons(self, resolved_persons: list[(Person, list[Relationship])]) -> None: + if resolved_persons: + for person, rs in sorted(resolved_persons, key=lambda t: t[0].person_id): + self.show_person(person) + for r in rs: + other = r.right if r.left == person else r.left + print(f" -> {r.repr_for(person)} de ({other.person_id}) {other}") + else: + print("No Person registered yet.") + + def _interactive_hit_enter(self): + while True: + s = self._input_or_exit_pylms() + + if len(s) == 0: + return + + print("Just hit ENTER") + + def _input_or_exit_pylms(self): + try: + return input() + except KeyboardInterrupt: + raise ExitPyLMS() + + def _interactive_person_id(self, valid_ids: list[int]) -> int: + if not valid_ids: + raise ValueError("valid_ids can not be empty.") + + while True: + n = self._input_or_exit_pylms() + + try: + res = int(n) + if res not in valid_ids: + print("Not a valid id.") + continue + + return res + except ValueError: + print("Not an integer.") + + def select_person(self, persons: list[Person]) -> Person | None: + print("Input id of person to update:") + for person in sorted(persons, key=lambda p: p.person_id): + self.show_person(person) + print("CTRL+C to exit") + + person_id = self._interactive_person_id([person.person_id for person in persons]) + for person in persons: + if person.person_id == person_id: + return person + + # should not happen + raise RuntimeError(f"id {person_id} does not exist in list of Persons") + + def update_person(self, person_to_update: Person) -> Person: + print("Input new first name and last name to update:") + self.show_person(person_to_update) + print("CTRL+C to exit") + + firstname: str + lastname: str + firstname, lastname = self._get_person_details() + + person_to_update.firstname = firstname + person_to_update.lastname = lastname + + return person_to_update + + def _get_person_details(self) -> tuple[str, str | None]: + while True: + text = self._input_or_exit_pylms() + + words = text.split(" ") + if len(words) > 2: + print("Too many words.") + continue + + if len(words) == 1: + return words[0], None + return words[0], words[1] + + def deleting_person(self, person_to_delete: Person) -> None: + print("Hit ENTER to delete:") + self.show_person(person_to_delete) + print("CTRL+C to exit") + self._interactive_hit_enter() + + def creating_link(self, rl_definition: RelationshipDefinition, person_left: Person, person_right: Person) -> None: + print(f'Hit ENTER to link as "{rl_definition.name}":') + self.show_person(person_left) + self.show_person(person_right) + print("CTRL+C to exit") + self._interactive_hit_enter() diff --git a/src/pylms/pylms.py b/src/pylms/pylms.py index 628722a..20fd763 100644 --- a/src/pylms/pylms.py +++ b/src/pylms/pylms.py @@ -44,119 +44,8 @@ def creating_link(self, rl_definition: RelationshipDefinition, person_left: Pers pass -class CLI(IOs, EventListener): - def creating_person(self, person: Person) -> None: - print(f"Create Person {person}.") - - def show_person(self, person: Person) -> None: - created = person.created - print( - f"({person.person_id})", - person, - f"({created.year}-{created.month}-{created.day} {created.hour}-{created.minute}-{created.second})", - ) - - def list_persons(self, resolved_persons: list[(Person, list[Relationship])]) -> None: - if resolved_persons: - for person, rs in sorted(resolved_persons, key=lambda t: t[0].person_id): - self.show_person(person) - for r in rs: - other = r.right if r.left == person else r.left - print(f" -> {r.repr_for(person)} de ({other.person_id}) {other}") - else: - print("No Person registered yet.") - - def _interactive_hit_enter(self): - while True: - s = self._input_or_exit_pylms() - - if len(s) == 0: - return - - print("Just hit ENTER") - continue - - def _input_or_exit_pylms(self): - try: - return input() - except KeyboardInterrupt: - raise ExitPyLMS() - - def _interactive_person_id(self, valid_ids: list[int]) -> int: - if not valid_ids: - raise ValueError("valid_ids can not be empty.") - - while True: - n = self._input_or_exit_pylms() - - try: - res = int(n) - if res not in valid_ids: - print("Not a valid id.") - continue - - return res - except ValueError: - print("Not an integer.") - - def select_person(self, persons: list[Person]) -> Person | None: - print("Input id of person to update:") - for person in sorted(persons, key=lambda p: p.person_id): - self.show_person(person) - print("CTRL+C to exit") - - person_id = self._interactive_person_id([person.person_id for person in persons]) - for person in persons: - if person.person_id == person_id: - return person - - # should not happen - raise RuntimeError(f"id {person_id} does not exist in list of Persons") - - def update_person(self, person_to_update: Person) -> Person: - print("Input new first name and last name to update:") - self.show_person(person_to_update) - print("CTRL+C to exit") - - firstname: str - lastname: str - firstname, lastname = self._get_person_details() - - person_to_update.firstname = firstname - person_to_update.lastname = lastname - - return person_to_update - - def _get_person_details(self) -> tuple[str, str | None]: - while True: - text = self._input_or_exit_pylms() - - words = text.split(" ") - if len(words) > 2: - print("Too many words.") - continue - - if len(words) == 1: - return words[0], None - return words[0], words[1] - - def deleting_person(self, person_to_delete: Person) -> None: - print("Hit ENTER to delete:") - self.show_person(person_to_delete) - print("CTRL+C to exit") - self._interactive_hit_enter() - - def creating_link(self, rl_definition: RelationshipDefinition, person_left: Person, person_right: Person) -> None: - print(f'Hit ENTER to link as "{rl_definition.name}":') - self.show_person(person_left) - self.show_person(person_right) - print("CTRL+C to exit") - self._interactive_hit_enter() - - -_cli: CLI = CLI() -ios: IOs = _cli -events: EventListener = _cli +ios: IOs +events: EventListener def list_persons() -> None: diff --git a/tests/pylms/cli_test.py b/tests/pylms/cli_test.py new file mode 100644 index 0000000..908ed6c --- /dev/null +++ b/tests/pylms/cli_test.py @@ -0,0 +1,159 @@ +from pylms.cli import CLI +from pylms.pylms import Person +from pytest import raises, mark +from unittest.mock import patch, call + +under_test = CLI() + + +class Test_list_persons: + + @patch("builtins.print") + def test_no_persons(self, mock_print): + under_test.list_persons([]) + + mock_print.assert_called_with("No Person registered yet.") + assert mock_print.call_count == 1 + + @patch("builtins.print") + @patch.object(under_test, "show_person") + def test_one_person_no_relationship(self, mock_show_person, mock_print): + person = Person(3, "John", "Doe") + + under_test.list_persons([(person, [])]) + + assert mock_show_person.call_count == 1 + mock_show_person.assert_called_once_with(person) + assert mock_print.call_count == 0 + + @patch("builtins.print") + @patch.object(under_test, "show_person") + def test_persons_no_relationship(self, mock_show_person, mock_print): + persons = [ + Person(3, "Bob"), + Person(1, "Seb", "King"), + Person(2, "Mario", "Bros"), + ] + + under_test.list_persons([(person, []) for person in persons]) + + assert mock_show_person.call_count == 3 + mock_show_person.assert_has_calls( + [ + call(persons[1]), + call(persons[2]), + call(persons[0]), + ] + ) + assert mock_print.call_count == 0 + + +class Test_interactive_person_id: + + def test_sanity_check(self): + with raises(ValueError, match="valid_ids can not be empty."): + under_test._interactive_person_id([]) + + @patch("builtins.input") + @mark.parametrize("valid_ids", [[1], [2, 1], [23, 7, 1, 2]]) + def test_straight_correct_input(self, mock_input, valid_ids): + mock_input.return_value = "1" + + res = under_test._interactive_person_id([2, 1]) + + assert res == 1 + + +class Test_interactive_hit_enter: + + @patch("builtins.input") + def test_interactive_hit_enter_straight_correct_input(self, mock_input): + mock_input.return_value = "" + + under_test._interactive_hit_enter() + + mock_input.assert_called_once_with() + assert mock_input.call_count == 1 + + @patch("builtins.input") + def test_interactive_hit_enter_incorrect_inputs(self, mock_input): + mock_input.side_effect = ["q", "12", "ENTER", ""] + + under_test._interactive_hit_enter() + + mock_input.assert_has_calls([call(), call(), call(), call()]) + assert mock_input.call_count == 4 + + +class Test_update_person: + @patch("builtins.print") + @patch.object(under_test, "_get_person_details") + @patch.object(under_test, "show_person") + def test_top_level(self, mock_show_person, mock_get_person_details, mock_print): + person = Person(1, "John", "Doe") + mock_get_person_details.return_value = ("foo", "bar") + + res = under_test.update_person(person) + + assert res.firstname == "foo" + assert res.lastname == "bar" + + mock_show_person.assert_called_once_with(person) + mock_get_person_details.assert_called_once_with() + mock_print.assert_has_calls( + [ + call("Input new first name and last name to update:"), + call("CTRL+C to exit"), + ] + ) + + @patch("builtins.print") + @patch("builtins.input") + @patch.object(under_test, "show_person") + def test_get_person_details_one_word(self, mock_show_person, mock_input, mock_print): + person = Person(1, "John", "Doe") + + mock_input.return_value = "foo" + + res = under_test.update_person(person) + + assert res.firstname == "foo" + assert res.lastname is None + mock_input.assert_called_once_with() + assert mock_print.call_count == 2 + + @patch("builtins.print") + @patch("builtins.input") + @patch.object(under_test, "show_person") + def test_get_person_details_two_words(self, mock_show_person, mock_input, mock_print): + person = Person(1, "John", "Doe") + + mock_input.return_value = "foo bar" + + res = under_test.update_person(person) + + assert res.firstname == "foo" + assert res.lastname == "bar" + mock_input.assert_called_once_with() + assert mock_print.call_count == 2 + + @patch("builtins.print") + @patch("builtins.input") + @patch.object(under_test, "show_person") + def test_get_person_three_words(self, mock_show_person, mock_input, mock_print): + person = Person(1, "John", "Doe") + + mock_input.side_effect = ["foo bar acme", "foo bar"] + + res = under_test.update_person(person) + + assert res.firstname == "foo" + assert res.lastname == "bar" + assert mock_input.call_count == 2 + mock_print.has_calls( + [ + call("Input new first name and last name to update:"), + call("CTRL+C to exit"), + call("Too many words."), + ] + ) diff --git a/tests/pylms/pylms_protected_test.py b/tests/pylms/pylms_protected_test.py index bfa6306..6b75362 100644 --- a/tests/pylms/pylms_protected_test.py +++ b/tests/pylms/pylms_protected_test.py @@ -1,13 +1,10 @@ from pylms.core import Person from pylms.core import RelationshipDefinition -from pylms.pylms import CLI from pylms.pylms import _search_person, _interactive_select_person from pylms.pylms import _parse_nl_link_request -from pytest import raises, mark +from pytest import mark from unittest.mock import patch, call -under_test = CLI() - @patch("pylms.pylms.storage") def test_search_person(mock_storage): @@ -25,64 +22,6 @@ def test_search_person(mock_storage): assert _search_person("foo") == [] -class Test_list_persons: - - @patch("builtins.print") - def test_no_persons(self, mock_print): - under_test.list_persons([]) - - mock_print.assert_called_with("No Person registered yet.") - assert mock_print.call_count == 1 - - @patch("builtins.print") - @patch.object(under_test, "show_person") - def test_one_person_no_relationship(self, mock_show_person, mock_print): - person = Person(3, "John", "Doe") - - under_test.list_persons([(person, [])]) - - assert mock_show_person.call_count == 1 - mock_show_person.assert_called_once_with(person) - assert mock_print.call_count == 0 - - @patch("builtins.print") - @patch.object(under_test, "show_person") - def test_persons_no_relationship(self, mock_show_person, mock_print): - persons = [ - Person(3, "Bob"), - Person(1, "Seb", "King"), - Person(2, "Mario", "Bros"), - ] - - under_test.list_persons([(person, []) for person in persons]) - - assert mock_show_person.call_count == 3 - mock_show_person.assert_has_calls( - [ - call(persons[1]), - call(persons[2]), - call(persons[0]), - ] - ) - assert mock_print.call_count == 0 - - -class Test_interactive_person_id: - - def test_sanity_check(self): - with raises(ValueError, match="valid_ids can not be empty."): - under_test._interactive_person_id([]) - - @patch("builtins.input") - @mark.parametrize("valid_ids", [[1], [2, 1], [23, 7, 1, 2]]) - def test_straight_correct_input(self, mock_input, valid_ids): - mock_input.return_value = "1" - - res = under_test._interactive_person_id([2, 1]) - - assert res == 1 - - class Test_interactive_select_person: @patch("builtins.print") @@ -129,27 +68,6 @@ def test_several_matches(self, mock_search_person, mock_select_person): mock_select_person.assert_called_once_with(persons) -class Test_interactive_hit_enter: - - @patch("builtins.input") - def test_interactive_hit_enter_straight_correct_input(self, mock_input): - mock_input.return_value = "" - - under_test._interactive_hit_enter() - - mock_input.assert_called_once_with() - assert mock_input.call_count == 1 - - @patch("builtins.input") - def test_interactive_hit_enter_incorrect_inputs(self, mock_input): - mock_input.side_effect = ["q", "12", "ENTER", ""] - - under_test._interactive_hit_enter() - - mock_input.assert_has_calls([call(), call(), call(), call()]) - assert mock_input.call_count == 4 - - relationship_definition_1 = RelationshipDefinition( name="foo", aliases=["aaa", "bbb", "ccc"], person_left_repr="leftFoo", person_right_repr="rightFoo" ) @@ -197,77 +115,3 @@ def test_select_the_right_definition_from_alias(self, alias, expected_definition @mark.parametrize("nl_request", ["aaa acme", "aaa", " aaa ", "fooaaa", "foo aaa"]) def test_return_none_if_missing_person_patterns(self, nl_request): assert _parse_nl_link_request(nl_request) is None - - -class Test_update_person: - @patch("builtins.print") - @patch.object(under_test, "_get_person_details") - @patch.object(under_test, "show_person") - def test_top_level(self, mock_show_person, mock_get_person_details, mock_print): - person = Person(1, "John", "Doe") - mock_get_person_details.return_value = ("foo", "bar") - - res = under_test.update_person(person) - - assert res.firstname == "foo" - assert res.lastname == "bar" - - mock_show_person.assert_called_once_with(person) - mock_get_person_details.assert_called_once_with() - mock_print.assert_has_calls( - [ - call("Input new first name and last name to update:"), - call("CTRL+C to exit"), - ] - ) - - @patch("builtins.print") - @patch("builtins.input") - @patch.object(under_test, "show_person") - def test_get_person_details_one_word(self, mock_show_person, mock_input, mock_print): - person = Person(1, "John", "Doe") - - mock_input.return_value = "foo" - - res = under_test.update_person(person) - - assert res.firstname == "foo" - assert res.lastname is None - mock_input.assert_called_once_with() - assert mock_print.call_count == 2 - - @patch("builtins.print") - @patch("builtins.input") - @patch.object(under_test, "show_person") - def test_get_person_details_two_words(self, mock_show_person, mock_input, mock_print): - person = Person(1, "John", "Doe") - - mock_input.return_value = "foo bar" - - res = under_test.update_person(person) - - assert res.firstname == "foo" - assert res.lastname == "bar" - mock_input.assert_called_once_with() - assert mock_print.call_count == 2 - - @patch("builtins.print") - @patch("builtins.input") - @patch.object(under_test, "show_person") - def test_get_person_three_words(self, mock_show_person, mock_input, mock_print): - person = Person(1, "John", "Doe") - - mock_input.side_effect = ["foo bar acme", "foo bar"] - - res = under_test.update_person(person) - - assert res.firstname == "foo" - assert res.lastname == "bar" - assert mock_input.call_count == 2 - mock_print.has_calls( - [ - call("Input new first name and last name to update:"), - call("CTRL+C to exit"), - call("Too many words."), - ] - )