diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 91c4481..429ace6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - --ignore-init-module-imports - repo: https://github.com/pycqa/isort - rev: 5.10.1 + rev: 5.11.2 hooks: - id: isort name: isort (python) diff --git a/pyproject.toml b/pyproject.toml index d8f895f..eafb0cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,11 @@ build-backend = "poetry.core.masonry.api" [tool.poe.tasks] test = { shell = "DATABASE_URL=postgresql://postgres:password@localhost:5433/postgres pytest src" } +test_domain = "pytest -k domain" +test_infrastructure = "pytest -k infrastructure" +test_application = "pytest -k application" +test_unit = "pytest -m unit" +test_integration = "pytest -m 'not unit'" test_coverage = "pytest --cov=src --cov-report=html" start = "uvicorn src.api.main:app --reload" start_cli = { shell = "cd src && python -m cli" } diff --git a/pytest.ini b/pytest.ini index 85fe117..b4745ab 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,7 @@ [pytest] -norecursedirs = tmp \ No newline at end of file +norecursedirs = tmp + +markers = + unit: marks test as unit test i.e. not using any external services (deselect with '-m "not unit"') + integration: marks tests as integration i.e. using a database (deselect with '-m "not integration"') + serial \ No newline at end of file diff --git a/src/api/routers/catalog.py b/src/api/routers/catalog.py index 892cc00..8752bce 100644 --- a/src/api/routers/catalog.py +++ b/src/api/routers/catalog.py @@ -26,7 +26,7 @@ async def get_all_listings( query = GetAllListings() with module.unit_of_work(): query_result = module.execute_query(query) - return dict(data=query_result.result) + return dict(data=query_result.payload) @router.get("/catalog/{listing_id}", tags=["catalog"], response_model=ListingReadModel) @@ -41,7 +41,7 @@ async def get_listing_details( query = GetListingDetails(listing_id=listing_id) with module.unit_of_work(): query_result = module.execute_query(query) - return query_result.result + return query_result.payload @router.post( @@ -66,7 +66,7 @@ async def create_listing( query = GetListingDetails(listing_id=command_result.result) query_result = module.execute_query(query) - return query_result.result + return query_result.payload # diff --git a/src/api/tests/test_catalog.py b/src/api/tests/test_catalog.py index b797c84..0f76e65 100644 --- a/src/api/tests/test_catalog.py +++ b/src/api/tests/test_catalog.py @@ -1,13 +1,17 @@ +import pytest + from modules.catalog.application.command import CreateListingDraftCommand from seedwork.domain.value_objects import Money +@pytest.mark.integration def test_empty_catalog_list(api_client): response = api_client.get("/catalog") assert response.status_code == 200 assert response.json() == {"data": []} +@pytest.mark.integration def test_catalog_list_with_one_item(api, api_client): # arrange catalog_module = api.container.catalog_module() @@ -28,7 +32,7 @@ def test_catalog_list_with_one_item(api, api_client): assert response.json() == { "data": [ { - "id": str(command_result.result), + "id": str(command_result.entity_id), "title": "Foo", "description": "Bar", "ask_price_amount": 10.0, @@ -38,6 +42,7 @@ def test_catalog_list_with_one_item(api, api_client): } +@pytest.mark.integration def test_catalog_list_with_two_items(api, api_client): # arrange catalog_module = api.container.catalog_module() diff --git a/src/api/tests/test_common.py b/src/api/tests/test_common.py index fc84a1d..580da59 100644 --- a/src/api/tests/test_common.py +++ b/src/api/tests/test_common.py @@ -1,3 +1,4 @@ +import pytest from fastapi.testclient import TestClient from api.main import app @@ -5,11 +6,13 @@ client = TestClient(app) +@pytest.mark.integration def test_homepage_returns_200(): response = client.get("/") assert response.status_code == 200 +@pytest.mark.integration def test_docs_page_returns_200(): response = client.get("/docs") assert response.status_code == 200 diff --git a/src/modules/bidding/__init__.py b/src/modules/bidding/__init__.py index c7891c5..57bb52d 100644 --- a/src/modules/bidding/__init__.py +++ b/src/modules/bidding/__init__.py @@ -5,14 +5,6 @@ ) from seedwork.application.modules import BusinessModule -# -# @dataclass -# class UnitOfWork: -# module: Any # FIXME: type -# db_session: Session -# correlation_id: uuid.UUID -# listing_repository: ListingRepository - class BiddingModule(BusinessModule): supported_commands = (PlaceBidCommand, RetractBidCommand) diff --git a/src/modules/bidding/application/command/place_bid.py b/src/modules/bidding/application/command/place_bid.py index 4fbc077..350ede5 100644 --- a/src/modules/bidding/application/command/place_bid.py +++ b/src/modules/bidding/application/command/place_bid.py @@ -23,6 +23,6 @@ def place_bid( bid = Bid(bidder=bidder, price=Money(command.amount)) listing: Listing = listing_repository.get_by_id(id=command.listing_id) - events = listing.place_bid(bid) + listing.place_bid(bid) - return CommandResult.ok(events=events) + return CommandResult.ok() diff --git a/src/modules/bidding/tests/test_module.py b/src/modules/bidding/tests/domain/__init__.py similarity index 100% rename from src/modules/bidding/tests/test_module.py rename to src/modules/bidding/tests/domain/__init__.py diff --git a/src/modules/bidding/domain/test_listing.py b/src/modules/bidding/tests/domain/test_listing.py similarity index 96% rename from src/modules/bidding/domain/test_listing.py rename to src/modules/bidding/tests/domain/test_listing.py index 90d087f..79d504e 100644 --- a/src/modules/bidding/domain/test_listing.py +++ b/src/modules/bidding/tests/domain/test_listing.py @@ -8,6 +8,7 @@ from seedwork.domain.value_objects import UUID +@pytest.mark.unit def test_listing_initial_price(): seller = Seller(id=UUID.v4()) listing = Listing( @@ -19,6 +20,7 @@ def test_listing_initial_price(): assert listing.winning_bid is None +@pytest.mark.unit def test_place_one_bid(): now = datetime.utcnow() seller = Seller(id=UUID.v4()) @@ -34,6 +36,7 @@ def test_place_one_bid(): assert listing.winning_bid == Bid(Money(20), bidder=bidder, placed_at=now) +@pytest.mark.unit def test_place_two_bids(): now = datetime.utcnow() seller = Seller(id=UUID.v4()) @@ -50,6 +53,7 @@ def test_place_two_bids(): assert listing.winning_bid == Bid(Money(30), bidder=bidder2, placed_at=now) +@pytest.mark.unit def test_place_two_bids_by_same_bidder(): now = datetime.utcnow() seller = Seller(id=UUID.v4()) @@ -67,6 +71,7 @@ def test_place_two_bids_by_same_bidder(): assert listing.winning_bid == Bid(price=Money(30), bidder=bidder, placed_at=now) +@pytest.mark.unit def test_cannot_place_bid_if_listing_ended(): seller = Seller(id=UUID.v4()) bidder = Bidder(id=UUID.v4()) @@ -88,6 +93,7 @@ def test_cannot_place_bid_if_listing_ended(): listing.place_bid(bid) +@pytest.mark.unit def test_retract_bid(): seller = Seller(id=UUID.v4()) bidder = Bidder(id=UUID.v4()) @@ -107,6 +113,7 @@ def test_retract_bid(): listing.retract_bid_of(bidder=bidder) +@pytest.mark.unit def test_cancel_listing(): now = datetime.utcnow() seller = Seller(id=UUID.v4()) @@ -122,6 +129,7 @@ def test_cancel_listing(): assert listing.time_left_in_listing == timedelta() +@pytest.mark.unit def test_can_cancel_listing_with_bids(): now = datetime.utcnow() seller = Seller(id=UUID.v4()) @@ -144,6 +152,7 @@ def test_can_cancel_listing_with_bids(): assert listing.time_left_in_listing == timedelta() +@pytest.mark.unit def test_cannot_cancel_listing_with_bids(): now = datetime.utcnow() seller = Seller(id=UUID.v4()) diff --git a/src/modules/bidding/tests/infrastructure/test_listing_repository.py b/src/modules/bidding/tests/infrastructure/test_listing_repository.py index d14430d..62866ec 100644 --- a/src/modules/bidding/tests/infrastructure/test_listing_repository.py +++ b/src/modules/bidding/tests/infrastructure/test_listing_repository.py @@ -1,6 +1,8 @@ import datetime import uuid +import pytest + from modules.bidding.domain.entities import Bid, Bidder, Listing, Money, Seller from modules.bidding.infrastructure.listing_repository import ( ListingDataMapper, @@ -10,11 +12,13 @@ from seedwork.domain.value_objects import UUID +@pytest.mark.integration def test_listing_repo_is_empty(db_session): repo = PostgresJsonListingRepository(db_session=db_session) assert repo.count() == 0 +@pytest.mark.unit def test_listing_data_mapper_maps_entity_to_model(): listing = Listing( id=UUID("00000000000000000000000000000001"), @@ -58,6 +62,7 @@ def test_listing_data_mapper_maps_entity_to_model(): assert actual.data == expected.data +@pytest.mark.unit def test_listing_data_mapper_maps_model_to_entity(): instance = ListingModel( id=UUID("00000000000000000000000000000001"), @@ -83,6 +88,7 @@ def test_listing_data_mapper_maps_model_to_entity(): assert actual == expected +@pytest.mark.integration def test_listing_persistence(db_session): original = Listing( id=Listing.next_id(), diff --git a/src/modules/catalog/application/command/create_listing_draft.py b/src/modules/catalog/application/command/create_listing_draft.py index 0de4b4c..0ea9a14 100644 --- a/src/modules/catalog/application/command/create_listing_draft.py +++ b/src/modules/catalog/application/command/create_listing_draft.py @@ -31,6 +31,6 @@ def create_listing_draft( seller_id=command.seller_id, ) repository.add(listing) - return CommandResult.ok( - result=listing.id, events=[ListingDraftCreatedEvent(listing_id=listing.id)] + return CommandResult.success( + entity_id=listing.id, events=[ListingDraftCreatedEvent(listing_id=listing.id)] ) diff --git a/src/modules/catalog/application/command/publish_listing.py b/src/modules/catalog/application/command/publish_listing.py index 7b6e425..b12ddc4 100644 --- a/src/modules/catalog/application/command/publish_listing.py +++ b/src/modules/catalog/application/command/publish_listing.py @@ -27,4 +27,4 @@ def publish_listing( events = seller.publish_listing(listing) - return CommandResult.ok(events=events) + return CommandResult.success(entity_id=listing.id, events=events) diff --git a/src/modules/catalog/application/command/update_listing_draft.py b/src/modules/catalog/application/command/update_listing_draft.py index cd2071d..c16de09 100644 --- a/src/modules/catalog/application/command/update_listing_draft.py +++ b/src/modules/catalog/application/command/update_listing_draft.py @@ -29,4 +29,4 @@ def update_listing_draft( description=command.description, ask_price=command.ask_price, ) - return CommandResult.ok(events=events) + return CommandResult.success(entity_id=listing.id, events=events) diff --git a/src/modules/catalog/application/query/get_all_listings.py b/src/modules/catalog/application/query/get_all_listings.py index 13dcf86..652e2b7 100644 --- a/src/modules/catalog/application/query/get_all_listings.py +++ b/src/modules/catalog/application/query/get_all_listings.py @@ -19,6 +19,6 @@ def get_all_listings( session: Session, ) -> QueryResult: queryset = session.query(ListingModel) - result = [map_listing_model_to_dao(row) for row in queryset.all()] + payload = [map_listing_model_to_dao(row) for row in queryset.all()] # TODO: add error handling - return QueryResult.ok(result) + return QueryResult.success(payload) diff --git a/src/modules/catalog/tests/application/test_create_listing_draft.py b/src/modules/catalog/tests/application/test_create_listing_draft.py new file mode 100644 index 0000000..1c46285 --- /dev/null +++ b/src/modules/catalog/tests/application/test_create_listing_draft.py @@ -0,0 +1,25 @@ +import pytest + +from modules.catalog.application.command.create_listing_draft import ( + CreateListingDraftCommand, + create_listing_draft, +) +from modules.catalog.domain.entities import Seller +from seedwork.domain.value_objects import Money +from seedwork.infrastructure.repository import InMemoryRepository + + +@pytest.mark.unit +def test_create_listing_draft(): + # arrange + command = CreateListingDraftCommand( + title="foo", description="bar", ask_price=Money(1), seller_id=Seller.next_id() + ) + repository = InMemoryRepository() + + # act + result = create_listing_draft(command, repository) + + # assert + assert repository.get_by_id(result.entity_id).title == "foo" + assert result.has_errors() is False diff --git a/src/modules/catalog/tests/application/test_command_handlers.py b/src/modules/catalog/tests/application/test_publish_listing.py similarity index 57% rename from src/modules/catalog/tests/application/test_command_handlers.py rename to src/modules/catalog/tests/application/test_publish_listing.py index a1e9b94..7540c1d 100644 --- a/src/modules/catalog/tests/application/test_command_handlers.py +++ b/src/modules/catalog/tests/application/test_publish_listing.py @@ -1,63 +1,16 @@ -from modules.catalog.application.command.create_listing_draft import ( - CreateListingDraftCommand, - create_listing_draft, -) +import pytest + from modules.catalog.application.command.publish_listing import ( PublishListingCommand, publish_listing, ) -from modules.catalog.application.command.update_listing_draft import ( - UpdateListingDraftCommand, - update_listing_draft, -) from modules.catalog.domain.entities import Listing, Seller from modules.catalog.domain.value_objects import ListingStatus -from seedwork.domain.value_objects import UUID, Money +from seedwork.domain.value_objects import Money from seedwork.infrastructure.repository import InMemoryRepository -def test_create_listing_draft(): - # arrange - command = CreateListingDraftCommand( - title="foo", description="bar", ask_price=Money(1), seller_id=Seller.next_id() - ) - repository = InMemoryRepository() - - # act - result = create_listing_draft(command, repository) - - # assert - assert repository.get_by_id(result.result).title == "foo" - assert result.has_errors() is False - - -def test_update_listing_draft(): - # arrange - repository = InMemoryRepository() - listing = Listing( - id=Listing.next_id(), - title="Tiny dragon", - description="Tiny dragon for sale", - ask_price=Money(1), - seller_id=UUID.v4(), - ) - repository.add(listing) - - command = UpdateListingDraftCommand( - listing_id=listing.id, - title="Tiny golden dragon", - description=listing.description, - ask_price=listing.ask_price, - modify_user_id=listing.seller_id, - ) - - # act - result = update_listing_draft(command, repository) - - # assert - assert result.is_ok() - - +@pytest.mark.unit def test_publish_listing(): # arrange seller_repository = InMemoryRepository() @@ -87,10 +40,11 @@ def test_publish_listing(): ) # assert - assert result.is_ok() + assert result.is_success() assert listing.status == ListingStatus.PUBLISHED +@pytest.mark.unit def test_publish_listing_and_break_business_rule(): # arrange seller_repository = InMemoryRepository() diff --git a/src/modules/catalog/tests/application/test_update_listing_draft.py b/src/modules/catalog/tests/application/test_update_listing_draft.py new file mode 100644 index 0000000..2f24d06 --- /dev/null +++ b/src/modules/catalog/tests/application/test_update_listing_draft.py @@ -0,0 +1,37 @@ +import pytest + +from modules.catalog.application.command.update_listing_draft import ( + UpdateListingDraftCommand, + update_listing_draft, +) +from modules.catalog.domain.entities import Listing +from seedwork.domain.value_objects import UUID, Money +from seedwork.infrastructure.repository import InMemoryRepository + + +@pytest.mark.unit +def test_update_listing_draft(): + # arrange + repository = InMemoryRepository() + listing = Listing( + id=Listing.next_id(), + title="Tiny dragon", + description="Tiny dragon for sale", + ask_price=Money(1), + seller_id=UUID.v4(), + ) + repository.add(listing) + + command = UpdateListingDraftCommand( + listing_id=listing.id, + title="Tiny golden dragon", + description=listing.description, + ask_price=listing.ask_price, + modify_user_id=listing.seller_id, + ) + + # act + result = update_listing_draft(command, repository) + + # assert + assert result.is_success() diff --git a/src/modules/catalog/tests/domain/test_entities.py b/src/modules/catalog/tests/domain/test_entities.py index 93b653f..9ff52c8 100644 --- a/src/modules/catalog/tests/domain/test_entities.py +++ b/src/modules/catalog/tests/domain/test_entities.py @@ -6,6 +6,7 @@ from seedwork.domain.value_objects import Money +@pytest.mark.unit def test_seller_publishes_listing_happy_path(): seller = Seller(id=Seller.next_id()) listing = Listing( @@ -21,6 +22,7 @@ def test_seller_publishes_listing_happy_path(): assert listing.status == ListingStatus.PUBLISHED +@pytest.mark.unit def test_seller_fails_to_publish_listing_with_zero_price(): seller = Seller(id=Seller.next_id()) listing = Listing( diff --git a/src/modules/catalog/tests/domain/test_rules.py b/src/modules/catalog/tests/domain/test_rules.py index dfcdcaf..9ad5e8f 100644 --- a/src/modules/catalog/tests/domain/test_rules.py +++ b/src/modules/catalog/tests/domain/test_rules.py @@ -1,12 +1,16 @@ +import pytest + from modules.catalog.domain.rules import ListingAskPriceMustBeGreaterThanZero from seedwork.domain.value_objects import Money +@pytest.mark.unit def test_AuctionItemPriceMustBeGreaterThanZero_rule(): rule = ListingAskPriceMustBeGreaterThanZero(ask_price=Money(1)) assert not rule.is_broken() +@pytest.mark.unit def test_AuctionItemPriceMustBeGreaterThanZero_rule_is_broken(): rule = ListingAskPriceMustBeGreaterThanZero(ask_price=Money(0)) assert rule.is_broken() diff --git a/src/modules/catalog/tests/infrastructure/test_listing_repository.py b/src/modules/catalog/tests/infrastructure/test_listing_repository.py index 291d976..21f1062 100644 --- a/src/modules/catalog/tests/infrastructure/test_listing_repository.py +++ b/src/modules/catalog/tests/infrastructure/test_listing_repository.py @@ -1,3 +1,5 @@ +import pytest + from modules.catalog.domain.entities import Listing, Money from modules.catalog.infrastructure.listing_repository import ( ListingDataMapper, @@ -9,11 +11,13 @@ # engine = sqlalchemy.create_engine("") +@pytest.mark.integration def test_listing_repo_is_empty(db_session): repo = PostgresJsonListingRepository(db_session=db_session) assert repo.count() == 0 +@pytest.mark.unit def test_listing_data_mapper_maps_entity_to_model(): listing = Listing( id=UUID("00000000000000000000000000000001"), @@ -43,6 +47,7 @@ def test_listing_data_mapper_maps_entity_to_model(): assert actual.data == expected.data +@pytest.mark.unit def test_listing_data_mapper_maps_model_to_entity(): instance = ListingModel( id=UUID("00000000000000000000000000000001"), @@ -71,6 +76,7 @@ def test_listing_data_mapper_maps_model_to_entity(): assert actual == expected +@pytest.mark.integration def test_listing_persistence(db_session): original = Listing( id=Listing.next_id(), diff --git a/src/seedwork/application/command_handlers.py b/src/seedwork/application/command_handlers.py index c1915ea..7591e2e 100644 --- a/src/seedwork/application/command_handlers.py +++ b/src/seedwork/application/command_handlers.py @@ -1,74 +1,39 @@ import sys +from dataclasses import dataclass, field from typing import Any -from pydantic import BaseModel - from seedwork.domain.type_hints import DomainEvent +from seedwork.domain.value_objects import UUID -class CommandResult(BaseModel): - result: Any = None - events: list[DomainEvent] = [] - errors: list[Any] = [] - - # # commands - # def add_error(self, message, exception=None, exception_info=None): - # self.__errors.append((message, exception, exception_info)) - # return self - - # # queries - # @property - # def result(self) -> Any: - # """Shortcut to get_result()""" - # return self.get_result() - # - # def get_result(self) -> Any: - # """Gets result""" - # assert ( - # not self.has_errors() - # ), f"Cannot access result. QueryResult has errors.\n Errors: {self.__errors}" - # return self.__result - # - # @property - # def events(self) -> List[DomainEvent]: - # """Shortcut to get_events()""" - # return self.get_events() - # - # def get_events(self) -> List[DomainEvent]: - # """Gets result""" - # assert ( - # not self.has_errors() - # ), f"Cannot access events. QueryResult has errors.\n Errors: {self.__errors}" - # return self.__events - # - # def get_errors(self): - # return self.__errors +@dataclass +class CommandResult: + entity_id: UUID = None + payload: Any = None + events: list[DomainEvent] = field(default_factory=list) + errors: list[Any] = field(default_factory=list) def has_errors(self): return len(self.errors) > 0 - def is_ok(self) -> bool: + def is_success(self) -> bool: return not self.has_errors() @classmethod - def ok(cls, result=None, events=[]) -> "CommandResult": - """Creates a successful result""" - return cls(result=result, events=events) - - @classmethod - def failed(cls, message="Failure", exception=None) -> "CommandResult": + def failure(cls, message="Failure", exception=None) -> "CommandResult": """Creates a failed result""" exception_info = sys.exc_info() errors = [(message, exception, exception_info)] result = cls(errors=errors) return result - -class QueryHandler: - """ - Base query handler class - """ + @classmethod + def success(cls, entity_id=None, payload=None, events=[]) -> "CommandResult": + """Creates a successful result""" + return cls(entity_id=entity_id, payload=payload, events=events) class CommandHandler: - pass + """ + Base class for command handlers + """ diff --git a/src/seedwork/application/decorators.py b/src/seedwork/application/decorators.py index 17afd80..c36cd6e 100644 --- a/src/seedwork/application/decorators.py +++ b/src/seedwork/application/decorators.py @@ -75,9 +75,9 @@ def decorator(*args, **kwargs): ) return fn(*args, **kwargs) except ValidationError as e: - return CommandResult.failed("Validation error", exception=e) + return CommandResult.failure("Validation error", exception=e) except BusinessRuleValidationException as e: - return CommandResult.failed("Business rule validation error", exception=e) + return CommandResult.failure("Business rule validation error", exception=e) handler_signature = signature(fn) kwargs_iterator = iter(handler_signature.parameters.items()) diff --git a/src/seedwork/application/query_handlers.py b/src/seedwork/application/query_handlers.py index 2376c04..b27cd49 100644 --- a/src/seedwork/application/query_handlers.py +++ b/src/seedwork/application/query_handlers.py @@ -1,46 +1,34 @@ +import sys +from dataclasses import dataclass, field +from typing import Any + + +@dataclass class QueryResult: - def __init__(self, result) -> None: - self.__result = result - self.__errors = [] - - # commands - def add_error(self, message, exception=None): - self.__errors.append((message, exception)) - return self - - # queries - @property - def result(self): - """Shortcut to get_result()""" - return self.get_result() - - def get_result(self): - """Gets result""" - assert ( - not self.has_errors() - ), f"Cannot access result. QueryResult has errors.\n Errors: {self.__errors}" - return self.__result + payload: Any = None + errors: list[Any] = field(default_factory=list) def has_errors(self): - return len(self.__errors) > 0 + return len(self.errors) > 0 - def is_ok(self): + def is_success(self) -> bool: return not self.has_errors() @classmethod - def ok(cls, result): - """Creates a successful result""" - return cls(result=result) - - @classmethod - def failed(cls, message="Failure", exception=None): + def failure(cls, message="Failure", exception=None) -> "QueryResult": """Creates a failed result""" - result = cls() - result.add_error(message, exception) + exception_info = sys.exc_info() + errors = [(message, exception, exception_info)] + result = cls(errors=errors) return result + @classmethod + def success(cls, payload=None) -> "QueryResult": + """Creates a successful result""" + return cls(payload=payload) + class QueryHandler: """ - Base query handler class + Base class for query handlers """ diff --git a/src/seedwork/tests/application/test_decorators.py b/src/seedwork/tests/application/test_decorators.py index 65d0f09..d6dfe3f 100644 --- a/src/seedwork/tests/application/test_decorators.py +++ b/src/seedwork/tests/application/test_decorators.py @@ -1,10 +1,13 @@ from dataclasses import dataclass +import pytest + from seedwork.application.commands import Command from seedwork.application.decorators import command_handler, query_handler, registry from seedwork.application.queries import Query +@pytest.mark.unit def test_command_handler_decorator_registers_command_handler(): registry.clear() @@ -20,6 +23,7 @@ def foo_command_handler(command: FooCommand): assert registry.get_command_handler_parameters_for(FooCommand) == {} +@pytest.mark.unit def test_command_handler_decorator_does_not_register_command_handler_if_type_mismatch(): registry.clear() @@ -38,6 +42,7 @@ def foo_command_handler(command: BarCommand): assert FooCommand not in registry.command_handlers +@pytest.mark.unit def test_query_handler_decorator_registers_query_handler(): registry.clear() diff --git a/src/seedwork/tests/application/test_module.py b/src/seedwork/tests/application/test_module.py index e69de29..022fc16 100644 --- a/src/seedwork/tests/application/test_module.py +++ b/src/seedwork/tests/application/test_module.py @@ -0,0 +1 @@ +# TODO: add business module tests diff --git a/src/seedwork/tests/domain/test_entity.py b/src/seedwork/tests/domain/test_entity.py index b1c498f..17daecb 100644 --- a/src/seedwork/tests/domain/test_entity.py +++ b/src/seedwork/tests/domain/test_entity.py @@ -1,5 +1,7 @@ from dataclasses import dataclass +import pytest + from seedwork.domain.entities import AggregateRoot, Entity @@ -13,11 +15,13 @@ class PersonAggregate(AggregateRoot): name: str +@pytest.mark.unit def test_entity(): bob = PersonEntity(id=PersonEntity.next_id(), name="Bob") assert bob.id is not None +@pytest.mark.unit def test_aggregate(): bob = PersonAggregate(id=PersonEntity.next_id(), name="Bob") assert bob.id is not None diff --git a/src/seedwork/domain/test_value_objects.py b/src/seedwork/tests/domain/test_value_objects.py similarity index 79% rename from src/seedwork/domain/test_value_objects.py rename to src/seedwork/tests/domain/test_value_objects.py index be9e0a3..7ffb5ec 100644 --- a/src/seedwork/domain/test_value_objects.py +++ b/src/seedwork/tests/domain/test_value_objects.py @@ -1,9 +1,13 @@ +import pytest + from seedwork.domain.value_objects import Money +@pytest.mark.unit def test_money_equality(): assert Money(10, "USD") == Money(10, "USD") +@pytest.mark.unit def test_money_ordering(): assert Money(10, "USD") < Money(100, "USD") diff --git a/src/seedwork/tests/infrastructure/test_data_mapper.py b/src/seedwork/tests/infrastructure/test_data_mapper.py index b4583be..1f0beb7 100644 --- a/src/seedwork/tests/infrastructure/test_data_mapper.py +++ b/src/seedwork/tests/infrastructure/test_data_mapper.py @@ -1,6 +1,8 @@ from dataclasses import dataclass from uuid import UUID +import pytest + from seedwork.domain.entities import Entity from seedwork.infrastructure.data_mapper import JSONDataMapper @@ -17,6 +19,7 @@ class PersonJSONDataMapper(JSONDataMapper): model_class = dict +@pytest.mark.unit def test_data_mapper_maps_entity_to_json(): mapper = PersonJSONDataMapper() entity_instance = PersonEntity( @@ -30,6 +33,7 @@ def test_data_mapper_maps_entity_to_json(): assert actual == expected +@pytest.mark.unit def test_data_mapper_maps_json_to_entity(): mapper = PersonJSONDataMapper() model_instance = { diff --git a/src/seedwork/infrastructure/test_repository.py b/src/seedwork/tests/infrastructure/test_repository.py similarity index 94% rename from src/seedwork/infrastructure/test_repository.py rename to src/seedwork/tests/infrastructure/test_repository.py index 44e1bdb..0477100 100644 --- a/src/seedwork/infrastructure/test_repository.py +++ b/src/seedwork/tests/infrastructure/test_repository.py @@ -1,5 +1,7 @@ from dataclasses import dataclass +import pytest + from seedwork.domain.entities import Entity from seedwork.infrastructure.repository import InMemoryRepository @@ -10,6 +12,7 @@ class Person(Entity): last_name: str +@pytest.mark.unit def test_InMemoryRepository_persist_one(): # arrange person = Person(id=Person.next_id(), first_name="John", last_name="Doe") @@ -22,6 +25,7 @@ def test_InMemoryRepository_persist_one(): assert repository.get_by_id(person.id) == person +@pytest.mark.unit def test_InMemoryRepository_persist_two(): # arrange person1 = Person(id=Person.next_id(), first_name="John", last_name="Doe")