Revamp authentication routes structure (#201)

* Fix #68: use makefun to generate dynamic dependencies

* Remove every Starlette imports

* Split every routers and remove event handlers

* Make users router optional

* Pass after_update handler to get_users_router

* Update documentation

* Remove test file

* Write migration doc for splitted routers
This commit is contained in:
François Voron
2020-05-24 10:18:01 +02:00
committed by GitHub
parent 0a0dcadfdc
commit 7721f8dcc1
48 changed files with 1633 additions and 1167 deletions

View File

@ -1,17 +1,14 @@
import asyncio
from typing import Any, List, Mapping, Optional, Tuple
from typing import List, Optional
import http.cookies
import httpx
import pytest
from asgi_lifespan import LifespanManager
from fastapi import Depends, FastAPI
from fastapi import Depends, Response, FastAPI
from fastapi.security import OAuth2PasswordBearer
from httpx_oauth.oauth2 import OAuth2
from pydantic import UUID4
from starlette.applications import ASGIApp
from starlette.requests import Request
from starlette.responses import Response
from fastapi_users import models
from fastapi_users.authentication import Authenticator, BaseAuthentication
@ -229,16 +226,15 @@ def mock_user_db_oauth(
return MockUserDatabase(UserDBOAuth)
class MockAuthentication(BaseAuthentication):
class MockAuthentication(BaseAuthentication[str]):
def __init__(self, name: str = "mock"):
super().__init__(name)
super().__init__(name, logout=True)
self.scheme = OAuth2PasswordBearer("/users/login", auto_error=False)
async def __call__(self, request: Request, user_db: BaseUserDatabase):
token = await self.scheme.__call__(request)
if token is not None:
async def __call__(self, credentials: Optional[str], user_db: BaseUserDatabase):
if credentials is not None:
try:
token_uuid = UUID4(token)
token_uuid = UUID4(credentials)
return await user_db.get(token_uuid)
except ValueError:
return None
@ -247,41 +243,15 @@ class MockAuthentication(BaseAuthentication):
async def get_login_response(self, user: BaseUserDB, response: Response):
return {"token": user.id}
async def get_logout_response(self, user: BaseUserDB, response: Response):
return None
@pytest.fixture
def mock_authentication():
return MockAuthentication()
@pytest.fixture
def request_builder():
def _request_builder(
headers: Mapping[str, Any] = None, cookies: Mapping[str, str] = None
) -> Request:
encoded_headers: List[Tuple[bytes, bytes]] = []
if headers is not None:
encoded_headers += [
(key.lower().encode("latin-1"), headers[key].encode("latin-1"))
for key in headers
]
if cookies is not None:
for key in cookies:
cookie = http.cookies.SimpleCookie() # type: http.cookies.BaseCookie
cookie[key] = cookies[key]
cookie_val = cookie.output(header="").strip()
encoded_headers.append((b"cookie", cookie_val.encode("latin-1")))
scope = {
"type": "http",
"headers": encoded_headers,
}
return Request(scope)
return _request_builder
@pytest.fixture
def get_test_client():
async def _get_test_client(app: ASGIApp) -> httpx.AsyncClient:
@ -304,7 +274,7 @@ def get_test_auth_client(mock_user_db, get_test_client):
authenticator = Authenticator(backends, mock_user_db)
@app.get("/test-current-user")
def test_current_user(user: UserDB = Depends(authenticator.get_current_user),):
def test_current_user(user: UserDB = Depends(authenticator.get_current_user)):
return user
@app.get("/test-current-active-user")

View File

@ -1,49 +1,63 @@
from typing import Optional
import pytest
from starlette import status
from starlette.requests import Request
from fastapi import Request, status
from fastapi.security.base import SecurityBase
from fastapi_users.authentication import BaseAuthentication
from fastapi_users.authentication import (
BaseAuthentication,
DuplicateBackendNamesError,
)
from fastapi_users.db import BaseUserDatabase
from fastapi_users.models import BaseUserDB
@pytest.fixture
def auth_backend_none():
class BackendNone(BaseAuthentication):
async def __call__(
self, request: Request, user_db: BaseUserDatabase
) -> Optional[BaseUserDB]:
return None
return BackendNone()
class MockSecurityScheme(SecurityBase):
def __call__(self, request: Request) -> Optional[str]:
return "mock"
@pytest.fixture
def auth_backend_user(user):
class BackendUser(BaseAuthentication):
async def __call__(
self, request: Request, user_db: BaseUserDatabase
) -> Optional[BaseUserDB]:
return user
class BackendNone(BaseAuthentication[str]):
def __init__(self, name="none"):
super().__init__(name, logout=False)
self.scheme = MockSecurityScheme()
return BackendUser()
async def __call__(
self, credentials: Optional[str], user_db: BaseUserDatabase
) -> Optional[BaseUserDB]:
return None
class BackendUser(BaseAuthentication[str]):
def __init__(self, user: BaseUserDB, name="user"):
super().__init__(name, logout=False)
self.scheme = MockSecurityScheme()
self.user = user
async def __call__(
self, credentials: Optional[str], user_db: BaseUserDatabase
) -> Optional[BaseUserDB]:
return self.user
@pytest.mark.authentication
@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])
async def test_authenticator(get_test_auth_client, user):
client = await get_test_auth_client([BackendNone(), BackendUser(user)])
response = await client.get("/test-current-user")
assert response.status_code == status.HTTP_200_OK
@pytest.mark.authentication
@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])
async def test_authenticator_none(get_test_auth_client):
client = await get_test_auth_client([BackendNone(), BackendNone(name="none-bis")])
response = await client.get("/test-current-user")
assert response.status_code == status.HTTP_401_UNAUTHORIZED
@pytest.mark.authentication
@pytest.mark.asyncio
async def test_authenticators_with_same_name(get_test_auth_client):
with pytest.raises(DuplicateBackendNamesError):
await get_test_auth_client([BackendNone(), BackendNone()])

