Основные изменения: - Добавлены E2E тесты для проверки ownership (TC-E2E-102/103): * test_admin_can_edit_any_post — admin может редактировать любой пост * test_user_cannot_edit_other_users_post — user не может редактировать чужой пост - Исправлены use cases (UpdatePost, DeletePost, PublishPost) — добавлена проверка роли admin - Обновлены web routes и API routes для передачи роли в use cases - Добавлены unit тесты для admin-сценариев Реструктуризация тестов: - Удалены старые API тесты (tests/api/) — требуют переработки - Удалены старые integration тесты (tests/integration/) - Переработаны E2E тесты: удалены старые, добавлены новые с POM - Добавлена документация тестов: FEATURE_*.md, TEST_MODEL.md, AGENTS.md Инфраструктура: - Добавлен MockKeycloakClient для dev-режима - Добавлены статические файлы: EasyMDE, Highlight.js, стили markdown - Обновлены шаблоны: base.html, post_form.html, post_detail.html - Обновлена DI конфигурация и провайдеры Документация: - tests/FEATURE_RBAC.md — матрица тестов RBAC - tests/FEATURE_POST_LIFECYCLE.md — тесты жизненного цикла поста - tests/FEATURE_DOMAIN_FOUNDATION.md — тесты доменного слоя - tests/FEATURE_INFRASTRUCTURE.md — тесты инфраструктуры - tests/TEST_MODEL.md — глобальная матрица покрытия - app/presentation/web/AGENTS.md — гайд по Web UI - tests/AGENTS.md — гайд по тестированию
247 lines
8.6 KiB
Markdown
247 lines
8.6 KiB
Markdown
# 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)
|