All checks were successful
ci/woodpecker/pr/pipeline Pipeline was successful
Implement full comments system: domain entities (Comment, CommentLike), value objects (CommentContent), use cases (CRUD, like toggle), SQLAlchemy repository, API v1 endpoints, web UI with comment form and nested replies, i18n translations (EN/RU/FR/DE), and E2E tests. Fix nested reply (reply-to-reply) not displaying — the flat reply_comments dict was only queried for top-level comment IDs, so deeply nested replies were saved to DB (incrementing comment count) but never rendered. Switch to a recursive Jinja2 macro that renders any nesting depth.
10 KiB
10 KiB
Test Model: Comments
Feature: Add comments to blog posts with Markdown editor, nested replies (parent_id), and per-comment like/unlike toggle.
Unit Test Cases
Domain Entities
TC-UNIT-829: Comment entity — valid creation
- Type: Positive
- Layer: Unit
- File:
unit/domain/test_comment_entity.py::TestCommentEntity::test_comment_creation - Preconditions: Valid post_id, author_id, and content
- Steps: Create Comment instance
- Expected:
post_idmatches inputauthor_idmatches inputcontentis CommentContent value objectidis a valid UUIDparent_idis Nonelike_countis 0created_atis set
- Last Verified: —
TC-UNIT-830: Comment entity — with parent_id (reply)
- Type: Positive
- Layer: Unit
- File:
unit/domain/test_comment_entity.py::TestCommentEntity::test_comment_with_parent - Preconditions: Valid post_id, author_id, content, and parent_id
- Steps: Create Comment instance with parent_id
- Expected:
parent_idmatches input- All other attributes set correctly
- Last Verified: —
TC-UNIT-831: CommentLike entity — valid creation
- Type: Positive
- Layer: Unit
- File:
unit/domain/test_comment_like_entity.py::TestCommentLikeEntity::test_comment_like_creation - Preconditions: Valid comment_id and liked_by
- Steps: Create CommentLike instance
- Expected:
comment_idmatches inputliked_bymatches inputidis a valid UUIDcreated_atis set
- Last Verified: —
CreateCommentUseCase
TC-UNIT-832: CreateCommentUseCase — on post (top-level)
- Type: Positive
- Layer: Unit
- File:
unit/application/test_create_comment.py::TestCreateCommentUseCase::test_create_comment_on_post - Preconditions: Post exists
- Steps: Execute with post_id, author_id, content, parent_id=None
- Expected:
- Comment created with correct post_id and author_id
parent_idis Nonecomment_repo.addcalled once- Transaction committed
- Last Verified: —
TC-UNIT-833: CreateCommentUseCase — reply to comment
- Type: Positive
- Layer: Unit
- File:
unit/application/test_create_comment.py::TestCreateCommentUseCase::test_create_comment_reply - Preconditions: Post and parent comment exist
- Steps: Execute with post_id, author_id, content, parent_id set
- Expected:
- Comment created with correct parent_id
comment_repo.addcalled once
- Last Verified: —
TC-UNIT-834: CreateCommentUseCase — post not found
- Type: Negative
- Layer: Unit
- File:
unit/application/test_create_comment.py::TestCreateCommentUseCase::test_create_comment_post_not_found - Preconditions: Post does not exist
- Steps: Execute with non-existent post_id
- Expected:
NotFoundExceptionraised - Last Verified: —
ListCommentsUseCase
TC-UNIT-835: ListCommentsUseCase — returns comments for post
- Type: Positive
- Layer: Unit
- File:
unit/application/test_list_comments.py::TestListCommentsUseCase::test_list_comments_by_post - Preconditions: Post has multiple comments and replies
- Steps: Execute with post_id
- Expected: Returns list of CommentResponseDTO with correct post_id
- Last Verified: —
DeleteCommentUseCase
TC-UNIT-836: DeleteCommentUseCase — own comment
- Type: Positive
- Layer: Unit
- File:
unit/application/test_delete_comment.py::TestDeleteCommentUseCase::test_delete_own_comment - Preconditions: Comment exists owned by user
- Steps: Execute with comment_id, user_id matching author_id
- Expected:
comment_repo.deletecalled - Last Verified: —
TC-UNIT-837: DeleteCommentUseCase — not found
- Type: Negative
- Layer: Unit
- File:
unit/application/test_delete_comment.py::TestDeleteCommentUseCase::test_delete_comment_not_found - Preconditions: Comment does not exist
- Steps: Execute with non-existent comment_id
- Expected:
NotFoundExceptionraised - Last Verified: —
ToggleCommentLikeUseCase
TC-UNIT-838: ToggleCommentLikeUseCase — like first time
- Type: Positive
- Layer: Unit
- File:
unit/application/test_toggle_comment_like.py::TestToggleCommentLikeUseCase::test_like_comment_first_time - Preconditions: Comment exists, no existing like for this user
- Steps: Execute toggle with comment_id and liked_by
- Expected:
add_likecalled onceremove_likenot called- Response DTO has
like_count=1
- Last Verified: —
TC-UNIT-839: ToggleCommentLikeUseCase — unlike (already liked)
- Type: Positive
- Layer: Unit
- File:
unit/application/test_toggle_comment_like.py::TestToggleCommentLikeUseCase::test_unlike_comment_already_liked - Preconditions: Comment exists, existing like found for this user
- Steps: Execute toggle with same comment_id and liked_by
- Expected:
remove_likecalled onceadd_likenot called- Response DTO has
like_count=0
- Last Verified: —
TC-UNIT-840: ToggleCommentLikeUseCase — comment not found
- Type: Negative
- Layer: Unit
- File:
unit/application/test_toggle_comment_like.py::TestToggleCommentLikeUseCase::test_like_comment_not_found - Preconditions: Comment does not exist
- Steps: Execute toggle with non-existent comment_id
- Expected:
NotFoundExceptionraised - Last Verified: —
API Test Cases
TC-API-118: Create comment — authenticated
- Type: Positive
- Layer: API
- File:
api/test_comments.py::TestCreateComment::test_create_comment_authenticated - Preconditions: Post exists, user authenticated
- Steps: POST
/api/v1/posts/{post_id}/commentswith auth header - Expected:
- Status 201
- Response has comment_id, post_id, content, author_id
- Last Verified: —
TC-API-119: Create comment — reply to comment
- Type: Positive
- Layer: API
- File:
api/test_comments.py::TestCreateComment::test_create_comment_reply - Preconditions: Post and comment exist, user authenticated
- Steps: POST with
parent_idset to existing comment - Expected:
- Status 201
- Response has correct
parent_id
- Last Verified: —
TC-API-120: Create comment — guest
- Type: Negative
- Layer: API
- File:
api/test_comments.py::TestCreateComment::test_create_comment_as_guest - Preconditions: Post exists, guest token used
- Steps: POST without auth header
- Expected: Status 401
- Last Verified: —
TC-API-121: List comments — by post
- Type: Positive
- Layer: API
- File:
api/test_comments.py::TestListComments::test_list_comments_by_post - Preconditions: Post exists with comments
- Steps: GET
/api/v1/posts/{post_id}/comments - Expected:
- Status 200
- Response contains list of comments
- Last Verified: —
TC-API-122: Delete comment — own comment
- Type: Positive
- Layer: API
- File:
api/test_comments.py::TestDeleteComment::test_delete_own_comment - Preconditions: Comment exists owned by authenticated user
- Steps: DELETE
/api/v1/comments/{comment_id}with auth header - Expected: Status 204
- Last Verified: —
TC-API-123: Delete comment — not owner
- Type: Negative
- Layer: API
- File:
api/test_comments.py::TestDeleteComment::test_delete_comment_not_owner - Preconditions: Comment exists owned by different user
- Steps: DELETE with another user's auth header
- Expected: Status 403
- Last Verified: —
TC-API-124: Toggle comment like — authenticated
- Type: Positive
- Layer: API
- File:
api/test_comments.py::TestLikeComment::test_like_comment_authenticated - Preconditions: Comment exists, user authenticated
- Steps: POST
/api/v1/comments/{comment_id}/likewith auth header - Expected:
- Status 200
like_count == 1
- Last Verified: —
TC-API-125: Toggle comment like — guest
- Type: Negative
- Layer: API
- File:
api/test_comments.py::TestLikeComment::test_like_comment_as_guest - Preconditions: Comment exists, guest token used
- Steps: POST without auth header
- Expected: Status 401
- Last Verified: —
E2E Test Cases
TC-E2E-109: Create comment via web UI
- Type: Positive
- Layer: E2E
- File:
tests/e2e/test_comments.py::test_create_comment - Scenario: Login → open post → write comment with Markdown → verify display
- Expected: Comment displayed on post detail page with rendered Markdown
- Last Verified: —
TC-E2E-110: Reply to comment
- Type: Positive
- Layer: E2E
- File:
tests/e2e/test_comments.py::test_reply_to_comment - Scenario: Create top-level comment → click Reply → write reply → verify nesting
- Expected: Reply appears below parent comment with indentation
- Last Verified: —
TC-E2E-111: Like/unlike comment
- Type: Positive
- Layer: E2E
- File:
tests/e2e/test_comments.py::test_like_unlike_comment - Scenario: Create comment → like → verify count → unlike → verify count
- Expected: Count toggles correctly (0→1→0)
- Last Verified: —
TC-E2E-112: Guest cannot comment
- Type: Negative
- Layer: E2E
- File:
tests/e2e/test_comments.py::test_guest_cannot_comment - Scenario: Guest opens published post → comment form not visible → cannot post
- Expected: Comment form hidden for guests
- Last Verified: —
Coverage Summary
| Component | Cases | Status |
|---|---|---|
| Domain Entities (Comment, CommentLike) | 3 | ⬜ Planned |
| CreateCommentUseCase | 3 | ⬜ Planned |
| ListCommentsUseCase | 1 | ⬜ Planned |
| DeleteCommentUseCase | 2 | ⬜ Planned |
| ToggleCommentLikeUseCase | 3 | ⬜ Planned |
| API Endpoints | 8 | ⬜ Planned |
| E2E Flows | 4 | ⬜ Planned |
Gaps (Not Yet Covered)
- Integration tests for SQLAlchemyCommentRepository
- Web-only tests (TC-WEB-004+)
- Admin delete any comment
- Edit comment
- Comment pagination with large number of comments