Files
fastapi-users/tests/test_fastapi_users.py
François Voron 373157c284 Finalize user activation feature (#439)
* Add routes for user activation (#403)

* Add routes for user activation

Generate a token after creating the user in register route, passing to `activation_callback`, if `activation_callback` supplied
Create new `/activate` route that will verify the token and activate the user
Add new error codes to `fastapi_users/router/common.py`
Update documentation
Add tests

Co-authored-by: Mark Todd <markpeter.todd@hotmail.co.uk>

* Rework routes for user activation

* Separate verification logic and token generation into `/fastapi_users/router/verify.py`, with per-route callbacks for custom behaviour

* Return register router to original state

* Added `is_verified` property to user models

* Added `requires_verification` argument to `get_users_router`and `get_auth_router`

* Additional dependencies added for verification in `fastapi_users/authentication/__init__.py`

* Update tests for new behaviour

* Update `README.md` to describe a workaround for possible problems during testing, by exceeding ulimit file descriptor limit

Co-authored-by: Mark Todd <markpeter.todd@hotmail.co.uk>

* Restored docs to original state.

* All other modifications reqested added

Kebab-case on request-verify-token
SECRET now used as test string
Other minor changes

Co-authored-by: Mark Todd <markpeter.todd@hotmail.co.uk>

* Embed token in body in verify route

* Reorganize checks in verify route and add unit test

* Ignore coverage on Protocol classes

* Tweak verify_user function to take full user in parameter

* Improve unit tests structure regarding parametrized test client

* Make after_verification_request optional to be more consistent with other routers

* Tweak status codes on verify routes

* Write documentation for verification feature

* Add not released warning on verify docs

Co-authored-by: Edd Salkield <edd@salkield.uk>
Co-authored-by: Mark Todd <markpeter.todd@hotmail.co.uk>
2021-01-12 10:44:42 +01:00

491 lines
18 KiB
Python

from typing import AsyncGenerator
import httpx
import pytest
from fastapi import Depends, FastAPI, status
from fastapi_users import FastAPIUsers
from tests.conftest import User, UserCreate, UserDB, UserUpdate
@pytest.fixture
@pytest.mark.asyncio
async def test_app_client(
mock_user_db, mock_authentication, oauth_client, get_test_client
) -> AsyncGenerator[httpx.AsyncClient, None]:
fastapi_users = FastAPIUsers(
mock_user_db,
[mock_authentication],
User,
UserCreate,
UserUpdate,
UserDB,
)
app = FastAPI()
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.include_router(fastapi_users.get_verify_router("SECRET"))
@app.get("/current-user")
def current_user(user=Depends(fastapi_users.get_current_user)):
return user
@app.get("/current-active-user")
def current_active_user(user=Depends(fastapi_users.get_current_active_user)):
return user
@app.get("/current-verified-user")
def current_verified_user(user=Depends(fastapi_users.get_current_verified_user)):
return user
@app.get("/current-superuser")
def current_superuser(user=Depends(fastapi_users.get_current_superuser)):
return user
@app.get("/current-verified-superuser")
def current_verified_superuser(
user=Depends(fastapi_users.get_current_verified_superuser),
):
return user
@app.get("/optional-current-user")
def optional_current_user(user=Depends(fastapi_users.get_optional_current_user)):
return user
@app.get("/optional-current-active-user")
def optional_current_active_user(
user=Depends(fastapi_users.get_optional_current_active_user),
):
return user
@app.get("/optional-current-verified-user")
def optional_current_verified_user(
user=Depends(fastapi_users.get_optional_current_verified_user),
):
return user
@app.get("/optional-current-superuser")
def optional_current_superuser(
user=Depends(fastapi_users.get_optional_current_superuser),
):
return user
@app.get("/optional-current-verified-superuser")
def optional_current_verified_superuser(
user=Depends(fastapi_users.get_optional_current_verified_superuser),
):
return user
async for client in get_test_client(app):
yield client
@pytest.mark.fastapi_users
@pytest.mark.asyncio
class TestRoutes:
async def test_routes_exist(self, test_app_client: httpx.AsyncClient):
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("/request-verify-token")
assert response.status_code not in (
status.HTTP_404_NOT_FOUND,
status.HTTP_405_METHOD_NOT_ALLOWED,
)
response = await test_app_client.post("/verify")
assert response.status_code not in (
status.HTTP_404_NOT_FOUND,
status.HTTP_405_METHOD_NOT_ALLOWED,
)
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("/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("/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 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 not in (
status.HTTP_404_NOT_FOUND,
status.HTTP_405_METHOD_NOT_ALLOWED,
)
@pytest.mark.fastapi_users
@pytest.mark.asyncio
class TestGetCurrentUser:
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
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
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:
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
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
async def test_valid_token_inactive_user(
self, test_app_client: httpx.AsyncClient, inactive_user: UserDB
):
response = await test_app_client.get(
"/current-active-user",
headers={"Authorization": f"Bearer {inactive_user.id}"},
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
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 TestGetCurrentVerifiedUser:
async def test_missing_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get("/current-verified-user")
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_invalid_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get(
"/current-verified-user", headers={"Authorization": "Bearer foo"}
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_valid_token_unverified_user(
self, test_app_client: httpx.AsyncClient, user: UserDB
):
response = await test_app_client.get(
"/current-verified-user",
headers={"Authorization": f"Bearer {user.id}"},
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_valid_token_verified_user(
self, test_app_client: httpx.AsyncClient, verified_user: UserDB
):
response = await test_app_client.get(
"/current-verified-user",
headers={"Authorization": f"Bearer {verified_user.id}"},
)
assert response.status_code == status.HTTP_200_OK
@pytest.mark.fastapi_users
@pytest.mark.asyncio
class TestGetCurrentSuperuser:
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
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
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
async def test_valid_token_superuser(
self, test_app_client: httpx.AsyncClient, superuser: UserDB
):
response = await test_app_client.get(
"/current-superuser", headers={"Authorization": f"Bearer {superuser.id}"}
)
assert response.status_code == status.HTTP_200_OK
@pytest.mark.fastapi_users
@pytest.mark.asyncio
class TestGetCurrentVerifiedSuperuser:
async def test_missing_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get("/current-verified-superuser")
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_invalid_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get(
"/current-verified-superuser", headers={"Authorization": "Bearer foo"}
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_valid_token_regular_user(
self, test_app_client: httpx.AsyncClient, user: UserDB
):
response = await test_app_client.get(
"/current-verified-superuser",
headers={"Authorization": f"Bearer {user.id}"},
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_valid_token_verified_user(
self, test_app_client: httpx.AsyncClient, verified_user: UserDB
):
response = await test_app_client.get(
"/current-verified-superuser",
headers={"Authorization": f"Bearer {verified_user.id}"},
)
assert response.status_code == status.HTTP_403_FORBIDDEN
async def test_valid_token_superuser(
self, test_app_client: httpx.AsyncClient, superuser: UserDB
):
response = await test_app_client.get(
"/current-verified-superuser",
headers={"Authorization": f"Bearer {superuser.id}"},
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
async def test_valid_token_verified_superuser(
self, test_app_client: httpx.AsyncClient, verified_superuser: UserDB
):
response = await test_app_client.get(
"/current-verified-superuser",
headers={"Authorization": f"Bearer {verified_superuser.id}"},
)
assert response.status_code == status.HTTP_200_OK
@pytest.mark.fastapi_users
@pytest.mark.asyncio
class TestOptionalGetCurrentUser:
async def test_missing_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get("/optional-current-user")
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_invalid_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get(
"/optional-current-user", headers={"Authorization": "Bearer foo"}
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_valid_token(self, test_app_client: httpx.AsyncClient, user: UserDB):
response = await test_app_client.get(
"/optional-current-user", headers={"Authorization": f"Bearer {user.id}"}
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is not None
@pytest.mark.fastapi_users
@pytest.mark.asyncio
class TestOptionalGetCurrentVerifiedUser:
async def test_missing_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get("/optional-current-verified-user")
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_invalid_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get(
"/optional-current-verified-user", headers={"Authorization": "Bearer foo"}
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_valid_token_unverified_user(
self, test_app_client: httpx.AsyncClient, user: UserDB
):
response = await test_app_client.get(
"/optional-current-verified-user",
headers={"Authorization": f"Bearer {user.id}"},
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_valid_token_verified_user(
self, test_app_client: httpx.AsyncClient, verified_user: UserDB
):
response = await test_app_client.get(
"/optional-current-verified-user",
headers={"Authorization": f"Bearer {verified_user.id}"},
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is not None
@pytest.mark.fastapi_users
@pytest.mark.asyncio
class TestOptionalGetCurrentActiveUser:
async def test_missing_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get("/optional-current-active-user")
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_invalid_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get(
"/optional-current-active-user", headers={"Authorization": "Bearer foo"}
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_valid_token_inactive_user(
self, test_app_client: httpx.AsyncClient, inactive_user: UserDB
):
response = await test_app_client.get(
"/optional-current-active-user",
headers={"Authorization": f"Bearer {inactive_user.id}"},
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_valid_token(self, test_app_client: httpx.AsyncClient, user: UserDB):
response = await test_app_client.get(
"/optional-current-active-user",
headers={"Authorization": f"Bearer {user.id}"},
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is not None
@pytest.mark.fastapi_users
@pytest.mark.asyncio
class TestOptionalGetCurrentSuperuser:
async def test_missing_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get("/optional-current-superuser")
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_invalid_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get(
"/optional-current-superuser", headers={"Authorization": "Bearer foo"}
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_valid_token_regular_user(
self, test_app_client: httpx.AsyncClient, user: UserDB
):
response = await test_app_client.get(
"/optional-current-superuser",
headers={"Authorization": f"Bearer {user.id}"},
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_valid_token_superuser(
self, test_app_client: httpx.AsyncClient, superuser: UserDB
):
response = await test_app_client.get(
"/optional-current-superuser",
headers={"Authorization": f"Bearer {superuser.id}"},
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is not None
@pytest.mark.fastapi_users
@pytest.mark.asyncio
class TestOptionalGetCurrentVerifiedSuperuser:
async def test_missing_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get("/optional-current-verified-superuser")
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_invalid_token(self, test_app_client: httpx.AsyncClient):
response = await test_app_client.get(
"/optional-current-verified-superuser",
headers={"Authorization": "Bearer foo"},
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_valid_token_regular_user(
self, test_app_client: httpx.AsyncClient, user: UserDB
):
response = await test_app_client.get(
"/optional-current-verified-superuser",
headers={"Authorization": f"Bearer {user.id}"},
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_valid_token_verified_user(
self, test_app_client: httpx.AsyncClient, verified_user: UserDB
):
response = await test_app_client.get(
"/optional-current-verified-superuser",
headers={"Authorization": f"Bearer {verified_user.id}"},
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_valid_token_superuser(
self, test_app_client: httpx.AsyncClient, superuser: UserDB
):
response = await test_app_client.get(
"/optional-current-verified-superuser",
headers={"Authorization": f"Bearer {superuser.id}"},
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is None
async def test_valid_token_verified_superuser(
self, test_app_client: httpx.AsyncClient, verified_superuser: UserDB
):
response = await test_app_client.get(
"/optional-current-verified-superuser",
headers={"Authorization": f"Bearer {verified_superuser.id}"},
)
assert response.status_code == status.HTTP_200_OK
assert response.json() is not None