diff --git a/Pipfile b/Pipfile index 8be921f9..092ab6a6 100644 --- a/Pipfile +++ b/Pipfile @@ -25,6 +25,8 @@ pygments = "*" pymdown-extensions = "*" bumpversion = "*" httpx-oauth = "*" +httpx = "*" +asgi_lifespan = "*" [packages] fastapi = ">=0.54.0,<0.55.0" diff --git a/Pipfile.lock b/Pipfile.lock index 1f4fbe33..da8a912e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0c74dc8fea8c64ef39e270bdfb235721bc6354c373a0ece09aa0d86d175a4c5e" + "sha256": "13ede3c78f004faead9c81285f4ff71ce2b709a7c1dc26de01e522b35fc75b89" }, "pipfile-spec": 6, "requires": { @@ -317,6 +317,14 @@ ], "version": "==1.4.3" }, + "asgi-lifespan": { + "hashes": [ + "sha256:1e1e4dfb2aa259f38cf3c55e58f9d3e25da9dd6e33190a3740fcca0e126b74e9", + "sha256:fb73fe407cfaf258b9f067caea32462f9783985ce671953bad8b99355ce8a3d4" + ], + "index": "pypi", + "version": "==1.0.0" + }, "asynctest": { "hashes": [ "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676", @@ -427,20 +435,13 @@ ], "version": "==0.16" }, - "entrypoints": { - "hashes": [ - "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", - "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" - ], - "version": "==0.3" - }, "flake8": { "hashes": [ - "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", - "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" + "sha256:c09e7e4ea0d91fa36f7b8439ca158e592be56524f0b67c39ab0ea2b85ed8f9a4", + "sha256:f33c5320eaa459cdee6367016a4bf4ba2a9b81499ce56e6a32abbf0b8d3a2eb4" ], "index": "pypi", - "version": "==3.7.9" + "version": "==3.8.0a2" }, "flake8-docstrings": { "hashes": [ @@ -716,10 +717,10 @@ }, "pycodestyle": { "hashes": [ - "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", - "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + "sha256:933bfe8d45355fbb35f9017d81fc51df8cb7ce58b82aca2568b870bf7bea1611", + "sha256:c1362bf675a7c0171fa5f795917c570c2e405a97e5dc473b51f3656075d73acc" ], - "version": "==2.5.0" + "version": "==2.6.0a1" }, "pydocstyle": { "hashes": [ @@ -730,10 +731,10 @@ }, "pyflakes": { "hashes": [ - "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", - "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", + "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" ], - "version": "==2.1.1" + "version": "==2.2.0" }, "pygments": { "hashes": [ @@ -768,11 +769,11 @@ }, "pytest-asyncio": { "hashes": [ - "sha256:9fac5100fd716cbecf6ef89233e8590a4ad61d729d1732e0a96b84182df1daaf", - "sha256:d734718e25cfc32d2bf78d346e99d33724deeba774cc4afdf491530c6184b63b" + "sha256:6096d101a1ae350d971df05e25f4a8b4d3cd13ffb1b32e42d902ac49670d2bfa", + "sha256:c54866f3cf5dd2063992ba2c34784edae11d3ed19e006d220a3cf0bfc4191fcb" ], "index": "pypi", - "version": "==0.10.0" + "version": "==0.11.0" }, "pytest-cov": { "hashes": [ diff --git a/docs/src/full_tortoise.py b/docs/src/full_tortoise.py index d4e57838..b9e2c36a 100644 --- a/docs/src/full_tortoise.py +++ b/docs/src/full_tortoise.py @@ -2,8 +2,8 @@ from fastapi import FastAPI from fastapi_users import FastAPIUsers, models from fastapi_users.authentication import JWTAuthentication from fastapi_users.db import TortoiseBaseUserModel, TortoiseUserDatabase -from tortoise.contrib.starlette import register_tortoise from starlette.requests import Request +from tortoise.contrib.starlette import register_tortoise DATABASE_URL = "sqlite://./test.db" SECRET = "SECRET" diff --git a/docs/src/oauth_full_tortoise.py b/docs/src/oauth_full_tortoise.py index 12581f1c..4e348888 100644 --- a/docs/src/oauth_full_tortoise.py +++ b/docs/src/oauth_full_tortoise.py @@ -7,9 +7,9 @@ from fastapi_users.db import ( TortoiseUserDatabase, ) from httpx_oauth.clients.google import GoogleOAuth2 +from starlette.requests import Request from tortoise import fields from tortoise.contrib.starlette import register_tortoise -from starlette.requests import Request DATABASE_URL = "sqlite://./test.db" SECRET = "SECRET" diff --git a/fastapi_users/db/tortoise.py b/fastapi_users/db/tortoise.py index 30760cbf..4e8fda18 100644 --- a/fastapi_users/db/tortoise.py +++ b/fastapi_users/db/tortoise.py @@ -1,6 +1,6 @@ from typing import List, Optional, Type -from tortoise import models, fields +from tortoise import fields, models from tortoise.exceptions import DoesNotExist from fastapi_users.db.base import BaseUserDatabase diff --git a/tests/conftest.py b/tests/conftest.py index 9f3957e8..43a8e632 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,16 @@ +import asyncio from typing import Any, List, Mapping, Optional, Tuple import http.cookies +import httpx import pytest +from asgi_lifespan import LifespanManager from fastapi import Depends, FastAPI from fastapi.security import OAuth2PasswordBearer from httpx_oauth.oauth2 import OAuth2 +from starlette.applications import ASGIApp from starlette.requests import Request from starlette.responses import Response -from starlette.testclient import TestClient from fastapi_users import models from fastapi_users.authentication import Authenticator, BaseAuthentication @@ -45,6 +48,13 @@ class UserDBOAuth(UserOAuth, UserDB): pass +@pytest.fixture +def event_loop(): + """Force the pytest-asyncio loop to be the main one.""" + loop = asyncio.get_event_loop() + yield loop + + @pytest.fixture def user() -> UserDB: return UserDB( @@ -284,8 +294,23 @@ def request_builder(): @pytest.fixture -def get_test_auth_client(mock_user_db): - def _get_test_auth_client(backends: List[BaseAuthentication]) -> TestClient: +def get_test_client(): + async def _get_test_client(app: ASGIApp) -> httpx.AsyncClient: + async with LifespanManager(app): + async with httpx.AsyncClient( + app=app, base_url="http://app.io" + ) as test_client: + return test_client + + return _get_test_client + + +@pytest.fixture +@pytest.mark.asyncio +def get_test_auth_client(mock_user_db, get_test_client): + async def _get_test_auth_client( + backends: List[BaseAuthentication], + ) -> httpx.AsyncClient: app = FastAPI() authenticator = Authenticator(backends, mock_user_db) @@ -305,12 +330,12 @@ def get_test_auth_client(mock_user_db): ): return user - return TestClient(app) + return await get_test_client(app) return _get_test_auth_client -@pytest.fixture() +@pytest.fixture def oauth_client() -> OAuth2: CLIENT_ID = "CLIENT_ID" CLIENT_SECRET = "CLIENT_SECRET" diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 60894259..2f83b797 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -9,7 +9,7 @@ from fastapi_users.db import BaseUserDatabase from fastapi_users.models import BaseUserDB -@pytest.fixture() +@pytest.fixture def auth_backend_none(): class BackendNone(BaseAuthentication): async def __call__( @@ -20,7 +20,7 @@ def auth_backend_none(): return BackendNone() -@pytest.fixture() +@pytest.fixture def auth_backend_user(user): class BackendUser(BaseAuthentication): async def __call__( @@ -32,14 +32,18 @@ def auth_backend_user(user): @pytest.mark.authentication -def test_authenticator(get_test_auth_client, auth_backend_none, auth_backend_user): - client = get_test_auth_client([auth_backend_none, auth_backend_user]) - response = client.get("/test-current-user") +@pytest.mark.asyncio +async def test_authenticator( + get_test_auth_client, auth_backend_none, auth_backend_user +): + client = await get_test_auth_client([auth_backend_none, auth_backend_user]) + response = await client.get("/test-current-user") assert response.status_code == status.HTTP_200_OK @pytest.mark.authentication -def test_authenticator_none(get_test_auth_client, auth_backend_none): - client = get_test_auth_client([auth_backend_none, auth_backend_none]) - response = client.get("/test-current-user") +@pytest.mark.asyncio +async def test_authenticator_none(get_test_auth_client, auth_backend_none): + client = await get_test_auth_client([auth_backend_none, auth_backend_none]) + response = await client.get("/test-current-user") assert response.status_code == status.HTTP_401_UNAUTHORIZED diff --git a/tests/test_fastapi_users.py b/tests/test_fastapi_users.py index 5e9b66ff..540fa4fc 100644 --- a/tests/test_fastapi_users.py +++ b/tests/test_fastapi_users.py @@ -1,8 +1,8 @@ import pytest +import httpx from fastapi import Depends, FastAPI from httpx_oauth.oauth2 import OAuth2 from starlette import status -from starlette.testclient import TestClient from fastapi_users import FastAPIUsers from fastapi_users.router import Event, EventHandlersRouter @@ -48,9 +48,9 @@ def fastapi_users( return fastapi_users -@pytest.fixture() -@pytest.mark.fastapi_users -def test_app_client(fastapi_users) -> TestClient: +@pytest.fixture +@pytest.mark.asyncio +async def test_app_client(fastapi_users, get_test_client) -> httpx.AsyncClient: app = FastAPI() app.include_router(fastapi_users.router, prefix="/users") @@ -66,7 +66,7 @@ def test_app_client(fastapi_users) -> TestClient: def current_superuser(user=Depends(fastapi_users.get_current_superuser)): return user - return TestClient(app) + return await get_test_client(app) @pytest.mark.fastapi_users @@ -78,99 +78,105 @@ class TestFastAPIUsers: @pytest.mark.fastapi_users +@pytest.mark.asyncio class TestRouter: - def test_routes_exist(self, test_app_client: TestClient): - response = test_app_client.post("/users/register") + async def test_routes_exist(self, test_app_client: httpx.AsyncClient): + response = await test_app_client.post("/users/register") assert response.status_code != status.HTTP_404_NOT_FOUND - response = test_app_client.post("/users/login") + response = await test_app_client.post("/users/login") assert response.status_code != status.HTTP_404_NOT_FOUND - response = test_app_client.post("/users/forgot-password") + response = await test_app_client.post("/users/forgot-password") assert response.status_code != status.HTTP_404_NOT_FOUND - response = test_app_client.post("/users/reset-password") + response = await test_app_client.post("/users/reset-password") assert response.status_code != status.HTTP_404_NOT_FOUND - response = test_app_client.get("/users") + response = await test_app_client.get("/users") assert response.status_code != status.HTTP_404_NOT_FOUND - response = test_app_client.get("/users/aaa") + response = await test_app_client.get("/users/aaa") assert response.status_code != status.HTTP_404_NOT_FOUND - response = test_app_client.patch("/users/aaa") + response = await test_app_client.patch("/users/aaa") assert response.status_code != status.HTTP_404_NOT_FOUND @pytest.mark.fastapi_users +@pytest.mark.asyncio class TestGetCurrentUser: - def test_missing_token(self, test_app_client: TestClient): - response = test_app_client.get("/current-user") + async def test_missing_token(self, test_app_client: httpx.AsyncClient): + response = await test_app_client.get("/current-user") assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_invalid_token(self, test_app_client: TestClient): - response = test_app_client.get( + async def test_invalid_token(self, test_app_client: httpx.AsyncClient): + response = await test_app_client.get( "/current-user", headers={"Authorization": "Bearer foo"} ) assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_valid_token(self, test_app_client: TestClient, user: UserDB): - response = test_app_client.get( + async def test_valid_token(self, test_app_client: httpx.AsyncClient, user: UserDB): + response = await test_app_client.get( "/current-user", headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_200_OK @pytest.mark.fastapi_users +@pytest.mark.asyncio class TestGetCurrentActiveUser: - def test_missing_token(self, test_app_client: TestClient): - response = test_app_client.get("/current-active-user") + async def test_missing_token(self, test_app_client: httpx.AsyncClient): + response = await test_app_client.get("/current-active-user") assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_invalid_token(self, test_app_client: TestClient): - response = test_app_client.get( + async def test_invalid_token(self, test_app_client: httpx.AsyncClient): + response = await test_app_client.get( "/current-active-user", headers={"Authorization": "Bearer foo"} ) assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_valid_token_inactive_user( - self, test_app_client: TestClient, inactive_user: UserDB + async def test_valid_token_inactive_user( + self, test_app_client: httpx.AsyncClient, inactive_user: UserDB ): - response = test_app_client.get( + response = await test_app_client.get( "/current-active-user", headers={"Authorization": f"Bearer {inactive_user.id}"}, ) assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_valid_token(self, test_app_client: TestClient, user: UserDB): - response = test_app_client.get( + async def test_valid_token(self, test_app_client: httpx.AsyncClient, user: UserDB): + response = await test_app_client.get( "/current-active-user", headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_200_OK @pytest.mark.fastapi_users +@pytest.mark.asyncio class TestGetCurrentSuperuser: - def test_missing_token(self, test_app_client: TestClient): - response = test_app_client.get("/current-superuser") + async def test_missing_token(self, test_app_client: httpx.AsyncClient): + response = await test_app_client.get("/current-superuser") assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_invalid_token(self, test_app_client: TestClient): - response = test_app_client.get( + async def test_invalid_token(self, test_app_client: httpx.AsyncClient): + response = await test_app_client.get( "/current-superuser", headers={"Authorization": "Bearer foo"} ) assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_valid_token_regular_user(self, test_app_client: TestClient, user: UserDB): - response = test_app_client.get( + async def test_valid_token_regular_user( + self, test_app_client: httpx.AsyncClient, user: UserDB + ): + response = await test_app_client.get( "/current-superuser", headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_403_FORBIDDEN - def test_valid_token_superuser( - self, test_app_client: TestClient, superuser: UserDB + async def test_valid_token_superuser( + self, test_app_client: httpx.AsyncClient, superuser: UserDB ): - response = test_app_client.get( + response = await test_app_client.get( "/current-superuser", headers={"Authorization": f"Bearer {superuser.id}"} ) assert response.status_code == status.HTTP_200_OK diff --git a/tests/test_router_oauth.py b/tests/test_router_oauth.py index 09d3d2ed..f62c8f94 100644 --- a/tests/test_router_oauth.py +++ b/tests/test_router_oauth.py @@ -1,11 +1,12 @@ from unittest.mock import MagicMock +from typing import Dict, Any, cast import asynctest +import httpx import pytest from fastapi import FastAPI from starlette import status from starlette.requests import Request -from starlette.testclient import TestClient from fastapi_users.authentication import Authenticator from fastapi_users.router.common import ErrorCode, Event @@ -29,11 +30,15 @@ def event_handler(request): return request.param() -@pytest.fixture() +@pytest.fixture def get_test_app_client( - mock_user_db_oauth, mock_authentication, oauth_client, event_handler + mock_user_db_oauth, + mock_authentication, + oauth_client, + event_handler, + get_test_client, ): - def _get_test_app_client(redirect_url: str = None) -> TestClient: + async def _get_test_app_client(redirect_url: str = None) -> httpx.AsyncClient: mock_authentication_bis = MockAuthentication(name="mock-bis") authenticator = Authenticator( [mock_authentication, mock_authentication_bis], mock_user_db_oauth @@ -53,41 +58,44 @@ def get_test_app_client( app = FastAPI() app.include_router(oauth_router) - return TestClient(app) + return await get_test_client(app) return _get_test_app_client -@pytest.fixture() -def test_app_client(get_test_app_client): - return get_test_app_client() +@pytest.fixture +@pytest.mark.asyncio +async def test_app_client(get_test_app_client): + return await get_test_app_client() -@pytest.fixture() -def test_app_client_redirect_url(get_test_app_client): - return get_test_app_client("http://www.tintagel.bt/callback") +@pytest.fixture +@pytest.mark.asyncio +async def test_app_client_redirect_url(get_test_app_client): + return await get_test_app_client("http://www.tintagel.bt/callback") @pytest.mark.router @pytest.mark.oauth +@pytest.mark.asyncio class TestAuthorize: - def test_missing_authentication_backend( - self, test_app_client: TestClient, oauth_client + async def test_missing_authentication_backend( + self, test_app_client: httpx.AsyncClient, oauth_client ): with asynctest.patch.object(oauth_client, "get_authorization_url") as mock: mock.return_value = "AUTHORIZATION_URL" - response = test_app_client.get( + response = await test_app_client.get( "/authorize", params={"scopes": ["scope1", "scope2"]}, ) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - def test_wrong_authentication_backend( - self, test_app_client: TestClient, oauth_client + async def test_wrong_authentication_backend( + self, test_app_client: httpx.AsyncClient, oauth_client ): with asynctest.patch.object(oauth_client, "get_authorization_url") as mock: mock.return_value = "AUTHORIZATION_URL" - response = test_app_client.get( + response = await test_app_client.get( "/authorize", params={ "authentication_backend": "foo", @@ -97,10 +105,10 @@ class TestAuthorize: assert response.status_code == status.HTTP_400_BAD_REQUEST - def test_success(self, test_app_client: TestClient, oauth_client): + async def test_success(self, test_app_client: httpx.AsyncClient, oauth_client): with asynctest.patch.object(oauth_client, "get_authorization_url") as mock: mock.return_value = "AUTHORIZATION_URL" - response = test_app_client.get( + response = await test_app_client.get( "/authorize", params={ "authentication_backend": "mock", @@ -114,12 +122,12 @@ class TestAuthorize: data = response.json() assert "authorization_url" in data - def test_with_redirect_url( - self, test_app_client_redirect_url: TestClient, oauth_client + async def test_with_redirect_url( + self, test_app_client_redirect_url: httpx.AsyncClient, oauth_client ): with asynctest.patch.object(oauth_client, "get_authorization_url") as mock: mock.return_value = "AUTHORIZATION_URL" - response = test_app_client_redirect_url.get( + response = await test_app_client_redirect_url.get( "/authorize", params={ "authentication_backend": "mock", @@ -136,9 +144,14 @@ class TestAuthorize: @pytest.mark.router @pytest.mark.oauth +@pytest.mark.asyncio class TestCallback: - def test_invalid_state( - self, test_app_client: TestClient, oauth_client, user_oauth, event_handler + async def test_invalid_state( + self, + test_app_client: httpx.AsyncClient, + oauth_client, + user_oauth, + event_handler, ): with asynctest.patch.object( oauth_client, "get_access_token" @@ -151,7 +164,7 @@ class TestCallback: oauth_client, "get_id_email" ) as get_id_email_mock: get_id_email_mock.return_value = ("user_oauth1", user_oauth.email) - response = test_app_client.get( + response = await test_app_client.get( "/callback", params={"code": "CODE", "state": "STATE"}, ) @@ -160,10 +173,10 @@ class TestCallback: assert event_handler.called is False - def test_existing_user_with_oauth( + async def test_existing_user_with_oauth( self, mock_user_db_oauth, - test_app_client: TestClient, + test_app_client: httpx.AsyncClient, oauth_client, user_oauth, event_handler, @@ -183,22 +196,22 @@ class TestCallback: mock_user_db_oauth, "update" ) as user_update_mock: get_id_email_mock.return_value = ("user_oauth1", user_oauth.email) - response = test_app_client.get( + response = await test_app_client.get( "/callback", params={"code": "CODE", "state": state_jwt}, ) get_id_email_mock.assert_awaited_once_with("TOKEN") user_update_mock.assert_awaited_once() - data = response.json() + data = cast(Dict[str, Any], response.json()) assert data["token"] == user_oauth.id assert event_handler.called is False - def test_existing_user_without_oauth( + async def test_existing_user_without_oauth( self, mock_user_db_oauth, - test_app_client: TestClient, + test_app_client: httpx.AsyncClient, oauth_client, superuser_oauth, event_handler, @@ -221,22 +234,22 @@ class TestCallback: "superuser_oauth1", superuser_oauth.email, ) - response = test_app_client.get( + response = await test_app_client.get( "/callback", params={"code": "CODE", "state": state_jwt}, ) get_id_email_mock.assert_awaited_once_with("TOKEN") user_update_mock.assert_awaited_once() - data = response.json() + data = cast(Dict[str, Any], response.json()) assert data["token"] == superuser_oauth.id assert event_handler.called is False - def test_unknown_user( + async def test_unknown_user( self, mock_user_db_oauth, - test_app_client: TestClient, + test_app_client: httpx.AsyncClient, oauth_client, event_handler, ): @@ -258,13 +271,13 @@ class TestCallback: "unknown_user_oauth1", "galahad@camelot.bt", ) - response = test_app_client.get( + response = await test_app_client.get( "/callback", params={"code": "CODE", "state": state_jwt}, ) get_id_email_mock.assert_awaited_once_with("TOKEN") user_create_mock.assert_awaited_once() - data = response.json() + data = cast(Dict[str, Any], response.json()) assert "token" in data @@ -274,10 +287,10 @@ class TestCallback: request = event_handler.call_args[0][1] assert isinstance(request, Request) - def test_inactive_user( + async def test_inactive_user( self, mock_user_db_oauth, - test_app_client: TestClient, + test_app_client: httpx.AsyncClient, oauth_client, inactive_user_oauth, event_handler, @@ -297,19 +310,20 @@ class TestCallback: "inactive_user_oauth1", inactive_user_oauth.email, ) - response = test_app_client.get( + response = await test_app_client.get( "/callback", params={"code": "CODE", "state": state_jwt}, ) assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json()["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS + data = cast(Dict[str, Any], response.json()) + assert data["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS assert event_handler.called is False - def test_redirect_url_router( + async def test_redirect_url_router( self, mock_user_db_oauth, - test_app_client_redirect_url: TestClient, + test_app_client_redirect_url: httpx.AsyncClient, oauth_client, user_oauth, ): @@ -325,13 +339,13 @@ class TestCallback: oauth_client, "get_id_email" ) as get_id_email_mock: get_id_email_mock.return_value = ("user_oauth1", user_oauth.email) - response = test_app_client_redirect_url.get( + response = await test_app_client_redirect_url.get( "/callback", params={"code": "CODE", "state": state_jwt}, ) get_access_token_mock.assert_awaited_once_with( "CODE", "http://www.tintagel.bt/callback" ) - data = response.json() + data = cast(Dict[str, Any], response.json()) assert data["token"] == user_oauth.id diff --git a/tests/test_router_users.py b/tests/test_router_users.py index 46418892..a3ff26aa 100644 --- a/tests/test_router_users.py +++ b/tests/test_router_users.py @@ -1,12 +1,13 @@ +from typing import cast, Dict, Any from unittest.mock import MagicMock import asynctest +import httpx import jwt import pytest from fastapi import FastAPI from starlette import status from starlette.requests import Request -from starlette.testclient import TestClient from fastapi_users.authentication import Authenticator from fastapi_users.router import ErrorCode, Event, get_user_router @@ -41,8 +42,11 @@ def event_handler(request): return request.param() -@pytest.fixture() -def test_app_client(mock_user_db, mock_authentication, event_handler) -> TestClient: +@pytest.fixture +@pytest.mark.asyncio +async def test_app_client( + mock_user_db, mock_authentication, event_handler, get_test_client +) -> httpx.AsyncClient: mock_authentication_bis = MockAuthentication(name="mock-bis") authenticator = Authenticator( [mock_authentication, mock_authentication_bis], mock_user_db @@ -66,132 +70,149 @@ def test_app_client(mock_user_db, mock_authentication, event_handler) -> TestCli app = FastAPI() app.include_router(user_router) - return TestClient(app) + return await get_test_client(app) @pytest.mark.router +@pytest.mark.asyncio class TestRegister: - def test_empty_body(self, test_app_client: TestClient, event_handler): - response = test_app_client.post("/register", json={}) + async def test_empty_body(self, test_app_client: httpx.AsyncClient, event_handler): + response = await test_app_client.post("/register", json={}) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY assert event_handler.called is False - def test_missing_password(self, test_app_client: TestClient, event_handler): + async def test_missing_password( + self, test_app_client: httpx.AsyncClient, event_handler + ): json = {"email": "king.arthur@camelot.bt"} - response = test_app_client.post("/register", json=json) + response = await test_app_client.post("/register", json=json) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY assert event_handler.called is False - def test_wrong_email(self, test_app_client: TestClient, event_handler): + async def test_wrong_email(self, test_app_client: httpx.AsyncClient, event_handler): json = {"email": "king.arthur", "password": "guinevere"} - response = test_app_client.post("/register", json=json) + response = await test_app_client.post("/register", json=json) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY assert event_handler.called is False - def test_existing_user(self, test_app_client: TestClient, event_handler): + async def test_existing_user( + self, test_app_client: httpx.AsyncClient, event_handler + ): json = {"email": "king.arthur@camelot.bt", "password": "guinevere"} - response = test_app_client.post("/register", json=json) + response = await test_app_client.post("/register", json=json) assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json()["detail"] == ErrorCode.REGISTER_USER_ALREADY_EXISTS + data = cast(Dict[str, Any], response.json()) + assert data["detail"] == ErrorCode.REGISTER_USER_ALREADY_EXISTS assert event_handler.called is False - def test_valid_body(self, test_app_client: TestClient, event_handler): + async def test_valid_body(self, test_app_client: httpx.AsyncClient, event_handler): json = {"email": "lancelot@camelot.bt", "password": "guinevere"} - response = test_app_client.post("/register", json=json) + response = await test_app_client.post("/register", json=json) assert response.status_code == status.HTTP_201_CREATED assert event_handler.called is True - response_json = response.json() - assert "hashed_password" not in response_json - assert "password" not in response_json - assert response_json["id"] is not None + data = cast(Dict[str, Any], response.json()) + assert "hashed_password" not in data + assert "password" not in data + assert data["id"] is not None actual_user = event_handler.call_args[0][0] - assert actual_user.id == response_json["id"] + assert actual_user.id == data["id"] request = event_handler.call_args[0][1] assert isinstance(request, Request) - def test_valid_body_is_superuser(self, test_app_client: TestClient, event_handler): + async def test_valid_body_is_superuser( + self, test_app_client: httpx.AsyncClient, event_handler + ): json = { "email": "lancelot@camelot.bt", "password": "guinevere", "is_superuser": True, } - response = test_app_client.post("/register", json=json) + response = await test_app_client.post("/register", json=json) assert response.status_code == status.HTTP_201_CREATED assert event_handler.called is True - response_json = response.json() - assert response_json["is_superuser"] is False + data = cast(Dict[str, Any], response.json()) + assert data["is_superuser"] is False - def test_valid_body_is_active(self, test_app_client: TestClient, event_handler): + async def test_valid_body_is_active( + self, test_app_client: httpx.AsyncClient, event_handler + ): json = { "email": "lancelot@camelot.bt", "password": "guinevere", "is_active": False, } - response = test_app_client.post("/register", json=json) + response = await test_app_client.post("/register", json=json) assert response.status_code == status.HTTP_201_CREATED assert event_handler.called is True - response_json = response.json() - assert response_json["is_active"] is True + data = cast(Dict[str, Any], response.json()) + assert data["is_active"] is True @pytest.mark.router @pytest.mark.parametrize("path", ["/login/mock", "/login/mock-bis"]) +@pytest.mark.asyncio class TestLogin: - def test_empty_body(self, path, test_app_client: TestClient): - response = test_app_client.post(path, data={}) + async def test_empty_body(self, path, test_app_client: httpx.AsyncClient): + response = await test_app_client.post(path, data={}) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - def test_missing_username(self, path, test_app_client: TestClient): + async def test_missing_username(self, path, test_app_client: httpx.AsyncClient): data = {"password": "guinevere"} - response = test_app_client.post(path, data=data) + response = await test_app_client.post(path, data=data) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - def test_missing_password(self, path, test_app_client: TestClient): + async def test_missing_password(self, path, test_app_client: httpx.AsyncClient): data = {"username": "king.arthur@camelot.bt"} - response = test_app_client.post(path, data=data) + response = await test_app_client.post(path, data=data) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - def test_not_existing_user(self, path, test_app_client: TestClient): + async def test_not_existing_user(self, path, test_app_client: httpx.AsyncClient): data = {"username": "lancelot@camelot.bt", "password": "guinevere"} - response = test_app_client.post(path, data=data) + response = await test_app_client.post(path, data=data) assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json()["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS + data = cast(Dict[str, Any], response.json()) + assert data["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS - def test_wrong_password(self, path, test_app_client: TestClient): + async def test_wrong_password(self, path, test_app_client: httpx.AsyncClient): data = {"username": "king.arthur@camelot.bt", "password": "percival"} - response = test_app_client.post(path, data=data) + response = await test_app_client.post(path, data=data) assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json()["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS + data = cast(Dict[str, Any], response.json()) + assert data["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS - def test_valid_credentials(self, path, test_app_client: TestClient, user: UserDB): + async def test_valid_credentials( + self, path, test_app_client: httpx.AsyncClient, user: UserDB + ): data = {"username": "king.arthur@camelot.bt", "password": "guinevere"} - response = test_app_client.post(path, data=data) + response = await test_app_client.post(path, data=data) assert response.status_code == status.HTTP_200_OK assert response.json() == {"token": user.id} - def test_inactive_user(self, path, test_app_client: TestClient): + async def test_inactive_user(self, path, test_app_client: httpx.AsyncClient): data = {"username": "percival@camelot.bt", "password": "angharad"} - response = test_app_client.post(path, data=data) + response = await test_app_client.post(path, data=data) assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json()["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS + data = cast(Dict[str, Any], response.json()) + assert data["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS @pytest.mark.router @pytest.mark.parametrize("path", ["/logout/mock", "/logout/mock-bis"]) +@pytest.mark.asyncio class TestLogout: - def test_missing_token(self, path, test_app_client: TestClient): - response = test_app_client.post(path) + async def test_missing_token(self, path, test_app_client: httpx.AsyncClient): + response = await test_app_client.post(path) assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_unimplemented_logout( - self, mocker, path, test_app_client: TestClient, user: UserDB + async def test_unimplemented_logout( + self, mocker, path, test_app_client: httpx.AsyncClient, user: UserDB ): get_logout_response_spy = mocker.spy(MockAuthentication, "get_logout_response") - response = test_app_client.post( + response = await test_app_client.post( path, headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_202_ACCEPTED @@ -200,27 +221,34 @@ class TestLogout: @pytest.mark.router +@pytest.mark.asyncio class TestForgotPassword: - def test_empty_body(self, test_app_client: TestClient, event_handler): - response = test_app_client.post("/forgot-password", json={}) + async def test_empty_body(self, test_app_client: httpx.AsyncClient, event_handler): + response = await test_app_client.post("/forgot-password", json={}) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY assert event_handler.called is False - def test_not_existing_user(self, test_app_client: TestClient, event_handler): + async def test_not_existing_user( + self, test_app_client: httpx.AsyncClient, event_handler + ): json = {"email": "lancelot@camelot.bt"} - response = test_app_client.post("/forgot-password", json=json) + response = await test_app_client.post("/forgot-password", json=json) assert response.status_code == status.HTTP_202_ACCEPTED assert event_handler.called is False - def test_inactive_user(self, test_app_client: TestClient, event_handler): + async def test_inactive_user( + self, test_app_client: httpx.AsyncClient, event_handler + ): json = {"email": "percival@camelot.bt"} - response = test_app_client.post("/forgot-password", json=json) + response = await test_app_client.post("/forgot-password", json=json) assert response.status_code == status.HTTP_202_ACCEPTED assert event_handler.called is False - def test_existing_user(self, test_app_client: TestClient, event_handler, user): + async def test_existing_user( + self, test_app_client: httpx.AsyncClient, event_handler, user + ): json = {"email": "king.arthur@camelot.bt"} - response = test_app_client.post("/forgot-password", json=json) + response = await test_app_client.post("/forgot-password", json=json) assert response.status_code == status.HTTP_202_ACCEPTED assert event_handler.called is True @@ -239,43 +267,50 @@ class TestForgotPassword: @pytest.mark.router +@pytest.mark.asyncio class TestResetPassword: - def test_empty_body(self, test_app_client: TestClient): - response = test_app_client.post("/reset-password", json={}) + async def test_empty_body(self, test_app_client: httpx.AsyncClient): + response = await test_app_client.post("/reset-password", json={}) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - def test_missing_token(self, test_app_client: TestClient): + async def test_missing_token(self, test_app_client: httpx.AsyncClient): json = {"password": "guinevere"} - response = test_app_client.post("/reset-password", json=json) + response = await test_app_client.post("/reset-password", json=json) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - def test_missing_password(self, test_app_client: TestClient): + async def test_missing_password(self, test_app_client: httpx.AsyncClient): json = {"token": "foo"} - response = test_app_client.post("/reset-password", json=json) + response = await test_app_client.post("/reset-password", json=json) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - def test_invalid_token(self, test_app_client: TestClient): + async def test_invalid_token(self, test_app_client: httpx.AsyncClient): json = {"token": "foo", "password": "guinevere"} - response = test_app_client.post("/reset-password", json=json) + response = await test_app_client.post("/reset-password", json=json) assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json()["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN + data = cast(Dict[str, Any], response.json()) + assert data["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN - def test_valid_token_missing_user_id_payload( - self, mocker, mock_user_db, test_app_client: TestClient, forgot_password_token + async def test_valid_token_missing_user_id_payload( + self, + mocker, + mock_user_db, + test_app_client: httpx.AsyncClient, + forgot_password_token, ): mocker.spy(mock_user_db, "update") json = {"token": forgot_password_token(), "password": "holygrail"} - response = test_app_client.post("/reset-password", json=json) + response = await test_app_client.post("/reset-password", json=json) assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json()["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN + data = cast(Dict[str, Any], response.json()) + assert data["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN assert mock_user_db.update.called is False - def test_inactive_user( + async def test_inactive_user( self, mocker, mock_user_db, - test_app_client: TestClient, + test_app_client: httpx.AsyncClient, forgot_password_token, inactive_user: UserDB, ): @@ -285,16 +320,17 @@ class TestResetPassword: "token": forgot_password_token(inactive_user.id), "password": "holygrail", } - response = test_app_client.post("/reset-password", json=json) + response = await test_app_client.post("/reset-password", json=json) assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json()["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN + data = cast(Dict[str, Any], response.json()) + assert data["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN assert mock_user_db.update.called is False - def test_existing_user( + async def test_existing_user( self, mocker, mock_user_db, - test_app_client: TestClient, + test_app_client: httpx.AsyncClient, forgot_password_token, user: UserDB, ): @@ -302,7 +338,7 @@ class TestResetPassword: current_hashed_passord = user.hashed_password json = {"token": forgot_password_token(user.id), "password": "holygrail"} - response = test_app_client.post("/reset-password", json=json) + response = await test_app_client.post("/reset-password", json=json) assert response.status_code == status.HTTP_200_OK assert mock_user_db.update.called is True @@ -311,52 +347,60 @@ class TestResetPassword: @pytest.mark.router +@pytest.mark.asyncio class TestMe: - def test_missing_token(self, test_app_client: TestClient): - response = test_app_client.get("/me") + async def test_missing_token(self, test_app_client: httpx.AsyncClient): + response = await test_app_client.get("/me") assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_inactive_user(self, test_app_client: TestClient, inactive_user: UserDB): - response = test_app_client.get( + async def test_inactive_user( + self, test_app_client: httpx.AsyncClient, inactive_user: UserDB + ): + response = await test_app_client.get( "/me", headers={"Authorization": f"Bearer {inactive_user.id}"} ) assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_active_user(self, test_app_client: TestClient, user: UserDB): - response = test_app_client.get( + async def test_active_user(self, test_app_client: httpx.AsyncClient, user: UserDB): + response = await test_app_client.get( "/me", headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_200_OK - response_json = response.json() - assert response_json["id"] == user.id - assert response_json["email"] == user.email + data = cast(Dict[str, Any], response.json()) + assert data["id"] == user.id + assert data["email"] == user.email @pytest.mark.router +@pytest.mark.asyncio class TestUpdateMe: - def test_missing_token(self, test_app_client: TestClient, event_handler): - response = test_app_client.patch("/me") + async def test_missing_token( + self, test_app_client: httpx.AsyncClient, event_handler + ): + response = await test_app_client.patch("/me") assert response.status_code == status.HTTP_401_UNAUTHORIZED assert event_handler.called is False - def test_inactive_user( - self, test_app_client: TestClient, inactive_user: UserDB, event_handler + async def test_inactive_user( + self, test_app_client: httpx.AsyncClient, inactive_user: UserDB, event_handler ): - response = test_app_client.patch( + response = await test_app_client.patch( "/me", headers={"Authorization": f"Bearer {inactive_user.id}"} ) assert response.status_code == status.HTTP_401_UNAUTHORIZED assert event_handler.called is False - def test_empty_body(self, test_app_client: TestClient, user: UserDB, event_handler): - response = test_app_client.patch( + async def test_empty_body( + self, test_app_client: httpx.AsyncClient, user: UserDB, event_handler + ): + response = await test_app_client.patch( "/me", json={}, headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_200_OK - response_json = response.json() - assert response_json["email"] == user.email + data = cast(Dict[str, Any], response.json()) + assert data["email"] == user.email assert event_handler.called is True actual_user = event_handler.call_args[0][0] @@ -366,15 +410,17 @@ class TestUpdateMe: request = event_handler.call_args[0][2] assert isinstance(request, Request) - def test_valid_body(self, test_app_client: TestClient, user: UserDB, event_handler): + async def test_valid_body( + self, test_app_client: httpx.AsyncClient, user: UserDB, event_handler + ): json = {"email": "king.arthur@tintagel.bt"} - response = test_app_client.patch( + response = await test_app_client.patch( "/me", json=json, headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_200_OK - response_json = response.json() - assert response_json["email"] == "king.arthur@tintagel.bt" + data = cast(Dict[str, Any], response.json()) + assert data["email"] == "king.arthur@tintagel.bt" assert event_handler.called is True actual_user = event_handler.call_args[0][0] @@ -384,17 +430,17 @@ class TestUpdateMe: request = event_handler.call_args[0][2] assert isinstance(request, Request) - def test_valid_body_is_superuser( - self, test_app_client: TestClient, user: UserDB, event_handler + async def test_valid_body_is_superuser( + self, test_app_client: httpx.AsyncClient, user: UserDB, event_handler ): json = {"is_superuser": True} - response = test_app_client.patch( + response = await test_app_client.patch( "/me", json=json, headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_200_OK - response_json = response.json() - assert response_json["is_superuser"] is False + data = cast(Dict[str, Any], response.json()) + assert data["is_superuser"] is False assert event_handler.called is True actual_user = event_handler.call_args[0][0] @@ -404,17 +450,17 @@ class TestUpdateMe: request = event_handler.call_args[0][2] assert isinstance(request, Request) - def test_valid_body_is_active( - self, test_app_client: TestClient, user: UserDB, event_handler + async def test_valid_body_is_active( + self, test_app_client: httpx.AsyncClient, user: UserDB, event_handler ): json = {"is_active": False} - response = test_app_client.patch( + response = await test_app_client.patch( "/me", json=json, headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_200_OK - response_json = response.json() - assert response_json["is_active"] is True + data = cast(Dict[str, Any], response.json()) + assert data["is_active"] is True assert event_handler.called is True actual_user = event_handler.call_args[0][0] @@ -424,11 +470,11 @@ class TestUpdateMe: request = event_handler.call_args[0][2] assert isinstance(request, Request) - def test_valid_body_password( + async def test_valid_body_password( self, mocker, mock_user_db, - test_app_client: TestClient, + test_app_client: httpx.AsyncClient, user: UserDB, event_handler, ): @@ -436,7 +482,7 @@ class TestUpdateMe: current_hashed_passord = user.hashed_password json = {"password": "merlin"} - response = test_app_client.patch( + response = await test_app_client.patch( "/me", json=json, headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_200_OK @@ -455,19 +501,22 @@ class TestUpdateMe: @pytest.mark.router +@pytest.mark.asyncio class TestListUsers: - def test_missing_token(self, test_app_client: TestClient): - response = test_app_client.get("/") + async def test_missing_token(self, test_app_client: httpx.AsyncClient): + response = await test_app_client.get("/") assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_regular_user(self, test_app_client: TestClient, user: UserDB): - response = test_app_client.get( + async def test_regular_user(self, test_app_client: httpx.AsyncClient, user: UserDB): + response = await test_app_client.get( "/", headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_403_FORBIDDEN - def test_superuser(self, test_app_client: TestClient, superuser: UserDB): - response = test_app_client.get( + async def test_superuser( + self, test_app_client: httpx.AsyncClient, superuser: UserDB + ): + response = await test_app_client.get( "/", headers={"Authorization": f"Bearer {superuser.id}"} ) assert response.status_code == status.HTTP_200_OK @@ -480,112 +529,118 @@ class TestListUsers: @pytest.mark.router +@pytest.mark.asyncio class TestGetUser: - def test_missing_token(self, test_app_client: TestClient): - response = test_app_client.get("/000") + async def test_missing_token(self, test_app_client: httpx.AsyncClient): + response = await test_app_client.get("/000") assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_regular_user(self, test_app_client: TestClient, user: UserDB): - response = test_app_client.get( + async def test_regular_user(self, test_app_client: httpx.AsyncClient, user: UserDB): + response = await test_app_client.get( "/000", headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_403_FORBIDDEN - def test_not_existing_user(self, test_app_client: TestClient, superuser: UserDB): - response = test_app_client.get( + async def test_not_existing_user( + self, test_app_client: httpx.AsyncClient, superuser: UserDB + ): + response = await test_app_client.get( "/000", headers={"Authorization": f"Bearer {superuser.id}"} ) assert response.status_code == status.HTTP_404_NOT_FOUND - def test_superuser( - self, test_app_client: TestClient, user: UserDB, superuser: UserDB + async def test_superuser( + self, test_app_client: httpx.AsyncClient, user: UserDB, superuser: UserDB ): - response = test_app_client.get( + response = await test_app_client.get( f"/{user.id}", headers={"Authorization": f"Bearer {superuser.id}"} ) assert response.status_code == status.HTTP_200_OK - response_json = response.json() - assert response_json["id"] == user.id - assert "hashed_password" not in response_json + data = cast(Dict[str, Any], response.json()) + assert data["id"] == user.id + assert "hashed_password" not in data @pytest.mark.router +@pytest.mark.asyncio class TestUpdateUser: - def test_missing_token(self, test_app_client: TestClient): - response = test_app_client.patch("/000") + async def test_missing_token(self, test_app_client: httpx.AsyncClient): + response = await test_app_client.patch("/000") assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_regular_user(self, test_app_client: TestClient, user: UserDB): - response = test_app_client.patch( + async def test_regular_user(self, test_app_client: httpx.AsyncClient, user: UserDB): + response = await test_app_client.patch( "/000", headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_403_FORBIDDEN - def test_not_existing_user(self, test_app_client: TestClient, superuser: UserDB): - response = test_app_client.patch( + async def test_not_existing_user( + self, test_app_client: httpx.AsyncClient, superuser: UserDB + ): + response = await test_app_client.patch( "/000", json={}, headers={"Authorization": f"Bearer {superuser.id}"} ) assert response.status_code == status.HTTP_404_NOT_FOUND - def test_empty_body( - self, test_app_client: TestClient, user: UserDB, superuser: UserDB + async def test_empty_body( + self, test_app_client: httpx.AsyncClient, user: UserDB, superuser: UserDB ): - response = test_app_client.patch( + response = await test_app_client.patch( f"/{user.id}", json={}, headers={"Authorization": f"Bearer {superuser.id}"} ) assert response.status_code == status.HTTP_200_OK - response_json = response.json() - assert response_json["email"] == user.email + data = cast(Dict[str, Any], response.json()) + assert data["email"] == user.email - def test_valid_body( - self, test_app_client: TestClient, user: UserDB, superuser: UserDB + async def test_valid_body( + self, test_app_client: httpx.AsyncClient, user: UserDB, superuser: UserDB ): json = {"email": "king.arthur@tintagel.bt"} - response = test_app_client.patch( + response = await test_app_client.patch( f"/{user.id}", json=json, headers={"Authorization": f"Bearer {superuser.id}"}, ) assert response.status_code == status.HTTP_200_OK - response_json = response.json() - assert response_json["email"] == "king.arthur@tintagel.bt" + data = cast(Dict[str, Any], response.json()) + assert data["email"] == "king.arthur@tintagel.bt" - def test_valid_body_is_superuser( - self, test_app_client: TestClient, user: UserDB, superuser: UserDB + async def test_valid_body_is_superuser( + self, test_app_client: httpx.AsyncClient, user: UserDB, superuser: UserDB ): json = {"is_superuser": True} - response = test_app_client.patch( + response = await test_app_client.patch( f"/{user.id}", json=json, headers={"Authorization": f"Bearer {superuser.id}"}, ) assert response.status_code == status.HTTP_200_OK - response_json = response.json() - assert response_json["is_superuser"] is True + data = cast(Dict[str, Any], response.json()) + assert data["is_superuser"] is True - def test_valid_body_is_active( - self, test_app_client: TestClient, user: UserDB, superuser: UserDB + async def test_valid_body_is_active( + self, test_app_client: httpx.AsyncClient, user: UserDB, superuser: UserDB ): json = {"is_active": False} - response = test_app_client.patch( + response = await test_app_client.patch( f"/{user.id}", json=json, headers={"Authorization": f"Bearer {superuser.id}"}, ) assert response.status_code == status.HTTP_200_OK - response_json = response.json() - assert response_json["is_active"] is False + data = cast(Dict[str, Any], response.json()) + assert data["is_active"] is False - def test_valid_body_password( + async def test_valid_body_password( self, mocker, mock_user_db, - test_app_client: TestClient, + test_app_client: httpx.AsyncClient, user: UserDB, superuser: UserDB, ): @@ -593,7 +648,7 @@ class TestUpdateUser: current_hashed_passord = user.hashed_password json = {"password": "merlin"} - response = test_app_client.patch( + response = await test_app_client.patch( f"/{user.id}", json=json, headers={"Authorization": f"Bearer {superuser.id}"}, @@ -606,34 +661,37 @@ class TestUpdateUser: @pytest.mark.router +@pytest.mark.asyncio class TestDeleteUser: - def test_missing_token(self, test_app_client: TestClient): - response = test_app_client.delete("/000") + async def test_missing_token(self, test_app_client: httpx.AsyncClient): + response = await test_app_client.delete("/000") assert response.status_code == status.HTTP_401_UNAUTHORIZED - def test_regular_user(self, test_app_client: TestClient, user: UserDB): - response = test_app_client.delete( + async def test_regular_user(self, test_app_client: httpx.AsyncClient, user: UserDB): + response = await test_app_client.delete( "/000", headers={"Authorization": f"Bearer {user.id}"} ) assert response.status_code == status.HTTP_403_FORBIDDEN - def test_not_existing_user(self, test_app_client: TestClient, superuser: UserDB): - response = test_app_client.delete( + async def test_not_existing_user( + self, test_app_client: httpx.AsyncClient, superuser: UserDB + ): + response = await test_app_client.delete( "/000", headers={"Authorization": f"Bearer {superuser.id}"} ) assert response.status_code == status.HTTP_404_NOT_FOUND - def test_superuser( + async def test_superuser( self, mocker, mock_user_db, - test_app_client: TestClient, + test_app_client: httpx.AsyncClient, user: UserDB, superuser: UserDB, ): mocker.spy(mock_user_db, "delete") - response = test_app_client.delete( + response = await test_app_client.delete( f"/{user.id}", headers={"Authorization": f"Bearer {superuser.id}"} ) assert response.status_code == status.HTTP_204_NO_CONTENT