From 3968dfd4ebda29369e047fecd307cea29358cd5c Mon Sep 17 00:00:00 2001 From: lvrossem Date: Tue, 18 Apr 2023 02:52:26 -0600 Subject: [PATCH] More refactoring --- src/crud/highscores.py | 1 - src/crud/learnableprogress.py | 25 +++++++++++++- src/main.py | 13 ++++++++ tests/base.py | 9 ++++- tests/config/database.py | 3 +- tests/test_authentication.py | 25 +++++++------- tests/test_courseprogress.py | 26 +++++++-------- tests/test_highscores.py | 62 ++++++++++++++++++++--------------- tests/test_learnables.py | 39 ++++++++++++++++++++++ tests/test_users.py | 17 +++++----- 10 files changed, 156 insertions(+), 64 deletions(-) create mode 100644 tests/test_learnables.py diff --git a/src/crud/highscores.py b/src/crud/highscores.py index db045da..e517066 100644 --- a/src/crud/highscores.py +++ b/src/crud/highscores.py @@ -53,7 +53,6 @@ def get_highest_high_scores( .first() ) if high_score: - print(str(high_score.time)) return [ Score( score_value=high_score.score_value, diff --git a/src/crud/learnableprogress.py b/src/crud/learnableprogress.py index f7bae1a..2d7a31f 100644 --- a/src/crud/learnableprogress.py +++ b/src/crud/learnableprogress.py @@ -6,10 +6,33 @@ from src.models import CourseProgress, LearnableProgress, User from src.schemas.learnableprogress import SavedLearnableProgress +def get_learnables(db: Session, user: User, course: CourseEnum): + """Get all learnables of a certain course""" + db_course = ( + db.query(CourseProgress) + .filter( + CourseProgress.owner_id == user.user_id, CourseProgress.course == course + ) + .first() + ) + db_learnable_query = ( + db.query(LearnableProgress) + .filter(LearnableProgress.course_progress_id == db_course.course_progress_id) + .all() + ) + + return [ + SavedLearnableProgress( + index=dbl.index, in_use=dbl.in_use, name=dbl.name, progress=dbl.progress + ) + for dbl in db_learnable_query + ] + + def create_learnable( db: Session, user: User, course: CourseEnum, learnable: SavedLearnableProgress ): - """Create a new learnable""" + """Create a new learnable for a given course""" if learnable.index < 0: raise HTTPException(status_code=400, detail="Negative index not allowed") diff --git a/src/main.py b/src/main.py index 1852e37..c9f2cc2 100644 --- a/src/main.py +++ b/src/main.py @@ -128,6 +128,19 @@ async def patch_course_progress( ) +@app.get( + "/learnables/{course}", + response_model=List[learnableprogress.SavedLearnableProgress], +) +async def create_learnable( + course: CourseEnum, + current_user_name: str = Depends(crud_authentication.get_current_user_name), + db: Session = Depends(get_db), +): + current_user = crud_users.get_user_by_username(db, current_user_name) + return crud_learnables.get_learnables(db, current_user, course) + + @app.post("/learnables/{course}") async def create_learnable( course: CourseEnum, diff --git a/tests/base.py b/tests/base.py index 569fff2..c0ceb8e 100644 --- a/tests/base.py +++ b/tests/base.py @@ -16,10 +16,17 @@ password = "password" avatar_index = 1 +def get_headers(token=None): + if token: + return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + else: + return {"Content-Type": "application/json"} + + async def register_user(): response = client.post( "/register", - headers={"Content-Type": "application/json"}, + headers=get_headers(), json={"username": username, "password": password, "avatar_index": avatar_index}, ) diff --git a/tests/config/database.py b/tests/config/database.py index ef82ea7..ed97dc4 100644 --- a/tests/config/database.py +++ b/tests/config/database.py @@ -2,7 +2,7 @@ from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from src.database import Base -from src.models import CourseProgress, HighScore, User +from src.models import CourseProgress, HighScore, LearnableProgress, User SQLALCHEMY_DATABASE_URL = "postgresql://admin:WeSign123!@localhost/wesigntest" @@ -17,6 +17,7 @@ def clear_db(): db = TestSessionLocal() db.query(HighScore).delete() + db.query(LearnableProgress).delete() db.query(CourseProgress).delete() db.query(User).delete() db.commit() diff --git a/tests/test_authentication.py b/tests/test_authentication.py index a5cf574..41b16b5 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -1,17 +1,18 @@ import pytest -from tests.base import avatar_index, client, password, register_user, username +from tests.base import (avatar_index, client, get_headers, password, + register_user, username) from tests.config.database import clear_db @pytest.mark.asyncio -async def test_register(): +async def test_register_should_succeed(): """Test the register endpoint""" clear_db() response = client.post( "/register", - headers={"Content-Type": "application/json"}, + headers=get_headers(), json={"username": username, "password": password, "avatar_index": avatar_index}, ) @@ -27,7 +28,7 @@ async def test_register_duplicate_name_should_fail(): response = client.post( "/register", - headers={"Content-Type": "application/json"}, + headers=get_headers(), json={"username": username, "password": password, "avatar_index": avatar_index}, ) @@ -42,7 +43,7 @@ async def test_register_without_username_should_fail(): response = client.post( "/register", - headers={"Content-Type": "application/json"}, + headers=get_headers(), json={"password": password, "avatar_index": avatar_index}, ) @@ -57,7 +58,7 @@ async def test_register_without_password_should_fail(): response = client.post( "/register", - headers={"Content-Type": "application/json"}, + headers=get_headers(), json={"username": username, "avatar_index": avatar_index}, ) @@ -72,7 +73,7 @@ async def test_register_without_avatar_should_fail(): response = client.post( "/register", - headers={"Content-Type": "application/json"}, + headers=get_headers(), json={"username": username, "password": password}, ) @@ -81,14 +82,14 @@ async def test_register_without_avatar_should_fail(): @pytest.mark.asyncio -async def test_login(): +async def test_login_should_succeed(): """Test the login endpoint""" clear_db() await register_user() response = client.post( "/login", - headers={"Content-Type": "application/json"}, + headers=get_headers(), json={"username": username, "password": password}, ) @@ -104,7 +105,7 @@ async def test_login_wrong_password_should_fail(): wrong_password = password + "extra characters" response = client.post( "/login", - headers={"Content-Type": "application/json"}, + headers=get_headers(), json={"username": username, "password": wrong_password}, ) @@ -120,7 +121,7 @@ async def test_login_without_username_should_fail(): response = client.post( "/login", - headers={"Content-Type": "application/json"}, + headers=get_headers(), json={"password": password}, ) @@ -136,7 +137,7 @@ async def test_login_without_password_should_fail(): response = client.post( "/login", - headers={"Content-Type": "application/json"}, + headers=get_headers(), json={"username": username}, ) diff --git a/tests/test_courseprogress.py b/tests/test_courseprogress.py index 12541e0..32a73a6 100644 --- a/tests/test_courseprogress.py +++ b/tests/test_courseprogress.py @@ -3,17 +3,17 @@ import random import pytest from src.enums import CourseEnum -from tests.base import client, register_user +from tests.base import client, get_headers, register_user from tests.config.database import clear_db @pytest.mark.asyncio -async def test_register_creates_progress_of_zero(): +async def test_register_should_create_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"} + headers = get_headers(token) for course in CourseEnum: if course != CourseEnum.All: @@ -27,12 +27,12 @@ async def test_register_creates_progress_of_zero(): @pytest.mark.asyncio -async def test_get_all_returns_all(): +async def test_get_all_sould_return_all(): """Test whether the 'All'-course fetches all course progress values""" clear_db() token = await register_user() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) response = client.get("/courseprogress/All", headers=headers) assert response.status_code == 200 @@ -48,7 +48,7 @@ async def test_get_course_progress_without_auth_should_fail(): """Test whether fetching a course progress value without authentication fails""" clear_db() - headers = {"Content-Type": "application/json"} + headers = get_headers() for course in CourseEnum: response = client.get(f"/courseprogress/{course}", headers=headers) @@ -64,19 +64,19 @@ async def test_get_nonexisting_course_should_fail(): fake_course = "FakeCourse" - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) response = client.get(f"/courseprogress/{fake_course}", headers=headers) assert response.status_code == 422 @pytest.mark.asyncio -async def test_patch_course_progress(): +async def test_patch_course_progress_should_succeed(): """Test whether patching the progress value of a course works properly""" clear_db() token = await register_user() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) for course in CourseEnum: if course != CourseEnum.All: @@ -98,7 +98,7 @@ async def test_patch_all_should_patch_all_courses(): clear_db() token = await register_user() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) progress = random.uniform(0, 1) @@ -128,7 +128,7 @@ async def test_patch_nonexisting_course_should_fail(): fake_course = "FakeCourse" - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) progress = random.uniform(0, 1) @@ -147,7 +147,7 @@ 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"} + headers = get_headers(token) too_high_progress = random.uniform(0, 1) + 2 too_low_progress = random.uniform(0, 1) - 2 @@ -174,7 +174,7 @@ async def test_patch_course_progress_without_auth_should_fail(): """Test whether updating a course progress value without authentication fails""" clear_db() - headers = {"Content-Type": "application/json"} + headers = get_headers() for course in CourseEnum: response = client.patch( diff --git a/tests/test_highscores.py b/tests/test_highscores.py index 2c6e86b..5de994d 100644 --- a/tests/test_highscores.py +++ b/tests/test_highscores.py @@ -3,17 +3,18 @@ import random import pytest from src.enums import MinigameEnum -from tests.base import avatar_index, client, password, register_user +from tests.base import (avatar_index, client, get_headers, password, + register_user) from tests.config.database import clear_db @pytest.mark.asyncio -async def test_put_highscore(): +async def test_put_highscore_should_succeed(): """Test whether putting a new high score succeeds""" clear_db() token = await register_user() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) for minigame in MinigameEnum: score_value = random.random() @@ -31,12 +32,12 @@ async def test_put_highscore(): @pytest.mark.asyncio -async def test_put_lower_highscore_does_not_change_old_value(): +async def test_put_lower_highscore_should_not_change_old_value(): """Test whether putting a new high score lower than the current one doesn't change the old one""" clear_db() token = await register_user() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) for minigame in MinigameEnum: score_value = random.random() @@ -74,7 +75,7 @@ async def test_put_highscore_for_nonexisting_minigame_should_fail(): fake_minigame = "FakeGame" - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) response = client.put( f"/highscores/{fake_minigame}", @@ -90,7 +91,7 @@ async def test_put_highscores_without_auth_should_fail(): """Test whether putting high scores without authentication fails""" clear_db() - headers = {"Content-Type": "application/json"} + headers = get_headers() for minigame in MinigameEnum: response = client.put( @@ -107,7 +108,7 @@ async def test_get_highscores_without_auth_should_fail(): """Test whether fetching high scores without authentication fails""" clear_db() - headers = {"Content-Type": "application/json"} + headers = get_headers() for minigame in MinigameEnum: response = client.get( @@ -133,7 +134,7 @@ async def test_get_highscore_for_nonexisting_minigame_should_fail(): fake_minigame = "FakeGame" - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) response = client.get( f"/highscores/{fake_minigame}", @@ -156,7 +157,7 @@ async def test_get_invalid_number_of_highscores_should_fail(): clear_db() token = await register_user() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) for minigame in MinigameEnum: response = client.get( @@ -168,12 +169,12 @@ async def test_get_invalid_number_of_highscores_should_fail(): @pytest.mark.asyncio -async def test_get_highscores_should_work_with_default_value(): +async def test_get_highscores_should_succeed_with_default_value(): """Test whether fetching high scores without passing an explicit amount still succeeds""" clear_db() token = await register_user() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) for minigame in MinigameEnum: response = client.get( @@ -185,16 +186,16 @@ async def test_get_highscores_should_work_with_default_value(): @pytest.mark.asyncio -async def test_get_highscores_returns_sorted_list_with_correct_length(): +async def test_get_highscores_should_return_sorted_list_with_correct_length(): """Test whether getting a list of high scores gets a list in descending order and of the correct length""" clear_db() token = await register_user() - headers = {"Content-Type": "application/json"} + headers = get_headers() for minigame in MinigameEnum: clear_db() - nr_entries = random.randint(5, 50) + nr_entries = random.randint(5, 10) token = "" users_score_tuples = [ @@ -218,10 +219,7 @@ async def test_get_highscores_returns_sorted_list_with_correct_length(): response = client.put( f"/highscores/{minigame}", - headers={ - "Authorization": f"Bearer {token}", - "Content-Type": "application/json", - }, + headers=get_headers(token), json={"score_value": score}, ) @@ -229,10 +227,7 @@ async def test_get_highscores_returns_sorted_list_with_correct_length(): response = client.get( f"/highscores/{minigame}?mine_only=false&amount={int(nr_entries)}", - headers={ - "Authorization": f"Bearer {token}", - "Content-Type": "application/json", - }, + headers=get_headers(token), ) assert response.status_code == 200 @@ -243,14 +238,27 @@ async def test_get_highscores_returns_sorted_list_with_correct_length(): for i in range(1, len(response)): assert response[i]["score_value"] <= response[i - 1]["score_value"] + response = client.get( + f"/highscores/{minigame}?most_recent=true&mine_only=false&amount={int(nr_entries)}", + headers=get_headers(token), + ) + + assert response.status_code == 200 + response = response.json() + + assert len(response) == nr_entries + + for i in range(1, len(response)): + assert response[i]["time"] <= response[i - 1]["time"] + @pytest.mark.asyncio -async def test_get_own_existing_high_score_should_return_high_score(): +async def test_get_own_existing_high_score_should_succeed(): """Test whether fetching your own high score of a game succeeds""" clear_db() token = await register_user() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) for minigame in MinigameEnum: response = client.put( @@ -276,7 +284,7 @@ async def test_get_own_nonexisting_high_score_should_return_empty_list(): clear_db() token = await register_user() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) for minigame in MinigameEnum: response = client.get( @@ -294,7 +302,7 @@ async def test_get_multiple_own_high_scores_of_same_game_should_fail(): clear_db() token = await register_user() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) for minigame in MinigameEnum: response = client.get( diff --git a/tests/test_learnables.py b/tests/test_learnables.py new file mode 100644 index 0000000..1c2d03b --- /dev/null +++ b/tests/test_learnables.py @@ -0,0 +1,39 @@ +import random + +import pytest + +from src.enums import CourseEnum +from tests.base import client, get_headers, register_user +from tests.config.database import clear_db + + +@pytest.mark.asyncio +async def test_create_learnables_should_succeed(): + clear_db() + token = await register_user() + + headers = get_headers(token) + + for course in CourseEnum: + if course != CourseEnum.All: + nr_learnables = random.randint(1, 5) + for i in range(nr_learnables): + response = client.post( + f"/learnables/{course}", + json={ + "index": i, + "in_use": bool(random.randint(0, 1)), + "name": f"{course} {i}", + }, + headers=headers, + ) + + assert response.status_code == 200 + + response = client.get(f"/learnables/{course}", headers=headers) + + assert response.status_code == 200 + + response = response.json() + + assert len(response) == nr_learnables diff --git a/tests/test_users.py b/tests/test_users.py index 9c18d39..037ff98 100644 --- a/tests/test_users.py +++ b/tests/test_users.py @@ -1,6 +1,7 @@ import pytest -from tests.base import avatar_index, client, register_user, username +from tests.base import (avatar_index, client, get_headers, register_user, + username) from tests.config.database import clear_db patched_username = "New name" @@ -9,13 +10,13 @@ patched_avatar_index = 2 @pytest.mark.asyncio -async def test_patch_user(): +async def test_patch_user_should_succeed(): """Test the patching of a user's username, password and avatar""" clear_db() token = await register_user() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) response = client.patch( "/users", @@ -30,14 +31,14 @@ async def test_patch_user(): response = client.post( "/login", - headers={"Content-Type": "application/json"}, + headers=get_headers(), json={"username": patched_username, "password": patched_password}, ) assert response.status_code == 200 token = response.json()["access_token"] - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) response = client.get("/saveddata", headers=headers) assert response.status_code == 200 @@ -47,13 +48,13 @@ async def test_patch_user(): @pytest.mark.asyncio -async def test_patch_user_with_empty_fields(): - """Patching a user with empty fields should fail""" +async def test_patch_user_with_empty_fields_should_succeed(): + """Patching a user with empty fields should still succeed""" clear_db() token = await register_user() - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + headers = get_headers(token) response = client.patch( "/users",