# Test Model: Domain Foundation Feature: Core domain building blocks — entities, value objects, and exceptions. These tests validate business rules at the domain layer with no external dependencies. ## Unit Test Cases ### Entities #### TC-UNIT-201: Post Creation - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_entities.py::TestPost::test_post_creation` - **Expected:** - `id` is a valid UUID - `title.value == "Test Title"` - `content.value` matches input - `slug.value == "test-title"` (auto-generated) - `author_id == "user-123"` - `published is False` - `tags == ["test", "python"]` - **Last Verified:** 2026-05-07 #### TC-UNIT-202: Post Publish - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_entities.py::TestPost::test_post_publish` - **Expected:** `published` transitions from `False` to `True` - **Last Verified:** 2026-05-07 #### TC-UNIT-203: Post Unpublish - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_entities.py::TestPost::test_post_unpublish` - **Expected:** `published` transitions from `True` to `False` - **Last Verified:** 2026-05-07 #### TC-UNIT-204: Post Update Title - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_entities.py::TestPost::test_post_update_title` - **Expected:** Title and slug updated, `updated_at` changed - **Last Verified:** 2026-05-07 #### TC-UNIT-205: Post Update Content - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_entities.py::TestPost::test_post_update_content` - **Expected:** Content updated, `updated_at` changed - **Last Verified:** 2026-05-07 #### TC-UNIT-206: Post Update Tags - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_entities.py::TestPost::test_post_update_tags` - **Expected:** Tags replaced with new list, `updated_at` changed - **Last Verified:** 2026-05-07 #### TC-UNIT-207: Post Add Tag - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_entities.py::TestPost::test_post_add_tag` - **Expected:** New tag appended to existing tags - **Last Verified:** 2026-05-07 #### TC-UNIT-208: Post Remove Tag - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_entities.py::TestPost::test_post_remove_tag` - **Expected:** Tag removed from list - **Last Verified:** 2026-05-07 ### Value Objects #### TC-UNIT-301: Title — Valid - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestTitle::test_valid_title` - **Expected:** `Title("Valid Title").value == "Valid Title"` - **Last Verified:** 2026-05-07 #### TC-UNIT-302: Title — Too Short - **Type:** Negative - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestTitle::test_title_too_short` - **Expected:** Raises `ValueError` with message containing "at least" - **Last Verified:** 2026-05-07 #### TC-UNIT-303: Title — Too Long - **Type:** Negative - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestTitle::test_title_too_long` - **Expected:** Raises `ValueError` with message containing "at most" - **Last Verified:** 2026-05-07 #### TC-UNIT-304: Title — Empty / Whitespace - **Type:** Negative - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestTitle::test_title_empty` - **Expected:** Raises `ValueError` with message containing "empty" - **Last Verified:** 2026-05-07 #### TC-UNIT-305: Title — Non-String - **Type:** Negative - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestTitle::test_title_not_string` - **Expected:** Raises `ValueError` with message containing "string" - **Last Verified:** 2026-05-07 #### TC-UNIT-306: Content — Valid - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestContent::test_valid_content` - **Expected:** Content created successfully - **Last Verified:** 2026-05-07 #### TC-UNIT-307: Content — Too Short - **Type:** Negative - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestContent::test_content_too_short` - **Expected:** Raises `ValueError` with "at least" - **Last Verified:** 2026-05-07 #### TC-UNIT-308: Content — Too Long - **Type:** Negative - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestContent::test_content_too_long` - **Expected:** Raises `ValueError` with "at most" - **Last Verified:** 2026-05-07 #### TC-UNIT-309: Content — Empty / Whitespace - **Type:** Negative - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestContent::test_content_empty` - **Expected:** Raises `ValueError` with "empty" - **Last Verified:** 2026-05-07 #### TC-UNIT-310: Slug — Valid - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestSlug::test_valid_slug` - **Expected:** `Slug("valid-slug").value == "valid-slug"` - **Last Verified:** 2026-05-07 #### TC-UNIT-311: Slug — From Title - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestSlug::test_slug_from_title` - **Expected:** `Slug.from_title("Hello World Post") == "hello-world-post"` - **Last Verified:** 2026-05-07 #### TC-UNIT-312: Slug — From Title with Special Characters - **Type:** Edge - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestSlug::test_slug_from_title_with_special_chars` - **Expected:** Special chars stripped, words hyphenated - **Last Verified:** 2026-05-07 #### TC-UNIT-313: Slug — From Title with Only Special Characters - **Type:** Edge - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestSlug::test_slug_from_title_only_special_chars` - **Expected:** Falls back to `"post"` - **Last Verified:** 2026-05-07 #### TC-UNIT-314: Slug — Invalid Characters (underscore) - **Type:** Negative - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestSlug::test_slug_invalid_chars` - **Expected:** Raises `ValueError` with "lowercase" - **Last Verified:** 2026-05-07 #### TC-UNIT-315: Slug — Uppercase Letters - **Type:** Negative - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestSlug::test_slug_uppercase` - **Expected:** Raises `ValueError` with "lowercase" - **Last Verified:** 2026-05-07 #### TC-UNIT-316: Slug — Equality and Hash - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_value_objects.py::TestSlug::test_slug_equality` - **Expected:** Equal slugs have equal values and hashes - **Last Verified:** 2026-05-07 ### Exceptions #### TC-UNIT-401: DomainException — Base - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_exceptions.py::TestDomainExceptions::test_base_exception` - **Expected:** Message stored and returned via `str()` - **Last Verified:** 2026-05-07 #### TC-UNIT-402: ValidationException - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_exceptions.py::TestDomainExceptions::test_validation_exception` - **Expected:** Inherits `DomainException`, stores message - **Last Verified:** 2026-05-07 #### TC-UNIT-403: NotFoundException - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_exceptions.py::TestDomainExceptions::test_not_found_exception` - **Expected:** Inherits `DomainException`, stores message - **Last Verified:** 2026-05-07 #### TC-UNIT-404: AlreadyExistsException - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_exceptions.py::TestDomainExceptions::test_already_exists_exception` - **Expected:** Inherits `DomainException`, stores message - **Last Verified:** 2026-05-07 #### TC-UNIT-405: UnauthorizedException - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_exceptions.py::TestDomainExceptions::test_unauthorized_exception` - **Expected:** Inherits `DomainException`, stores message - **Last Verified:** 2026-05-07 #### TC-UNIT-406: ForbiddenException - **Type:** Positive - **Layer:** Unit - **File:** `unit/domain/test_exceptions.py::TestDomainExceptions::test_forbidden_exception` - **Expected:** Inherits `DomainException`, stores message - **Last Verified:** 2026-05-07 ## Coverage Summary | Component | Cases | Status | |-----------|-------|--------| | Post Entity | 8 | ✅ All core operations covered | | Title VO | 5 | ✅ Validation rules fully covered | | Content VO | 4 | ✅ Validation rules fully covered | | Slug VO | 7 | ✅ Generation and validation covered | | Domain Exceptions | 6 | ✅ All exception types covered | ## Gaps (Not Yet Covered) - [ ] TC-UNIT-209: Post Entity — `updated_at` does not change when update values are identical - [ ] TC-UNIT-210: Post Entity — attempt to publish already published post (idempotent behavior) - [ ] TC-UNIT-317: Slug — collision handling (unique constraint) at domain level - [ ] TC-UNIT-318: Content — exact boundary values (min length - 1, max length + 1)