View File

@ -1,5 +1,5 @@
import pytest
from starlette.responses import Response
from fastapi import Response
from fastapi_users.authentication import BaseAuthentication
@ -12,12 +12,9 @@ def base_authentication():
@pytest.mark.authentication
class TestAuthenticate:
@pytest.mark.asyncio
async def test_not_implemented(
self, base_authentication, mock_user_db, request_builder
):
request = request_builder({})
async def test_not_implemented(self, base_authentication, mock_user_db):
with pytest.raises(NotImplementedError):
await base_authentication(request, mock_user_db)
await base_authentication(None, mock_user_db)
@pytest.mark.authentication

View File

@ -2,7 +2,7 @@ import re
import jwt
import pytest
from starlette.responses import Response
from fastapi import Response
from fastapi_users.authentication.cookie import CookieAuthentication
from fastapi_users.utils import JWT_ALGORITHM, generate_jwt
@ -28,10 +28,10 @@ cookie_authentication_httponly = CookieAuthentication(
@pytest.fixture
def token():
def _token(user=None, lifetime=LIFETIME):
def _token(user_id=None, lifetime=LIFETIME):
data = {"aud": "fastapi-users:auth"}
if user is not None:
data["user_id"] = str(user.id)
if user_id is not None:
data["user_id"] = str(user_id)
return generate_jwt(data, lifetime, SECRET, JWT_ALGORITHM)
return _token
@ -45,35 +45,28 @@ def test_default_name():
@pytest.mark.authentication
class TestAuthenticate:
@pytest.mark.asyncio
async def test_missing_token(self, mock_user_db, request_builder):
request = request_builder()
authenticated_user = await cookie_authentication(request, mock_user_db)
async def test_missing_token(self, mock_user_db):
authenticated_user = await cookie_authentication(None, mock_user_db)
assert authenticated_user is None
@pytest.mark.asyncio
async def test_invalid_token(self, mock_user_db, request_builder):
cookies = {}
cookies[COOKIE_NAME] = "foo"
request = request_builder(cookies=cookies)
authenticated_user = await cookie_authentication(request, mock_user_db)
async def test_invalid_token(self, mock_user_db):
authenticated_user = await cookie_authentication("foo", mock_user_db)
assert authenticated_user is None
@pytest.mark.asyncio
async def test_valid_token_missing_user_payload(
self, mock_user_db, request_builder, token
):
cookies = {}
cookies[COOKIE_NAME] = token()
request = request_builder(cookies=cookies)
authenticated_user = await cookie_authentication(request, mock_user_db)
async def test_valid_token_missing_user_payload(self, mock_user_db, token):
authenticated_user = await cookie_authentication(token(), mock_user_db)
assert authenticated_user is None
@pytest.mark.asyncio
async def test_valid_token(self, mock_user_db, request_builder, token, user):
cookies = {}
cookies[COOKIE_NAME] = token(user)
request = request_builder(cookies=cookies)
authenticated_user = await cookie_authentication(request, mock_user_db)
async def test_valid_token_invalid_uuid(self, mock_user_db, token):
authenticated_user = await cookie_authentication(token("foo"), mock_user_db)
assert authenticated_user is None
@pytest.mark.asyncio
async def test_valid_token(self, mock_user_db, token, user):
authenticated_user = await cookie_authentication(token(user.id), mock_user_db)
assert authenticated_user.id == user.id

View File

@ -1,6 +1,6 @@
import jwt
import pytest
from starlette.responses import Response
from fastapi import Response
from fastapi_users.authentication.jwt import JWTAuthentication
from fastapi_users.utils import JWT_ALGORITHM, generate_jwt
@ -34,43 +34,32 @@ def test_default_name(jwt_authentication):
@pytest.mark.authentication
class TestAuthenticate:
@pytest.mark.asyncio
async def test_missing_token(
self, jwt_authentication, mock_user_db, request_builder
):
request = request_builder(headers={})
authenticated_user = await jwt_authentication(request, mock_user_db)
async def test_missing_token(self, jwt_authentication, mock_user_db):
authenticated_user = await jwt_authentication(None, mock_user_db)
assert authenticated_user is None
@pytest.mark.asyncio
async def test_invalid_token(
self, jwt_authentication, mock_user_db, request_builder
):
request = request_builder(headers={"Authorization": "Bearer foo"})
authenticated_user = await jwt_authentication(request, mock_user_db)
async def test_invalid_token(self, jwt_authentication, mock_user_db):
authenticated_user = await jwt_authentication("foo", mock_user_db)
assert authenticated_user is None
@pytest.mark.asyncio
async def test_valid_token_missing_user_payload(
self, jwt_authentication, mock_user_db, request_builder, token
self, jwt_authentication, mock_user_db, token
):
request = request_builder(headers={"Authorization": f"Bearer {token()}"})
authenticated_user = await jwt_authentication(request, mock_user_db)
authenticated_user = await jwt_authentication(token(), mock_user_db)
assert authenticated_user is None
@pytest.mark.asyncio
async def test_valid_token_invalid_uuid(
self, jwt_authentication, mock_user_db, request_builder, token
self, jwt_authentication, mock_user_db, token
):
request = request_builder(headers={"Authorization": f"Bearer {token('foo')}"})
authenticated_user = await jwt_authentication(request, mock_user_db)
authenticated_user = await jwt_authentication(token("foo"), mock_user_db)
assert authenticated_user is None
@pytest.mark.asyncio
async def test_valid_token(
self, jwt_authentication, mock_user_db, request_builder, token, user
):
request = request_builder(headers={"Authorization": f"Bearer {token(user.id)}"})
authenticated_user = await jwt_authentication(request, mock_user_db)
async def test_valid_token(self, jwt_authentication, mock_user_db, token, user):
authenticated_user = await jwt_authentication(token(user.id), mock_user_db)
assert authenticated_user.id == user.id

View File

@ -1,58 +1,26 @@
import pytest
import httpx
from fastapi import Depends, FastAPI
from httpx_oauth.oauth2 import OAuth2
from starlette import status
from fastapi import Depends, FastAPI, status
from fastapi_users import FastAPIUsers
from fastapi_users.router import Event, EventHandlersRouter
from tests.conftest import User, UserCreate, UserUpdate, UserDB
def sync_event_handler():
return None
async def async_event_handler():
return None
@pytest.fixture(params=[sync_event_handler, async_event_handler])
def fastapi_users(
request, mock_user_db, mock_authentication, oauth_client
) -> FastAPIUsers:
fastapi_users = FastAPIUsers(
mock_user_db,
[mock_authentication],
User,
UserCreate,
UserUpdate,
UserDB,
"SECRET",
)
fastapi_users.get_oauth_router(oauth_client, "SECRET")
@fastapi_users.on_after_register()
def on_after_register():
return request.param()
@fastapi_users.on_after_forgot_password()
def on_after_forgot_password():
return request.param()
@fastapi_users.on_after_update()
def on_after_update():
return request.param()
return fastapi_users
@pytest.fixture
@pytest.mark.asyncio
async def test_app_client(fastapi_users, get_test_client) -> httpx.AsyncClient:
async def test_app_client(
mock_user_db, mock_authentication, oauth_client, get_test_client
) -> httpx.AsyncClient:
fastapi_users = FastAPIUsers(
mock_user_db, [mock_authentication], User, UserCreate, UserUpdate, UserDB,
)
app = FastAPI()
app.include_router(fastapi_users.router, prefix="/users")
app.include_router(fastapi_users.get_register_router())
app.include_router(fastapi_users.get_reset_password_router("SECRET"))
app.include_router(fastapi_users.get_auth_router(mock_authentication))
app.include_router(fastapi_users.get_oauth_router(oauth_client, "SECRET"))
app.include_router(fastapi_users.get_users_router(), prefix="/users")
@app.get("/current-user")
def current_user(user=Depends(fastapi_users.get_current_user)):
@ -69,35 +37,51 @@ async def test_app_client(fastapi_users, get_test_client) -> httpx.AsyncClient:
return await get_test_client(app)
@pytest.mark.fastapi_users
class TestFastAPIUsers:
def test_event_handlers(self, fastapi_users):
event_handlers = fastapi_users.router.event_handlers
assert len(event_handlers[Event.ON_AFTER_REGISTER]) == 1
assert len(event_handlers[Event.ON_AFTER_FORGOT_PASSWORD]) == 1
@pytest.mark.fastapi_users
@pytest.mark.asyncio
class TestRouter:
class TestRoutes:
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 = await test_app_client.post("/register")
assert response.status_code not in (
status.HTTP_404_NOT_FOUND,
status.HTTP_405_METHOD_NOT_ALLOWED,
)
response = await test_app_client.post("/users/login")
assert response.status_code != status.HTTP_404_NOT_FOUND
response = await test_app_client.post("/forgot-password")
assert response.status_code not in (
status.HTTP_404_NOT_FOUND,
status.HTTP_405_METHOD_NOT_ALLOWED,
)
response = await test_app_client.post("/users/forgot-password")
assert response.status_code != status.HTTP_404_NOT_FOUND
response = await test_app_client.post("/reset-password")
assert response.status_code not in (
status.HTTP_404_NOT_FOUND,
status.HTTP_405_METHOD_NOT_ALLOWED,
)
response = await test_app_client.post("/users/reset-password")
assert response.status_code != status.HTTP_404_NOT_FOUND
response = await test_app_client.post("/login")
assert response.status_code not in (
status.HTTP_404_NOT_FOUND,
status.HTTP_405_METHOD_NOT_ALLOWED,
)
response = await test_app_client.post("/logout")
assert response.status_code not in (
status.HTTP_404_NOT_FOUND,
status.HTTP_405_METHOD_NOT_ALLOWED,
)
response = await test_app_client.get("/users/aaa")
assert response.status_code != status.HTTP_404_NOT_FOUND
assert response.status_code not in (
status.HTTP_404_NOT_FOUND,
status.HTTP_405_METHOD_NOT_ALLOWED,
)
response = await test_app_client.patch("/users/aaa")
assert response.status_code != status.HTTP_404_NOT_FOUND
assert response.status_code not in (
status.HTTP_404_NOT_FOUND,
status.HTTP_405_METHOD_NOT_ALLOWED,
)
@pytest.mark.fastapi_users
@ -177,21 +161,3 @@ class TestGetCurrentSuperuser:
"/current-superuser", headers={"Authorization": f"Bearer {superuser.id}"}
)
assert response.status_code == status.HTTP_200_OK
@pytest.mark.fastapi_users
def test_get_oauth_router(mocker, fastapi_users: FastAPIUsers, oauth_client: OAuth2):
# Check that existing OAuth router declared
# before the handlers decorators is correctly binded
existing_oauth_router = fastapi_users.oauth_routers[0]
event_handlers = existing_oauth_router.event_handlers
assert len(event_handlers[Event.ON_AFTER_REGISTER]) == 1
assert len(event_handlers[Event.ON_AFTER_FORGOT_PASSWORD]) == 1
# Check that OAuth router declared
# after the handlers decorators is correctly binded
oauth_router = fastapi_users.get_oauth_router(oauth_client, "SECRET")
assert isinstance(oauth_router, EventHandlersRouter)
event_handlers = oauth_router.event_handlers
assert len(event_handlers[Event.ON_AFTER_REGISTER]) == 1
assert len(event_handlers[Event.ON_AFTER_FORGOT_PASSWORD]) == 1

