diff --git a/server/app/controllers/musicians.py b/server/app/controllers/musicians.py index 01e0030..3d9f583 100644 --- a/server/app/controllers/musicians.py +++ b/server/app/controllers/musicians.py @@ -85,22 +85,22 @@ class MusicianController(BaseController): """ musician = await self.get_musician(musician_id) if new_bio != musician.bio: - return await self._update_musician_bio(musician.id, new_bio) + return await self._update_musician_bio(musician, new_bio) if file is not None: - return await self._upload_headshot(musician.id, file) + return await self._upload_headshot(musician, file) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Update operation not implemented. Neither the bio or headshot was updated.", ) - async def update_musician_headshot( - self, musician_id: int, headshot_id: str + async def _update_musician_headshot( + self, musician: Musician, headshot_id: str ) -> Musician: """Updates a musician's headshot in the database. Args: - id (int): The numeric ID of the musician to update + musician (Musician): The musician object to update headshot_id (str): The public ID of the new headshot (as determined by Cloudinary) Raises: @@ -109,21 +109,20 @@ class MusicianController(BaseController): Returns: Musician: The updated Musician object """ - await self.get_musician(musician_id) try: - await self.db.update_headshot(musician_id, headshot_id) + await self.db.update_headshot(musician, headshot_id) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error updating musician headshot: {e}", ) - return await self.get_musician(musician_id) + return await self.get_musician(musician.id) - async def _update_musician_bio(self, musician_id: int, bio: str) -> Musician: + async def _update_musician_bio(self, musician: Musician, bio: str) -> Musician: """Updates a musician's bio in the database. Args: - id (int): The numeric ID of the musician to update + musician (Musician): The musician object to update bio (str): The new biography for the musician Raises: @@ -132,21 +131,20 @@ class MusicianController(BaseController): Returns: Musician: The updated Musician object """ - await self.get_musician(musician_id) # Check if musician exists try: - await self.db.update_bio(musician_id, bio) + await self.db.update_bio(musician, bio) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error updating musician bio: {e}", ) - return await self.get_musician(musician_id) + return await self.get_musician(musician.id) - async def _upload_headshot(self, id: int, file: UploadFile) -> Musician: + async def _upload_headshot(self, musician: Musician, file: UploadFile) -> Musician: """Uploads a new headshot for a musician and updates the database with the new public ID. Args: - id (int): The numeric ID of the musician to update + musician (Musician): The musician object to update file (UploadFile): The new headshot file Raises: @@ -163,6 +161,6 @@ class MusicianController(BaseController): status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to upload image", ) - await self.update_musician_headshot(id, public_id) + await self._update_musician_headshot(musician, public_id) - return await self.get_musician(id) + return await self.get_musician(musician.id) diff --git a/server/app/db/musicians.py b/server/app/db/musicians.py index 518813c..90627a4 100644 --- a/server/app/db/musicians.py +++ b/server/app/db/musicians.py @@ -3,6 +3,7 @@ from icecream import ic from app.constants import MUSICIAN_TABLE from app.db.base_queries import BaseQueries from app.db.conn import connect_db +from app.models.musician import Musician class MusicianQueries(BaseQueries): @@ -10,20 +11,33 @@ class MusicianQueries(BaseQueries): super().__init__() self.table = MUSICIAN_TABLE - async def update_bio(self, id: int, bio: str) -> None: + async def update_bio(self, musician: Musician, bio: str) -> None: + """Updates a musician's biography in the database. + + Args: + musician (Musician): The musician object to update + bio (str): The new biography for the musician + """ db = connect_db() cursor = db.cursor() query = f"UPDATE {self.table} SET bio = %s WHERE id = %s" - cursor.execute(query, (bio, id)) + cursor.execute(query, (bio, musician.id)) db.commit() cursor.close() db.close() - async def update_headshot(self, id: int, headshot_id: str) -> None: + async def update_headshot(self, musician: Musician, headshot_id: str) -> None: + """Updates a musician's headshot ID in the database. + The image itself is stored with Cloudinary. + + Args: + musician (Musician): The musician object to update + headshot_id (str): The public ID of the new headshot (as determined by Cloudinary) + """ db = connect_db() cursor = db.cursor() query = f"UPDATE {self.table} SET headshot_id = %s WHERE id = %s" - cursor.execute(query, (headshot_id, id)) + cursor.execute(query, (headshot_id, musician.id)) db.commit() cursor.close() db.close() diff --git a/server/tests/controllers/test_musician_controller.py b/server/tests/controllers/test_musician_controller.py index 48eea85..f8b5e0d 100644 --- a/server/tests/controllers/test_musician_controller.py +++ b/server/tests/controllers/test_musician_controller.py @@ -1,6 +1,7 @@ -from unittest.mock import Mock +from unittest.mock import MagicMock, Mock import pytest +from fastapi import HTTPException, UploadFile, status from icecream import ic from app.controllers.musicians import MusicianController @@ -9,9 +10,105 @@ from app.models.musician import Musician mock_queries = Mock() ec = MusicianController(musician_queries=mock_queries) +sample_data = [ + { + "id": 1, + "name": "John Doe", + "bio": "A musician", + "headshot_id": "headshot123", + }, + { + "id": 2, + "name": "Jane Doe", + "bio": "Another musician", + "headshot_id": "headshotABC", + }, +] + +bad_data = [ + # no bio + { + "id": "three", + "name": "Jack Doe", + "headshot_id": "headshot456", + } +] + + +async def mock_select_all_series(): + return sample_data + + +async def mock_select_all_series_sad(): + return bad_data + + +async def mock_select_one_by_id(musician_id: int): + for musician in sample_data: + if musician.get("id") == musician_id: + return musician + return None + + +mock_queries.select_all_series = mock_select_all_series +mock_queries.select_one_by_id = mock_select_one_by_id + def test_type(): assert isinstance(ec, MusicianController) -# TODO: Write tests for MusicianController +""" +TODO: write tests for following methods: + +- _update_musician_headshot +- _update_musician_bio +- _upload_headshot +""" + + +@pytest.mark.asyncio +async def test_happy_get_musicians(): + musicians = await ec.get_musicians() + assert isinstance(musicians, list) + assert len(musicians) == 2 + for musician in musicians: + assert isinstance(musician, Musician) + m1, m2 = musicians + assert m1.id == 1 + assert m1.name == "John Doe" + assert m1.bio == "A musician" + assert m1.headshot_id == "headshot123" + assert m2.id == 2 + assert m2.name == "Jane Doe" + assert m2.bio == "Another musician" + assert m2.headshot_id == "headshotABC" + + +@pytest.mark.asyncio +async def test_sad_get_musicians(): + mock_queries.select_all_series = mock_select_all_series_sad + with pytest.raises(HTTPException) as e: + await ec.get_musicians() + mock_queries.select_all_series = mock_select_all_series + assert isinstance(e.value, HTTPException) + assert e.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR + + +@pytest.mark.asyncio +async def test_happy_get_musician(): + musician = await ec.get_musician(1) + assert isinstance(musician, Musician) + assert musician.id == 1 + assert musician.name == "John Doe" + assert musician.bio == "A musician" + assert musician.headshot_id == "headshot123" + + +@pytest.mark.asyncio +async def test_musician_not_found(): + with pytest.raises(HTTPException) as e: + await ec.get_musician(3) + assert isinstance(e.value, HTTPException) + assert e.value.status_code == status.HTTP_404_NOT_FOUND + assert e.value.detail == "Musician not found"