Merge pull request #9 from ljensen505/more-unittests

More unittests
This commit is contained in:
Lucas Jensen
2024-05-03 18:26:10 -07:00
committed by GitHub
7 changed files with 305 additions and 23 deletions

View File

@@ -7,12 +7,31 @@ from app.models.group import Group
class GroupController(BaseController): class GroupController(BaseController):
def __init__(self) -> None: """
Handles all group-related operations and serves as an intermediate controller between
the main controller and the model layer.
Inherits from BaseController, which provides logging and other generic methods.
The corresponding table contains only one row.
Testing: pass a mocked GroupQueries object to the constructor.
"""
def __init__(self, group_queries=group_queries) -> None:
super().__init__() super().__init__()
self.db: GroupQueries = group_queries self.group_queries: GroupQueries = group_queries
def get_group(self) -> Group: def get_group(self) -> Group:
if (data := self.db.select_one_by_id()) is None: """Retrieves the group from the database and returns it as a Group object.
Raises:
HTTPException: If the group is not found (status code 404)
HTTPException: If any error occurs during the retrieval process (status code 500)
Returns:
Group: A Group object which is suitable for a response body
"""
if (data := self.group_queries.select_one_by_id()) is None:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Group not found" status_code=status.HTTP_404_NOT_FOUND, detail="Group not found"
) )
@@ -25,8 +44,19 @@ class GroupController(BaseController):
) )
def update_group_bio(self, bio: str) -> Group: def update_group_bio(self, bio: str) -> Group:
"""Updates the group's bio in the database and returns the updated Group object.
Args:
bio (str): The new bio for the group
Raises:
HTTPException: If any error occurs during the update process (status code 500)
Returns:
Group: The updated Group object which is suitable for a response body
"""
try: try:
self.db.update_group_bio(bio) self.group_queries.update_group_bio(bio)
except Exception as e: except Exception as e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,

View File

@@ -9,11 +9,28 @@ from app.models.user import User
class UserController(BaseController): class UserController(BaseController):
def __init__(self) -> None: """
Handles all user-related operations and serves as an intermediate controller between
the main controller and the model layer.
Inherits from BaseController, which provides logging and other generic methods.
Testing: pass a mocked UserQueries object to the constructor.
"""
def __init__(self, user_queries=user_queries) -> None:
super().__init__() super().__init__()
self.db: UserQueries = user_queries self.db: UserQueries = user_queries
def get_users(self) -> list[User]: def get_users(self) -> list[User]:
"""Retrieves all users from the database and returns them as a list of User objects.
Raises:
HTTPException: If any error occurs during the retrieval process (status code 500)
Returns:
list[User]: A list of User objects which are suitable for a response body
"""
data = self.db.select_all_series() data = self.db.select_all_series()
try: try:
return [User(**e) for e in data] return [User(**e) for e in data]
@@ -23,8 +40,20 @@ class UserController(BaseController):
detail=f"Error creating user objects: {e}", detail=f"Error creating user objects: {e}",
) )
def get_user_by_id(self, id: int) -> User: def get_user_by_id(self, user_id: int) -> User:
if (data := self.db.select_one_by_id(id)) is None: """Retrieves a single user from the database and returns it as a User object.
Args:
user_id (int): The ID of the user to retrieve
Raises:
HTTPException: If the user is not found (status code 404)
HTTPException: If any error occurs during the retrieval process (status code 500)
Returns:
User: A User object which is suitable for a response body
"""
if (data := self.db.select_one_by_id(user_id)) is None:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="User not found" status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
) )
@@ -37,7 +66,19 @@ class UserController(BaseController):
) )
def get_user_by_email(self, email: str) -> User: def get_user_by_email(self, email: str) -> User:
if (data := self.db.get_one_by_email(email)) is None: """Retrieves a single user from the database and returns it as a User object.
Args:
email (str): The email of the user to retrieve
Raises:
HTTPException: If the user is not found (status code 404)
HTTPException: If any error occurs during the retrieval process (status code 500)
Returns:
User: A User object which is suitable for a response body
"""
if (data := self.db.select_one_by_email(email)) is None:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="User does not exist" status_code=status.HTTP_404_NOT_FOUND, detail="User does not exist"
) )
@@ -50,7 +91,19 @@ class UserController(BaseController):
) )
def get_user_by_sub(self, sub: str) -> User: def get_user_by_sub(self, sub: str) -> User:
if (data := self.db.get_one_by_sub(sub)) is None: """Retrieves a single user from the database and returns it as a User object.
Args:
sub (str): The sub of the user to retrieve
Raises:
HTTPException: If the user is not found (status code 404)
HTTPException: If any error occurs during the retrieval process (status code 500)
Returns:
User: A User object which is suitable for a response body
"""
if (data := self.db.select_one_by_sub(sub)) is None:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="User not found" status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
) )