96
tests/test_router_auth.py Normal file
View File

@ -0,0 +1,96 @@
from typing import cast, Dict, Any
import httpx
import pytest
from fastapi import FastAPI, status
from fastapi_users.authentication import Authenticator
from fastapi_users.router import ErrorCode, get_auth_router
from tests.conftest import MockAuthentication, UserDB
@pytest.fixture
@pytest.mark.asyncio
async def test_app_client(
mock_user_db, mock_authentication, get_test_client
) -> httpx.AsyncClient:
mock_authentication_bis = MockAuthentication(name="mock-bis")
authenticator = Authenticator(
[mock_authentication, mock_authentication_bis], mock_user_db
)
mock_auth_router = get_auth_router(mock_authentication, mock_user_db, authenticator)
mock_bis_auth_router = get_auth_router(
mock_authentication_bis, mock_user_db, authenticator
)
app = FastAPI()
app.include_router(mock_auth_router, prefix="/mock")
app.include_router(mock_bis_auth_router, prefix="/mock-bis")
return await get_test_client(app)
@pytest.mark.router
@pytest.mark.parametrize("path", ["/mock/login", "/mock-bis/login"])
@pytest.mark.asyncio
class TestLogin:
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
async def test_missing_username(self, path, test_app_client: httpx.AsyncClient):
data = {"password": "guinevere"}
response = await test_app_client.post(path, data=data)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
async def test_missing_password(self, path, test_app_client: httpx.AsyncClient):
data = {"username": "king.arthur@camelot.bt"}
response = await test_app_client.post(path, data=data)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
async def test_not_existing_user(self, path, test_app_client: httpx.AsyncClient):
data = {"username": "lancelot@camelot.bt", "password": "guinevere"}
response = await test_app_client.post(path, data=data)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS
async def test_wrong_password(self, path, test_app_client: httpx.AsyncClient):
data = {"username": "king.arthur@camelot.bt", "password": "percival"}
response = await test_app_client.post(path, data=data)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS
async def test_valid_credentials(
self, path, test_app_client: httpx.AsyncClient, user: UserDB
):
data = {"username": "king.arthur@camelot.bt", "password": "guinevere"}
response = await test_app_client.post(path, data=data)
assert response.status_code == status.HTTP_200_OK
assert response.json() == {"token": str(user.id)}
async def test_inactive_user(self, path, test_app_client: httpx.AsyncClient):
data = {"username": "percival@camelot.bt", "password": "angharad"}
response = await test_app_client.post(path, data=data)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS
@pytest.mark.router
@pytest.mark.parametrize("path", ["/mock/logout", "/mock-bis/logout"])
@pytest.mark.asyncio
class TestLogout:
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
async def test_valid_credentials(
self, mocker, path, test_app_client: httpx.AsyncClient, user: UserDB
):
response = await test_app_client.post(
path, headers={"Authorization": f"Bearer {user.id}"}
)
assert response.status_code == status.HTTP_200_OK

