feat: adjustment on the dependencies

This commit is contained in:
akhdanre 2025-05-07 23:46:54 +07:00
parent 339da89e72
commit f747880122
39 changed files with 411 additions and 138 deletions

Binary file not shown.

2
app/__init__.py Normal file
View File

@ -0,0 +1,2 @@
# from flask import Flask
from app.main import createApp

View File

@ -1,6 +1,6 @@
from flask import Blueprint
from controllers import AuthController
from di_container import Container
from app.controllers import AuthController
from app.di_container import Container
from dependency_injector.wiring import inject, Provide

View File

@ -1,6 +1,6 @@
from flask import Blueprint
from controllers import HistoryController
from di_container import Container
from app.controllers import HistoryController
from app.di_container import Container
from dependency_injector.wiring import inject, Provide
history_blueprint = Blueprint("history", __name__)

View File

@ -1,7 +1,7 @@
from flask import Blueprint, request
from di_container import Container
from app.di_container import Container
from dependency_injector.wiring import inject, Provide
from controllers import QuizController
from app.controllers import QuizController
quiz_bp = Blueprint("quiz", __name__)

View File

@ -1,7 +1,7 @@
from flask import Blueprint, request
from di_container import Container
from dependency_injector.wiring import inject, Provide
from controllers import SessionController
from app.di_container import Container
from app.controllers import SessionController
session_bp = Blueprint("session", __name__)

View File

@ -1,7 +1,7 @@
from flask import Blueprint, request
from di_container import Container
from dependency_injector.wiring import inject, Provide
from controllers import SubjectController
from app.di_container import Container
from app.controllers import SubjectController
subject_blueprint = Blueprint("subject", __name__)

View File

@ -1,6 +1,6 @@
from flask import Blueprint
from controllers import UserController
from di_container import Container
from app.di_container import Container
from app.controllers import UserController
from dependency_injector.wiring import inject, Provide
user_blueprint = Blueprint("user", __name__)

View File

@ -1,13 +1,13 @@
from flask import jsonify, request, current_app
from pydantic import ValidationError
from models.login.login_response import UserResponseModel
from schemas.basic_response_schema import ResponseSchema
from schemas.google_login_schema import GoogleLoginSchema
from schemas import LoginSchema
from services import UserService, AuthService
from exception import AuthException
from mapper import UserMapper
from helpers import make_response
from app.models.login.login_response import UserResponseModel
from app.schemas.basic_response_schema import ResponseSchema
from app.schemas.google_login_schema import GoogleLoginSchema
from app.schemas import LoginSchema
from app.services import UserService, AuthService
from app.exception import AuthException
from app.mapper import UserMapper
from app.helpers import make_response
import logging
logging = logging.getLogger(__name__)

View File

@ -1,5 +1,5 @@
from services import HistoryService
from helpers import make_error_response, make_response
from app.services import HistoryService
from app.helpers import make_error_response, make_response
class HistoryController:

View File

@ -1,10 +1,10 @@
import json
from pydantic import ValidationError
from schemas.requests import QuizCreateSchema, UserAnswerSchema
from schemas.response import QuizCreationResponse
from services import QuizService, AnswerService
from helpers import make_response, make_error_response
from exception import ValidationException, DataNotFoundException
from app.schemas.requests import QuizCreateSchema, UserAnswerSchema
from app.schemas.response import QuizCreationResponse
from app.services import QuizService, AnswerService
from app.helpers import make_response, make_error_response
from app.exception import ValidationException, DataNotFoundException
class QuizController:

View File

@ -1,6 +1,7 @@
from flask import request, jsonify
from flask.views import MethodView
from services.session_service import SessionService
from app.services.session_service import SessionService
from app.helpers import make_response
class SessionController(MethodView):
@ -24,4 +25,8 @@ class SessionController(MethodView):
limit_participan=data["limit_participan"],
)
return jsonify(session.dict()), 201
return make_response(
message="succes create room",
data=session,
status_code=201,
)

View File