View File

@@ -7,7 +7,7 @@ class UserQueries(BaseQueries):
super().__init__() super().__init__()
self.table = USER_TABLE self.table = USER_TABLE
def get_one_by_email(self, email: str) -> dict | None: def select_one_by_email(self, email: str) -> dict | None:
query = f"SELECT * FROM {self.table} WHERE email = %s" query = f"SELECT * FROM {self.table} WHERE email = %s"
db = self.connect_db() db = self.connect_db()
cursor = db.cursor(dictionary=True) cursor = db.cursor(dictionary=True)
@@ -18,7 +18,7 @@ class UserQueries(BaseQueries):
return data return data
def get_one_by_sub(self, sub: str) -> dict | None: def select_one_by_sub(self, sub: str) -> dict | None:
query = f"SELECT * FROM {self.table} WHERE sub = %s" query = f"SELECT * FROM {self.table} WHERE sub = %s"
db = self.connect_db() db = self.connect_db()
cursor = db.cursor(dictionary=True) cursor = db.cursor(dictionary=True)

View File

@@ -1,5 +1,5 @@
from datetime import datetime from datetime import datetime
from unittest.mock import Mock from unittest.mock import MagicMock
import pytest import pytest
from icecream import ic from icecream import ic
@@ -8,7 +8,7 @@ from pydantic_core import Url
from app.controllers.events import EventController from app.controllers.events import EventController
from app.models.event import Event, EventSeries from app.models.event import Event, EventSeries
mock_queries = Mock() mock_queries = MagicMock()
ec = EventController(event_queries=mock_queries) ec = EventController(event_queries=mock_queries)
eventbrite_url = "https://www.eventbrite.com/e/the-grapefruits-duo-presents-works-for-horn-and-piano-tickets-1234567890" eventbrite_url = "https://www.eventbrite.com/e/the-grapefruits-duo-presents-works-for-horn-and-piano-tickets-1234567890"
@@ -182,7 +182,7 @@ def test_all_series_with_many_series():
def test_all_series_with_error(): def test_all_series_with_error():
"""Tests an error during the retrieval process.""" """Tests an error during the retrieval process."""
mock_log_error = Mock() mock_log_error = MagicMock()
ec.log_error = mock_log_error ec.log_error = mock_log_error
def invalid_series() -> list[dict]: def invalid_series() -> list[dict]:
@@ -198,7 +198,7 @@ def test_all_series_with_error():
mock_queries.select_all_series = invalid_series mock_queries.select_all_series = invalid_series
with pytest.raises(Exception): with pytest.raises(Exception):
ec.get_all_series() ec.get_all_series()
Mock.assert_called_once(mock_log_error) MagicMock.assert_called_once(mock_log_error)
def test_one_series(): def test_one_series():

View File

