def test_tags_shows_open_tasks_without_tag( repo: Repository, config: Config, capsys: CaptureFixture[Any] ) -> None: """ Given: Two tasks with no tags, only one open When: Printing the tags report Then: Only the open task is shown in the report """ tasks = factories.TaskFactory.create_batch(2) tasks[1].close("done") for task in tasks: repo.add(task) repo.commit() expected_output = [ r".*", r" +Name +│ Open Tasks *", r".*", r" +None +│ +1", r".*", ] capsys.readouterr() out, err = run_report("tags", {"repo": repo}, capsys) # act assert report_prints_expected(out, expected_output, err)
def test_print_recurring_report(self, runner: CliRunner, repo_e2e: Repository) -> None: """Test that recurring returns the expected output.""" parent = RecurrentTaskFactory.create(description="D", priority=1, area="A") repo_e2e.add(parent) repo_e2e.commit() # ECE001: Expression is too complex. Life is tough expected_output = [ # noqa: ECE001 r".*", r" +ID +│ +Descr.* +│ +Recur +│ +RecurType +│ +Area +| +Pri +│ +Due.*", r".*", fr" +{parent.id_} +│ +{parent.description} +│ +{parent.recurrence} +│ +" fr"{parent.recurrence_type.value.title()} +│ +{parent.area} +│ +" fr"{parent.priority} +│ +{parent.due.year}.*", r".*", ] result = runner.invoke(cli, ["recurring"]) assert result.exit_code == 0 assert report_prints_expected(result.stdout, expected_output, result.stderr)
def test_areas_prints_only_counts_open_tasks( repo: Repository, config: Config, capsys: CaptureFixture[Any] ) -> None: """ Given: Three tasks with the same area, one open, one completed, and other deleted When: Printing the areas report Then: Only the open task is shown """ tasks = factories.TaskFactory.create_batch(3, area="Area 1") tasks[1].close("done") tasks[2].close("deleted") for task in tasks: repo.add(task) repo.commit() expected_output = [ r".*", r" +Name +│ Open Tasks *", r".*", r" +Area 1 +│ +1", r".*", ] capsys.readouterr() out, err = run_report("areas", {"repo": repo}, capsys) # act assert report_prints_expected(out, expected_output, err)
def task_(repo: Repository) -> Task: """Insert a Task in the FakeRepository.""" task = factories.TaskFactory.create(state="backlog") repo.add(task) repo.commit() return task
def test_areas_prints_only_areas_with_open_tasks( repo: Repository, config: Config, capsys: CaptureFixture[Any] ) -> None: """ Given: Three tasks with different areas, where only one is open When: Printing the areas report Then: Only the area with the open task is shown """ tasks = factories.TaskFactory.create_batch(3) tasks[1].close("done") tasks[2].close("deleted") for task in tasks: repo.add(task) repo.commit() expected_output = [ r".*", r" +Name +│ Open Tasks *", r".*", fr" +{tasks[0].area} +│ +1", r".*", ] capsys.readouterr() out, err = run_report("areas", {"repo": repo}, capsys) # act assert report_prints_expected(out, expected_output, err)
def test_print_frozen_report_can_specify_filter( self, runner: CliRunner, insert_frozen_parent_task_e2e: RecurrentTask, repo_e2e: Repository, ) -> None: """Test that frozen accepts a task filter.""" task = insert_frozen_parent_task_e2e task.description = "d" task.area = "special" task.priority = 1 repo_e2e.add(task) repo_e2e.commit() # ECE001: Expression is too complex. Life is tough expected_output = [ # noqa: ECE001 r".*", r" +ID +│ +Description +│ +Recur +│ +RecurType +│ +Area +| +Pri +│ +Due.*", r".*", fr" +{task.id_} +│ +{task.description} +│ +{task.recurrence} +│ +" fr"{task.recurrence_type.value.title()} +│ +{task.area} +│ +" fr"{task.priority} +│ +{task.due.year}.*", r".*", ] result = runner.invoke(cli, ["frozen", "area:special"]) assert result.exit_code == 0 assert report_prints_expected(result.stdout, expected_output, result.stderr)
def test_task_report_can_print_tags( repo: Repository, config: Config, capsys: CaptureFixture[Any] ) -> None: """Test that the open report prints the task tags.""" # Generate the tasks task = factories.TaskFactory.create( description="Description", tags=["tag1", "tag2"] ) repo.add(task) repo.commit() # Generate the output expected_output = [ r".*", r" +ID.*│ Tags.*", r".*", r".* +│ tag1, tag2", r".*", ] out, err = run_report( "print_task_report", {"repo": repo, "config": config, "report_name": "open"}, capsys, ) # act assert report_prints_expected(out, expected_output, err)
def test_modify_task_can_remove_tag_that_starts_with_p( self, runner: CliRunner, insert_tasks_e2e: List[Task], faker: Faker, repo_e2e: Repository, caplog: LogCaptureFixture, ) -> None: """ Given: A task with a tag that starts with a p When: removing the tag Then: the tag is removed It's necessary in case we start using the `--parent` flag as `-p`, in that case when using `pydo mod 0 -python`, it interprets that the parent flag is set and that the tag is ython. """ task = insert_tasks_e2e[0] task.tags = ["python"] repo_e2e.add(task) repo_e2e.commit() result = runner.invoke(cli, ["mod", str(task.id_), "-python"]) modified_task = repo_e2e.get(task.id_, [Task]) assert result.exit_code == 0 assert re.match(f"Modified task {task.id_}", caplog.records[0].msg) assert modified_task.tags == [] assert modified_task.description == task.description
def test_print_report_report_allows_sorting( self, runner: CliRunner, repo_e2e: Repository, faker: Faker, ) -> None: """ Given: Three tasks When: printing the open report sorting first by ascending priority, and then by descending id. Then: The tasks are printed in the desired order """ tasks = [ Task(id_=0, description="Last", priority=3), Task(id_=1, description="Middle", priority=3), Task(id_=2, description="First", priority=1), ] for task in tasks: repo_e2e.add(task) repo_e2e.commit() expected_output = [ r".*", r" +ID +│ +Description +│ +Pri.*", r".*", fr" +{tasks[2].id_} +│ +{tasks[2].description} +│ +{tasks[2].priority}.*", fr" +{tasks[1].id_} +│ +{tasks[1].description} +│ +{tasks[1].priority}.*", fr" +{tasks[0].id_} +│ +{tasks[0].description} +│ +{tasks[0].priority}.*", r".*", ] result = runner.invoke(cli, ["report", "open", "sort:+priority,-id_"]) assert result.exit_code == 0 assert report_prints_expected(result.stdout, expected_output, result.stderr)
def test_thaw_task_by_id( self, runner: CliRunner, insert_frozen_parent_task_e2e: RecurrentTask, repo_e2e: Repository, caplog: LogCaptureFixture, ) -> None: """ Given: A frozen recurrent task When: Thawed Then: The task is back active and it's next children is breed. """ parent_task = insert_frozen_parent_task_e2e now = datetime.now() result = runner.invoke(cli, ["thaw", str(parent_task.id_)]) parent_task = repo_e2e.get(parent_task.id_, [RecurrentTask]) child_task = repo_e2e.search({"parent_id": parent_task.id_}, [Task])[0] assert result.exit_code == 0 assert ( "pydo.services", logging.INFO, f"Thawed task {parent_task.id_}: {parent_task.description}, and created " f"it's next child task with id {child_task.id_}", ) in caplog.record_tuples assert parent_task.state == TaskState.BACKLOG assert parent_task.active is True assert child_task.state == TaskState.BACKLOG assert child_task.active is True assert child_task.due is not None assert child_task.due > now
def test_print_open_report( self, runner: CliRunner, repo_e2e: Repository, faker: Faker, ) -> None: """Test that open returns the expected output.""" task = Task(description="Description", due=faker.date_time(), priority=3) repo_e2e.add(task) repo_e2e.commit() expected_output = [ r".*", r" +ID +│ +Description +│ +Pri.*", r".*", fr" +{task.id_} +│ +{task.description} +│ +{task.priority}.*", r".*", ] result = runner.invoke(cli, ["open"]) assert result.exit_code == 0 assert report_prints_expected(result.stdout, expected_output, result.stderr)
def test_repo_add_entity_merges_with_stored_values_before_adding( self, database: Any, repo: Repository, repo_tester: RepositoryTester[Repository], ) -> None: """ Given: A repository with an entity When: Adding that entity with updated values Then: The entities are merged before they are commited. The Genre model has the `rating` attribute in the `skip_on_merge` configuration therefore even if the added entity has a different value, it's not propagated. """ entity = GenreFactory.build() repo_tester.insert_entity(database, entity) original_entity = entity.copy() entity.rating = 3 entity.name = "new name" repo.add(entity, merge=True) repo.commit() # act stored_entity = repo_tester.get_all(database, Genre)[0] assert stored_entity.rating == original_entity.rating assert stored_entity.name == "new name"
def add_task(repo: Repository, change: TaskChanges) -> Union[RecurrentTask, Task]: """Create a new task. If it's a RecurrentTask, it returns the parent. """ task: Optional[TaskType] = None if len(change.tags_to_add) > 0: change.task_attributes["tags"] = change.tags_to_add if change.task_attributes.get("recurrence_type", None) in [ "recurring", "repeating", ]: task = repo.add(RecurrentTask(**change.task_attributes)) child_task = repo.add(task.breed_children()) log.info(f"Added {task.recurrence_type} task {task.id_}:" f" {task.description}") log.info(f"Added first child task with id {child_task.id_}") else: task = repo.add(Task(**change.task_attributes)) log.info(f"Added task {task.id_}: {task.description}") repo.commit() return task
def insert_multiple_tasks(repo: Repository) -> List[Task]: """Insert three Tasks in the repository.""" tasks = sorted(factories.TaskFactory.create_batch(20, state="backlog")) [repo.add(task) for task in tasks] repo.commit() return tasks
def tasks_(repo: Repository) -> List[Task]: """Insert three Tasks in the FakeRepository.""" tasks = sorted(factories.TaskFactory.create_batch(3, state="backlog")) for task in tasks: repo.add(task) repo.commit() return tasks
def insert_frozen_parent_task_e2e(repo_e2e: Repository) -> RecurrentTask: """Insert a RecurrentTask in frozen state.""" parent_task = factories.RecurrentTaskFactory.create(state="backlog") parent_task.freeze() repo_e2e.add(parent_task) repo_e2e.commit() return parent_task
def test_repository_handles_connection_errors(self, repo: Repository) -> None: """ Given: A database url pointing to an inexistent file When: the repository is initialized with an inexistent directory Then: a ConnectionError is raised. This doesn't apply to FakeRepository as it doesn't create a database """ with pytest.raises(ConnectionError): repo.__class__(database_url="/inexistent_dir/database.db") # act
def test_repository_closes_connection(self, repo: Repository) -> None: """ Given: A configured repository When: calling the close method Then: the connection to the database is closed. """ repo.close() # act assert repo.is_closed
def test_repository_close_is_idempotent(self, repo: Repository) -> None: """ Given: A closed repository When: calling the close method Then: the connection to the database is closed and no exception is raised. """ repo.close() repo.close() # act assert repo.is_closed
def insert_frozen_parent_tasks_e2e( repo_e2e: Repository) -> List[RecurrentTask]: """Insert many RecurrentTask in frozen state.""" parent_tasks = factories.RecurrentTaskFactory.create_batch(3, state="backlog") for parent_task in parent_tasks: parent_task.freeze() repo_e2e.add(parent_task) repo_e2e.commit() return parent_tasks
def insert_tasks_e2e(repo_e2e: Repository) -> List[Task]: """Insert many tasks in the end to end repository.""" tasks = factories.TaskFactory.create_batch(3, priority=3, state="backlog") different_task = factories.TaskFactory.create(priority=2, state="backlog") tasks.append(different_task) for task in tasks: repo_e2e.add(task) repo_e2e.commit() return tasks
def test_repository_can_search_by_bool_property( self, repo: Repository, ) -> None: """Search should return the objects that have a bool property.""" expected_entity = BoolEntity(name="Name", active=True) repo.add(expected_entity) repo.commit() result = repo.search({"active": True}, BoolEntity) assert result == [expected_entity]
def test_print_tags_report(self, runner: CliRunner, repo_e2e: Repository, insert_tasks_e2e: List[Task]) -> None: """Test that tags returns the expected output.""" tasks = insert_tasks_e2e tasks[0].tags = ["tag1"] repo_e2e.add(tasks[0]) repo_e2e.commit() result = runner.invoke(cli, ["tags"]) assert result.exit_code == 0 assert re.search(r" +Name.*Open Tasks", result.output)
def test_repository_doesnt_allow_adding_non_entity_types( self, repo: Repository, merge: bool, ) -> None: """ Given: an empty repository. When: an object that is not an entity is added. Then: an error is returned. """ with pytest.raises(ValueError, match="Please add an entity or a list of entities"): repo.add(1, merge=merge) # type: ignore
def test_repository_can_retrieve_an_entity_if_no_model_defined( self, repo: Repository, inserted_entity: Entity, ) -> None: """Given an entity_id the repository returns the entity object.""" repo.models = [type(inserted_entity)] # type: ignore with pytest.warns(UserWarning, match="In 2022-06-10.*deprecated"): result: Entity = repo.get(inserted_entity.id_) assert result == inserted_entity assert result.id_ == inserted_entity.id_
def test_repository_raises_error_if_no_entity_found_by_get( self, repo: Repository, entity: Entity, ) -> None: """As the entity is not inserted into the repository, it shouldn't be found.""" with pytest.raises( EntityNotFoundError, match=( f"There are no entities of type {entity.model_name} in the " f"repository with id_ {entity.id_}."), ): repo.get(entity.id_, type(entity))
def test_repository_can_retrieve_an_entity_if_list_of_models_defined( self, repo: Repository, inserted_entity: Entity, ) -> None: """Given an entity_id the repository returns the entity object.""" entity_models: List[Type[Entity]] = [type(inserted_entity), OtherEntity] repo.models = entity_models # type: ignore with pytest.warns(UserWarning, match="In 2022-06-10.*deprecated"): result = repo.get(inserted_entity.id_, entity_models) assert result == inserted_entity assert result.id_ == inserted_entity.id_
def test_repository_can_search_by_property_specifying_a_list_of_types( self, repo: Repository, inserted_entities: List[Entity], ) -> None: """Search should return the objects that match the desired property.""" entity_types: List[Type[Entity]] = [type(inserted_entities[0]), OtherEntity] expected_entity = inserted_entities[1] repo.models = entity_types # type: ignore with pytest.warns(UserWarning, match="In 2022-06-10.*deprecated"): result = repo.search({"id_": expected_entity.id_}, entity_types) assert result == [expected_entity]
def test_repo_can_search_in_list_of_str_attribute(self, repo: Repository) -> None: """ Given: A repository with an entity that contains an attribute with a list of str When: search is called with a regexp that matches one of the list elements Then: the entity is returned """ expected_entity = ListEntityFactory.build() repo.add(expected_entity) repo.commit() regexp = rf"{expected_entity.elements[0][:-1]}." result = repo.search({"elements": regexp}, ListEntity) assert result == [expected_entity]
def insert_parent_tasks_e2e( repo_e2e: Repository, ) -> Tuple[List[RecurrentTask], List[Task]]: """Insert a RecurrentTask and it's children Task in the repository.""" parent_tasks = factories.RecurrentTaskFactory.create_batch(3, state="backlog") child_tasks = [ parent_task.breed_children() for parent_task in parent_tasks ] [repo_e2e.add(parent_task) for parent_task in parent_tasks] [repo_e2e.add(child_task) for child_task in child_tasks] repo_e2e.commit() return parent_tasks, child_tasks