Files
blog.pyaqa.ru/tests/unit/domain/test_value_objects.py
Sergey Vanyushkin 87b094220d refactor: migrate to DDD architecture with Dishka DI
Complete architectural refactoring from simple MVC to Clean Architecture/DDD pattern:

Domain Layer:

- Add entities (Post, BaseEntity) with business logic

- Add value objects (Title, Content, Slug) with validation

- Add repository interfaces (PostRepository)

- Add domain exceptions

Application Layer:

- Add use cases (CreatePost, GetPost, UpdatePost, DeletePost, ListPosts, PublishPost)

- Add DTOs for data transfer

- Add TransactionManager interface

Infrastructure Layer:

- Add SQLAlchemy models and async database connection

- Add SQLAlchemyPostRepository implementation

- Add Dishka DI container with providers

- Add error handlers and middleware

Presentation Layer:

- Add FastAPI routes with Dishka integration

- Add Pydantic schemas

- Add dependency injection using FromDishka[T]

Other Changes:

- Remove old flat structure (api/, common/, core/, modules/)

- Add hatchling build system for package scripts

- Add blog CLI command

- Update AGENTS.md with new architecture docs

- All 48 tests passing, mypy clean, ruff clean
2026-05-01 20:20:41 +03:00

94 lines
3.1 KiB
Python

"""Tests for domain value objects."""
import pytest
from app.domain.value_objects import Content, Slug, Title
class TestTitle:
def test_valid_title(self) -> None:
"""Test creating a valid title."""
title = Title("Valid Title")
assert title.value == "Valid Title"
def test_title_too_short(self) -> None:
"""Test title that is too short."""
with pytest.raises(ValueError, match="at least"):
Title("ab")
def test_title_too_long(self) -> None:
"""Test title that is too long."""
with pytest.raises(ValueError, match="at most"):
Title("a" * 201)
def test_title_empty(self) -> None:
"""Test empty title."""
with pytest.raises(ValueError, match="empty"):
Title(" ")
def test_title_not_string(self) -> None:
"""Test non-string title."""
with pytest.raises(ValueError, match="string"):
Title(123) # type: ignore[arg-type]
class TestContent:
def test_valid_content(self) -> None:
"""Test creating valid content."""
content = Content("This is valid content with enough characters")
assert content.value == "This is valid content with enough characters"
def test_content_too_short(self) -> None:
"""Test content that is too short."""
with pytest.raises(ValueError, match="at least"):
Content("short")
def test_content_too_long(self) -> None:
"""Test content that is too long."""
with pytest.raises(ValueError, match="at most"):
Content("a" * 50001)
def test_content_empty(self) -> None:
"""Test empty content."""
with pytest.raises(ValueError, match="empty"):
Content(" ")
class TestSlug:
def test_valid_slug(self) -> None:
"""Test creating a valid slug."""
slug = Slug("valid-slug")
assert slug.value == "valid-slug"
def test_slug_from_title(self) -> None:
"""Test generating slug from title."""
slug = Slug.from_title("Hello World Post")
assert slug.value == "hello-world-post"
def test_slug_from_title_with_special_chars(self) -> None:
"""Test generating slug from title with special characters."""
slug = Slug.from_title("Hello, World! Post @#$%")
assert slug.value == "hello-world-post"
def test_slug_from_title_only_special_chars(self) -> None:
"""Test generating slug from title with only special characters."""
slug = Slug.from_title("!@#$%")
assert slug.value == "post"
def test_slug_invalid_chars(self) -> None:
"""Test slug with invalid characters."""
with pytest.raises(ValueError, match="lowercase"):
Slug("Invalid_Slug")
def test_slug_uppercase(self) -> None:
"""Test slug with uppercase letters."""
with pytest.raises(ValueError, match="lowercase"):
Slug("Uppercase-Slug")
def test_slug_equality(self) -> None:
"""Test slug value equality."""
slug1 = Slug("test-slug")
slug2 = Slug("test-slug")
assert slug1 == slug2
assert hash(slug1) == hash(slug2)