feat: adding unit test for service

This commit is contained in:
akhdanre 2025-05-08 01:55:44 +07:00
parent f747880122
commit 7a07543971
28 changed files with 776 additions and 148 deletions

View File

@ -42,7 +42,7 @@ class QuizController:
result = self.quiz_service.get_quiz_recommendation() result = self.quiz_service.get_quiz_recommendation()
if not result: if not result:
return make_response(message="Quiz not found", status_code=404) return make_response(message="Quiz not found", status_code=404)
return make_response(message="Quiz Found", data=result.dict()) return make_response(message="Quiz Found", data=result.model_dump())
except Exception as e: except Exception as e:
return make_error_response(e) return make_error_response(e)

View File

@ -1,8 +1,10 @@
from .user_mapper import UserMapper from .user_mapper import UserMapper
from .quiz_mapper import QuizMapper from .quiz_mapper import QuizMapper
from .subject_mapper import SubjectMapper
__all__ = [ __all__ = [
"UserMapper", "UserMapper",
"QuizMapper", "QuizMapper",
"SubjectMapper",
] ]

View File

@ -0,0 +1,12 @@
from app.schemas.requests import SubjectCreateRequest
from app.models.entities import SubjectEntity
class SubjectMapper:
@staticmethod
def to_entity(data: SubjectCreateRequest) -> SubjectEntity:
return SubjectEntity(
name=data.name,
short_name=data.alias,
description=data.description,
)

View File

@ -18,7 +18,7 @@ class QuizEntity(BaseModel):
total_user_playing: int = 0 total_user_playing: int = 0
question_listings: Optional[list[QuestionItemEntity]] = [] question_listings: Optional[list[QuestionItemEntity]] = []
class Config: class ConfigDict:
arbitrary_types_allowed = True arbitrary_types_allowed = True
populate_by_name = True populate_by_name = True
json_encoders = {PyObjectId: str} json_encoders = {PyObjectId: str}

View File

@ -11,14 +11,7 @@ class SubjectEntity(BaseModel):
description: Optional[str] = None description: Optional[str] = None
icon: Optional[str] = None icon: Optional[str] = None
class Config: class ConfigDict:
populate_by_name = True populate_by_name = True
json_encoders = {ObjectId: str} json_encoders = {ObjectId: str}
json_schema_extra = { json_schema_extra = {}
"example": {
"_id": "sejarah",
"name": "Sejarah",
"description": "Kuis tentang sejarah Indonesia",
"icon": "http://",
}
}

View File

@ -17,7 +17,7 @@ class UserAnswerEntity(BaseModel):
total_score: int total_score: int
total_correct: int total_correct: int
class Config: class ConfigDict:
populate_by_name = True populate_by_name = True
arbitrary_types_allowed = True arbitrary_types_allowed = True
json_encoders = {ObjectId: str} json_encoders = {ObjectId: str}

View File

@ -1,5 +1,5 @@
from typing import Optional from typing import Optional
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, ConfigDict
from datetime import datetime from datetime import datetime
from .base import PyObjectId from .base import PyObjectId
@ -17,6 +17,4 @@ class UserEntity(BaseModel):
created_at: Optional[datetime] = None created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None updated_at: Optional[datetime] = None
class Config: model_config = ConfigDict(populate_by_name=True, json_encoders={PyObjectId: str})
populate_by_name = True
json_encoders = {PyObjectId: str}

View File

