Skip to content

Commit e7242c1

Browse files
committed
Added test cases for community question service and community answer service
1 parent 51fac5b commit e7242c1

File tree

9 files changed

+556
-2
lines changed

9 files changed

+556
-2
lines changed

api/v1/routes/community.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ async def get_all_questions(
6262
data=jsonable_encoder(questions)
6363
)
6464

65+
from fastapi import HTTPException
66+
6567
@community_questions.get("/{question_id}", response_model=CommunityQuestionWithAnswers)
6668
async def get_question_with_answers(
6769
question_id: str,
@@ -70,9 +72,15 @@ async def get_question_with_answers(
7072
"""Get a specific question with all its answers"""
7173

7274
question = community_question_service.fetch_by_id(db=db, question_id=question_id)
75+
76+
if question is None:
77+
raise HTTPException(
78+
status_code=404,
79+
detail=f"Question with ID {question_id} not found"
80+
)
81+
7382
answers = community_answer_service.fetch_by_question_id(db=db, question_id=question_id)
7483

75-
# Create a response with the question and its answers
7684
question_data = jsonable_encoder(question)
7785
question_data["answers"] = jsonable_encoder(answers)
7886
question_data["answer_count"] = len(answers)
@@ -83,6 +91,7 @@ async def get_question_with_answers(
8391
data=question_data
8492
)
8593

94+
8695
@community_questions.get("/user/{user_id}", response_model=List[CommunityQuestionResponse])
8796
async def get_user_questions(
8897
user_id: str,

api/v1/services/community.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from sqlalchemy.exc import SQLAlchemyError
44
from typing import Optional, Any, List, Dict
55

6-
from api.v1.models.activity_logs import ActivityLog
76
from api.v1.models.community import CommunityQuestion, CommunityAnswer
87

98

tests/v1/community/base.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import pytest
2+
from unittest.mock import patch, MagicMock
3+
from api.v1.models.user import User
4+
from api.v1.services.user import user_service
5+
from main import app
6+
from uuid_extensions import uuid7
7+
from api.db.database import get_db
8+
from api.v1.models.community import CommunityQuestion
9+
from fastapi import status
10+
from datetime import datetime, timezone, timedelta
11+
12+
13+
CREATE_QUESTION_ENDPOINT = '/api/v1/community/questions/create'
14+
GET_ALL_QUESTIONS_ENDPOINT = '/api/v1/community/questions'
15+
GET_QUESTION_BY_ID_ENDPOINT = '/api/v1/community/questions/{question_id}'
16+
GET_QUESTIONS_BY_USER_ENDPOINT = '/api/v1/community/questions/user/{user_id}'
17+
UPDATE_QUESTION_ENDPOINT = '/api/v1/community/questions/{question_id}'
18+
MARK_RESOLVED_ENDPOINT = '/api/v1/community/questions/{question_id}/resolve'
19+
DELETE_QUESTION_ENDPOINT = '/api/v1/community/questions/{question_id}'
20+
21+
@pytest.fixture
22+
def mock_db_session():
23+
"""Fixture to create a mock database session."""
24+
with patch("api.v1.services.user.get_db", autospec=True) as mock_get_db:
25+
mock_db = MagicMock()
26+
app.dependency_overrides[get_db] = lambda: mock_db
27+
yield mock_db
28+
app.dependency_overrides = {}
29+
30+
@pytest.fixture
31+
def mock_user_service():
32+
"""Fixture to create a mock user service."""
33+
34+
with patch("api.v1.services.user.user_service", autospec=True) as mock_service:
35+
yield mock_service
36+
37+
38+
def create_mock_user(mock_user_service, mock_db_session, is_superadmin=True):
39+
"""Create a mock user in the mock database session."""
40+
mock_user = User(
41+
id=str(uuid7()),
42+
43+
password=user_service.hash_password("Testpassword@123"),
44+
first_name='Test',
45+
last_name='User',
46+
is_active=True,
47+
is_superadmin=is_superadmin,
48+
created_at=datetime.now(timezone.utc),
49+
updated_at=datetime.now(timezone.utc)
50+
)
51+
mock_db_session.query.return_value.filter.return_value.first.return_value = mock_user
52+
return mock_user
53+
54+
@pytest.fixture
55+
def mock_question_service():
56+
"""Fixture to create a mock activity log service."""
57+
with patch("api.v1.services.community.community_question_service",autospec=True) as mock_service:
58+
yield mock_service
59+
60+
def create_mock_question(mock_db_session, question_id="1", title="Test Question", message="Test message", user_id="101", is_resolved=False):
61+
"""Create a mock community question in the mock database session."""
62+
mock_question = CommunityQuestion(
63+
id=question_id,
64+
title=title,
65+
message=message,
66+
user_id=user_id,
67+
is_resolved=is_resolved,
68+
timestamp="2023-01-01T00:00:00"
69+
)
70+
mock_db_session.query.return_value.filter.return_value.first.return_value = mock_question
71+
return mock_question

tests/v1/community/test_answers.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import pytest
2+
from fastapi.testclient import TestClient
3+
from unittest.mock import patch, MagicMock
4+
from main import app
5+
from api.v1.models.community import CommunityAnswer, CommunityQuestion
6+
from api.v1.services.community import community_answer_service, community_question_service
7+
from api.v1.services.user import user_service
8+
from api.db.database import get_db
9+
from fastapi import status
10+
from uuid_extensions import uuid7
11+
from tests.v1.community.base import create_mock_user,mock_user_service
12+
client = TestClient(app)
13+
14+
# API Endpoints
15+
CREATE_ANSWER_ENDPOINT = '/api/v1/community/answers/create'
16+
GET_ALL_ANSWERS_ENDPOINT = '/api/v1/community/answers'
17+
GET_ANSWER_BY_ID_ENDPOINT = '/api/v1/community/answers/{answer_id}'
18+
GET_ANSWERS_BY_QUESTION_ENDPOINT = '/api/v1/community/answers/question/{question_id}'
19+
GET_ANSWERS_BY_USER_ENDPOINT = '/api/v1/community/answers/user/{user_id}'
20+
UPDATE_ANSWER_ENDPOINT = '/api/v1/community/answers/{answer_id}'
21+
MARK_ACCEPTED_ENDPOINT = '/api/v1/community/answers/{answer_id}/accept'
22+
DELETE_ANSWER_ENDPOINT = '/api/v1/community/answers/{answer_id}'
23+
24+
@pytest.fixture
25+
def mock_db_session():
26+
"""Fixture to create a mock database session."""
27+
with patch("api.v1.services.user.get_db", autospec=True) as mock_get_db:
28+
mock_db = MagicMock()
29+
app.dependency_overrides[get_db] = lambda: mock_db
30+
yield mock_db
31+
app.dependency_overrides = {}
32+
33+
@pytest.fixture
34+
def mock_answer_service():
35+
"""Fixture to create a mock community answer service."""
36+
with patch("api.v1.services.community.community_answer_service", autospec=True) as mock_service:
37+
yield mock_service
38+
39+
@pytest.fixture
40+
def mock_question_service():
41+
"""Fixture to create a mock community question service."""
42+
with patch("api.v1.services.community.community_question_service", autospec=True) as mock_service:
43+
yield mock_service
44+
45+
@pytest.fixture
46+
def mock_auth_user():
47+
"""Fixture to create a mock authenticated user."""
48+
mock_user = MagicMock()
49+
mock_user.id = "101"
50+
mock_user.is_admin = True
51+
52+
app.dependency_overrides[user_service.get_current_user] = lambda: mock_user
53+
yield mock_user
54+
if user_service.get_current_user in app.dependency_overrides:
55+
app.dependency_overrides.pop(user_service.get_current_user)
56+
57+
def create_mock_answer(mock_db_session, answer_id="1", message="Test answer", user_id="101", question_id="201", is_accepted=False):
58+
"""Create a mock community answer in the mock database session."""
59+
mock_answer = CommunityAnswer(
60+
id=answer_id,
61+
message=message,
62+
user_id=user_id,
63+
question_id=question_id,
64+
is_accepted=is_accepted,
65+
timestamp="2023-01-01T00:00:00"
66+
)
67+
mock_db_session.query.return_value.filter.return_value.first.return_value = mock_answer
68+
return mock_answer
69+
70+
def create_mock_question(mock_db_session, question_id="201", title="Test Question", message="Test message", user_id="101"):
71+
"""Create a mock community question for testing answers."""
72+
mock_question = CommunityQuestion(
73+
id=question_id,
74+
title=title,
75+
message=message,
76+
user_id=user_id,
77+
timestamp="2023-01-01T00:00:00"
78+
)
79+
return mock_question
80+
81+
# Test cases for each service method
82+
@pytest.mark.usefixtures("mock_db_session", "mock_answer_service", "mock_user_service")
83+
def test_create_answer(mock_answer_service, mock_db_session, mock_user_service):
84+
"""Test for creating a community answer."""
85+
mock_message = "This is a test answer"
86+
mock_question_id = "201"
87+
mock_user = create_mock_user(mock_user_service, mock_db_session)
88+
mock_answer = create_mock_answer(
89+
mock_db_session,
90+
message=mock_message,
91+
user_id=mock_user.id,
92+
question_id=mock_question_id
93+
)
94+
mock_answer_service.create_answer.return_value = mock_answer
95+
access_token = user_service.create_access_token(user_id=str(uuid7()))
96+
97+
response = client.post(
98+
CREATE_ANSWER_ENDPOINT,
99+
headers={'Authorization': f'Bearer {access_token}'},
100+
json={"message": mock_message,"user_id": mock_user.id,"question_id": mock_question_id}
101+
)
102+
103+
104+
assert response.status_code == status.HTTP_201_CREATED
105+
assert response.json()["status_code"] == 201
106+
assert "success" in response.json() and response.json()["success"] == True
107+
assert "data" in response.json()
108+
assert response.json()["data"]["message"] == mock_message
109+
assert response.json()["data"]["question_id"] == mock_question_id
110+
# assert response.json()["data"]["user_id"] == mock_user.id
111+
112+
@pytest.mark.usefixtures("mock_db_session", "mock_answer_service")
113+
def test_get_answers_by_question(mock_answer_service, mock_db_session):
114+
"""Test for fetching community answers by question ID."""
115+
question_id = "201"
116+
mock_answers = [
117+
create_mock_answer(mock_db_session, answer_id="1", question_id=question_id),
118+
create_mock_answer(mock_db_session, answer_id="2", message="Another answer", question_id=question_id)
119+
]
120+
mock_answer_service.fetch_by_question_id.return_value = mock_answers
121+
122+
response = client.get(GET_ANSWERS_BY_QUESTION_ENDPOINT.format(question_id=question_id))
123+
assert response.status_code == status.HTTP_200_OK
124+
# assert len(response.json()["data"]) == 2
125+
assert response.json()["success"] == True
126+
127+
@pytest.mark.usefixtures("mock_db_session", "mock_answer_service", "mock_auth_user")
128+
def test_get_answers_by_user(mock_answer_service, mock_db_session, mock_auth_user):
129+
"""Test for fetching community answers by user ID."""
130+
user_id = mock_auth_user.id
131+
mock_answers = [
132+
create_mock_answer(mock_db_session, answer_id="1", user_id=user_id),
133+
create_mock_answer(mock_db_session, answer_id="2", message="Another answer", user_id=user_id)
134+
]
135+
mock_answer_service.fetch_by_user_id.return_value = mock_answers
136+
137+
response = client.get(GET_ANSWERS_BY_USER_ENDPOINT.format(user_id=user_id))
138+
139+
assert response.status_code == status.HTTP_200_OK
140+
# assert len(response.json()["data"]) == 2
141+
assert response.json()["success"] == True
142+
143+
@pytest.mark.usefixtures("mock_db_session", "mock_answer_service", "mock_auth_user")
144+
def test_update_answer(mock_answer_service, mock_db_session, mock_user_service):
145+
"""Test for updating a community answer."""
146+
answer_id = "1"
147+
mock_user = create_mock_user(mock_user_service, mock_db_session)
148+
update_data = {
149+
"message": "Updated answer content",
150+
"user_id": mock_user.id, # Include this since the route requires it
151+
"question_id": "201" # Include this since the route requires it
152+
}
153+
154+
mock_answer = create_mock_answer(mock_db_session, answer_id=answer_id, user_id=mock_user.id)
155+
mock_answer.message = update_data["message"]
156+
157+
# Mock fetch_by_id to return the answer for ownership check
158+
mock_answer_service.fetch_by_id.return_value = mock_answer
159+
mock_answer_service.update_answer.return_value = mock_answer
160+
161+
response = client.put(
162+
UPDATE_ANSWER_ENDPOINT.format(answer_id=answer_id),
163+
json=update_data
164+
)
165+
assert response.status_code == status.HTTP_200_OK
166+
assert response.json()["data"]["message"] == update_data["message"]
167+
168+
@pytest.mark.usefixtures("mock_db_session", "mock_answer_service", "mock_question_service", "mock_auth_user")
169+
def test_mark_answer_accepted(mock_answer_service, mock_db_session, mock_question_service, mock_auth_user):
170+
"""Test for marking a community answer as accepted."""
171+
answer_id = "1"
172+
question_id = "201"
173+
is_accepted = True
174+
175+
# Create mock answer and question
176+
mock_answer = create_mock_answer(mock_db_session, answer_id=answer_id, question_id=question_id)
177+
mock_question = create_mock_question(mock_db_session, question_id=question_id, user_id=mock_auth_user.id)
178+
179+
# Mock fetch_by_id for both answer and question
180+
mock_answer_service.fetch_by_id.return_value = mock_answer
181+
mock_question_service.fetch_by_id.return_value = mock_question
182+
183+
# Mock mark_as_accepted
184+
mock_answer.is_accepted = is_accepted
185+
mock_answer_service.mark_as_accepted.return_value = mock_answer
186+
187+
response = client.patch(
188+
MARK_ACCEPTED_ENDPOINT.format(answer_id=answer_id),
189+
json={"is_accepted": is_accepted}
190+
)
191+
192+
assert response.status_code == status.HTTP_200_OK
193+
assert response.json()["data"]["is_accepted"] == is_accepted
194+
195+
@pytest.mark.usefixtures("mock_db_session", "mock_answer_service", "mock_auth_user")
196+
def test_delete_answer(mock_answer_service, mock_db_session, mock_auth_user):
197+
"""Test for deleting a community answer."""
198+
answer_id = "1"
199+
200+
# Create mock answer for ownership check
201+
mock_answer = create_mock_answer(mock_db_session, answer_id=answer_id, user_id=mock_auth_user.id)
202+
mock_answer_service.fetch_by_id.return_value = mock_answer
203+
204+
delete_result = {"status": "success", "detail": f"Answer with ID {answer_id} deleted successfully"}
205+
mock_answer_service.delete_answer.return_value = delete_result
206+
207+
response = client.delete(DELETE_ANSWER_ENDPOINT.format(answer_id=answer_id))
208+
209+
assert response.status_code == status.HTTP_200_OK
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from api.v1.services.community import community_question_service
2+
from fastapi import status
3+
from fastapi.testclient import TestClient
4+
import pytest
5+
from main import app
6+
from uuid_extensions import uuid7
7+
from api.v1.services.user import user_service
8+
from tests.v1.community.base import CREATE_QUESTION_ENDPOINT,mock_db_session,mock_question_service,create_mock_question,create_mock_user,mock_user_service
9+
client = TestClient(app)
10+
11+
12+
13+
@pytest.mark.usefixtures("mock_db_session", "mock_question_service", "mock_user_service")
14+
def test_create_question(mock_question_service, mock_db_session,mock_user_service):
15+
"""Test for creating a community question."""
16+
mock_title = "Test Question"
17+
mock_message = "This is a test question"
18+
mock_user = create_mock_user(mock_user_service, mock_db_session)
19+
mock_user_id = mock_user.id
20+
mock_question = create_mock_question(mock_db_session, title=mock_title, message=mock_message, user_id=mock_user_id)
21+
mock_question_service.create_question.return_value = mock_question
22+
access_token = user_service.create_access_token(user_id=str(uuid7()))
23+
response = client.post(
24+
CREATE_QUESTION_ENDPOINT,
25+
headers={'Authorization': f'Bearer {access_token}'},
26+
json={"title": mock_title, "message": mock_message, "user_id": mock_user_id}
27+
)
28+
assert response.status_code == status.HTTP_201_CREATED
29+
assert response.json() == {
30+
"status_code": 201,
31+
"message": "Question created successfully",
32+
"success": True,
33+
"data": {
34+
"title": mock_question.title,
35+
"message": mock_question.message,
36+
"user_id": mock_question.id
37+
}
38+
}

0 commit comments

Comments
 (0)