@@ -0,0 +1,59 @@
from unittest.mock import MagicMock
import pytest
from fastapi import HTTPException, status
from icecream import ic
from app.controllers.group import GroupController
from app.models.group import Group
mock_queries = MagicMock()
gc = GroupController(group_queries=mock_queries)
valid_group_data = {
"id": 1,
"name": "Test Group",
"bio": "Test Bio",
}
invalid_group_data = {
"id": 1,
"name": "Test Group",
}
def test_type():
"""Tests the type of the controller object."""
assert isinstance(gc, GroupController)
def test_get_group():
"""Tests the retrieval of a group from the database with valid data."""
mock_queries.select_one_by_id.return_value = valid_group_data
group = gc.get_group()
assert isinstance(group, Group)
assert group.id == 1
assert group.name == "Test Group"
assert group.bio == "Test Bio"
def test_get_group_failure():
"""Tests a failure during the retrieval process or if the group is not found."""
mock_queries.select_one_by_id.return_value = invalid_group_data
with pytest.raises(HTTPException) as e:
gc.get_group()
assert isinstance(e.value, HTTPException)
assert e.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
def test_update_group_bio():
"""This test does not test updating of the bio, but rather tests that the corresponding
method in the queries module is called with the correct arguments.
"""
new_bio = "New Bio"
mock_queries.update_group_bio = MagicMock()
mock_queries.select_one_by_id.return_value = valid_group_data
group = gc.update_group_bio(new_bio)
MagicMock.assert_called_once_with(mock_queries.update_group_bio, new_bio)
assert isinstance(group, Group)

View File