View File

@ -4,12 +4,10 @@ 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 fastapi import FastAPI, status, Request
from fastapi_users.authentication import Authenticator
from fastapi_users.router.common import ErrorCode, Event
from fastapi_users.router.common import ErrorCode
from fastapi_users.router.oauth import generate_state_token, get_oauth_router
from tests.conftest import MockAuthentication, UserDB
@ -17,16 +15,16 @@ from tests.conftest import MockAuthentication, UserDB
SECRET = "SECRET"
def event_handler_sync():
def after_register_sync():
return MagicMock(return_value=None)
def event_handler_async():
def after_register_async():
return asynctest.CoroutineMock(return_value=None)
@pytest.fixture(params=[event_handler_sync, event_handler_async])
def event_handler(request):
@pytest.fixture(params=[after_register_sync, after_register_async])
def after_register(request):
return request.param()
@ -35,7 +33,7 @@ def get_test_app_client(
mock_user_db_oauth,
mock_authentication,
oauth_client,
event_handler,
after_register,
get_test_client,
):
async def _get_test_app_client(redirect_url: str = None) -> httpx.AsyncClient:
@ -51,10 +49,9 @@ def get_test_app_client(
authenticator,
SECRET,
redirect_url,
after_register,
)
oauth_router.add_event_handler(Event.ON_AFTER_REGISTER, event_handler)
app = FastAPI()
app.include_router(oauth_router)
@ -151,7 +148,7 @@ class TestCallback:
test_app_client: httpx.AsyncClient,
oauth_client,
user_oauth,
event_handler,
after_register,
):
with asynctest.patch.object(
oauth_client, "get_access_token"
@ -171,7 +168,7 @@ class TestCallback:
get_id_email_mock.assert_awaited_once_with("TOKEN")
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert event_handler.called is False
assert after_register.called is False
async def test_existing_user_with_oauth(
self,
@ -179,7 +176,7 @@ class TestCallback:
test_app_client: httpx.AsyncClient,
oauth_client,
user_oauth,
event_handler,
after_register,
):
state_jwt = generate_state_token({"authentication_backend": "mock"}, "SECRET")
with asynctest.patch.object(
@ -206,7 +203,7 @@ class TestCallback:
assert data["token"] == str(user_oauth.id)
assert event_handler.called is False
assert after_register.called is False
async def test_existing_user_without_oauth(
self,
@ -214,7 +211,7 @@ class TestCallback:
test_app_client: httpx.AsyncClient,
oauth_client,
superuser_oauth,
event_handler,
after_register,
):
state_jwt = generate_state_token({"authentication_backend": "mock"}, "SECRET")
with asynctest.patch.object(
@ -244,14 +241,14 @@ class TestCallback:
assert data["token"] == str(superuser_oauth.id)
assert event_handler.called is False
assert after_register.called is False
async def test_unknown_user(
self,
mock_user_db_oauth,
test_app_client: httpx.AsyncClient,
oauth_client,
event_handler,
after_register,
):
state_jwt = generate_state_token({"authentication_backend": "mock"}, "SECRET")
with asynctest.patch.object(
@ -281,10 +278,10 @@ class TestCallback:
assert "token" in data
assert event_handler.called is True
actual_user = event_handler.call_args[0][0]
assert after_register.called is True
actual_user = after_register.call_args[0][0]
assert str(actual_user.id) == data["token"]
request = event_handler.call_args[0][1]
request = after_register.call_args[0][1]
assert isinstance(request, Request)
async def test_inactive_user(
@ -293,7 +290,7 @@ class TestCallback:
test_app_client: httpx.AsyncClient,
oauth_client,
inactive_user_oauth,
event_handler,
after_register,
):
state_jwt = generate_state_token({"authentication_backend": "mock"}, "SECRET")
with asynctest.patch.object(
@ -318,7 +315,7 @@ class TestCallback:
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS
assert event_handler.called is False
assert after_register.called is False
async def test_redirect_url_router(
self,

View File

@ -0,0 +1,122 @@
from typing import cast, Dict, Any
from unittest.mock import MagicMock
import asynctest
import httpx
import pytest
from fastapi import FastAPI, status, Request
from fastapi_users.router import ErrorCode, get_register_router
from tests.conftest import User, UserCreate, UserDB
SECRET = "SECRET"
LIFETIME = 3600
def after_register_sync():
return MagicMock(return_value=None)
def after_register_async():
return asynctest.CoroutineMock(return_value=None)
@pytest.fixture(params=[after_register_sync, after_register_async])
def after_register(request):
return request.param()
@pytest.fixture
@pytest.mark.asyncio
async def test_app_client(
mock_user_db, mock_authentication, after_register, get_test_client
) -> httpx.AsyncClient:
register_router = get_register_router(
mock_user_db, User, UserCreate, UserDB, after_register,
)
app = FastAPI()
app.include_router(register_router)
return await get_test_client(app)
@pytest.mark.router
@pytest.mark.asyncio
class TestRegister:
async def test_empty_body(self, test_app_client: httpx.AsyncClient, after_register):
response = await test_app_client.post("/register", json={})
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
assert after_register.called is False
async def test_missing_password(
self, test_app_client: httpx.AsyncClient, after_register
):
json = {"email": "king.arthur@camelot.bt"}
response = await test_app_client.post("/register", json=json)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
assert after_register.called is False
async def test_wrong_email(
self, test_app_client: httpx.AsyncClient, after_register
):
json = {"email": "king.arthur", "password": "guinevere"}
response = await test_app_client.post("/register", json=json)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
assert after_register.called is False
async def test_existing_user(
self, test_app_client: httpx.AsyncClient, after_register
):
json = {"email": "king.arthur@camelot.bt", "password": "guinevere"}
response = await test_app_client.post("/register", json=json)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.REGISTER_USER_ALREADY_EXISTS
assert after_register.called is False
async def test_valid_body(self, test_app_client: httpx.AsyncClient, after_register):
json = {"email": "lancelot@camelot.bt", "password": "guinevere"}
response = await test_app_client.post("/register", json=json)
assert response.status_code == status.HTTP_201_CREATED
assert after_register.called is True
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 = after_register.call_args[0][0]
assert str(actual_user.id) == data["id"]
request = after_register.call_args[0][1]
assert isinstance(request, Request)
async def test_valid_body_is_superuser(
self, test_app_client: httpx.AsyncClient, after_register
):
json = {
"email": "lancelot@camelot.bt",
"password": "guinevere",
"is_superuser": True,
}
response = await test_app_client.post("/register", json=json)
assert response.status_code == status.HTTP_201_CREATED
assert after_register.called is True
data = cast(Dict[str, Any], response.json())
assert data["is_superuser"] is False
async def test_valid_body_is_active(
self, test_app_client: httpx.AsyncClient, after_register
):
json = {
"email": "lancelot@camelot.bt",
"password": "guinevere",
"is_active": False,
}
response = await test_app_client.post("/register", json=json)
assert response.status_code == status.HTTP_201_CREATED
assert after_register.called is True
data = cast(Dict[str, Any], response.json())
assert data["is_active"] is True

198
tests/test_router_reset.py Normal file
View File

@ -0,0 +1,198 @@
from typing import cast, Dict, Any
from unittest.mock import MagicMock
import asynctest
import httpx
import jwt
import pytest
from fastapi import FastAPI, status, Request
from fastapi_users.router import ErrorCode, get_reset_password_router
from fastapi_users.utils import JWT_ALGORITHM, generate_jwt
from tests.conftest import UserDB
SECRET = "SECRET"
LIFETIME = 3600
@pytest.fixture
def forgot_password_token():
def _forgot_password_token(user_id=None, lifetime=LIFETIME):
data = {"aud": "fastapi-users:reset"}
if user_id is not None:
data["user_id"] = str(user_id)
return generate_jwt(data, lifetime, SECRET, JWT_ALGORITHM)
return _forgot_password_token
def after_forgot_password_sync():
return MagicMock(return_value=None)
def after_forgot_password_async():
return asynctest.CoroutineMock(return_value=None)
@pytest.fixture(params=[after_forgot_password_sync, after_forgot_password_async])
def after_forgot_password(request):
return request.param()
@pytest.fixture
@pytest.mark.asyncio
async def test_app_client(
mock_user_db, mock_authentication, after_forgot_password, get_test_client
) -> httpx.AsyncClient:
reset_router = get_reset_password_router(
mock_user_db, SECRET, LIFETIME, after_forgot_password
)
app = FastAPI()
app.include_router(reset_router)
return await get_test_client(app)
@pytest.mark.router
@pytest.mark.asyncio
class TestForgotPassword:
async def test_empty_body(
self, test_app_client: httpx.AsyncClient, after_forgot_password
):
response = await test_app_client.post("/forgot-password", json={})
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
assert after_forgot_password.called is False
async def test_not_existing_user(
self, test_app_client: httpx.AsyncClient, after_forgot_password
):
json = {"email": "lancelot@camelot.bt"}
response = await test_app_client.post("/forgot-password", json=json)
assert response.status_code == status.HTTP_202_ACCEPTED
assert after_forgot_password.called is False
async def test_inactive_user(
self, test_app_client: httpx.AsyncClient, after_forgot_password
):
json = {"email": "percival@camelot.bt"}
response = await test_app_client.post("/forgot-password", json=json)
assert response.status_code == status.HTTP_202_ACCEPTED
assert after_forgot_password.called is False
async def test_existing_user(
self, test_app_client: httpx.AsyncClient, after_forgot_password, user
):
json = {"email": "king.arthur@camelot.bt"}
response = await test_app_client.post("/forgot-password", json=json)
assert response.status_code == status.HTTP_202_ACCEPTED
assert after_forgot_password.called is True
actual_user = after_forgot_password.call_args[0][0]
assert actual_user.id == user.id
actual_token = after_forgot_password.call_args[0][1]
decoded_token = jwt.decode(
actual_token,
SECRET,
audience="fastapi-users:reset",
algorithms=[JWT_ALGORITHM],
)
assert decoded_token["user_id"] == str(user.id)
request = after_forgot_password.call_args[0][2]
assert isinstance(request, Request)
@pytest.mark.router
@pytest.mark.asyncio
class TestResetPassword:
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
async def test_missing_token(self, test_app_client: httpx.AsyncClient):
json = {"password": "guinevere"}
response = await test_app_client.post("/reset-password", json=json)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
async def test_missing_password(self, test_app_client: httpx.AsyncClient):
json = {"token": "foo"}
response = await test_app_client.post("/reset-password", json=json)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
async def test_invalid_token(self, test_app_client: httpx.AsyncClient):
json = {"token": "foo", "password": "guinevere"}
response = await test_app_client.post("/reset-password", json=json)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.RESET_PASSWORD_BAD_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 = await test_app_client.post("/reset-password", json=json)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN
assert mock_user_db.update.called is False
async def test_valid_token_invalid_uuid(
self,
mocker,
mock_user_db,
test_app_client: httpx.AsyncClient,
forgot_password_token,
):
mocker.spy(mock_user_db, "update")
json = {"token": forgot_password_token("foo"), "password": "holygrail"}
response = await test_app_client.post("/reset-password", json=json)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN
assert mock_user_db.update.called is False
async def test_inactive_user(
self,
mocker,
mock_user_db,
test_app_client: httpx.AsyncClient,
forgot_password_token,
inactive_user: UserDB,
):
mocker.spy(mock_user_db, "update")
json = {
"token": forgot_password_token(inactive_user.id),
"password": "holygrail",
}
response = await test_app_client.post("/reset-password", json=json)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN
assert mock_user_db.update.called is False
async def test_existing_user(
self,
mocker,
mock_user_db,
test_app_client: httpx.AsyncClient,
forgot_password_token,
user: UserDB,
):
mocker.spy(mock_user_db, "update")
current_hashed_passord = user.hashed_password
json = {"token": forgot_password_token(user.id), "password": "holygrail"}
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
updated_user = mock_user_db.update.call_args[0][0]
assert updated_user.hashed_password != current_hashed_passord

View File

@ -3,365 +3,50 @@ 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 fastapi import FastAPI, status, Request
from fastapi_users.authentication import Authenticator
from fastapi_users.router import ErrorCode, Event, get_user_router
from fastapi_users.utils import JWT_ALGORITHM, generate_jwt
from tests.conftest import MockAuthentication, User, UserCreate, UserUpdate, UserDB
from fastapi_users.router import get_users_router
from tests.conftest import MockAuthentication, User, UserUpdate, UserDB
SECRET = "SECRET"
LIFETIME = 3600
@pytest.fixture
def forgot_password_token():
def _forgot_password_token(user_id=None, lifetime=LIFETIME):
data = {"aud": "fastapi-users:reset"}
if user_id is not None:
data["user_id"] = str(user_id)
return generate_jwt(data, lifetime, SECRET, JWT_ALGORITHM)
return _forgot_password_token
def event_handler_sync():
def after_update_sync():
return MagicMock(return_value=None)
def event_handler_async():
def after_update_async():
return asynctest.CoroutineMock(return_value=None)
@pytest.fixture(params=[event_handler_sync, event_handler_async])
def event_handler(request):
@pytest.fixture(params=[after_update_sync, after_update_async])
def after_update(request):
return request.param()
@pytest.fixture
@pytest.mark.asyncio
async def test_app_client(
mock_user_db, mock_authentication, event_handler, get_test_client
mock_user_db, mock_authentication, after_update, get_test_client
) -> httpx.AsyncClient:
mock_authentication_bis = MockAuthentication(name="mock-bis")
authenticator = Authenticator(
[mock_authentication, mock_authentication_bis], mock_user_db
)
user_router = get_user_router(
mock_user_db,
User,
UserCreate,
UserUpdate,
UserDB,
authenticator,
SECRET,
LIFETIME,
user_router = get_users_router(
mock_user_db, User, UserUpdate, UserDB, authenticator, after_update,
)
user_router.add_event_handler(Event.ON_AFTER_REGISTER, event_handler)
user_router.add_event_handler(Event.ON_AFTER_FORGOT_PASSWORD, event_handler)
user_router.add_event_handler(Event.ON_AFTER_UPDATE, event_handler)
app = FastAPI()
app.include_router(user_router)
return await get_test_client(app)
@pytest.mark.router
@pytest.mark.asyncio
class TestRegister:
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
async def test_missing_password(
self, test_app_client: httpx.AsyncClient, event_handler
):
json = {"email": "king.arthur@camelot.bt"}
response = await test_app_client.post("/register", json=json)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
assert event_handler.called is False
async def test_wrong_email(self, test_app_client: httpx.AsyncClient, event_handler):
json = {"email": "king.arthur", "password": "guinevere"}
response = await test_app_client.post("/register", json=json)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
assert event_handler.called is False
async def test_existing_user(
self, test_app_client: httpx.AsyncClient, event_handler
):
json = {"email": "king.arthur@camelot.bt", "password": "guinevere"}
response = await test_app_client.post("/register", json=json)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.REGISTER_USER_ALREADY_EXISTS
assert event_handler.called is False
async def test_valid_body(self, test_app_client: httpx.AsyncClient, event_handler):
json = {"email": "lancelot@camelot.bt", "password": "guinevere"}
response = await test_app_client.post("/register", json=json)
assert response.status_code == status.HTTP_201_CREATED
assert event_handler.called is True
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 str(actual_user.id) == data["id"]
request = event_handler.call_args[0][1]
assert isinstance(request, Request)
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 = await test_app_client.post("/register", json=json)
assert response.status_code == status.HTTP_201_CREATED
assert event_handler.called is True
data = cast(Dict[str, Any], response.json())
assert data["is_superuser"] is False
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 = await test_app_client.post("/register", json=json)
assert response.status_code == status.HTTP_201_CREATED
assert event_handler.called 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:
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
async def test_missing_username(self, path, test_app_client: httpx.AsyncClient):
data = {"password": "guinevere"}
response = await test_app_client.post(path, data=data)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
async def test_missing_password(self, path, test_app_client: httpx.AsyncClient):
data = {"username": "king.arthur@camelot.bt"}
response = await test_app_client.post(path, data=data)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
async def test_not_existing_user(self, path, test_app_client: httpx.AsyncClient):
data = {"username": "lancelot@camelot.bt", "password": "guinevere"}
response = await test_app_client.post(path, data=data)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS
async def test_wrong_password(self, path, test_app_client: httpx.AsyncClient):
data = {"username": "king.arthur@camelot.bt", "password": "percival"}
response = await test_app_client.post(path, data=data)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS
async def test_valid_credentials(
self, path, test_app_client: httpx.AsyncClient, user: UserDB
):
data = {"username": "king.arthur@camelot.bt", "password": "guinevere"}
response = await test_app_client.post(path, data=data)
assert response.status_code == status.HTTP_200_OK
assert response.json() == {"token": str(user.id)}
async def test_inactive_user(self, path, test_app_client: httpx.AsyncClient):
data = {"username": "percival@camelot.bt", "password": "angharad"}
response = await test_app_client.post(path, data=data)
assert response.status_code == status.HTTP_400_BAD_REQUEST
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:
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
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 = await test_app_client.post(
path, headers={"Authorization": f"Bearer {user.id}"}
)
assert response.status_code == status.HTTP_202_ACCEPTED
get_logout_response_spy.assert_called_once()
@pytest.mark.router
@pytest.mark.asyncio
class TestForgotPassword:
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
async def test_not_existing_user(
self, test_app_client: httpx.AsyncClient, event_handler
):
json = {"email": "lancelot@camelot.bt"}
response = await test_app_client.post("/forgot-password", json=json)
assert response.status_code == status.HTTP_202_ACCEPTED
assert event_handler.called is False
async def test_inactive_user(
self, test_app_client: httpx.AsyncClient, event_handler
):
json = {"email": "percival@camelot.bt"}
response = await test_app_client.post("/forgot-password", json=json)
assert response.status_code == status.HTTP_202_ACCEPTED
assert event_handler.called is False
async def test_existing_user(
self, test_app_client: httpx.AsyncClient, event_handler, user
):
json = {"email": "king.arthur@camelot.bt"}
response = await test_app_client.post("/forgot-password", json=json)
assert response.status_code == status.HTTP_202_ACCEPTED
assert event_handler.called is True
actual_user = event_handler.call_args[0][0]
assert actual_user.id == user.id
actual_token = event_handler.call_args[0][1]
decoded_token = jwt.decode(
actual_token,
SECRET,
audience="fastapi-users:reset",
algorithms=[JWT_ALGORITHM],
)
assert decoded_token["user_id"] == str(user.id)
request = event_handler.call_args[0][2]
assert isinstance(request, Request)
@pytest.mark.router
@pytest.mark.asyncio
class TestResetPassword:
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
async def test_missing_token(self, test_app_client: httpx.AsyncClient):
json = {"password": "guinevere"}
response = await test_app_client.post("/reset-password", json=json)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
async def test_missing_password(self, test_app_client: httpx.AsyncClient):
json = {"token": "foo"}
response = await test_app_client.post("/reset-password", json=json)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
async def test_invalid_token(self, test_app_client: httpx.AsyncClient):
json = {"token": "foo", "password": "guinevere"}
response = await test_app_client.post("/reset-password", json=json)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.RESET_PASSWORD_BAD_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 = await test_app_client.post("/reset-password", json=json)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN
assert mock_user_db.update.called is False
async def test_valid_token_invalid_uuid(
self,
mocker,
mock_user_db,
test_app_client: httpx.AsyncClient,
forgot_password_token,
):
mocker.spy(mock_user_db, "update")
json = {"token": forgot_password_token("foo"), "password": "holygrail"}
response = await test_app_client.post("/reset-password", json=json)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN
assert mock_user_db.update.called is False
async def test_inactive_user(
self,
mocker,
mock_user_db,
test_app_client: httpx.AsyncClient,
forgot_password_token,
inactive_user: UserDB,
):
mocker.spy(mock_user_db, "update")
json = {
"token": forgot_password_token(inactive_user.id),
"password": "holygrail",
}
response = await test_app_client.post("/reset-password", json=json)
assert response.status_code == status.HTTP_400_BAD_REQUEST
data = cast(Dict[str, Any], response.json())
assert data["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN
assert mock_user_db.update.called is False
async def test_existing_user(
self,
mocker,
mock_user_db,
test_app_client: httpx.AsyncClient,
forgot_password_token,
user: UserDB,
):
mocker.spy(mock_user_db, "update")
current_hashed_passord = user.hashed_password
json = {"token": forgot_password_token(user.id), "password": "holygrail"}
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
updated_user = mock_user_db.update.call_args[0][0]
assert updated_user.hashed_password != current_hashed_passord
@pytest.mark.router
@pytest.mark.asyncio
class TestMe:
@ -392,23 +77,23 @@ class TestMe:
@pytest.mark.asyncio
class TestUpdateMe:
async def test_missing_token(
self, test_app_client: httpx.AsyncClient, event_handler
self, test_app_client: httpx.AsyncClient, after_update
):
response = await test_app_client.patch("/me")
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert event_handler.called is False
assert after_update.called is False
async def test_inactive_user(
self, test_app_client: httpx.AsyncClient, inactive_user: UserDB, event_handler
self, test_app_client: httpx.AsyncClient, inactive_user: UserDB, after_update
):
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
assert after_update.called is False
async def test_empty_body(
self, test_app_client: httpx.AsyncClient, user: UserDB, event_handler
self, test_app_client: httpx.AsyncClient, user: UserDB, after_update
):
response = await test_app_client.patch(
"/me", json={}, headers={"Authorization": f"Bearer {user.id}"}
@ -418,16 +103,16 @@ class TestUpdateMe:
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]
assert after_update.called is True
actual_user = after_update.call_args[0][0]
assert actual_user.id == user.id
updated_fields = event_handler.call_args[0][1]
updated_fields = after_update.call_args[0][1]
assert updated_fields == {}
request = event_handler.call_args[0][2]
request = after_update.call_args[0][2]
assert isinstance(request, Request)
async def test_valid_body(
self, test_app_client: httpx.AsyncClient, user: UserDB, event_handler
self, test_app_client: httpx.AsyncClient, user: UserDB, after_update
):
json = {"email": "king.arthur@tintagel.bt"}
response = await test_app_client.patch(
@ -438,16 +123,16 @@ class TestUpdateMe:
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]
assert after_update.called is True
actual_user = after_update.call_args[0][0]
assert actual_user.id == user.id
updated_fields = event_handler.call_args[0][1]
updated_fields = after_update.call_args[0][1]
assert updated_fields == {"email": "king.arthur@tintagel.bt"}
request = event_handler.call_args[0][2]
request = after_update.call_args[0][2]
assert isinstance(request, Request)
async def test_valid_body_is_superuser(
self, test_app_client: httpx.AsyncClient, user: UserDB, event_handler
self, test_app_client: httpx.AsyncClient, user: UserDB, after_update
):
json = {"is_superuser": True}
response = await test_app_client.patch(
@ -458,16 +143,16 @@ class TestUpdateMe:
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]
assert after_update.called is True
actual_user = after_update.call_args[0][0]
assert actual_user.id == user.id
updated_fields = event_handler.call_args[0][1]
updated_fields = after_update.call_args[0][1]
assert updated_fields == {}
request = event_handler.call_args[0][2]
request = after_update.call_args[0][2]
assert isinstance(request, Request)
async def test_valid_body_is_active(
self, test_app_client: httpx.AsyncClient, user: UserDB, event_handler
self, test_app_client: httpx.AsyncClient, user: UserDB, after_update
):
json = {"is_active": False}
response = await test_app_client.patch(
@ -478,12 +163,12 @@ class TestUpdateMe:
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]
assert after_update.called is True
actual_user = after_update.call_args[0][0]
assert actual_user.id == user.id
updated_fields = event_handler.call_args[0][1]
updated_fields = after_update.call_args[0][1]
assert updated_fields == {}
request = event_handler.call_args[0][2]
request = after_update.call_args[0][2]
assert isinstance(request, Request)
async def test_valid_body_password(
@ -492,7 +177,7 @@ class TestUpdateMe:
mock_user_db,
test_app_client: httpx.AsyncClient,
user: UserDB,
event_handler,
after_update,
):
mocker.spy(mock_user_db, "update")
current_hashed_passord = user.hashed_password
@ -507,12 +192,12 @@ class TestUpdateMe:
updated_user = mock_user_db.update.call_args[0][0]
assert updated_user.hashed_password != current_hashed_passord
assert event_handler.called is True
actual_user = event_handler.call_args[0][0]
assert after_update.called is True
actual_user = after_update.call_args[0][0]
assert actual_user.id == user.id
updated_fields = event_handler.call_args[0][1]
updated_fields = after_update.call_args[0][1]
assert updated_fields == {"password": "merlin"}
request = event_handler.call_args[0][2]
request = after_update.call_args[0][2]
assert isinstance(request, Request)