"""API tests for blog post comments. This module tests comment CRUD operations, nested replies, and comment like/unlike toggle via API endpoints. """ from typing import Any from uuid import UUID from fastapi.testclient import TestClient from tests.api.conftest import API_PREFIX COMMENT_CONTENT = "This is a test comment with enough length." class TestCreateComment: """Tests for POST /api/v1/posts/{post_id}/comments — create a comment.""" def test_create_comment_authenticated( self, client: TestClient, user_headers: dict[str, str], created_post: dict[str, Any], ) -> None: """Test creating a comment as authenticated user. TC-API-118: Positive — create top-level comment. """ post_id = created_post["id"] response = client.post( f"{API_PREFIX}/{post_id}/comments", json={"content": COMMENT_CONTENT}, headers=user_headers, ) assert response.status_code == 201 data = response.json() assert data["post_id"] == post_id assert data["author_id"] == "dev-user" assert data["content"] == COMMENT_CONTENT assert data["parent_id"] is None assert data["like_count"] == 0 assert UUID(data["id"]) def test_create_comment_reply( self, client: TestClient, user_headers: dict[str, str], created_post: dict[str, Any], ) -> None: """Test creating a reply to an existing comment. TC-API-119: Positive — reply to comment with parent_id. """ post_id = created_post["id"] parent = client.post( f"{API_PREFIX}/{post_id}/comments", json={"content": COMMENT_CONTENT}, headers=user_headers, ) assert parent.status_code == 201 parent_id = parent.json()["id"] response = client.post( f"{API_PREFIX}/{post_id}/comments", json={"content": "Reply to comment.", "parent_id": parent_id}, headers=user_headers, ) assert response.status_code == 201 data = response.json() assert data["parent_id"] == parent_id assert data["post_id"] == post_id def test_create_comment_as_guest( self, client: TestClient, guest_headers: dict[str, str], created_post: dict[str, Any], ) -> None: """Test creating a comment as guest returns 401. TC-API-120: Negative — guest cannot create comment. """ post_id = created_post["id"] response = client.post( f"{API_PREFIX}/{post_id}/comments", json={"content": COMMENT_CONTENT}, headers=guest_headers, ) assert response.status_code == 401 class TestListComments: """Tests for GET /api/v1/posts/{post_id}/comments — list comments.""" def test_list_comments_by_post( self, client: TestClient, user_headers: dict[str, str], created_post: dict[str, Any], ) -> None: """Test listing comments for a post. TC-API-121: Positive — returns list of comments. """ post_id = created_post["id"] client.post( f"{API_PREFIX}/{post_id}/comments", json={"content": COMMENT_CONTENT}, headers=user_headers, ) client.post( f"{API_PREFIX}/{post_id}/comments", json={"content": "Second comment."}, headers=user_headers, ) response = client.get(f"{API_PREFIX}/{post_id}/comments") assert response.status_code == 200 data = response.json() assert len(data) == 2 assert data[0]["content"] == COMMENT_CONTENT assert data[1]["content"] == "Second comment." class TestDeleteComment: """Tests for DELETE /api/v1/comments/{comment_id} — delete a comment.""" def test_delete_own_comment( self, client: TestClient, user_headers: dict[str, str], created_post: dict[str, Any], ) -> None: """Test deleting own comment returns 204. TC-API-122: Positive — delete own comment. """ post_id = created_post["id"] comment_resp = client.post( f"{API_PREFIX}/{post_id}/comments", json={"content": COMMENT_CONTENT}, headers=user_headers, ) comment_id = comment_resp.json()["id"] response = client.delete( f"/api/v1/comments/{comment_id}", headers=user_headers, ) assert response.status_code == 204 def test_delete_comment_not_owner( self, client: TestClient, user_headers: dict[str, str], user2_headers: dict[str, str], created_post: dict[str, Any], ) -> None: """Test deleting another user's comment returns 403. TC-API-123: Negative — not owner. """ post_id = created_post["id"] comment_resp = client.post( f"{API_PREFIX}/{post_id}/comments", json={"content": COMMENT_CONTENT}, headers=user_headers, ) comment_id = comment_resp.json()["id"] response = client.delete( f"/api/v1/comments/{comment_id}", headers=user2_headers, ) assert response.status_code == 403 class TestLikeComment: """Tests for POST /api/v1/comments/{comment_id}/like — like a comment.""" def test_like_comment_authenticated( self, client: TestClient, user_headers: dict[str, str], created_post: dict[str, Any], ) -> None: """Test liking a comment as authenticated user. TC-API-124: Positive — authenticated like on. """ post_id = created_post["id"] comment_resp = client.post( f"{API_PREFIX}/{post_id}/comments", json={"content": COMMENT_CONTENT}, headers=user_headers, ) comment_id = comment_resp.json()["id"] response = client.post( f"/api/v1/comments/{comment_id}/like", headers=user_headers, ) assert response.status_code == 200 data = response.json() assert data["like_count"] == 1 assert data["id"] == comment_id def test_like_comment_as_guest( self, client: TestClient, guest_headers: dict[str, str], user_headers: dict[str, str], created_post: dict[str, Any], ) -> None: """Test liking a comment as guest returns 401. TC-API-125: Negative — guest cannot like. """ post_id = created_post["id"] comment_resp = client.post( f"{API_PREFIX}/{post_id}/comments", json={"content": COMMENT_CONTENT}, headers=user_headers, ) comment_id = comment_resp.json()["id"] response = client.post( f"/api/v1/comments/{comment_id}/like", headers=guest_headers, ) assert response.status_code == 401