From 8e433ee7d01447bfa8d10cf50f1dfdedcaeb999d Mon Sep 17 00:00:00 2001 From: David van Buiten Date: Tue, 13 Feb 2024 11:51:55 +0100 Subject: [PATCH] Added a User base model + BaseUserRepository + User actions (create, list, retrieve) + tests --- .../actions}/__init__.py | 0 .../{actions.py => actions/melding.py} | 0 meldingen_core/actions/user.py | 38 +++++ meldingen_core/models.py | 7 + meldingen_core/repositories.py | 6 +- tests/test_actions/__init__.py | 0 .../test_melding_actions.py} | 2 +- tests/test_actions/test_user_actions.py | 151 ++++++++++++++++++ 8 files changed, 202 insertions(+), 2 deletions(-) rename {tests/test_meldingen => meldingen_core/actions}/__init__.py (100%) rename meldingen_core/{actions.py => actions/melding.py} (100%) create mode 100644 meldingen_core/actions/user.py create mode 100644 tests/test_actions/__init__.py rename tests/{test_meldingen/test_actions.py => test_actions/test_melding_actions.py} (97%) create mode 100644 tests/test_actions/test_user_actions.py diff --git a/tests/test_meldingen/__init__.py b/meldingen_core/actions/__init__.py similarity index 100% rename from tests/test_meldingen/__init__.py rename to meldingen_core/actions/__init__.py diff --git a/meldingen_core/actions.py b/meldingen_core/actions/melding.py similarity index 100% rename from meldingen_core/actions.py rename to meldingen_core/actions/melding.py diff --git a/meldingen_core/actions/user.py b/meldingen_core/actions/user.py new file mode 100644 index 0000000..4dbd839 --- /dev/null +++ b/meldingen_core/actions/user.py @@ -0,0 +1,38 @@ +from meldingen_core.models import User +from meldingen_core.repositories import BaseUserRepository + + +class UserCreateAction: + """Action that stores a user.""" + + repository: BaseUserRepository + + def __init__(self, repository: BaseUserRepository): + self.repository = repository + + def __call__(self, user: User) -> None: + self.repository.add(user) + + +class UserListAction: + """Action that retrieves a list of users.""" + + repository: BaseUserRepository + + def __init__(self, repository: BaseUserRepository): + self.repository = repository + + def __call__(self, *, limit: int | None = None, offset: int | None = None) -> list[User]: + return self.repository.list(limit=limit, offset=offset) + + +class UserRetrieveAction: + """Action that retrieves a user.""" + + repository: BaseUserRepository + + def __init__(self, repository: BaseUserRepository): + self.repository = repository + + def __call__(self, pk: int) -> User | None: + return self.repository.retrieve(pk=pk) diff --git a/meldingen_core/models.py b/meldingen_core/models.py index 7ce5def..c19abbc 100644 --- a/meldingen_core/models.py +++ b/meldingen_core/models.py @@ -2,3 +2,10 @@ class Melding: """This is the base model for a 'melding'.""" text: str + + +class User: + """This is the base model for a 'user'.""" + + username: str + email: str diff --git a/meldingen_core/repositories.py b/meldingen_core/repositories.py index 01837d4..8c62be2 100644 --- a/meldingen_core/repositories.py +++ b/meldingen_core/repositories.py @@ -1,7 +1,7 @@ from abc import ABCMeta, abstractmethod from typing import Generic, TypeVar -from meldingen_core.models import Melding +from meldingen_core.models import Melding, User T = TypeVar("T") T_co = TypeVar("T_co", covariant=True) @@ -23,3 +23,7 @@ def retrieve(self, pk: int) -> T_co | None: class BaseMeldingRepository(BaseRepository[Melding, Melding], metaclass=ABCMeta): """Repository for Melding.""" + + +class BaseUserRepository(BaseRepository[User, User], metaclass=ABCMeta): + """Repository for User.""" diff --git a/tests/test_actions/__init__.py b/tests/test_actions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_meldingen/test_actions.py b/tests/test_actions/test_melding_actions.py similarity index 97% rename from tests/test_meldingen/test_actions.py rename to tests/test_actions/test_melding_actions.py index 3a4e498..b4eb3b1 100644 --- a/tests/test_meldingen/test_actions.py +++ b/tests/test_actions/test_melding_actions.py @@ -1,6 +1,6 @@ import pytest -from meldingen_core.actions import MeldingCreateAction, MeldingListAction, MeldingRetrieveAction +from meldingen_core.actions.melding import MeldingCreateAction, MeldingListAction, MeldingRetrieveAction from meldingen_core.models import Melding from meldingen_core.repositories import BaseMeldingRepository diff --git a/tests/test_actions/test_user_actions.py b/tests/test_actions/test_user_actions.py new file mode 100644 index 0000000..d80b4b9 --- /dev/null +++ b/tests/test_actions/test_user_actions.py @@ -0,0 +1,151 @@ +import pytest + +from meldingen_core.actions.user import UserCreateAction, UserListAction, UserRetrieveAction +from meldingen_core.models import User +from meldingen_core.repositories import BaseUserRepository + +# Fixtures + + +@pytest.fixture +def unpopulated_users_repository() -> BaseUserRepository: + class TestUserRepository(BaseUserRepository): + data: list[User] + + def __init__(self) -> None: + self.data = [] + + def add(self, user: User) -> None: + self.data.append(user) + + def list(self, *, limit: int | None = None, offset: int | None = None) -> list[User]: + if limit and offset: + return self.data[offset : offset + limit] + elif limit and not offset: + return self.data[:limit] + elif not limit and offset: + return self.data[offset:] + else: + return self.data + + def retrieve(self, pk: int) -> User | None: + for _user in self.data: + if _user.username == str(pk): + return _user + return None + + def find_by_email(self, email: str) -> User | None: + for _user in self.data: + if _user.email == email: + return _user + return None + + repository = TestUserRepository() + return repository + + +@pytest.fixture +def populated_users_repository( + unpopulated_users_repository: BaseUserRepository, +) -> BaseUserRepository: + for _pk in range(10): + user = User() + user.username = f"{_pk}" + user.email = f"user-{_pk}@example.com" + unpopulated_users_repository.add(user) + + return unpopulated_users_repository + + +@pytest.fixture +def users_create_action( + unpopulated_users_repository: BaseUserRepository, +) -> UserCreateAction: + return UserCreateAction(unpopulated_users_repository) + + +@pytest.fixture +def users_list_action( + populated_users_repository: BaseUserRepository, +) -> UserListAction: + return UserListAction(populated_users_repository) + + +@pytest.fixture +def users_retrieve_action( + populated_users_repository: BaseUserRepository, +) -> UserRetrieveAction: + return UserRetrieveAction(populated_users_repository) + + +# PyTest Classes + + +class TestUserCreateAction: + def test_add(self, users_create_action: UserCreateAction) -> None: + assert len(users_create_action.repository.list()) == 0 + + user = User() + user.username = "1" + + users_create_action(user) + + assert len(users_create_action.repository.list()) == 1 + assert users_create_action.repository.retrieve(pk=1) == user + + +class TestUserListAction: + def test_list_all(self, users_list_action: UserListAction) -> None: + users = users_list_action() + + assert len(users) == 10 + + @pytest.mark.parametrize( + "limit, expected_result", + [(1, 1), (5, 5), (10, 10), (20, 10)], + ) + def test_list_limit(self, users_list_action: UserListAction, limit: int, expected_result: int) -> None: + users = users_list_action(limit=limit) + + assert len(users) == expected_result + + @pytest.mark.parametrize("offset, expected_result", [(1, 9), (5, 5), (10, 0), (20, 0)]) + def test_list_offset( + self, + users_list_action: UserListAction, + offset: int, + expected_result: int, + ) -> None: + users = users_list_action(offset=offset) + + assert len(users) == expected_result + + @pytest.mark.parametrize( + "limit, offset, expected_result", + [(10, 0, 10), (5, 0, 5), (10, 10, 0), (20, 0, 10)], + ) + def test_list_limit_offset( + self, + users_list_action: UserListAction, + limit: int, + offset: int, + expected_result: int, + ) -> None: + users = users_list_action(limit=limit, offset=offset) + + assert len(users) == expected_result + + +class TestUserRetrieveAction: + @pytest.mark.parametrize("pk", [1, 2, 3, 4, 5]) + def test_retrieve_existing_users(self, users_retrieve_action: UserRetrieveAction, pk: int) -> None: + user = users_retrieve_action(pk=pk) + + assert user is not None + assert user.username == str(pk) + + @pytest.mark.parametrize("pk", [101, 102, 103, 104, 105]) + def test_retrieve_non_existing_users(self, users_retrieve_action: UserRetrieveAction, pk: int) -> None: + user = users_retrieve_action(pk=pk) + + assert user is None