- Create Value[T] (frozen dataclass + ABC + Generic) in domain/framework/ - Migrate ClassifiedAdTitle and ClassifiedAdText to inherit Value[str] - Standardize all value access via .value instead of type-specific fields
437 lines
13 KiB
Python
437 lines
13 KiB
Python
from decimal import Decimal
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from domain.framework.entity import Entity
|
|
from domain.marketplace.classified_ad import ClassifiedAd
|
|
from domain.marketplace.classified_ad_id import ClassifiedAdId
|
|
from domain.marketplace.classified_ad_state import ClassifiedAdState
|
|
from domain.marketplace.classified_ad_text import ClassifiedAdText
|
|
from domain.marketplace.classified_ad_title import ClassifiedAdTitle
|
|
from domain.marketplace.domain_event import (
|
|
ClassifiedAdCreated,
|
|
ClassifiedAdPriceUpdated,
|
|
ClassifiedAdSentForReview,
|
|
ClassifiedAdTextUpdated,
|
|
ClassifiedAdTitleChanged,
|
|
)
|
|
from domain.marketplace.invalid_entity_state_exception import (
|
|
InvalidEntityStateException,
|
|
)
|
|
from domain.marketplace.price import Price
|
|
from domain.marketplace.user_id import UserId
|
|
|
|
|
|
def test_classified_ad_creation():
|
|
ad_id = ClassifiedAdId(value=uuid4())
|
|
owner_id = UserId(value=uuid4())
|
|
|
|
ad = ClassifiedAd(
|
|
id=ad_id,
|
|
_ownerId=owner_id,
|
|
title=ClassifiedAdTitle(value="Test Title"),
|
|
text=ClassifiedAdText(value="Test text content"),
|
|
price=Price(amount=Decimal("99.99")),
|
|
)
|
|
|
|
assert ad.id == ad_id
|
|
assert ad._ownerId == owner_id
|
|
assert ad.title == ClassifiedAdTitle(value="Test Title")
|
|
assert ad.text == ClassifiedAdText(value="Test text content")
|
|
assert ad.price == Price(amount=Decimal("99.99"))
|
|
|
|
|
|
def test_classified_ad_can_not_be_created_without_id():
|
|
with pytest.raises(TypeError):
|
|
ClassifiedAd(
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
|
|
def test_classified_ad_can_not_be_created_without_owner_id():
|
|
with pytest.raises(TypeError):
|
|
ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
|
|
def test_classified_ad_can_be_created_without_price():
|
|
ad_id = ClassifiedAdId(value=uuid4())
|
|
owner_id = UserId(value=uuid4())
|
|
|
|
ad = ClassifiedAd(
|
|
id=ad_id,
|
|
_ownerId=owner_id,
|
|
title=ClassifiedAdTitle(value="Test Title"),
|
|
text=ClassifiedAdText(value="Test text content"),
|
|
)
|
|
|
|
assert ad.id == ad_id
|
|
assert ad._ownerId == owner_id
|
|
assert ad.title == ClassifiedAdTitle(value="Test Title")
|
|
assert ad.text == ClassifiedAdText(value="Test text content")
|
|
assert ad.price == Price(amount=Decimal("0.00"))
|
|
|
|
|
|
def test_classified_ad_can_not_have_negative_price():
|
|
with pytest.raises(ValueError):
|
|
ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("-10.00")),
|
|
)
|
|
|
|
|
|
def test_classified_ad_is_immutable():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
try:
|
|
ad.title = "New Title" # type: ignore
|
|
assert False, "Should have raised FrozenInstanceError"
|
|
except AttributeError:
|
|
pass
|
|
|
|
|
|
def test_classified_ad_equality():
|
|
ad_id = ClassifiedAdId(value=uuid4())
|
|
owner_id = UserId(value=uuid4())
|
|
|
|
ad1 = ClassifiedAd(
|
|
id=ad_id,
|
|
_ownerId=owner_id,
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
ad2 = ClassifiedAd(
|
|
id=ad_id,
|
|
_ownerId=owner_id,
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
assert ad1 == ad2
|
|
|
|
|
|
def test_classified_ad_title_validation():
|
|
long_title = "a" * 101
|
|
with pytest.raises(ValueError):
|
|
ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value=long_title),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
|
|
def test_request_to_publish_sets_state_to_pending_review():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
published = ad.request_to_publish()
|
|
|
|
assert published.state == ClassifiedAdState.PendingReview
|
|
|
|
changes = published.get_changes()
|
|
assert len(changes) == 2
|
|
assert isinstance(changes[0], ClassifiedAdCreated)
|
|
assert isinstance(changes[1], ClassifiedAdSentForReview)
|
|
assert changes[1].id == ad.id.value
|
|
|
|
|
|
def test_request_to_publish_fails_when_title_empty():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value=""),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
with pytest.raises(InvalidEntityStateException) as exc_info:
|
|
ad.request_to_publish()
|
|
|
|
assert "title" in str(exc_info.value)
|
|
|
|
|
|
def test_request_to_publish_fails_when_text_empty():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value=""),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
with pytest.raises(InvalidEntityStateException) as exc_info:
|
|
ad.request_to_publish()
|
|
|
|
assert "text" in str(exc_info.value)
|
|
|
|
|
|
def test_request_to_publish_fails_when_price_is_zero():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
)
|
|
|
|
with pytest.raises(InvalidEntityStateException) as exc_info:
|
|
ad.request_to_publish()
|
|
|
|
assert "price" in str(exc_info.value)
|
|
|
|
|
|
def test_request_to_publish_reports_all_errors():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value=""),
|
|
text=ClassifiedAdText(value=""),
|
|
)
|
|
|
|
with pytest.raises(InvalidEntityStateException) as exc_info:
|
|
ad.request_to_publish()
|
|
|
|
assert "title" in str(exc_info.value)
|
|
assert "text" in str(exc_info.value)
|
|
assert "price" in str(exc_info.value)
|
|
|
|
|
|
def test_request_to_publish_does_not_mutate_original():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
ad.request_to_publish()
|
|
|
|
assert ad.state == ClassifiedAdState.Inactive
|
|
assert ad is not ad.request_to_publish()
|
|
|
|
|
|
def test_classified_ad_is_instance_of_entity():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
assert isinstance(ad, Entity)
|
|
|
|
|
|
def test_classified_ad_can_use_entity_apply():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
event = ClassifiedAdSentForReview(id=ad.id.value)
|
|
|
|
updated_ad = ad.apply(event)
|
|
|
|
assert updated_ad.get_changes() == (ad.get_changes()[0], event)
|
|
assert len(ad.get_changes()) == 1
|
|
assert isinstance(ad.get_changes()[0], ClassifiedAdCreated)
|
|
|
|
|
|
def test_set_title_updates_title():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Old Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
new_title = ClassifiedAdTitle(value="New Title")
|
|
|
|
updated = ad.set_title(new_title)
|
|
|
|
assert updated.title == new_title
|
|
assert ad.title == ClassifiedAdTitle(value="Old Title")
|
|
|
|
|
|
def test_set_title_preserves_other_fields():
|
|
ad_id = ClassifiedAdId(value=uuid4())
|
|
owner_id = UserId(value=uuid4())
|
|
text = ClassifiedAdText(value="Text")
|
|
price = Price(amount=Decimal("10.00"))
|
|
ad = ClassifiedAd(
|
|
id=ad_id,
|
|
_ownerId=owner_id,
|
|
title=ClassifiedAdTitle(value="Old Title"),
|
|
text=text,
|
|
price=price,
|
|
)
|
|
|
|
updated = ad.set_title(ClassifiedAdTitle(value="New Title"))
|
|
|
|
assert updated.id == ad_id
|
|
assert updated._ownerId == owner_id
|
|
assert updated.text == text
|
|
assert updated.price == price
|
|
|
|
|
|
def test_set_title_fails_when_new_title_empty():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
with pytest.raises(InvalidEntityStateException) as exc_info:
|
|
ad.set_title(ClassifiedAdTitle(value=""))
|
|
|
|
assert "title" in str(exc_info.value)
|
|
|
|
|
|
def test_set_title_raises_classified_ad_title_changed_event():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Old Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
updated = ad.set_title(ClassifiedAdTitle(value="New Title"))
|
|
|
|
changes = updated.get_changes()
|
|
assert len(changes) == 2
|
|
assert isinstance(changes[0], ClassifiedAdCreated)
|
|
assert isinstance(changes[1], ClassifiedAdTitleChanged)
|
|
assert changes[1].id == ad.id.value
|
|
assert changes[1].title == "New Title"
|
|
|
|
|
|
def test_update_text_updates_text():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Old text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
new_text = ClassifiedAdText(value="New text")
|
|
|
|
updated = ad.update_text(new_text)
|
|
|
|
assert updated.text == new_text
|
|
assert ad.text == ClassifiedAdText(value="Old text")
|
|
|
|
|
|
def test_update_text_fails_when_new_text_empty():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
with pytest.raises(InvalidEntityStateException) as exc_info:
|
|
ad.update_text(ClassifiedAdText(value=""))
|
|
|
|
assert "text" in str(exc_info.value)
|
|
|
|
|
|
def test_update_text_raises_classified_ad_text_updated_event():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Old text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
updated = ad.update_text(ClassifiedAdText(value="New text"))
|
|
|
|
changes = updated.get_changes()
|
|
assert len(changes) == 2
|
|
assert isinstance(changes[0], ClassifiedAdCreated)
|
|
assert isinstance(changes[1], ClassifiedAdTextUpdated)
|
|
assert changes[1].id == ad.id.value
|
|
assert changes[1].ad_text == "New text"
|
|
|
|
|
|
def test_update_price_updates_price():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
new_price = Price(amount=Decimal("25.00"))
|
|
|
|
updated = ad.update_price(new_price)
|
|
|
|
assert updated.price == new_price
|
|
assert ad.price == Price(amount=Decimal("10.00"))
|
|
|
|
|
|
def test_update_price_fails_when_price_zero():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
with pytest.raises(InvalidEntityStateException) as exc_info:
|
|
ad.update_price(Price(amount=Decimal("0.00")))
|
|
|
|
assert "price" in str(exc_info.value)
|
|
|
|
|
|
def test_update_price_raises_classified_ad_price_updated_event():
|
|
ad = ClassifiedAd(
|
|
id=ClassifiedAdId(value=uuid4()),
|
|
_ownerId=UserId(value=uuid4()),
|
|
title=ClassifiedAdTitle(value="Title"),
|
|
text=ClassifiedAdText(value="Text"),
|
|
price=Price(amount=Decimal("10.00")),
|
|
)
|
|
|
|
updated = ad.update_price(Price(amount=Decimal("25.00")))
|
|
|
|
changes = updated.get_changes()
|
|
assert len(changes) == 2
|
|
assert isinstance(changes[0], ClassifiedAdCreated)
|
|
assert isinstance(changes[1], ClassifiedAdPriceUpdated)
|
|
assert changes[1].id == ad.id.value
|
|
assert changes[1].price == Decimal("25.00")
|