@ -13,7 +13,7 @@ class UserResponseModel(BaseModel):
phone: Optional[str] = None phone: Optional[str] = None
locale: str locale: str
class Config: class ConfigDict:
populate_by_name = True populate_by_name = True
json_encoders = { json_encoders = {
datetime: lambda v: v.isoformat(), datetime: lambda v: v.isoformat(),

View File

@ -8,6 +8,6 @@ class GetSubjectResponse(BaseModel):
alias: str alias: str
description: Optional[str] description: Optional[str]
class Config: class ConfigDict:
from_attributes = True from_attributes = True
populate_by_name = True populate_by_name = True

View File

@ -55,7 +55,7 @@ class AnswerService:
elif question.type == "true_false": elif question.type == "true_false":
correct = user_answer.answer == question.target_answer correct = user_answer.answer == question.target_answer
elif question.type == "option": elif question.type == "option":
try:
answer_index = int(user_answer.answer) answer_index = int(user_answer.answer)
if 0 <= answer_index < len(question.options): if 0 <= answer_index < len(question.options):
correct = str(answer_index) == question.target_answer correct = str(answer_index) == question.target_answer
@ -63,10 +63,7 @@ class AnswerService:
raise ValueError( raise ValueError(
f"Index jawaban tidak valid untuk soal {question.index}" f"Index jawaban tidak valid untuk soal {question.index}"
) )
except ValueError:
raise ValueError(
f"Jawaban bukan index valid untuk soal {question.index}"
)
else: else:
raise ValueError(f"Tipe soal tidak dikenali: {question.type}") raise ValueError(f"Tipe soal tidak dikenali: {question.type}")

View File

@ -17,9 +17,6 @@ class AuthService:
id_token_str, requests.Request(), Config.GOOGLE_CLIENT_ID id_token_str, requests.Request(), Config.GOOGLE_CLIENT_ID
) )
if not payload:
raise AuthException("Invalid Google ID Token")
google_id = payload.get("sub") google_id = payload.get("sub")
email = payload.get("email") email = payload.get("email")

View File

@ -1,5 +1,9 @@
from app.repositories import UserAnswerRepository, QuizRepository from app.repositories import UserAnswerRepository, QuizRepository
from app.schemas.response import HistoryResultSchema, QuizHistoryResponse, QuestionResult from app.schemas.response import (
HistoryResultSchema,
QuizHistoryResponse,
QuestionResult,
)
class HistoryService: class HistoryService:
@ -19,7 +23,7 @@ class HistoryService:
quiz_ids = [asn.quiz_id for asn in answer_data] quiz_ids = [asn.quiz_id for asn in answer_data]
quiz_data = self.quiz_repository.get_by_ids(quiz_ids) quiz_data = self.quiz_repository.get_by_ids(quiz_ids)
quiz_map = {str(quiz.id): quiz for quiz in quiz_data} quiz_map = {str(quiz.id): quiz for quiz in quiz_data}
print(quiz_map)
result = [] result = []
for answer in answer_data: for answer in answer_data:
quiz = quiz_map.get(answer.quiz_id) quiz = quiz_map.get(answer.quiz_id)

View File

