diff --git a/classquiz/db/models.py b/classquiz/db/models.py index 688f851..4fa793d 100644 --- a/classquiz/db/models.py +++ b/classquiz/db/models.py @@ -2,7 +2,6 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. import os -import re import uuid from datetime import datetime from typing import Optional @@ -13,7 +12,6 @@ from pydantic import BaseModel, Json, validator from enum import Enum from . import metadata, database from .quiztivity import QuizTivityPage -from ..config import server_regex from sqlalchemy import func @@ -165,15 +163,6 @@ class QuizInput(BaseModel): questions: list[QuizQuestion] background_image: str | None - @validator("background_image") - def must_come_from_local_cdn(cls, v): - if v is None: - return v - elif bool(re.match(server_regex, v)): - return v - else: - raise ValueError("does not match url scheme") - class Quiz(ormar.Model): id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4(), nullable=False, unique=True) @@ -190,15 +179,6 @@ class Quiz(ormar.Model): background_image: str | None = ormar.Text(nullable=True, unique=False) kahoot_id: uuid.UUID | None = ormar.UUID(nullable=True, default=None) - @validator("background_image") - def must_come_from_local_cdn(cls, v): - if v is None: - return v - elif bool(re.match(server_regex, v)): - return v - else: - raise ValueError("does not match url scheme") - class Meta: tablename = "quiz" metadata = metadata diff --git a/classquiz/routers/quiztivity/shares.py b/classquiz/routers/quiztivity/shares.py index 95d4bec..7b3d403 100644 --- a/classquiz/routers/quiztivity/shares.py +++ b/classquiz/routers/quiztivity/shares.py @@ -77,7 +77,6 @@ async def get_share(uuid: UUID) -> QuizTivity: share = await QuizTivityShare.objects.select_related("quiztivity").get_or_none(id=uuid) if share is None: raise HTTPException(status_code=404, detail="Share not found") - print(share.quiztivity) if share.expire_at is None: return share.quiztivity if share.expire_at < datetime.now(): diff --git a/classquiz/storage/local_storage.py b/classquiz/storage/local_storage.py index 5138fd8..a97665a 100644 --- a/classquiz/storage/local_storage.py +++ b/classquiz/storage/local_storage.py @@ -12,11 +12,6 @@ import aiofiles.os _DEFAULT_CHUNK_SIZE = 32768 # bytes; arbitrary -async def aioshutil_copyfileobj(async_fsrc, async_fdst, *, chunksize: int = _DEFAULT_CHUNK_SIZE) -> None: - while (chunk := await async_fsrc.read(chunksize)) != b"": - await async_fdst.write(chunk) - - class LocalStorage: def __init__(self, base_path: str): self.base_path = base_path diff --git a/classquiz/storage/s3_storage.py b/classquiz/storage/s3_storage.py index 50d4f3f..b5cfe14 100644 --- a/classquiz/storage/s3_storage.py +++ b/classquiz/storage/s3_storage.py @@ -134,8 +134,9 @@ class S3Storage: ) def size(self, file_name: str) -> int | None: - res = self.client.stat_object(bucket_name=self.bucket_name, object_name=file_name) - if res is None: + try: + res = self.client.stat_object(bucket_name=self.bucket_name, object_name=file_name) + except minio.error.S3Error: return None return res.size diff --git a/classquiz/tests/__init__.py b/classquiz/tests/__init__.py index b073aa3..7ce1b8a 100644 --- a/classquiz/tests/__init__.py +++ b/classquiz/tests/__init__.py @@ -35,6 +35,9 @@ class ValueStorage: image_id = None cookies = None file_id = None + quiztivity_id = None + share_id = None + expired_share_id = None example_quiz = { @@ -64,6 +67,19 @@ example_quiz = { test_user_email = "sth@byom.de" test_user_password = "test" +example_quiztivity = { + "title": "Some test Quiztivity", + "pages": [ + { + "title": "Some test question", + "type": "ABCD", + "data": { + "question": "Is ClassQuiz cool?", + "answers": [{"correct": True, "answer": "Yes"}, {"correct": False, "answer": "No"}], + }, + } + ], +} # mock_test_results = {'0': [{'username': 'Player 1', 'answer': 'Byte, Bit, KB, MB, GB, TB', 'right': False}, # {'username': 'Player 2', 'answer': 'Byte, Bit, KB, MB, GB, TB', 'right': False}, {'username': 'Player 3', diff --git a/classquiz/tests/test_server.py b/classquiz/tests/test_server.py index 9a5f1ed..cf1f802 100644 --- a/classquiz/tests/test_server.py +++ b/classquiz/tests/test_server.py @@ -7,7 +7,7 @@ import uuid import pytest from redis import Redis from classquiz.config import settings -from classquiz.tests import test_user_email, test_user_password +from classquiz.tests import test_user_email, test_user_password, example_quiztivity from classquiz.tests import test_client, example_quiz, ValueStorage # noqa : F401 from fastapi.testclient import TestClient @@ -508,6 +508,176 @@ class TestStorage: assert data["used"] == 0 +class TestQuizivity: + @pytest.mark.asyncio + async def test_create_quiztivity(self, test_client: TestClient): # noqa : F811 + resp = test_client.post("/api/v1/quiztivity/create", cookies=ValueStorage.cookies, json=example_quiztivity) + assert resp.status_code == 200 + data = resp.json() + ValueStorage.quiztivity_id = data["id"] + + @pytest.mark.asyncio + async def test_get_quiztivity(self, test_client: TestClient): # noqa : F811 + resp = test_client.get("/api/v1/quiztivity/8bd77201-65ed-46fe-9160-cfe71dad501f", cookies=ValueStorage.cookies) + assert resp.status_code == 404 + resp = test_client.get(f"/api/v1/quiztivity/{ValueStorage.quiztivity_id}", cookies=ValueStorage.cookies) + assert resp.status_code == 200 + data = resp.json() + assert data["id"] == ValueStorage.quiztivity_id + + @pytest.mark.asyncio + async def test_put_quiztivity(self, test_client: TestClient): # noqa : F811 + example_quiztivity["title"] = "New title" + resp = test_client.put( + f"/api/v1/quiztivity/{ValueStorage.quiztivity_id}", json=example_quiztivity, cookies=ValueStorage.cookies + ) + assert resp.status_code == 200 + resp = test_client.put( + "/api/v1/quiztivity/8bd77201-65ed-46fe-9160-cfe71dad501f", + json=example_quiztivity, + cookies=ValueStorage.cookies, + ) + assert resp.status_code == 404 + + @pytest.mark.asyncio + async def test_get_all_quiztivities(self, test_client: TestClient): # noqa : F811 + resp = test_client.get("/api/v1/quiztivity/", cookies=ValueStorage.cookies) + assert resp.status_code == 200 + data = resp.json() + assert type(data) is list + assert data[0]["id"] == ValueStorage.quiztivity_id + + @pytest.mark.asyncio + async def test_create_share(self, test_client: TestClient): # noqa : F811 + resp = test_client.post( + "/api/v1/quiztivity/shares/", + cookies=ValueStorage.cookies, + json={"quiztivity": ValueStorage.quiztivity_id, "expire_in": None}, + ) + assert resp.status_code == 200 + data = resp.json() + ValueStorage.share_id = data["id"] + resp = test_client.post( + "/api/v1/quiztivity/shares/", + cookies=ValueStorage.cookies, + json={"quiztivity": "a090077f-9059-42bc-9783-f2cd01e069b8", "expire_in": None}, + ) + assert resp.status_code == 400 + resp = test_client.post( + "/api/v1/quiztivity/shares/", + cookies=ValueStorage.cookies, + json={"quiztivity": ValueStorage.quiztivity_id, "expire_in": 0}, + ) + data = resp.json() + ValueStorage.expired_share_id = data["id"] + assert resp.status_code == 200 + + @pytest.mark.asyncio + async def test_get_share(self, test_client: TestClient): # noqa : F811 + resp = test_client.get( + "/api/v1/quiztivity/shares/8bd77201-65ed-46fe-9160-cfe71dad501f", cookies=ValueStorage.cookies + ) + assert resp.status_code == 404 + resp = test_client.get(f"/api/v1/quiztivity/shares/{ValueStorage.share_id}", cookies=ValueStorage.cookies) + assert resp.status_code == 200 + data = resp.json() + assert data["id"] == ValueStorage.quiztivity_id + resp = test_client.get( + f"/api/v1/quiztivity/shares/{ValueStorage.expired_share_id}", cookies=ValueStorage.cookies + ) + assert resp.status_code == 410 + + @pytest.mark.asyncio + async def test_update_share(self, test_client: TestClient): # noqa : F811 + resp = test_client.put( + "/api/v1/quiztivity/shares/8bd77201-65ed-46fe-9160-cfe71dad501f", + cookies=ValueStorage.cookies, + json={"expire_in": 50}, + ) + assert resp.status_code == 404 + resp = test_client.put( + f"/api/v1/quiztivity/shares/{ValueStorage.share_id}", cookies=ValueStorage.cookies, json={"expire_in": 50} + ) + assert resp.status_code == 200 + + async def test_delete_share(self, test_client: TestClient): # noqa : F811 + resp = test_client.delete( + "/api/v1/quiztivity/shares/8bd77201-65ed-46fe-9160-cfe71dad501f", cookies=ValueStorage.cookies + ) + assert resp.status_code == 404 + resp = test_client.delete( + f"/api/v1/quiztivity/shares/{ValueStorage.expired_share_id}", cookies=ValueStorage.cookies + ) + assert resp.status_code == 200 + + @pytest.mark.asyncio + async def test_get_shares(self, test_client: TestClient): # noqa : F811 + resp = test_client.get("/api/v1/quiztivity/shares/", cookies=ValueStorage.cookies) + assert resp.status_code == 200 + data = resp.json() + assert type(data) is list + assert data[0]["id"] == ValueStorage.share_id + assert data[0]["expire_in"] == 49 + + @pytest.mark.asyncio + async def test_get_shares_by_quiztivity(self, test_client: TestClient): # noqa : F811 + resp = test_client.get(f"/api/v1/quiztivity/{ValueStorage.quiztivity_id}/shares", cookies=ValueStorage.cookies) + assert resp.status_code == 200 + data = resp.json() + assert type(data) is list + assert data[0]["id"] == ValueStorage.share_id + + @pytest.mark.asyncio + async def test_get_shares(self, test_client: TestClient): # noqa : F811 + resp = test_client.get("/api/v1/quiztivity/shares/", cookies=ValueStorage.cookies) + assert resp.status_code == 200 + data = resp.json() + assert type(data) is list + assert data[0]["id"] == ValueStorage.share_id + + @pytest.mark.asyncio + async def test_delete_quiztivity(self, test_client: TestClient): # noqa : F811 + test_client.delete(f"/api/v1/quiztivity/shares/{ValueStorage.share_id}", cookies=ValueStorage.cookies) + resp = test_client.delete( + "/api/v1/quiztivity/8bd77201-65ed-46fe-9160-cfe71dad501f", cookies=ValueStorage.cookies + ) + assert resp.status_code == 404 + resp = test_client.delete(f"/api/v1/quiztivity/{ValueStorage.quiztivity_id}", cookies=ValueStorage.cookies) + assert resp.status_code == 200 + + +class TestAvatar: + @pytest.mark.asyncio + async def test_get_customized_avatar(self, test_client: TestClient): # noqa : F811 + resp = test_client.get("/api/v1/avatar/custom?skin_color=69", cookies=ValueStorage.cookies) + assert resp.status_code == 400 + resp = test_client.get("/api/v1/avatar/custom", cookies=ValueStorage.cookies) + assert resp.status_code == 200 + assert "image/svg+xml" in resp.headers.get("Content-Type") + + @pytest.mark.asyncio + async def test_save_avatar(self, test_client: TestClient): # noqa : F811 + resp = test_client.post("/api/v1/avatar/save?skin_color=69", cookies=ValueStorage.cookies) + assert resp.status_code == 400 + resp = test_client.post("/api/v1/avatar/save", cookies=ValueStorage.cookies) + assert resp.status_code == 200 + + @pytest.mark.asyncio + async def test_get_own_avatar(self, test_client: TestClient): # noqa : F811 + resp = test_client.get("/api/v1/users/avatar", cookies=ValueStorage.cookies) + assert resp.status_code == 200 + + @pytest.mark.asyncio + async def test_get_other_avatar(self, test_client: TestClient): # noqa : F811 + resp = test_client.get( + "/api/v1/users/8bd77201-65ed-46fe-9160-cfe71dad501f/avatar", cookies=ValueStorage.cookies + ) + assert resp.status_code == 404 + user_id = test_client.get("/api/v1/users/me", cookies=ValueStorage.cookies).json()["id"] + resp = test_client.get(f"/api/v1/users/avatar/{user_id}", cookies=ValueStorage.cookies) + assert resp.status_code == 200 + + class TestExImport: @pytest.mark.asyncio async def test_export_quiz(self, test_client: TestClient): # noqa : F811 diff --git a/classquiz/tests/test_storage.py b/classquiz/tests/test_storage.py index 49e8daf..bec7ec4 100644 --- a/classquiz/tests/test_storage.py +++ b/classquiz/tests/test_storage.py @@ -31,6 +31,10 @@ async def storage_tester(storage: Storage): res = storage.download(file_name="test.txt") async for chunk in res: assert bytes(chunk) == file_contents + res = await storage.get_file_size(file_name="test.txt") + assert res == len(file_contents) + res = await storage.get_file_size(file_name="asdsadasdasdadfdsf.txt") + assert res is None res = await storage.delete(file_names=["test.txt"]) assert res is None res = storage.download(file_name="test.txt")