@@ -1,4 +1,4 @@
from unittest.mock import MagicMock, Mock from unittest.mock import MagicMock
import pytest import pytest
from fastapi import HTTPException, UploadFile, status from fastapi import HTTPException, UploadFile, status
@@ -7,8 +7,8 @@ from icecream import ic
from app.controllers.musicians import MusicianController from app.controllers.musicians import MusicianController
from app.models.musician import Musician from app.models.musician import Musician
mock_queries = Mock() mock_queries = MagicMock()
ec = MusicianController(musician_queries=mock_queries) mc = MusicianController(musician_queries=mock_queries)
sample_data = [ sample_data = [
{ {
@@ -55,7 +55,7 @@ mock_queries.select_one_by_id = mock_select_one_by_id
def test_type(): def test_type():
assert isinstance(ec, MusicianController) assert isinstance(mc, MusicianController)
""" """
@@ -68,7 +68,7 @@ TODO: write tests for following methods:
def test_happy_get_musicians(): def test_happy_get_musicians():
musicians = ec.get_musicians() musicians = mc.get_musicians()
assert isinstance(musicians, list) assert isinstance(musicians, list)
assert len(musicians) == 2 assert len(musicians) == 2
for musician in musicians: for musician in musicians:
@@ -87,14 +87,14 @@ def test_happy_get_musicians():
def test_sad_get_musicians(): def test_sad_get_musicians():
mock_queries.select_all_series = mock_select_all_series_sad mock_queries.select_all_series = mock_select_all_series_sad
with pytest.raises(HTTPException) as e: with pytest.raises(HTTPException) as e:
ec.get_musicians() mc.get_musicians()
mock_queries.select_all_series = mock_select_all_series mock_queries.select_all_series = mock_select_all_series
assert isinstance(e.value, HTTPException) assert isinstance(e.value, HTTPException)
assert e.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR assert e.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
def test_happy_get_musician(): def test_happy_get_musician():
musician = ec.get_musician(1) musician = mc.get_musician(1)
assert isinstance(musician, Musician) assert isinstance(musician, Musician)
assert musician.id == 1 assert musician.id == 1
assert musician.name == "John Doe" assert musician.name == "John Doe"
@@ -104,7 +104,7 @@ def test_happy_get_musician():
def test_musician_not_found(): def test_musician_not_found():
with pytest.raises(HTTPException) as e: with pytest.raises(HTTPException) as e:
ec.get_musician(3) mc.get_musician(3)
assert isinstance(e.value, HTTPException) assert isinstance(e.value, HTTPException)
assert e.value.status_code == status.HTTP_404_NOT_FOUND assert e.value.status_code == status.HTTP_404_NOT_FOUND
assert e.value.detail == "Musician not found" assert e.value.detail == "Musician not found"

View File

@@ -0,0 +1,140 @@
from unittest.mock import MagicMock
import pytest
from fastapi import HTTPException, status
from icecream import ic
from app.controllers.users import UserController
from app.models.user import User
mock_queries = MagicMock()
uc = UserController(user_queries=mock_queries)
valid_user_data = [
{
"id": 1,
"name": "John Doe",
"email": "john@doe.com",
},
{"id": 2, "name": "Jane Doe", "email": "jane@doe.com", "sub": "1234567890"},
]
invalid_user_data = [
{
"id": 1,
"name": "Jack Doe",
}
]
def mock_select_one_by_id(id: int):
for user in valid_user_data:
if user.get("id") == id:
return user
return None
def mock_select_one_by_email(email: str):
for user in valid_user_data:
if user.get("email") == email:
return user
return None
def mock_select_one_by_sub(sub: str):
for user in valid_user_data:
if user.get("sub") == sub:
return user
return None
mock_queries.select_one_by_id = mock_select_one_by_id
mock_queries.select_one_by_email = mock_select_one_by_email
mock_queries.select_one_by_sub = mock_select_one_by_sub
def test_type():
"""Tests the type of the controller object."""
assert isinstance(uc, UserController)
def test_get_users():
"""Tests the retrieval of users from the database."""
mock_queries.select_all_series.return_value = valid_user_data
users = uc.get_users()
assert isinstance(users, list)
assert len(users) == 2
sub_found = False
none_sub_found = False
for user in users:
assert isinstance(user, User)
if user.sub:
sub_found = True
assert isinstance(user.sub, str)
else:
none_sub_found = True
u1, u2 = users
assert u1.id == 1
assert u1.name == "John Doe"
assert isinstance(u1.email, str)
assert u2.id == 2
assert u2.name == "Jane Doe"
assert isinstance(u2.email, str)
assert sub_found and none_sub_found
def test_get_users_sad():
"""Tests the retrieval of users from the database with invalid data."""
mock_queries.select_all_series.return_value = invalid_user_data
with pytest.raises(HTTPException) as e:
uc.get_users()
assert isinstance(e.value, HTTPException)
assert e.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
def test_get_user_by_id():
"""Tests the retrieval of a user by id from the database."""
user = uc.get_user_by_id(1)
assert isinstance(user, User)
def test_get_user_by_invalid_id():
"""Tests the retrieval of a user by id from the database with invalid data."""
with pytest.raises(HTTPException) as e:
uc.get_user_by_id(10)
assert isinstance(e.value, HTTPException)
assert e.value.status_code == status.HTTP_404_NOT_FOUND
def test_get_user_by_email():
"""Tests the retrieval of a user by email from the database."""
user = uc.get_user_by_email(valid_user_data[0]["email"])
assert isinstance(user, User)
def test_get_user_by_invalid_email():
"""Tests the retrieval of a user by email from the database with invalid data."""
with pytest.raises(HTTPException) as e:
uc.get_user_by_email("carol@cat.com")
assert isinstance(e.value, HTTPException)
assert e.value.status_code == status.HTTP_404_NOT_FOUND
def test_get_user_by_sub():
"""Tests the retrieval of a user by sub from the database."""
user = uc.get_user_by_sub(valid_user_data[1]["sub"])
assert isinstance(user, User)
def test_get_user_by_invalid_sub():
"""Tests the retrieval of a user by sub from the database with invalid data."""
with pytest.raises(HTTPException) as e:
uc.get_user_by_sub("123abc")
assert isinstance(e.value, HTTPException)
assert e.value.status_code == status.HTTP_404_NOT_FOUND