@ -46,7 +46,7 @@ class QuizService:
if author is None: if author is None:
continue continue
mapped_quizzes.append( mapped_quizzes.append(
QuizMapper.quiz_to_recomendation_app.mapper( QuizMapper.quiz_to_recomendation_mapper(
quiz_entity=quiz, quiz_entity=quiz,
user_entity=author, user_entity=author,
) )
@ -68,7 +68,7 @@ class QuizService:
user = self.user_repostory.get_user_by_id(user_id) user = self.user_repostory.get_user_by_id(user_id)
quiz_data = [ quiz_data = [
QuizMapper.quiz_to_recomendation_app.mapper(quiz, user) for quiz in quizzes QuizMapper.quiz_to_recomendation_mapper(quiz, user) for quiz in quizzes
] ]
return UserQuizListResponse(total=total_user_quiz, quizzes=quiz_data) return UserQuizListResponse(total=total_user_quiz, quizzes=quiz_data)
@ -110,7 +110,7 @@ class QuizService:
for quiz in data: for quiz in data:
author = self.user_repostory.get_user_by_id(user_id=quiz.author_id) author = self.user_repostory.get_user_by_id(user_id=quiz.author_id)
result.append( result.append(
QuizMapper.quiz_to_recomendation_app.mapper( QuizMapper.quiz_to_recomendation_mapper(
quiz_entity=quiz, quiz_entity=quiz,
user_entity=author, user_entity=author,
) )

View File

@ -3,6 +3,7 @@ from app.models.entities import SubjectEntity
from app.schemas.requests import SubjectCreateRequest, SubjectUpdateRequest from app.schemas.requests import SubjectCreateRequest, SubjectUpdateRequest
from app.schemas.response import GetSubjectResponse from app.schemas.response import GetSubjectResponse
from app.repositories import SubjectRepository from app.repositories import SubjectRepository
from app.mapper import SubjectMapper
class SubjectService: class SubjectService:
@ -10,7 +11,7 @@ class SubjectService:
self.repository = repository self.repository = repository
def create_subject(self, request: SubjectCreateRequest) -> str: def create_subject(self, request: SubjectCreateRequest) -> str:
subject = SubjectEntity(**request) subject = SubjectMapper.to_entity(request)
return self.repository.create(subject) return self.repository.create(subject)
def get_all_subjects(self) -> List[GetSubjectResponse]: def get_all_subjects(self) -> List[GetSubjectResponse]:
@ -37,7 +38,7 @@ class SubjectService:
return None return None
def update_subject(self, subject_id: str, request: SubjectUpdateRequest) -> bool: def update_subject(self, subject_id: str, request: SubjectUpdateRequest) -> bool:
update_data = request.dict(exclude_unset=True) update_data = request.model_dump(exclude_unset=True)
return self.repository.update(subject_id, update_data) return self.repository.update(subject_id, update_data)
def delete_subject(self, subject_id: str) -> bool: def delete_subject(self, subject_id: str) -> bool:

View File

@ -1,3 +1,7 @@
# pytest.ini # pytest.ini
[pytest] [pytest]
pythonpath = . pythonpath = .
filterwarnings =
ignore::DeprecationWarning
ignore::UserWarning

5
run.py
View File

@ -3,6 +3,5 @@ from app.main import createApp, socketio
app = createApp() app = createApp()
# if __name__ == "__main__": if __name__ == "__main__":
# # Untuk dev/testing socketio.run(app, host="0.0.0.0", port=5000, debug=True)
# socketio.run(app, host="0.0.0.0", port=5000, debug=True)

View File

@ -0,0 +1,178 @@
import pytest
from unittest.mock import MagicMock
from datetime import datetime
from app.services.answer_service import AnswerService
from app.schemas.requests import UserAnswerSchema, AnswerItemSchema
from app.models.entities import QuestionItemEntity
from app.models import UserAnswerEntity
from app.exception import ValidationException
@pytest.fixture
def mock_answer_repository():
return MagicMock()
@pytest.fixture
def mock_quiz_repository():
return MagicMock()
@pytest.fixture
def mock_user_repository():
return MagicMock()
@pytest.fixture
def answer_service(mock_answer_repository, mock_quiz_repository, mock_user_repository):
return AnswerService(
answer_repository=mock_answer_repository,
quiz_repository=mock_quiz_repository,
user_repositroy=mock_user_repository,
)
def test_create_answer_success(
answer_service, mock_quiz_repository, mock_user_repository, mock_answer_repository
):
# Setup dummy quiz
mock_quiz = MagicMock()
mock_quiz.id = "quiz1"
mock_quiz.total_user_playing = 5
mock_quiz.question_listings = [
QuestionItemEntity(
index=0,
question="Soal 1",
type="fill_the_blank",
target_answer="Jakarta",
duration=10,
options=[],
),
QuestionItemEntity(
index=1,
question="Soal 2",
type="true_false",
target_answer="True",
duration=10,
options=[],
),
QuestionItemEntity(
index=2,
question="Soal 3",
type="option",
target_answer="1",
duration=10,
options=["A", "B", "C"],
),
]
mock_quiz_repository.get_by_id.return_value = mock_quiz
# Setup dummy user
mock_user_repository.get_user_by_id.return_value = MagicMock()
# Setup user answer input
answer_data = UserAnswerSchema(
session_id="session1",
quiz_id="quiz1",
user_id="user1",
answered_at=datetime.now(),
answers=[
AnswerItemSchema(
question_index=0,
answer="Jakarta",
time_spent=10,
is_correct=True,
),
AnswerItemSchema(
question_index=1,
answer="True",
time_spent=5,
is_correct=True,
),
AnswerItemSchema(
question_index=2,
answer="1",
time_spent=7,
is_correct=True,
),
],
)
mock_answer_repository.create.return_value = "answer_id_123"
result = answer_service.create_answer(answer_data)
assert result == "answer_id_123"
mock_quiz_repository.update_user_playing.assert_called_once_with(
quiz_id="quiz1", total_user=6
)
mock_answer_repository.create.assert_called_once()
assert all([a.is_correct is not None for a in answer_data.answers])
def test_create_answer_quiz_not_found(answer_service, mock_quiz_repository):
mock_quiz_repository.get_by_id.return_value = None
answer_data = UserAnswerSchema(
session_id="s1",
quiz_id="nonexistent",
user_id="u1",
answered_at=datetime.now(),
answers=[],
)
with pytest.raises(ValidationException, match="Quiz not found"):
answer_service.create_answer(answer_data)
def test_create_answer_user_not_found(
answer_service, mock_quiz_repository, mock_user_repository
):
mock_quiz_repository.get_by_id.return_value = MagicMock()
mock_user_repository.get_user_by_id.return_value = None
answer_data = UserAnswerSchema(
session_id="s1",
quiz_id="q1",
user_id="unknown_user",
answered_at=datetime.now(),
answers=[],
)
with pytest.raises(ValidationException, match="user is not registered"):
answer_service.create_answer(answer_data)
def test_create_answer_invalid_option_index(
answer_service, mock_quiz_repository, mock_user_repository
):
quiz = MagicMock()
quiz.id = "quiz1"
quiz.total_user_playing = 0
quiz.question_listings = [
QuestionItemEntity(
index=0,
question="Soal 1",
type="option",
target_answer="1",
duration=10,
options=["A", "B", "C"],
),
]
mock_quiz_repository.get_by_id.return_value = quiz
mock_user_repository.get_user_by_id.return_value = MagicMock()
answer_data = UserAnswerSchema(
session_id="s1",
quiz_id="quiz1",
user_id="user1",
answered_at=datetime.now(),
answers=[
AnswerItemSchema(
question_index=0, answer="5", time_spent=3, is_correct=False
)
],
)
with pytest.raises(ValueError, match="Index jawaban tidak valid"):
answer_service.create_answer(answer_data)

View File

@ -0,0 +1,113 @@
import pytest
from unittest.mock import MagicMock, patch
from app.services.auth_service import AuthService
from app.schemas import LoginSchema
from app.exception import AuthException
from werkzeug.security import generate_password_hash
@pytest.fixture
def mock_user_repository():
return MagicMock()
@pytest.fixture
def auth_service(mock_user_repository):
return AuthService(userRepository=mock_user_repository)
@pytest.fixture
def dummy_user():
return MagicMock(
id="user123",
email="user@example.com",
password=generate_password_hash("secret"),
)
# --- verify_google_id_token tests ---
@patch("app.services.auth_service.id_token.verify_oauth2_token")
def test_verify_google_existing_user(
mock_verify, auth_service, mock_user_repository, dummy_user
):
# Simulate valid token
mock_verify.return_value = {"sub": "google-id-123", "email": "user@example.com"}
mock_user_repository.get_by_google_id.return_value = dummy_user
user = auth_service.verify_google_id_token("valid_token")
assert user == dummy_user
mock_user_repository.get_by_google_id.assert_called_once()
@patch("app.services.auth_service.id_token.verify_oauth2_token")
def test_verify_google_new_user(
mock_verify, auth_service, mock_user_repository, dummy_user
):
mock_verify.return_value = {
"sub": "new-google-id",
"email": "newuser@example.com",
"name": "New User",
}
mock_user_repository.get_by_google_id.return_value = None
mock_user_repository.insert_user.return_value = "new-user-id"
mock_user_repository.get_user_by_id.return_value = dummy_user
with patch(
"app.services.auth_service.UserMapper.from_google_payload"
) as mock_mapper:
mock_mapper.return_value = dummy_user
user = auth_service.verify_google_id_token("new_token")
assert user == dummy_user
mock_user_repository.insert_user.assert_called_once()
mock_user_repository.get_user_by_id.assert_called_once_with(user_id="new-user-id")
@patch("app.services.auth_service.id_token.verify_oauth2_token")
def test_verify_google_email_mismatch(
mock_verify, auth_service, mock_user_repository, dummy_user
):
mock_verify.return_value = {"sub": "google-id-123", "email": "wrong@example.com"}
dummy_user.email = "correct@example.com"
mock_user_repository.get_by_google_id.return_value = dummy_user
with pytest.raises(AuthException, match="Email not match"):
auth_service.verify_google_id_token("token")
# @patch("app.services.auth_service.id_token.verify_oauth2_token")
# def test_verify_google_invalid_token(mock_verify, auth_service):
# mock_verify.side_effect = ValueError("Invalid token")
# with pytest.raises(AuthException):
# auth_service.verify_google_id_token("invalid_token")
# --- login tests ---
def test_login_success(auth_service, mock_user_repository, dummy_user):
mock_user_repository.get_user_by_email.return_value = dummy_user
schema = LoginSchema(email="user@example.com", password="secret")
user = auth_service.login(schema)
assert user.email == "user@example.com"
assert user.password is None
def test_login_wrong_password(auth_service, mock_user_repository, dummy_user):
mock_user_repository.get_user_by_email.return_value = dummy_user
schema = LoginSchema(email="user@example.com", password="wrong")
user = auth_service.login(schema)
assert user is None
def test_login_user_not_found(auth_service, mock_user_repository):
mock_user_repository.get_user_by_email.return_value = None
schema = LoginSchema(email="unknown@example.com", password="any")
user = auth_service.login(schema)
assert user is None

View File

@ -0,0 +1,129 @@
import pytest
from unittest.mock import MagicMock
from datetime import datetime
from app.services.history_service import HistoryService
from app.models.entities import QuizEntity, UserAnswerEntity
from app.schemas.response import HistoryResultSchema, QuizHistoryResponse
from app.models.entities import AnswerItemEntity, QuestionItemEntity
import datetime
from bson import ObjectId
@pytest.fixture
def mock_quiz_repository():
return MagicMock()
@pytest.fixture
def mock_answer_repository():
return MagicMock()
@pytest.fixture
def history_service(mock_quiz_repository, mock_answer_repository):
return HistoryService(
quiz_repository=mock_quiz_repository, answer_repository=mock_answer_repository
)
def test_get_history_by_user_id(
history_service, mock_answer_repository, mock_quiz_repository
):
mock_answers = [
UserAnswerEntity(
id="answer1",
session_id="",
quiz_id="6815da9f37a1ce472ba72819",
user_id="user123",
total_correct=8,
total_score=80,
answered_at=datetime.datetime(2024, 1, 1, 10, 30, 0),
answers=[],
)
]
mock_quiz = [
QuizEntity(
_id=ObjectId("6815da9f37a1ce472ba72819"),
subject_id="",
title="Quiz Matematika",
description="Soal dasar matematika",
total_quiz=10,
author_id="author1",
date=datetime.datetime(2024, 2, 2, 14, 0, 0),
question_listings=[],
)
]
mock_answer_repository.get_by_user.return_value = mock_answers
mock_quiz_repository.get_by_ids.return_value = mock_quiz
result = history_service.get_history_by_user_id("user123")
assert len(result) == 1
assert isinstance(result[0], HistoryResultSchema)
assert result[0].quiz_id == "6815da9f37a1ce472ba72819"
assert result[0].total_correct == 8
assert result[0].total_question == 10
def test_get_history_by_user_id_no_data(history_service, mock_answer_repository):
mock_answer_repository.get_by_user.return_value = []
result = history_service.get_history_by_user_id("user123")
assert result == []
def test_get_history_by_answer_id(
history_service, mock_answer_repository, mock_quiz_repository
):
mock_answer = UserAnswerEntity(
id="answer1",
session_id="",
user_id="user1",
quiz_id="quiz1",
total_correct=7,
total_score=70,
answered_at=datetime.datetime(2024, 2, 2, 14, 0, 0),
answers=[
AnswerItemEntity(
question_index=0,
answer="B",
is_correct=True,
time_spent=12,
)
],
)
mock_quiz = QuizEntity(
id="quiz1",
title="Quiz IPA",
description="Ilmu Pengetahuan Alam",
subject_id="subject1",
date=datetime.datetime(2025, 5, 5, 0, 0),
total_quiz=10,
author_id="author1",
question_listings=[
QuestionItemEntity(
index=0,
question="Apa ibu kota Indonesia?",
type="multiple_choice",
target_answer="B",
options=["A. Surabaya", "B. Jakarta", "C. Bandung"],
duration=20,
)
],
)
mock_answer_repository.get_by_id.return_value = mock_answer
mock_quiz_repository.get_by_id.return_value = mock_quiz
result = history_service.get_history_by_answer_id("answer1")
assert isinstance(result, QuizHistoryResponse)
assert result.total_correct == 7
assert result.total_score == 70
assert result.total_solve_time == 12
assert len(result.question_listings) == 1
assert result.question_listings[0].is_correct is True
assert result.question_listings[0].user_answer == "B"

View File

@ -1,111 +1,157 @@
import unittest # import pytest
from unittest.mock import MagicMock # from datetime import datetime
from app.services import QuizService # from app.services.quiz_service import QuizService
from app.app.schemas.requests import QuizCreateSchema # from app.exception import DataNotFoundException, ValidationException
from app.app.schemas.response import UserQuizListResponse # from app.models.entities import QuizEntity, SubjectEntity, QuestionItemEntity
from app.exception import DataNotFoundException, ValidationException # from bson import ObjectId
# from unittest.mock import MagicMock
class TestQuizService(unittest.TestCase): # @pytest.fixture
def setUp(self): # def mock_repositories():
self.quiz_repo = MagicMock() # return {
self.user_repo = MagicMock() # "quiz_repository": MagicMock(),
self.subject_repo = MagicMock() # "user_repository": MagicMock(),
# "subject_repository": MagicMock(),
self.service = QuizService( # }
quiz_repository=self.quiz_repo,
user_repository=self.user_repo,
subject_repository=self.subject_repo,
)
def test_get_quiz_success(self):
fake_quiz = MagicMock(subject_id="subj1")
fake_subject = MagicMock()
self.quiz_repo.get_by_id.return_value = fake_quiz
self.subject_repo.get_by_id.return_value = fake_subject
result = self.service.get_quiz("quiz123")
self.assertIsNotNone(result)
self.quiz_repo.get_by_id.assert_called_once()
def test_get_quiz_not_found(self):
self.quiz_repo.get_by_id.return_value = None
with self.assertRaises(DataNotFoundException):
self.service.get_quiz("invalid_id")
def test_search_quiz_success(self):
fake_quiz = MagicMock(author_id="user123")
self.quiz_repo.search_by_title_or_category.return_value = [fake_quiz]
self.quiz_repo.count_by_search.return_value = 1
self.user_repo.get_user_by_id.return_value = MagicMock()
result, total = self.service.search_quiz("math", "subj1")
self.assertEqual(total, 1)
self.assertTrue(len(result) > 0)
def test_search_quiz_author_missing(self):
fake_quiz = MagicMock(author_id="user123")
self.quiz_repo.search_by_title_or_category.return_value = [fake_quiz]
self.quiz_repo.count_by_search.return_value = 1
self.user_repo.get_user_by_id.return_value = None # simulate missing author
result, total = self.service.search_quiz("math", "subj1")
self.assertEqual(result, []) # filtered out
self.assertEqual(total, 1)
def test_create_quiz_with_invalid_options(self):
question = MagicMock(type="option", options=["a", "b"]) # only 2 options
quiz_schema = MagicMock(question_listings=[question])
with self.assertRaises(ValidationException):
self.service.create_quiz(quiz_schema)
def test_create_quiz_valid(self):
question = MagicMock(type="option", options=["a", "b", "c", "d"], duration=30)
quiz_schema = MagicMock(question_listings=[question])
self.quiz_repo.create.return_value = "quiz_id_123"
result = self.service.create_quiz(quiz_schema)
self.assertEqual(result, "quiz_id_123")
def test_get_user_quiz_success(self):
self.quiz_repo.get_by_user_id.return_value = [MagicMock()]
self.quiz_repo.count_by_user_id.return_value = 1
self.user_repo.get_user_by_id.return_value = MagicMock()
result = self.service.get_user_quiz("user123")
self.assertIsInstance(result, UserQuizListResponse)
self.assertEqual(result.total, 1)
def test_get_user_quiz_empty(self):
self.quiz_repo.get_by_user_id.return_value = []
result = self.service.get_user_quiz("user123")
self.assertEqual(result.total, 0)
self.assertEqual(result.quizzes, [])
def test_get_quiz_recommendation_success(self):
quiz = MagicMock(author_id="user123")
self.quiz_repo.get_top_played_quizzes.return_value = [quiz]
self.user_repo.get_user_by_id.return_value = MagicMock()
result = self.service.get_quiz_recommendation(1, 10)
self.assertTrue(len(result) > 0)
def test_get_quiz_recommendation_empty(self):
self.quiz_repo.get_top_played_quizzes.return_value = []
with self.assertRaises(DataNotFoundException):
self.service.get_quiz_recommendation(1, 10)
def test_update_quiz(self):
self.quiz_repo.update.return_value = True
result = self.service.update_quiz("quiz_id", {"title": "updated"})
self.assertTrue(result)
def test_delete_quiz(self):
self.quiz_repo.delete.return_value = True
result = self.service.delete_quiz("quiz_id")
self.assertTrue(result)
if __name__ == "__main__": # @pytest.fixture
unittest.main() # def quiz_service(mock_repositories):
# return QuizService(
# quiz_repository=mock_repositories["quiz_repository"],
# user_repository=mock_repositories["user_repository"],
# subject_repository=mock_repositories["subject_repository"],
# )
# def test_get_quiz_found(quiz_service, mock_repositories):
# mock_quiz = QuizEntity(
# id=ObjectId(),
# author_id="user1",
# subject_id="subj1",
# title="Ulangan Harian",
# description="Tes harian",
# is_public=True,
# date=datetime.now(),
# total_quiz=2,
# limit_duration=60,
# total_user_playing=10,
# question_listings=[],
# )
# mock_subject = SubjectEntity(
# id=ObjectId(),
# name="Matematika",
# short_name="MTK",
# description="Deskripsi MTK",
# icon="math.png",
# )
# mock_repositories["quiz_repository"].get_by_id.return_value = mock_quiz
# mock_repositories["subject_repository"].get_by_id.return_value = mock_subject
# result = quiz_service.get_quiz("quiz123")
# assert result.title == "Ulangan Harian"
# mock_repositories["quiz_repository"].get_by_id.assert_called_once_with("quiz123")
# def test_get_quiz_not_found(quiz_service, mock_repositories):
# mock_repositories["quiz_repository"].get_by_id.return_value = None
# with pytest.raises(DataNotFoundException):
# quiz_service.get_quiz("invalid_id")
# def test_create_quiz_valid(quiz_service, mock_repositories):
# quiz_data = MagicMock()
# quiz_data.question_listings = [
# MagicMock(type="option", options=["a", "b", "c", "d"], duration=30),
# MagicMock(type="true_false", duration=10),
# ]
# quiz_service.create_quiz(quiz_data)
# assert mock_repositories["quiz_repository"].create.called
# def test_create_quiz_invalid_options(quiz_service):
# quiz_data = MagicMock()
# quiz_data.question_listings = [
# MagicMock(type="option", options=["a", "b"], duration=30)
# ]
# with pytest.raises(ValidationException):
# quiz_service.create_quiz(quiz_data)
# def test_search_quiz(quiz_service, mock_repositories):
# quiz = QuizEntity(
# id=ObjectId(),
# author_id="user1",
# subject_id="subj1",
# title="Kuis Sejarah",
# description=None,
# is_public=True,
# date=datetime.now(),
# total_quiz=1,
# limit_duration=30,
# total_user_playing=0,
# question_listings=[],
# )
# author = MagicMock()
# mock_repositories["quiz_repository"].search_by_title_or_category.return_value = [
# quiz
# ]
# mock_repositories["quiz_repository"].count_by_search.return_value = 1
# mock_repositories["user_repository"].get_user_by_id.return_value = author
# quizzes, total = quiz_service.search_quiz("sejarah", "subj1")
# assert total == 1
# assert len(quizzes) == 1
# def test_get_user_quiz_empty(quiz_service, mock_repositories):
# mock_repositories["quiz_repository"].get_by_user_id.return_value = []
# result = quiz_service.get_user_quiz("user1")
# assert result.total == 0
# assert result.quizzes == []
# def test_get_quiz_recommendation_found(quiz_service, mock_repositories):
# quiz = QuizEntity(
# id=ObjectId(),
# author_id="user1",
# subject_id="subj1",
# title="Top Quiz",
# description=None,
# is_public=True,
# date=datetime.now(),
# total_quiz=1,
# limit_duration=30,
# total_user_playing=100,
# question_listings=[],
# )
# mock_repositories["quiz_repository"].get_top_played_quizzes.return_value = [quiz]
# mock_repositories["user_repository"].get_user_by_id.return_value = MagicMock()
# result = quiz_service.get_quiz_recommendation(1, 5)
# assert len(result) == 1
# def test_get_quiz_recommendation_not_found(quiz_service, mock_repositories):
# mock_repositories["quiz_repository"].get_top_played_quizzes.return_value = []
# with pytest.raises(DataNotFoundException):
# quiz_service.get_quiz_recommendation(1, 5)
# def test_update_quiz(quiz_service, mock_repositories):
# mock_repositories["quiz_repository"].update.return_value = True
# result = quiz_service.update_quiz("quiz123", {"title": "Updated Title"})
# assert result is True
# mock_repositories["quiz_repository"].update.assert_called_once_with(
# "quiz123", {"title": "Updated Title"}
# )
# def test_delete_quiz(quiz_service, mock_repositories):
# mock_repositories["quiz_repository"].delete.return_value = True
# result = quiz_service.delete_quiz("quiz123")
# assert result is True
# mock_repositories["quiz_repository"].delete.assert_called_once_with("quiz123")

View File

@ -0,0 +1,86 @@
import pytest
from unittest.mock import MagicMock
from app.services.subject_service import SubjectService
from app.schemas.requests import SubjectCreateRequest, SubjectUpdateRequest
from app.models.entities import SubjectEntity
from app.schemas.response import GetSubjectResponse
@pytest.fixture
def mock_subject_repository():
return MagicMock()
@pytest.fixture
def subject_service(mock_subject_repository):
return SubjectService(repository=mock_subject_repository)
def test_create_subject(subject_service, mock_subject_repository):
request = SubjectCreateRequest(
name="Fisika", short_name="FIS", description="Pelajaran fisika"
)
mock_subject_repository.create.return_value = "subject123"
result = subject_service.create_subject(request)
mock_subject_repository.create.assert_called_once()
assert result == "subject123"
def test_get_all_subjects(subject_service, mock_subject_repository):
mock_subject_repository.get_all.return_value = [
SubjectEntity(
id="1",
name="Biologi",
short_name="BIO",
description="Ilmu tentang makhluk hidup",
)
]
results = subject_service.get_all_subjects()
assert len(results) == 1
assert isinstance(results[0], GetSubjectResponse)
assert results[0].name == "Biologi"
assert results[0].alias == "BIO"
assert results[0].description == "Ilmu tentang makhluk hidup"
def test_get_subject_by_id_found(subject_service, mock_subject_repository):
mock_subject_repository.get_by_id.return_value = SubjectEntity(
id="2", name="Matematika", short_name="MTK", description="Ilmu hitung"
)
result = subject_service.get_subject_by_id("2")
assert result is not None
assert result.name == "Matematika"
assert result.alias == "MTK"
def test_get_subject_by_id_not_found(subject_service, mock_subject_repository):
mock_subject_repository.get_by_id.return_value = None
result = subject_service.get_subject_by_id("999")
assert result is None
def test_update_subject(subject_service, mock_subject_repository):
request = SubjectUpdateRequest(name="Sejarah")
mock_subject_repository.update.return_value = True
result = subject_service.update_subject("3", request)
mock_subject_repository.update.assert_called_once_with("3", {"name": "Sejarah"})
assert result is True
def test_delete_subject(subject_service, mock_subject_repository):
mock_subject_repository.delete.return_value = True
result = subject_service.delete_subject("4")
mock_subject_repository.delete.assert_called_once_with("4")
assert result is True

View File

@ -0,0 +1,69 @@
import pytest
from unittest.mock import MagicMock
from werkzeug.security import check_password_hash
from bson import ObjectId
from app.services.user_service import UserService
from app.schemas import RegisterSchema
from app.exception import AlreadyExistException
from app.models.entities import UserEntity
import datetime
@pytest.fixture
def mock_user_repository():
return MagicMock()
@pytest.fixture
def user_service(mock_user_repository):
return UserService(user_repository=mock_user_repository)
def test_register_user_success(user_service, mock_user_repository):
# Arrange
register_data = RegisterSchema(
name="John Doe",
email="john@example.com",
password="plainpassword",
birth_date="01-01-2000",
phone="08123456789",
)
mock_user_repository.get_user_by_email.return_value = None
mock_user_repository.insert_user.return_value = "new_user_id"
# Act
result = user_service.register_user(register_data)
# Assert
assert result == "new_user_id"
assert register_data.password != "plainpassword" # Ensure password is hashed
assert check_password_hash(register_data.password, "plainpassword")
mock_user_repository.insert_user.assert_called_once()
def test_register_user_email_already_exists(user_service, mock_user_repository):
# Arrange
register_data = RegisterSchema(
name="Jane Doe",
email="jane@example.com",
password="password123",
birth_date="12-12-1990",
phone="08987654321",
)
mock_user_repository.get_user_by_email.return_value = UserEntity(
id=ObjectId(),
name="Jane Doe",
email="jane@example.com",
password="hashedpassword",
birth_date=datetime.datetime(1990, 12, 12, 0, 0),
phone="08987654321",
)
# Act & Assert
with pytest.raises(AlreadyExistException) as exc_info:
user_service.register_user(register_data)
assert str(exc_info.value) == "Email already exists"
mock_user_repository.insert_user.assert_not_called()