diff --git a/alembic.ini b/alembic.ini deleted file mode 100644 index 80ca7c9..0000000 --- a/alembic.ini +++ /dev/null @@ -1,102 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts -script_location = alembic - -# template used to generate migration files -# file_template = %%(rev)s_%%(slug)s - -# sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. -prepend_sys_path = . - -# timezone to use when rendering the date within the migration file -# as well as the filename. -# If specified, requires the python-dateutil library that can be -# installed by adding `alembic[tz]` to the pip requirements -# string value is passed to dateutil.tz.gettz() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the -# "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; This defaults -# to alembic/versions. When using multiple version -# directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "version_path_separator" below. -# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions - -# version path separator; As mentioned above, this is the character used to split -# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. -# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. -# Valid values for version_path_separator are: -# -# version_path_separator = : -# version_path_separator = ; -# version_path_separator = space -version_path_separator = os # Use os.pathsep. Default configuration used for new projects. - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -sqlalchemy.url = asdas - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/classquiz/tests/__init__.py b/classquiz/tests/__init__.py index 04cdf13..346853f 100644 --- a/classquiz/tests/__init__.py +++ b/classquiz/tests/__init__.py @@ -1,16 +1,35 @@ -import pytest from typing import Generator from fastapi.testclient import TestClient -from classquiz import app + +# from classquiz.socket_server import sio +from classquiz import app as fastapi_app + +# from typing import List, Optional + +# stdlib imports +# import asyncio + +# 3rd party imports +import pytest + +# import socketio +# import uvicorn + +# FastAPI imports +# from fastapi import FastAPI + +PORT = 8000 class ValueStorage: quiz_id = None - imported_quizzes = [None] + imported_quizzes = [] + game_pin = None + game_id = None example_quiz = { - "public": False, + "public": True, "title": "Some test question", "description": "A description", "questions": [ @@ -37,5 +56,52 @@ test_user_password = "test" @pytest.fixture(scope="module") def test_client() -> Generator: - with TestClient(app) as testclient: + with TestClient(fastapi_app) as testclient: yield testclient + + +# +# sio.eio.start_service_task = False +# +# +# class UvicornTestServer(uvicorn.Server): +# """Uvicorn test server +# +# Usage: +# @pytest.fixture +# async def start_stop_server(): +# server = UvicornTestServer() +# await server.up() +# yield +# await server.down() +# """ +# +# def __init__(self, app: FastAPI = fastapi_app, host: str = '127.0.0.1', port: int = PORT): +# """Create a Uvicorn test server +# +# Args: +# app (FastAPI, optional): the FastAPI app. Defaults to main.app. +# host (str, optional): the host ip. Defaults to '127.0.0.1'. +# port (int, optional): the port. Defaults to PORT. +# """ +# self._startup_done = asyncio.Event() +# super().__init__(config=uvicorn.Config(app, host=host, port=port)) +# +# async def startup(self, sockets: Optional[List] = None) -> None: +# """Override uvicorn startup""" +# await super().startup(sockets=sockets) +# self.config.setup_event_loop() +# self._startup_done.set() +# +# async def up(self) -> None: +# """Start up server asynchronously""" +# self._serve_task = asyncio.create_task(self.serve()) +# await self._startup_done.wait() +# +# async def down(self) -> None: +# """Shut down server asynchronously""" +# self.should_exit = True +# await self._serve_task +# +# +# diff --git a/classquiz/tests/test_server.py b/classquiz/tests/test_server.py index c0eaf14..afff5e4 100644 --- a/classquiz/tests/test_server.py +++ b/classquiz/tests/test_server.py @@ -8,6 +8,17 @@ from classquiz.tests import test_user_email, test_user_password from classquiz.tests import test_client, example_quiz, ValueStorage +# @pytest.fixture +# async def startup_and_shutdown_server(): +# """Start server as test fixture and tear down after test""" +# print("starting up") +# server = UvicornTestServer() +# await server.up() +# print("Server up") +# yield +# await server.down() + + class TestUsers: @pytest.mark.asyncio async def test_create_test_user(self, test_client): @@ -330,21 +341,14 @@ class TestQuiz: "/api/v1/quiz/update/saddsaasddsadsa", json=example_quiz, cookies={"access_token": token} ) assert resp.status_code == 400 - - @pytest.mark.asyncio - async def test_get_public_quiz(self, test_client): - resp = test_client.post( - "/api/v1/users/token/cookie", data={"username": test_user_email, "password": test_user_password} + example_quiz["public"] = False + test_client.put( + f"/api/v1/quiz/update/{ValueStorage.quiz_id}", json=example_quiz, cookies={"access_token": token} ) - token = resp.cookies["access_token"] - resp = test_client.get(f"/api/v1/quiz/get/{ValueStorage.quiz_id}", cookies={"access_token": token}) - assert resp.status_code == 200 - resp = test_client.get( - "/api/v1/quiz/get/public/f183e091-a863-44ec-a1b7-c70eb92e3f6a", cookies={"access_token": token} + example_quiz["public"] = True + test_client.put( + f"/api/v1/quiz/update/{ValueStorage.quiz_id}", json=example_quiz, cookies={"access_token": token} ) - assert resp.status_code == 404 - resp = test_client.get("/api/v1/quiz/get/public/dadasdas92e3f6a", cookies={"access_token": token}) - assert resp.status_code == 400 @pytest.mark.asyncio async def test_import_quiz(self, test_client): @@ -360,8 +364,101 @@ class TestQuiz: resp = test_client.post("/api/v1/quiz/import/1f95eb0bdassdadasdas", cookies={"access_token": token}) assert resp.text == '"quiz not found"' + @pytest.mark.asyncio + async def test_get_public_quiz(self, test_client): + resp = test_client.post( + "/api/v1/users/token/cookie", data={"username": test_user_email, "password": test_user_password} + ) + token = resp.cookies["access_token"] + resp = test_client.get(f"/api/v1/quiz/get/public/{ValueStorage.imported_quizzes[0]}") + assert resp.status_code == 200 + resp = test_client.get( + "/api/v1/quiz/get/public/f183e091-a863-44ec-a1b7-c70eb92e3f6a", cookies={"access_token": token} + ) + assert resp.status_code == 404 + resp = test_client.get("/api/v1/quiz/get/public/dadasdas92e3f6a", cookies={"access_token": token}) + assert resp.status_code == 400 + + @pytest.mark.asyncio + async def test_search_get(self, test_client): + resp = test_client.get("/api/v1/search/?q=*") + assert resp.status_code == 200 + assert len(resp.json()["hits"]) > 0 + + @pytest.mark.asyncio + async def test_search_post(self, test_client): + resp = test_client.post("/api/v1/search/", json={"q": "*"}) + assert resp.status_code == 200 + assert len(resp.json()["hits"]) > 0 + + @pytest.mark.asyncio + async def test_image_cdn(self, test_client): + resp = test_client.get(f"/api/v1/quiz/get/public/{ValueStorage.imported_quizzes[0]}") + assert resp.status_code == 200 + quiz = resp.json() + image_url = quiz["questions"][0]["image"] + resp = test_client.get(image_url) + assert resp.status_code == 200 + resp = test_client.get(f"{image_url}sadgvsadgvhsad") + assert resp.status_code == 400 + + +class TestPlayQuiz: + @pytest.mark.asyncio + async def test_start_quiz(self, test_client): + resp = test_client.post( + "/api/v1/users/token/cookie", data={"username": test_user_email, "password": test_user_password} + ) + token = resp.cookies["access_token"] + resp = test_client.post( + "/api/v1/quiz/start/fb5adc91-629e-416e-8b98-ae400e36417c", cookies={"access_token": token} + ) + assert resp.status_code == 404 + resp = test_client.post( + "/api/v1/quiz/start/fb5adc91-629e-416e-8b98-ae400sdadsasadsadasddsae36417c", cookies={"access_token": token} + ) + assert resp.status_code == 400 + resp = test_client.post(f"/api/v1/quiz/start/{ValueStorage.quiz_id}", cookies={"access_token": token}) + ValueStorage.game_pin = resp.json()["game_pin"] + ValueStorage.game_id = resp.json()["game_id"] + + # @pytest.mark.asyncio + # async def test_actual_play(self, startup_and_shutdown_server): + # admin_client = socketio.AsyncClient() + # player1_client = socketio.AsyncClient() + # player2_client = socketio.AsyncClient() + # future = asyncio.get_running_loop().create_future() + # time.sleep(1) + # await admin_client.connect(f'http://localhost:{PORT}', socketio_path='/socket.io/') + # await player1_client.connect(f'http://localhost:{PORT}', socketio_path="/socket.io") + # await player2_client.connect(f'http://localhost:{PORT}', socketio_path="/socket.io") + # + # await admin_client.disconnect() + # await player1_client.disconnect() + # await player2_client.disconnect() + + +class TestDeleteStuff: + @pytest.mark.asyncio + async def test_delete_quiz(self, test_client): + resp = test_client.post( + "/api/v1/users/token/cookie", data={"username": test_user_email, "password": test_user_password} + ) + token = resp.cookies["access_token"] + resp = test_client.delete( + f"/api/v1/quiz/delete/{ValueStorage.imported_quizzes[0]}", cookies={"access_token": token} + ) + assert resp.status_code == 200 + resp = test_client.delete( + "/api/v1/quiz/delete/be582c77-da03-4271-929c-5d582056eb78", cookies={"access_token": token} + ) + assert resp.status_code == 404 + + resp = test_client.delete( + "/api/v1/quiz/delete/be582c77-da03-sdaasdadsasddas4271-929c-5d582056eb78", cookies={"access_token": token} + ) + assert resp.status_code == 400 -class TestDeleteUser: @pytest.mark.asyncio async def test_delete_user(self, test_client): resp = test_client.post(