Files
blog.pyaqa.ru/tests/FEATURE_COMMENTS.md
Sergey Vanyushkin 7ff3fa0992
All checks were successful
ci/woodpecker/pr/pipeline Pipeline was successful
feat: add comments feature with nested replies and recursive rendering
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.
2026-05-11 15:34:20 +03:00

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_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