@ -1,7 +1,8 @@
# socket_controller.py
from flask_socketio import SocketIO, emit, join_room, leave_room
from flask import request
from services import SessionService
from app.services import SessionService
import threading
import time
class SocketController:
@ -22,33 +23,81 @@ class SocketController:
@self.socketio.on("join_room")
def handle_join_room(data):
session_code = data.get("session_code")
user_id = data.get("user_id")
session_code = data["session_code"]
user_id = data["user_id"]
if not session_code or not user_id:
emit("error", {"message": "session_id and user_id are required"})
emit("error", {"message": "session_code and user_id are required"})
return
session = self.session_service.join_session(session_code, user_id)
session = self.session_service.join_session(
session_code=session_code, user_id=user_id
)
if session is None:
emit("error", {"message": "Failed to join session or session inactive"})
return
join_room(session_code)
user_data = self.session_service.join_session()
if session["is_admin"] == True:
emit(
"room_message",
{
"message": f"admin has joined the room.",
"room": session_code,
"argument": "adm_update",
},
room=session_code,
)
return
emit(
"room_message",
{
"message": "someone has joined the room.",
"message": f"user {session['username']} has joined the room.",
"room": session_code,
"argument": "adm_update",
"data": session,
},
room=session_code,
)
@self.socketio.on("submit_answer")
def handle_submit_answer(data):
session_id = data.get("session_id")
user_id = data.get("user_id")
question_index = data.get("question_index")
answer = data.get("answer")
if not all([session_id, user_id, question_index is not None, answer]):
emit(
"error",
{
"message": "session_id, user_id, question_index, and answer are required"
},
)
return
print(f"User {user_id} answered question {question_index} with {answer}")
# TODO: kamu bisa menyimpan jawaban ke database di sini
# self.answer_service.save_answer(session_id, user_id, question_index, answer)
# Kirim notifikasi ke admin (host) atau semua peserta kalau perlu
emit(
"answer_submitted",
{
"user_id": user_id,
"question_index": question_index,
"answer": answer,
},
room=session_id,
)
@self.socketio.on("leave_room")
def handle_leave_room(data):
session_id = data.get("session_id")
user_id = data.get("user_id")
username = data.get("username", "anonymous")
leave_room(session_id)
@ -69,3 +118,80 @@ class SocketController:
{"message": message, "from": username},
room=session_id,
)
@self.socketio.on("end_session")
def handle_end_session(data):
session_id = data.get("session_id")
user_id = data.get("user_id")
if not session_id or not user_id:
emit("error", {"message": "session_id and user_id required"})
return
self.session_service.end_session(session_id=session_id, user_id=user_id)
emit(
"room_closed",
{"message": "Session has ended.", "room": session_id},
room=session_id,
)
@self.socketio.on("start_quiz")
def handle_start_quiz(data):
session_code = data.get("session_code")
if not session_code:
emit("error", {"message": "session_code is required"})
return
emit("quiz_started", {"message": "Quiz has started!"}, room=session_code)
# Jalankan thread untuk mengirim soal simulasi setiap 5 detik
threading.Thread(
target=self._simulate_quiz_flow, args=(session_code,)
).start()
def _simulate_quiz_flow(self, session_code):
questions = [
{
"question_index": 0,
"question": "Apa ibu kota Indonesia?",
"type": "option",
"options": ["Jakarta", "Bandung", "Surabaya"],
},
{
"question_index": 1,
"question": "2 + 2 = ?",
"type": "option",
"options": ["3", "4", "5"],
},
{
"question_index": 2,
"question": "Siapa presiden pertama Indonesia?",
"type": "option",
"options": ["Sukarno", "Soeharto", "Jokowi"],
},
{
"question_index": 3,
"question": "Tuliskan nama lengkap presiden pertama Indonesia.",
"type": "fill_in_the_blank",
"options": [],
},
{
"question_index": 4,
"question": "Indonesia merdeka pada tahun 1945.",
"type": "true_false",
"options": [],
},
]
for q in questions:
print(f"Sending question {q['question_index']} to {session_code}")
self.socketio.emit("quiz_question", q, room=session_code)
time.sleep(20)
# send true ansewr
time.sleep(5)
# Setelah selesai semua soal, kirim command bahwa quiz selesai
self.socketio.emit(
"quiz_done", {"message": "Quiz has ended!"}, room=session_code
)

View File

@ -1,5 +1,5 @@
from services.subject_service import SubjectService
from helpers import make_response, make_error_response
from app.services.subject_service import SubjectService
from app.helpers import make_response, make_error_response
class SubjectController:

View File

@ -1,11 +1,11 @@
# /controllers/user_controller.py
from flask import jsonify, request, current_app
from services import UserService
from schemas import RegisterSchema
from app.services import UserService
from app.schemas import RegisterSchema
from pydantic import ValidationError
from schemas import ResponseSchema
from exception import AlreadyExistException
from helpers import make_response
from app.schemas import ResponseSchema
from app.exception import AlreadyExistException
from app.helpers import make_response
class UserController:

