Skip to content

Commit

Permalink
Added a User base model + BaseUserRepository + User actions (#6)
Browse files Browse the repository at this point in the history
* Added a User base model + BaseUserRepository + User actions (create, list, retrieve) + tests

* Added user delete action and repository action + tests

* Fixing Black issues

* Making code async

* running iSort
  • Loading branch information
vanbuiten authored Feb 14, 2024
1 parent 6094ec0 commit ae474a8
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 2 deletions.
File renamed without changes.
File renamed without changes.
52 changes: 52 additions & 0 deletions meldingen_core/actions/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from collections.abc import Collection

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

async def __call__(self, user: User) -> None:
await self.repository.add(user)


class UserListAction:
"""Action that retrieves a list of users."""

repository: BaseUserRepository

def __init__(self, repository: BaseUserRepository):
self.repository = repository

async def __call__(self, *, limit: int | None = None, offset: int | None = None) -> Collection[User]:
return await self.repository.list(limit=limit, offset=offset)


class UserRetrieveAction:
"""Action that retrieves a user."""

repository: BaseUserRepository

def __init__(self, repository: BaseUserRepository):
self.repository = repository

async def __call__(self, pk: int) -> User | None:
return await self.repository.retrieve(pk=pk)


class UserDeleteAction:
"""Action that deletes a user."""

repository: BaseUserRepository

def __init__(self, repository: BaseUserRepository):
self.repository = repository

async def __call__(self, pk: int) -> None:
await self.repository.delete(pk=pk)
7 changes: 7 additions & 0 deletions meldingen_core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 8 additions & 1 deletion meldingen_core/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from collections.abc import Collection
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)
Expand All @@ -21,6 +21,13 @@ async def list(self, *, limit: int | None = None, offset: int | None = None) ->
async def retrieve(self, pk: int) -> T_co | None:
...

async def delete(self, pk: int) -> None:
raise NotImplemented


class BaseMeldingRepository(BaseRepository[Melding, Melding], metaclass=ABCMeta):
"""Repository for Melding."""


class BaseUserRepository(BaseRepository[User, User], metaclass=ABCMeta):
"""Repository for User."""
Empty file added tests/test_actions/__init__.py
Empty file.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest
import pytest_asyncio

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

Expand Down
195 changes: 195 additions & 0 deletions tests/test_actions/test_user_actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import pytest
import pytest_asyncio

from meldingen_core.actions.user import UserCreateAction, UserDeleteAction, 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 = []

async def add(self, user: User) -> None:
self.data.append(user)

async 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

async def retrieve(self, pk: int) -> User | None:
for _user in self.data:
if _user.username == str(pk):
return _user
return None

async def find_by_email(self, email: str) -> User | None:
for _user in self.data:
if _user.email == email:
return _user
return None

async def delete(self, pk: int) -> None:
for _user in self.data[:]:
if _user.username == str(pk):
self.data.remove(_user)
return None

repository = TestUserRepository()
return repository


@pytest_asyncio.fixture
async 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"
await 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.fixture
def users_delete_action(
populated_users_repository: BaseUserRepository,
) -> UserDeleteAction:
return UserDeleteAction(populated_users_repository)


# PyTest Classes


class TestUserCreateAction:
@pytest.mark.asyncio
async def test_add(self, users_create_action: UserCreateAction) -> None:
assert len(await users_create_action.repository.list()) == 0

user = User()
user.username = "1"

await users_create_action(user)

assert len(await users_create_action.repository.list()) == 1
assert await users_create_action.repository.retrieve(pk=1) == user


class TestUserListAction:
@pytest.mark.asyncio
async def test_list_all(self, users_list_action: UserListAction) -> None:
users = await users_list_action()

assert len(users) == 10

@pytest.mark.parametrize(
"limit, expected_result",
[(1, 1), (5, 5), (10, 10), (20, 10)],
)
@pytest.mark.asyncio
async def test_list_limit(self, users_list_action: UserListAction, limit: int, expected_result: int) -> None:
users = await users_list_action(limit=limit)

assert len(users) == expected_result

@pytest.mark.parametrize("offset, expected_result", [(1, 9), (5, 5), (10, 0), (20, 0)])
@pytest.mark.asyncio
async def test_list_offset(
self,
users_list_action: UserListAction,
offset: int,
expected_result: int,
) -> None:
users = await 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)],
)
@pytest.mark.asyncio
async def test_list_limit_offset(
self,
users_list_action: UserListAction,
limit: int,
offset: int,
expected_result: int,
) -> None:
users = await users_list_action(limit=limit, offset=offset)

assert len(users) == expected_result


class TestUserRetrieveAction:
@pytest.mark.parametrize("pk", [1, 2, 3, 4, 5])
@pytest.mark.asyncio
async def test_retrieve_existing_users(self, users_retrieve_action: UserRetrieveAction, pk: int) -> None:
user = await users_retrieve_action(pk=pk)

assert user is not None
assert user.username == str(pk)

@pytest.mark.parametrize("pk", [101, 102, 103, 104, 105])
@pytest.mark.asyncio
async def test_retrieve_non_existing_users(self, users_retrieve_action: UserRetrieveAction, pk: int) -> None:
user = await users_retrieve_action(pk=pk)

assert user is None


class TestUserDeleteAction:
@pytest.mark.parametrize("pk", [1, 2, 3, 4, 5])
@pytest.mark.asyncio
async def test_delete_existing_user(self, users_delete_action: UserDeleteAction, pk: int) -> None:
assert await users_delete_action.repository.retrieve(pk=pk) is not None
assert len(await users_delete_action.repository.list()) == 10

await users_delete_action(pk=pk)

assert await users_delete_action.repository.retrieve(pk=pk) is None
assert len(await users_delete_action.repository.list()) == 9

@pytest.mark.parametrize("pk", [101, 102, 103, 104, 105])
@pytest.mark.asyncio
async def test_delete_non_existing_user(self, users_delete_action: UserDeleteAction, pk: int) -> None:
assert await users_delete_action.repository.retrieve(pk=pk) is None
assert len(await users_delete_action.repository.list()) == 10

await users_delete_action(pk=pk)

assert len(await users_delete_action.repository.list()) == 10

0 comments on commit ae474a8

Please sign in to comment.