# 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_id` matches input - `author_id` matches input - `content` is CommentContent value object - `id` is a valid UUID - `parent_id` is None - `like_count` is 0 - `created_at` is 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_id` matches 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_id` matches input - `liked_by` matches input - `id` is a valid UUID - `created_at` is 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_id` is None - `comment_repo.add` called 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.add` called 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:** `NotFoundException` raised - **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.delete` called - **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:** `NotFoundException` raised - **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_like` called once - `remove_like` not 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_like` called once - `add_like` not 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:** `NotFoundException` raised - **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}/comments` with 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_id` set 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}/like` with 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