View File

@ -1,5 +1,5 @@
from dependency_injector import containers, providers
from repositories import (
from app.repositories import (
UserRepository,
QuizRepository,
UserAnswerRepository,
@ -7,7 +7,7 @@ from repositories import (
SessionRepository,
)
from services import (
from app.services import (
UserService,
AuthService,
QuizService,
@ -17,7 +17,7 @@ from services import (
SessionService,
)
from controllers import (
from app.controllers import (
UserController,
AuthController,
QuizController,
@ -80,6 +80,7 @@ class Container(containers.DeclarativeContainer):
session_service = providers.Factory(
SessionService,
session_repository,
user_repository,
)
# controllers

View File

@ -1,6 +1,6 @@
from flask import jsonify, current_app
from typing import Optional, Union
from schemas import ResponseSchema, MetaSchema
from app.schemas import ResponseSchema, MetaSchema
import math

View File

@ -10,11 +10,11 @@ import logging
from flask import Flask
from flask_socketio import SocketIO
sys.path.append(os.path.dirname(__file__))
# sys.path.append(os.path.dirname(__file__))
from di_container import Container
from configs import Config, LoggerConfig
from blueprints import (
from app.di_container import Container
from app.configs import Config, LoggerConfig
from app.blueprints import (
auth_blueprint,
user_blueprint,
quiz_bp,
@ -23,7 +23,7 @@ from blueprints import (
subject_blueprint,
session_bp,
)
from database import init_db
from app.database import init_db
socketio = SocketIO(cors_allowed_origins="*")
@ -52,12 +52,12 @@ def createApp() -> Flask:
container.wire(
modules=[
"blueprints.auth",
"blueprints.user",
"blueprints.quiz",
"blueprints.history",
"blueprints.subject",
"blueprints.session",
"app.blueprints.auth",
"app.blueprints.user",
"app.blueprints.quiz",
"app.blueprints.history",
"app.blueprints.subject",
"app.blueprints.session",
]
)
@ -70,8 +70,3 @@ def createApp() -> Flask:
app.register_blueprint(session_bp, url_prefix="/api/session")
return app
if __name__ == "__main__":
app = createApp()
socketio.run(app, host="0.0.0.0", port=5000, debug=Config.DEBUG)

View File

@ -1,10 +1,10 @@
from datetime import datetime
from helpers import DatetimeUtil
from models import QuizEntity, QuestionItemEntity, UserEntity
from models.entities import SubjectEntity
from schemas import QuizGetSchema, QuestionItemSchema
from schemas.response import ListingQuizResponse
from schemas.requests import QuizCreateSchema
from app.helpers import DatetimeUtil
from app.models import QuizEntity, QuestionItemEntity, UserEntity
from app.models.entities import SubjectEntity
from app.schemas import QuizGetSchema, QuestionItemSchema
from app.schemas.response import ListingQuizResponse
from app.schemas.requests import QuizCreateSchema
class QuizMapper:

View File

@ -1,7 +1,7 @@
from datetime import datetime
from typing import Dict, Optional
from models import UserEntity, UserResponseModel
from schemas import RegisterSchema
from app.models import UserEntity, UserResponseModel
from app.schemas import RegisterSchema
class UserMapper:

View File

@ -1,7 +1,7 @@
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, Field
from models.entities import PyObjectId
from app.models.entities import PyObjectId
class SessionEntity(BaseModel):

View File

@ -1,7 +1,7 @@
from typing import Optional
from bson import ObjectId
from pydantic import BaseModel, Field
from models.entities import PyObjectId
from app.models.entities import PyObjectId
class SubjectEntity(BaseModel):

View File

@ -1,7 +1,7 @@
from pymongo.collection import Collection
from bson import ObjectId
from typing import Optional, List
from models import UserAnswerEntity
from app.models import UserAnswerEntity
class UserAnswerRepository:

View File

@ -1,10 +1,8 @@
from bson import ObjectId
from typing import List, Optional
from models import QuizEntity
from app.models.entities import QuizEntity
from pymongo.database import Database
from pymongo.collection import Collection
from datetime import datetime
from helpers import DatetimeUtil
class QuizRepository:

View File

@ -1,7 +1,7 @@
from pymongo.collection import Collection
from pymongo.database import Database
from typing import Optional
from models.entities import SessionEntity
from app.models.entities import SessionEntity
class SessionRepository:
@ -30,7 +30,7 @@ class SessionRepository:
"""Update specific fields using $set"""
result = self.collection.update_one(
{"_id": session_id},
{"$set": update_fields.model_dump(exclude_unset=True)},
{"$set": update_fields},
)
return result.modified_count > 0

View File

@ -2,7 +2,7 @@ from typing import List, Optional
from pymongo.database import Database
from pymongo.collection import Collection
from bson import ObjectId, errors as bson_errors
from models.entities import SubjectEntity
from app.models.entities import SubjectEntity
class SubjectRepository:

View File

@ -1,6 +1,6 @@
from typing import Optional
from bson import ObjectId
from models import UserEntity
from app.models.entities import UserEntity
class UserRepository:

View File

@ -1,6 +1,6 @@
from typing import List
from pydantic import BaseModel
from schemas.response.recomendation.recomendation_response_schema import (
from app.schemas.response.recomendation.recomendation_response_schema import (
ListingQuizResponse,
)

View File

@ -1,8 +1,8 @@
from repositories import UserAnswerRepository, QuizRepository, UserRepository
from schemas.requests import UserAnswerSchema
from models import UserAnswerEntity
from models.entities import AnswerItemEntity
from exception import ValidationException
from app.repositories import UserAnswerRepository, QuizRepository, UserRepository
from app.schemas.requests import UserAnswerSchema
from app.models import UserAnswerEntity
from app.models.entities import AnswerItemEntity
from app.exception import ValidationException
class AnswerService:

View File

@ -1,10 +1,10 @@
from schemas import LoginSchema
from repositories import UserRepository
from mapper import UserMapper
from app.schemas import LoginSchema
from app.repositories import UserRepository
from app.mapper import UserMapper
from google.oauth2 import id_token
from google.auth.transport import requests
from configs import Config
from exception import AuthException
from app.configs import Config
from app.exception import AuthException
from werkzeug.security import check_password_hash

View File

@ -1,5 +1,5 @@
from repositories import UserAnswerRepository, QuizRepository
from schemas.response import HistoryResultSchema, QuizHistoryResponse, QuestionResult
from app.repositories import UserAnswerRepository, QuizRepository
from app.schemas.response import HistoryResultSchema, QuizHistoryResponse, QuestionResult
class HistoryService:

View File

@ -1,10 +1,13 @@
from repositories import QuizRepository, UserRepository, SubjectRepository
from schemas.requests import QuizCreateSchema
from schemas.response import UserQuizListResponse, ListingQuizResponse, QuizGetSchema
from exception import DataNotFoundException
from mapper import QuizMapper
from exception import ValidationException
from helpers import DatetimeUtil
from app.repositories import QuizRepository, UserRepository, SubjectRepository
from app.schemas.requests import QuizCreateSchema
from app.schemas.response import (
UserQuizListResponse,
ListingQuizResponse,
QuizGetSchema,
)
from app.exception import DataNotFoundException, ValidationException
from app.mapper import QuizMapper
from app.helpers import DatetimeUtil
class QuizService:
@ -43,7 +46,7 @@ class QuizService:
if author is None:
continue
mapped_quizzes.append(
QuizMapper.quiz_to_recomendation_mapper(
QuizMapper.quiz_to_recomendation_app.mapper(
quiz_entity=quiz,
user_entity=author,
)
@ -65,7 +68,7 @@ class QuizService:
user = self.user_repostory.get_user_by_id(user_id)
quiz_data = [
QuizMapper.quiz_to_recomendation_mapper(quiz, user) for quiz in quizzes
QuizMapper.quiz_to_recomendation_app.mapper(quiz, user) for quiz in quizzes
]
return UserQuizListResponse(total=total_user_quiz, quizzes=quiz_data)
@ -107,7 +110,7 @@ class QuizService:
for quiz in data:
author = self.user_repostory.get_user_by_id(user_id=quiz.author_id)
result.append(
QuizMapper.quiz_to_recomendation_mapper(
QuizMapper.quiz_to_recomendation_app.mapper(
quiz_entity=quiz,
user_entity=author,
)

View File

@ -1,9 +1,8 @@
from datetime import datetime
from typing import Optional
from uuid import uuid4
from repositories import SessionRepository, UserRepository
from models.entities import SessionEntity
from helpers import DatetimeUtil
from app.repositories import SessionRepository, UserRepository
from app.models.entities import SessionEntity
from app.helpers import DatetimeUtil
class SessionService:
@ -11,11 +10,10 @@ class SessionService:
self.repository = repository
self.user_repository = user_repository
def create_session(
self, quiz_id: str, host_id: str, limit_participan: int
) -> SessionEntity:
def create_session(self, quiz_id: str, host_id: str, limit_participan: int) -> str:
generateed_code = uuid4().hex[:6].upper()
session = SessionEntity(
session_code=uuid4().hex[:6].upper(),
session_code=generateed_code,
quiz_id=quiz_id,
host_id=host_id,
created_at=DatetimeUtil.now_iso(),
@ -24,36 +22,59 @@ class SessionService:
current_question_index=0,
is_active=True,
)
self.repository.insert(session)
return session
def join_session(self, session_code: str, user_id: str) -> Optional[SessionEntity]:
return {
"session_id": self.repository.insert(session),
"session_code": generateed_code,
}
def join_session(self, session_code: str, user_id: str) -> dict:
user = self.user_repository.get_user_by_id(user_id)
session = self.repository.find_by_session_code(session_code=session_code)
if session is None or not session["is_active"]:
if session is None or session.is_active == False:
return None
if user_id not in session["participants"]:
session["participants"].append(user_id)
self.repository.update(
session.id, {"participants": session["participants"]}
)
if session.host_id == user_id:
return {"is_admin": True, "message": "admin joined"}
if user_id not in session.participants:
session.participants.append(user_id)
self.repository.update(session.id, {"participants": session.participants})
response = {
"user_id": user.id,
"username": user.id,
"is_admin": False,
"user_id": str(user.id),
"username": user.name,
"user_pic": user.pic_url,
"session_id": session.id,
"session_id": str(session.id),
}
return response
def leave_session(self, session_id: str, user_id: str) -> dict:
session = self.repository.get_by_id(session_id)
if session is None:
return {"error": "Session not found"}
if user_id == session.host_id:
return {"message": "Host cannot leave the session"}
if user_id in session.participants:
session.participants.remove(user_id)
self.repository.update(session.id, {"participants": session.participants})
return {"message": "User has left the session"}
return {"message": "User not in session"}
def start_session(self, session_id: str) -> bool:
now = DatetimeUtil.now_iso()
return self.repository.update(session_id, {"started_at": now})
def end_session(self, session_id: str) -> bool:
now = DatetimeUtil.now_iso()
return self.repository.update(session_id, {"ended_at": now, "is_active": False})
def end_session(self, session_id: str, user_id: str):
session = self.repository.find_by_id(session_id)
if session and session.host_id == user_id:
session.is_active = False
self.repository.update(session_id, {"is_active": False})
def advance_question(self, session_id: str) -> Optional[int]:
session = self.repository.find_by_session_id(session_id)

View File

@ -1,8 +1,8 @@
from typing import List, Optional
from models.entities import SubjectEntity
from schemas.requests import SubjectCreateRequest, SubjectUpdateRequest
from schemas.response import GetSubjectResponse
from repositories import SubjectRepository
from app.models.entities import SubjectEntity
from app.schemas.requests import SubjectCreateRequest, SubjectUpdateRequest
from app.schemas.response import GetSubjectResponse
from app.repositories import SubjectRepository
class SubjectService:

View File

@ -1,8 +1,8 @@
from flask import current_app
from repositories import UserRepository
from schemas import RegisterSchema
from mapper import UserMapper
from exception import AlreadyExistException
from app.repositories import UserRepository
from app.schemas import RegisterSchema
from app.mapper import UserMapper
from app.exception import AlreadyExistException
from werkzeug.security import generate_password_hash

3
pytest.ini Normal file
View File

@ -0,0 +1,3 @@
# pytest.ini
[pytest]
pythonpath = .

8
run.py Normal file
View File

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

View File

@ -0,0 +1,111 @@
import unittest
from unittest.mock import MagicMock
from app.services import QuizService
from app.app.schemas.requests import QuizCreateSchema
from app.app.schemas.response import UserQuizListResponse
from app.exception import DataNotFoundException, ValidationException
class TestQuizService(unittest.TestCase):
def setUp(self):
self.quiz_repo = MagicMock()
self.user_repo = MagicMock()
self.subject_repo = 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__":
unittest.main()