diff --git a/src/crud/courseprogress.py b/src/crud/courseprogress.py index 4818f1e..66eda01 100644 --- a/src/crud/courseprogress.py +++ b/src/crud/courseprogress.py @@ -1,6 +1,7 @@ from sqlalchemy.orm import Session +from fastapi import HTTPException -from src.enums import CourseEnum, course_enum_list +from src.enums import CourseEnum from src.models import CourseProgress, User from src.schemas.courseprogress import CourseProgressBase @@ -41,13 +42,19 @@ def get_course_progress(db: Session, user: User, course: CourseEnum): def initialize_user(db: Session, user: User): """Create CourseProgress records with a value of 0 for a new user""" - for course in course_enum_list: - db.add(CourseProgress(progress_value=0.0, course=course, owner_id=user.user_id)) - db.commit() + for course in CourseEnum: + if course != CourseEnum.All: + db.add( + CourseProgress(progress_value=0.0, course=course, owner_id=user.user_id) + ) + db.commit() def patch_course_progress(db: Session, user: User, course_progress: CourseProgressBase): """Change the progress value for a given course""" + if course_progress.progress_value > 1 or course_progress.progress_value < 0: + raise HTTPException(status_code=400, detail="Invalid progress value") + db_course_progress_list = [] if course_progress.course != CourseEnum.All: db_course_progress_list = ( @@ -64,7 +71,12 @@ def patch_course_progress(db: Session, user: User, course_progress: CourseProgre .filter(CourseProgress.owner_id == user.user_id) .all() ) - + print(f"LENGTH OF LIST OF {course_progress.course}: {len(db_course_progress_list)}") for db_course_progress in db_course_progress_list: db_course_progress.progress_value = course_progress.progress_value db.commit() + + return [ + CourseProgressBase(course=db_cp.course, progress_value=db_cp.progress_value) + for db_cp in db_course_progress_list + ] diff --git a/src/enums.py b/src/enums.py index 3b6ecd4..3ec4085 100644 --- a/src/enums.py +++ b/src/enums.py @@ -34,15 +34,3 @@ class CourseEnum(StrEnum): Colors = "Colors" FruitsVegetables = "FruitsVegetables" All = "All" - - -# This is needed because for some reason iterating over an enum doesn't work properly... -course_enum_list = [ - CourseEnum.Fingerspelling, - # CourseEnum.Basics, - CourseEnum.Hobbies, - CourseEnum.Animals, - CourseEnum.Colors, - CourseEnum.FruitsVegetables, - CourseEnum.All, -] diff --git a/src/main.py b/src/main.py index d518c4e..71cf170 100644 --- a/src/main.py +++ b/src/main.py @@ -92,7 +92,7 @@ async def get_course_progress( return crud_courseprogress.get_course_progress(db, current_user, course) -@app.patch("/courseprogress") +@app.patch("/courseprogress", response_model=List[courseprogress.CourseProgressBase]) async def patch_course_progress( course_progress: courseprogress.CourseProgressBase, current_user_name: str = Depends(crud_authentication.get_current_user_name), diff --git a/tests/test_authentication.py b/tests/test_authentication.py new file mode 100644 index 0000000..825d73e --- /dev/null +++ b/tests/test_authentication.py @@ -0,0 +1,139 @@ +import sys + +import pytest +from fastapi.testclient import TestClient + +sys.path.append("..") + +from src.main import app, get_db +from tests.config.database import clear_db, override_get_db + +app.dependency_overrides[get_db] = override_get_db + +client = TestClient(app) + +username1 = "user1" +username2 = "user2" +password = "password" +avatar = "lion" + + +@pytest.mark.asyncio +async def test_register(): + """LEAVE THIS TEST AT THE TOP OF THE FILE!""" + """Test the register endpoint""" + clear_db() + + response = client.post( + "/register", + headers={"Content-Type": "application/json"}, + json={"username": username1, "password": password, "avatar": avatar}, + ) + + assert response.status_code == 200 + assert len(response.json()["access_token"]) > 0 + + +@pytest.mark.asyncio +async def test_register_duplicate_name_should_fail(): + """Test whether registering a user with an existing username fails""" + response = client.post( + "/register", + headers={"Content-Type": "application/json"}, + json={"username": username1, "password": password, "avatar": avatar}, + ) + + assert response.status_code == 400 + assert "access_token" not in response.json() + + +@pytest.mark.asyncio +async def test_register_without_username_should_fail(): + """Test whether registering a user without passing a username fails""" + response = client.post( + "/register", + headers={"Content-Type": "application/json"}, + json={"password": password, "avatar": avatar}, + ) + + assert response.status_code == 422 + assert "access_token" not in response.json() + + +@pytest.mark.asyncio +async def test_register_without_password_should_fail(): + """Test whether registering a user without passing a password fails""" + response = client.post( + "/register", + headers={"Content-Type": "application/json"}, + json={"username": username2, "avatar": avatar}, + ) + + assert response.status_code == 422 + assert "access_token" not in response.json() + + +@pytest.mark.asyncio +async def test_register_without_avatar_should_fail(): + """Test whether registering a user without passing an avatar fails""" + response = client.post( + "/register", + headers={"Content-Type": "application/json"}, + json={"username": username2, "password": password}, + ) + + # Not ideal that this is 400 instead of 422, but had no other choice than to give this field a default value + assert response.status_code == 400 + assert "access_token" not in response.json() + + +@pytest.mark.asyncio +async def test_login(): + """Test the login endpoint""" + response = client.post( + "/login", + headers={"Content-Type": "application/json"}, + json={"username": username1, "password": password}, + ) + + assert response.status_code == 200 + assert len(response.json()["access_token"]) > 0 + + +@pytest.mark.asyncio +async def test_login_wrong_password_should_fail(): + wrong_password = password + "extra characters" + response = client.post( + "/login", + headers={"Content-Type": "application/json"}, + json={"username": username1, "password": wrong_password}, + ) + + assert response.status_code == 401 + assert "access_token" not in response.json() + + +@pytest.mark.asyncio +async def test_login_without_username_should_fail(): + """Test whether logging in without passing a username fails""" + response = client.post( + "/login", + headers={"Content-Type": "application/json"}, + json={"username": username1}, + ) + + assert response.status_code == 422 + assert "access_token" not in response.json() + + +@pytest.mark.asyncio +async def test_login_without_password_should_fail(): + """Test whether logging in without passing a password fails""" + response = client.post( + "/login", + headers={"Content-Type": "application/json"}, + json={"username": username1}, + ) + + assert response.status_code == 422 + assert "access_token" not in response.json() diff --git a/tests/test_courseprogress.py b/tests/test_courseprogress.py new file mode 100644 index 0000000..ec41d56 --- /dev/null +++ b/tests/test_courseprogress.py @@ -0,0 +1,174 @@ +import random +import sys + +import pytest +from fastapi.testclient import TestClient + +sys.path.append("..") + +from src.enums import CourseEnum +from src.main import app, get_db +from tests.config.database import clear_db, override_get_db + +app.dependency_overrides[get_db] = override_get_db + +client = TestClient(app) + +username = "user1" +password = "password" +avatar = "lion" + + +async def register_user(): + response = client.post( + "/register", + headers={"Content-Type": "application/json"}, + json={"username": username, "password": password, "avatar": avatar}, + ) + + assert response.status_code == 200 + + return response.json()["access_token"] + + +@pytest.mark.asyncio +async def test_register_creates_progress_of_zero(): + """Test whether registering a new user initializes all progress values to 0.0""" + clear_db() + + token = await register_user() + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + for course in CourseEnum: + if course != CourseEnum.All: + response = client.get(f"/courseprogress/{course}", headers=headers) + assert response.status_code == 200 + + response = response.json()[0] + + assert response["progress_value"] == 0.0 + assert response["course"] == course + + +@pytest.mark.asyncio +async def test_get_all_returns_all(): + clear_db() + token = await register_user() + + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + response = client.get("/courseprogress/All", headers=headers) + + assert response.status_code == 200 + response = response.json() + + for course in CourseEnum: + if course != CourseEnum.All: + assert {"progress_value": 0.0, "course": course} in response + + +@pytest.mark.asyncio +async def test_get_nonexisting_course_should_fail(): + clear_db() + token = await register_user() + + fake_course = "FakeCourse" + + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + response = client.get(f"/courseprogress/{fake_course}", headers=headers) + + assert response.status_code == 422 + + +@pytest.mark.asyncio +async def test_patch_course_progress(): + clear_db() + token = await register_user() + + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + for course in CourseEnum: + if course != CourseEnum.All: + progress_value = random.uniform(0, 1) + + response = client.patch( + f"/courseprogress", + headers=headers, + json={"progress_value": progress_value, "course": course}, + ) + + assert response.status_code == 200 + assert response.json()[0]["progress_value"] == progress_value + + +@pytest.mark.asyncio +async def test_patch_all_should_patch_all_courses(): + clear_db() + token = await register_user() + + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + progress_value = random.uniform(0, 1) + + response = client.patch( + f"/courseprogress", + headers=headers, + json={"progress_value": progress_value, "course": "All"}, + ) + + assert response.status_code == 200 + + response = client.get("/courseprogress/All", headers=headers) + + assert response.status_code == 200 + response = response.json() + + for course in CourseEnum: + if course != CourseEnum.All: + assert {"progress_value": progress_value, "course": course} in response + + +@pytest.mark.asyncio +async def test_patch_nonexisting_course_should_fail(): + clear_db() + token = await register_user() + + fake_course = "FakeCourse" + + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + progress_value = random.uniform(0, 1) + + response = client.patch( + f"/courseprogress", + headers=headers, + json={"progress_value": progress_value, "course": fake_course}, + ) + + assert response.status_code == 422 + + +@pytest.mark.asyncio +async def test_patch_course_with_invalid_value_should_fail(): + clear_db() + token = await register_user() + + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + too_high_progress_value = random.uniform(0, 1) + 2 + too_low_progress_value = random.uniform(0, 1) - 2 + + response = client.patch( + f"/courseprogress", + headers=headers, + json={"progress_value": too_high_progress_value, "course": "All"}, + ) + + assert response.status_code == 400 + + response = client.patch( + f"/courseprogress", + headers=headers, + json={"progress_value": too_low_progress_value, "course": "All"}, + ) + + assert response.status_code == 400 diff --git a/tests/test_highscores.py b/tests/test_highscores.py new file mode 100644 index 0000000..5e145a4 --- /dev/null +++ b/tests/test_highscores.py @@ -0,0 +1,17 @@ +import sys + +import pytest +from fastapi.testclient import TestClient + +sys.path.append("..") + +from src.main import app, get_db +from tests.config.database import clear_db, override_get_db + +app.dependency_overrides[get_db] = override_get_db + +client = TestClient(app) + +username = "user1" +password = "password" +avatar = "lion"