Skip to content

Commit

Permalink
Add database interface and in-memory database infrastructure
Browse files Browse the repository at this point in the history
  • Loading branch information
dmb225 committed Oct 4, 2024
1 parent a412d73 commit 7e2b059
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 0 deletions.
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ develop = [
"ruff==0.6.8",
"pre-commit==3.8.0",
"pytest==8.3.3",
"pytest-asyncio==0.24.0",
"coverage==7.6.1",
]

Expand All @@ -25,6 +26,7 @@ strict = true

[tool.pytest.ini_options]
addopts = "-v --tb=short"
asyncio_default_fixture_loop_scope = "function"

[tool.ruff]
line-length = 100
Expand Down
Empty file.
32 changes: 32 additions & 0 deletions src/application/interfaces/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from abc import ABC, abstractmethod
from typing import Generic, Protocol, TypeVar, Optional
from uuid import UUID


class HasID(Protocol):
id: UUID # Enforce that any entity passed to the repository must have an `id` attribute


T = TypeVar("T", bound=HasID)


class Database(ABC, Generic[T]):
@abstractmethod
async def add(self, entity: T) -> None:
pass

@abstractmethod
async def get(self, id: UUID) -> Optional[T]:
pass

@abstractmethod
async def update(self, entity: T) -> None:
pass

@abstractmethod
async def delete(self, id: UUID) -> None:
pass

@abstractmethod
async def list_all(self) -> list[T]:
pass
Empty file added src/infrastructure/__init__.py
Empty file.
Empty file.
26 changes: 26 additions & 0 deletions src/infrastructure/databases/in_memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import Optional
from uuid import UUID

from application.interfaces.database import T, Database


class InMemoryDatabase(Database[T]):
def __init__(self) -> None:
self._data: dict[UUID, T] = {}

async def add(self, entity: T) -> None:
self._data[entity.id] = entity

async def get(self, id: UUID) -> Optional[T]:
return self._data.get(id)

async def update(self, entity: T) -> None:
if entity.id in self._data:
self._data[entity.id] = entity

async def delete(self, id: UUID) -> None:
if id in self._data:
del self._data[id]

async def list_all(self) -> list[T]:
return list(self._data.values())
42 changes: 42 additions & 0 deletions src/tests/unit/application/test_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from uuid import UUID

from application.entities.user import User


def test_user_creation() -> None:
user = User(
name="Alice Smith", email="[email protected]", password="securepassword", confirmed=False
)

assert user.name == "Alice Smith"
assert user.email == "[email protected]"
assert user.password == "securepassword"
assert user.confirmed is False
assert isinstance(user.id, UUID)


def test_user_default_id() -> None:
user1 = User(
name="Alice Smith", email="[email protected]", password="securepassword", confirmed=False
)
user2 = User(
name="Bob Brown", email="[email protected]", password="anothersecurepassword", confirmed=True
)

assert user1.id != user2.id


def test_user_email_format() -> None:
user = User(
name="Alice Smith", email="[email protected]", password="securepassword", confirmed=False
)

assert "@" in user.email
assert "." in user.email.split("@")[-1]


def test_user_confirmation() -> None:
user = User(
name="Alice Smith", email="[email protected]", password="securepassword", confirmed=True
)
assert user.confirmed is True
Empty file.
Empty file.
63 changes: 63 additions & 0 deletions src/tests/unit/infrastrucure/databases/test_in_memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import pytest

from application.entities.user import User
from infrastructure.databases.in_memory import InMemoryDatabase


@pytest.fixture
def in_memory_db() -> InMemoryDatabase[User]:
return InMemoryDatabase[User]()


@pytest.mark.asyncio
async def test_in_memory_database_operations(in_memory_db: InMemoryDatabase[User]) -> None:
# Test data
user1 = User(name="John Doe", email="[email protected]", password="password123", confirmed=True)
user2 = User(
name="Jane Smith", email="[email protected]", password="password456", confirmed=False
)

# 1. Add user1
await in_memory_db.add(user1)
fetched_user1 = await in_memory_db.get(user1.id)
assert fetched_user1 is not None
assert fetched_user1.name == user1.name
assert fetched_user1.email == user1.email

# 2. Update user1's name
user1.name = "John Doe Updated"
await in_memory_db.update(user1)
updated_user1 = await in_memory_db.get(user1.id)
assert updated_user1
assert updated_user1.name == "John Doe Updated"

# 3. Add user2
await in_memory_db.add(user2)
fetched_user2 = await in_memory_db.get(user2.id)
assert fetched_user2 is not None
assert fetched_user2.name == user2.name

# 4. List all users
all_users = await in_memory_db.list_all()
assert len(all_users) == 2
assert user1 in all_users
assert user2 in all_users

# 5. Delete user1
await in_memory_db.delete(user1.id)
deleted_user1 = await in_memory_db.get(user1.id)
assert deleted_user1 is None

# 6. Check remaining users
remaining_users = await in_memory_db.list_all()
assert len(remaining_users) == 1
assert user2 in remaining_users
assert user1 not in remaining_users

# 7. Delete user2
await in_memory_db.delete(user2.id)
deleted_user2 = await in_memory_db.get(user2.id)
assert deleted_user2 is None

# 8. Final check for empty database
assert await in_memory_db.list_all() == []

0 comments on commit 7e2b059

Please sign in to comment.