Files
blog.pyaqa.ru/tests/FEATURE_POST_LIFECYCLE.md
Sergey Vanyushkin 46cc06b596 feat: RBAC E2E тесты и фикс admin-прав для редактирования постов
Основные изменения:
- Добавлены 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 — гайд по тестированию
2026-05-07 19:55:15 +03:00

233 lines
8.8 KiB
Markdown

# Test Model: Post Lifecycle
Feature: Create, read, update, delete, publish, and unpublish blog posts.
Covers both API use cases and web UI end-to-end flows.
## Unit Test Cases
### TC-UNIT-001: CreatePostUseCase — Success
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/application/test_use_cases.py::TestCreatePostUseCase::test_create_post_success`
- **Preconditions:** Mock repository, mock transaction manager
- **Steps:**
1. Mock `slug_exists` to return `False`
2. Execute `CreatePostUseCase` with valid DTO
- **Expected:**
- Returns `PostResponseDTO` with correct title and author
- `repository.add` called once
- `transaction_manager.commit` called once
- **Last Verified:** 2026-05-07
### TC-UNIT-002: CreatePostUseCase — Duplicate Slug
- **Type:** Negative
- **Layer:** Unit
- **File:** `unit/application/test_use_cases.py::TestCreatePostUseCase::test_create_post_slug_exists`
- **Preconditions:** Mock repository returns `slug_exists=True`
- **Steps:** Execute `CreatePostUseCase` with DTO that would collide
- **Expected:** Raises `AlreadyExistsException`, no DB write
- **Last Verified:** 2026-05-07
### TC-UNIT-003: CreatePostUseCase — Validation Error (implied by VO tests)
- **Type:** Negative
- **Layer:** Unit
- **File:** Covered indirectly via `Title` / `Content` VO tests
- **Gap Note:** No explicit use-case-level validation error test exists.
### TC-UNIT-004: DeletePostUseCase — Success
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/application/test_use_cases.py::TestDeletePostUseCase`
- **Expected:** Post removed, commit called
- **Last Verified:** 2026-05-07
### TC-UNIT-005: GetPostUseCase — By ID Success
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/application/test_use_cases.py::TestGetPostUseCase`
- **Expected:** Returns `PostResponseDTO` for existing post
- **Last Verified:** 2026-05-07
### TC-UNIT-006: GetPostUseCase — By Slug Success
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/application/test_use_cases.py::TestGetPostUseCase`
- **Expected:** Returns `PostResponseDTO` for existing slug
- **Last Verified:** 2026-05-07
### TC-UNIT-007: GetPostUseCase — Not Found
- **Type:** Negative
- **Layer:** Unit
- **File:** `unit/application/test_use_cases.py::TestGetPostUseCase`
- **Expected:** Raises `NotFoundException`
- **Last Verified:** 2026-05-07
### TC-UNIT-008: UpdatePostUseCase — Success
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/application/test_use_cases.py::TestUpdatePostUseCase`
- **Expected:** Post updated, commit called
- **Last Verified:** 2026-05-07
### TC-UNIT-009: UpdatePostUseCase — Forbidden (other author)
- **Type:** Policy
- **Layer:** Unit
- **File:** `unit/application/test_use_cases.py::TestUpdatePostUseCase`
- **Expected:** Raises `ForbiddenException`
- **Last Verified:** 2026-05-07
### TC-UNIT-010: PublishPostUseCase — Publish Success
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/application/test_publish_post.py::TestPublishPost::test_publish_success`
- **Preconditions:** Mock repository returns unpublished post
- **Steps:** Call `publish(post_id, author_id)`
- **Expected:**
- Returns `PostResponseDTO` with `published=True`
- `repository.update` and `commit` called once
- **Last Verified:** 2026-05-07
### TC-UNIT-011: PublishPostUseCase — Publish Not Found
- **Type:** Negative
- **Layer:** Unit
- **File:** `unit/application/test_publish_post.py::TestPublishPost::test_publish_not_found`
- **Expected:** Raises `NotFoundException`
- **Last Verified:** 2026-05-07
### TC-UNIT-012: PublishPostUseCase — Publish Forbidden
- **Type:** Policy
- **Layer:** Unit
- **File:** `unit/application/test_publish_post.py::TestPublishPost::test_publish_forbidden`
- **Expected:** Raises `ForbiddenException` when caller is not the author
- **Last Verified:** 2026-05-07
### TC-UNIT-013: PublishPostUseCase — Unpublish Success
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/application/test_publish_post.py::TestUnpublishPost::test_unpublish_success`
- **Expected:** Returns `PostResponseDTO` with `published=False`
- **Last Verified:** 2026-05-07
### TC-UNIT-014: PublishPostUseCase — Unpublish Not Found
- **Type:** Negative
- **Layer:** Unit
- **File:** `unit/application/test_publish_post.py::TestUnpublishPost::test_unpublish_not_found`
- **Expected:** Raises `NotFoundException`
- **Last Verified:** 2026-05-07
### TC-UNIT-015: PublishPostUseCase — Unpublish Forbidden
- **Type:** Policy
- **Layer:** Unit
- **File:** `unit/application/test_publish_post.py::TestUnpublishPost::test_unpublish_forbidden`
- **Expected:** Raises `ForbiddenException` when caller is not the author
- **Last Verified:** 2026-05-07
### TC-UNIT-016: ListPostsUseCase — All Posts
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/application/test_list_posts.py::TestAllPosts::test_all_posts`
- **Expected:** Returns all posts as DTOs
- **Last Verified:** 2026-05-07
### TC-UNIT-017: ListPostsUseCase — Published Posts
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/application/test_list_posts.py::TestPublishedPosts::test_published_posts`
- **Expected:** Returns only published posts
- **Last Verified:** 2026-05-07
### TC-UNIT-018: ListPostsUseCase — Published Posts with Pagination
- **Type:** Edge
- **Layer:** Unit
- **File:** `unit/application/test_list_posts.py::TestPublishedPosts::test_published_posts_with_limit_offset`
- **Expected:** Repository called with correct limit/offset
- **Last Verified:** 2026-05-07
### TC-UNIT-019: ListPostsUseCase — By Author
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/application/test_list_posts.py::TestByAuthor::test_by_author`
- **Expected:** Returns posts filtered by author_id
- **Last Verified:** 2026-05-07
### TC-UNIT-020: ListPostsUseCase — By Tag
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/application/test_list_posts.py::TestByTag::test_by_tag`
- **Expected:** Returns posts containing the tag
- **Last Verified:** 2026-05-07
### TC-UNIT-021: ListPostsUseCase — Search
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/application/test_list_posts.py::TestSearch::test_search`
- **Expected:** Returns posts matching the query
- **Last Verified:** 2026-05-07
### TC-UNIT-022: ListPostsUseCase — Search No Results
- **Type:** Edge
- **Layer:** Unit
- **File:** `unit/application/test_list_posts.py::TestSearch::test_search_no_results`
- **Expected:** Returns empty list
- **Last Verified:** 2026-05-07
## E2E Test Cases
### TC-E2E-001: Positive — Create and Publish Post
- **Type:** Positive
- **Layer:** E2E
- **File:** `e2e/test_post_lifecycle.py::test_user_creates_and_publishes_post_visible_to_guest_and_admin`
- **Preconditions:** Dev server running, `user_page`, `guest_page`, `admin_page` fixtures
- **Steps:**
1. Generate post data via `PostDataGenerator`
2. Open home page and click "Write a Post"
3. Fill form (title, content, tags)
4. Click "Publish Post"
- **Expected:**
- Redirect to `/web/posts/{slug}`
- Status badge shows "Published"
- Post visible on home page for user, guest, and admin
- Post detail accessible to guest and admin
- **Last Verified:** 2026-05-07
### TC-E2E-002: Policy — Draft Visibility Across Roles
- **Type:** Policy
- **Layer:** E2E
- **File:** `e2e/test_post_lifecycle.py::test_post_visibility_policies_across_users`
- **Preconditions:** Dev server running, `user_page`, `user2_page`, `guest_page`, `admin_page` fixtures
- **Steps:**
1. User creates a draft post
2. User creates and publishes another post
3. Check visibility for each role on the home page
4. Attempt direct access to draft by user2
- **Expected:**
- User sees both posts
- User2 and guest see only the published post
- Admin sees both posts
- User2 receives 404 when accessing draft directly
- **Last Verified:** 2026-05-07
## Coverage Summary
| Aspect | Coverage | Notes |
|--------|----------|-------|
| Create post | Unit + E2E | Both happy path and duplicate slug covered |
| Read post (by id/slug) | Unit | E2E implicitly via detail page |
| Update post | Unit | No dedicated E2E |
| Delete post | Unit | No dedicated E2E |
| Publish / Unpublish | Unit + E2E | Authz checks covered in both layers |
| List posts (all filters) | Unit | Pagination arguments passed but not edge-case tested |
| Search posts | Unit | No E2E search flow |
## Gaps (Not Yet Covered)
- [ ] TC-UNIT-023: CreatePostUseCase — explicit validation error (title too short, content empty)
- [ ] TC-UNIT-024: UpdatePostUseCase — not found scenario
- [ ] TC-UNIT-025: UpdatePostUseCase — validation error
- [ ] TC-UNIT-026: ListPostsUseCase — pagination edge cases (page boundaries, empty page)
- [ ] TC-E2E-003: Edit post via web UI and verify changes
- [ ] TC-E2E-004: Delete post via web UI and verify removal
- [ ] TC-E2E-005: Save post as draft and verify it does not appear to guests
- [ ] TC-E2E-006: Search posts via web UI
- [ ] TC-E2E-007: Pagination navigation on home page