mirror of
				https://github.com/fastapi-users/fastapi-users.git
				synced 2025-11-04 06:37:51 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			389 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			389 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from typing import Any, AsyncGenerator, Dict, cast
 | 
						|
from unittest.mock import MagicMock
 | 
						|
 | 
						|
import asynctest
 | 
						|
import httpx
 | 
						|
import pytest
 | 
						|
from fastapi import FastAPI, Request, status
 | 
						|
 | 
						|
from fastapi_users.authentication import Authenticator
 | 
						|
from fastapi_users.router import get_users_router
 | 
						|
from tests.conftest import MockAuthentication, User, UserDB, UserUpdate
 | 
						|
 | 
						|
SECRET = "SECRET"
 | 
						|
LIFETIME = 3600
 | 
						|
 | 
						|
 | 
						|
def after_update_sync():
 | 
						|
    return MagicMock(return_value=None)
 | 
						|
 | 
						|
 | 
						|
def after_update_async():
 | 
						|
    return asynctest.CoroutineMock(return_value=None)
 | 
						|
 | 
						|
 | 
						|
@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, after_update, get_test_client
 | 
						|
) -> AsyncGenerator[httpx.AsyncClient, None]:
 | 
						|
    mock_authentication_bis = MockAuthentication(name="mock-bis")
 | 
						|
    authenticator = Authenticator(
 | 
						|
        [mock_authentication, mock_authentication_bis], mock_user_db
 | 
						|
    )
 | 
						|
 | 
						|
    user_router = get_users_router(
 | 
						|
        mock_user_db,
 | 
						|
        User,
 | 
						|
        UserUpdate,
 | 
						|
        UserDB,
 | 
						|
        authenticator,
 | 
						|
        after_update,
 | 
						|
    )
 | 
						|
 | 
						|
    app = FastAPI()
 | 
						|
    app.include_router(user_router)
 | 
						|
 | 
						|
    async for client in get_test_client(app):
 | 
						|
        yield client
 | 
						|
 | 
						|
 | 
						|
@pytest.mark.router
 | 
						|
@pytest.mark.asyncio
 | 
						|
class TestMe:
 | 
						|
    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
 | 
						|
 | 
						|
    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
 | 
						|
 | 
						|
    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
 | 
						|
 | 
						|
        data = cast(Dict[str, Any], response.json())
 | 
						|
        assert data["id"] == str(user.id)
 | 
						|
        assert data["email"] == user.email
 | 
						|
 | 
						|
 | 
						|
@pytest.mark.router
 | 
						|
@pytest.mark.asyncio
 | 
						|
class TestUpdateMe:
 | 
						|
    async def test_missing_token(
 | 
						|
        self, test_app_client: httpx.AsyncClient, after_update
 | 
						|
    ):
 | 
						|
        response = await test_app_client.patch("/me")
 | 
						|
        assert response.status_code == status.HTTP_401_UNAUTHORIZED
 | 
						|
        assert after_update.called is False
 | 
						|
 | 
						|
    async def test_inactive_user(
 | 
						|
        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 after_update.called is False
 | 
						|
 | 
						|
    async def test_empty_body(
 | 
						|
        self, test_app_client: httpx.AsyncClient, user: UserDB, after_update
 | 
						|
    ):
 | 
						|
        response = await test_app_client.patch(
 | 
						|
            "/me", json={}, headers={"Authorization": f"Bearer {user.id}"}
 | 
						|
        )
 | 
						|
        assert response.status_code == status.HTTP_200_OK
 | 
						|
 | 
						|
        data = cast(Dict[str, Any], response.json())
 | 
						|
        assert data["email"] == user.email
 | 
						|
 | 
						|
        assert after_update.called is True
 | 
						|
        actual_user = after_update.call_args[0][0]
 | 
						|
        assert actual_user.id == user.id
 | 
						|
        updated_fields = after_update.call_args[0][1]
 | 
						|
        assert updated_fields == {}
 | 
						|
        request = after_update.call_args[0][2]
 | 
						|
        assert isinstance(request, Request)
 | 
						|
 | 
						|
    async def test_valid_body(
 | 
						|
        self, test_app_client: httpx.AsyncClient, user: UserDB, after_update
 | 
						|
    ):
 | 
						|
        json = {"email": "king.arthur@tintagel.bt"}
 | 
						|
        response = await test_app_client.patch(
 | 
						|
            "/me", json=json, headers={"Authorization": f"Bearer {user.id}"}
 | 
						|
        )
 | 
						|
        assert response.status_code == status.HTTP_200_OK
 | 
						|
 | 
						|
        data = cast(Dict[str, Any], response.json())
 | 
						|
        assert data["email"] == "king.arthur@tintagel.bt"
 | 
						|
 | 
						|
        assert after_update.called is True
 | 
						|
        actual_user = after_update.call_args[0][0]
 | 
						|
        assert actual_user.id == user.id
 | 
						|
        updated_fields = after_update.call_args[0][1]
 | 
						|
        assert updated_fields == {"email": "king.arthur@tintagel.bt"}
 | 
						|
        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, after_update
 | 
						|
    ):
 | 
						|
        json = {"is_superuser": True}
 | 
						|
        response = await test_app_client.patch(
 | 
						|
            "/me", json=json, headers={"Authorization": f"Bearer {user.id}"}
 | 
						|
        )
 | 
						|
        assert response.status_code == status.HTTP_200_OK
 | 
						|
 | 
						|
        data = cast(Dict[str, Any], response.json())
 | 
						|
        assert data["is_superuser"] is False
 | 
						|
 | 
						|
        assert after_update.called is True
 | 
						|
        actual_user = after_update.call_args[0][0]
 | 
						|
        assert actual_user.id == user.id
 | 
						|
        updated_fields = after_update.call_args[0][1]
 | 
						|
        assert updated_fields == {}
 | 
						|
        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, after_update
 | 
						|
    ):
 | 
						|
        json = {"is_active": False}
 | 
						|
        response = await test_app_client.patch(
 | 
						|
            "/me", json=json, headers={"Authorization": f"Bearer {user.id}"}
 | 
						|
        )
 | 
						|
        assert response.status_code == status.HTTP_200_OK
 | 
						|
 | 
						|
        data = cast(Dict[str, Any], response.json())
 | 
						|
        assert data["is_active"] is True
 | 
						|
 | 
						|
        assert after_update.called is True
 | 
						|
        actual_user = after_update.call_args[0][0]
 | 
						|
        assert actual_user.id == user.id
 | 
						|
        updated_fields = after_update.call_args[0][1]
 | 
						|
        assert updated_fields == {}
 | 
						|
        request = after_update.call_args[0][2]
 | 
						|
        assert isinstance(request, Request)
 | 
						|
 | 
						|
    async def test_valid_body_password(
 | 
						|
        self,
 | 
						|
        mocker,
 | 
						|
        mock_user_db,
 | 
						|
        test_app_client: httpx.AsyncClient,
 | 
						|
        user: UserDB,
 | 
						|
        after_update,
 | 
						|
    ):
 | 
						|
        mocker.spy(mock_user_db, "update")
 | 
						|
        current_hashed_passord = user.hashed_password
 | 
						|
 | 
						|
        json = {"password": "merlin"}
 | 
						|
        response = await test_app_client.patch(
 | 
						|
            "/me", json=json, headers={"Authorization": f"Bearer {user.id}"}
 | 
						|
        )
 | 
						|
        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
 | 
						|
 | 
						|
        assert after_update.called is True
 | 
						|
        actual_user = after_update.call_args[0][0]
 | 
						|
        assert actual_user.id == user.id
 | 
						|
        updated_fields = after_update.call_args[0][1]
 | 
						|
        assert updated_fields == {"password": "merlin"}
 | 
						|
        request = after_update.call_args[0][2]
 | 
						|
        assert isinstance(request, Request)
 | 
						|
 | 
						|
 | 
						|
@pytest.mark.router
 | 
						|
@pytest.mark.asyncio
 | 
						|
class TestGetUser:
 | 
						|
    async def test_missing_token(self, test_app_client: httpx.AsyncClient):
 | 
						|
        response = await test_app_client.get("/d35d213e-f3d8-4f08-954a-7e0d1bea286f")
 | 
						|
        assert response.status_code == status.HTTP_401_UNAUTHORIZED
 | 
						|
 | 
						|
    async def test_regular_user(self, test_app_client: httpx.AsyncClient, user: UserDB):
 | 
						|
        response = await test_app_client.get(
 | 
						|
            "/d35d213e-f3d8-4f08-954a-7e0d1bea286f",
 | 
						|
            headers={"Authorization": f"Bearer {user.id}"},
 | 
						|
        )
 | 
						|
        assert response.status_code == status.HTTP_403_FORBIDDEN
 | 
						|
 | 
						|
    async def test_not_existing_user(
 | 
						|
        self, test_app_client: httpx.AsyncClient, superuser: UserDB
 | 
						|
    ):
 | 
						|
        response = await test_app_client.get(
 | 
						|
            "/d35d213e-f3d8-4f08-954a-7e0d1bea286f",
 | 
						|
            headers={"Authorization": f"Bearer {superuser.id}"},
 | 
						|
        )
 | 
						|
        assert response.status_code == status.HTTP_404_NOT_FOUND
 | 
						|
 | 
						|
    async def test_superuser(
 | 
						|
        self, test_app_client: httpx.AsyncClient, user: UserDB, superuser: UserDB
 | 
						|
    ):
 | 
						|
        response = await test_app_client.get(
 | 
						|
            f"/{user.id}", headers={"Authorization": f"Bearer {superuser.id}"}
 | 
						|
        )
 | 
						|
        assert response.status_code == status.HTTP_200_OK
 | 
						|
 | 
						|
        data = cast(Dict[str, Any], response.json())
 | 
						|
        assert data["id"] == str(user.id)
 | 
						|
        assert "hashed_password" not in data
 | 
						|
 | 
						|
 | 
						|
@pytest.mark.router
 | 
						|
@pytest.mark.asyncio
 | 
						|
class TestUpdateUser:
 | 
						|
    async def test_missing_token(self, test_app_client: httpx.AsyncClient):
 | 
						|
        response = await test_app_client.patch("/d35d213e-f3d8-4f08-954a-7e0d1bea286f")
 | 
						|
        assert response.status_code == status.HTTP_401_UNAUTHORIZED
 | 
						|
 | 
						|
    async def test_regular_user(self, test_app_client: httpx.AsyncClient, user: UserDB):
 | 
						|
        response = await test_app_client.patch(
 | 
						|
            "/d35d213e-f3d8-4f08-954a-7e0d1bea286f",
 | 
						|
            headers={"Authorization": f"Bearer {user.id}"},
 | 
						|
        )
 | 
						|
        assert response.status_code == status.HTTP_403_FORBIDDEN
 | 
						|
 | 
						|
    async def test_not_existing_user(
 | 
						|
        self, test_app_client: httpx.AsyncClient, superuser: UserDB
 | 
						|
    ):
 | 
						|
        response = await test_app_client.patch(
 | 
						|
            "/d35d213e-f3d8-4f08-954a-7e0d1bea286f",
 | 
						|
            json={},
 | 
						|
            headers={"Authorization": f"Bearer {superuser.id}"},
 | 
						|
        )
 | 
						|
        assert response.status_code == status.HTTP_404_NOT_FOUND
 | 
						|
 | 
						|
    async def test_empty_body(
 | 
						|
        self, test_app_client: httpx.AsyncClient, user: UserDB, superuser: UserDB
 | 
						|
    ):
 | 
						|
        response = await test_app_client.patch(
 | 
						|
            f"/{user.id}", json={}, headers={"Authorization": f"Bearer {superuser.id}"}
 | 
						|
        )
 | 
						|
        assert response.status_code == status.HTTP_200_OK
 | 
						|
 | 
						|
        data = cast(Dict[str, Any], response.json())
 | 
						|
        assert data["email"] == user.email
 | 
						|
 | 
						|
    async def test_valid_body(
 | 
						|
        self, test_app_client: httpx.AsyncClient, user: UserDB, superuser: UserDB
 | 
						|
    ):
 | 
						|
        json = {"email": "king.arthur@tintagel.bt"}
 | 
						|
        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
 | 
						|
 | 
						|
        data = cast(Dict[str, Any], response.json())
 | 
						|
        assert data["email"] == "king.arthur@tintagel.bt"
 | 
						|
 | 
						|
    async def test_valid_body_is_superuser(
 | 
						|
        self, test_app_client: httpx.AsyncClient, user: UserDB, superuser: UserDB
 | 
						|
    ):
 | 
						|
        json = {"is_superuser": True}
 | 
						|
        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
 | 
						|
 | 
						|
        data = cast(Dict[str, Any], response.json())
 | 
						|
        assert data["is_superuser"] is True
 | 
						|
 | 
						|
    async def test_valid_body_is_active(
 | 
						|
        self, test_app_client: httpx.AsyncClient, user: UserDB, superuser: UserDB
 | 
						|
    ):
 | 
						|
        json = {"is_active": False}
 | 
						|
        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
 | 
						|
 | 
						|
        data = cast(Dict[str, Any], response.json())
 | 
						|
        assert data["is_active"] is False
 | 
						|
 | 
						|
    async def test_valid_body_password(
 | 
						|
        self,
 | 
						|
        mocker,
 | 
						|
        mock_user_db,
 | 
						|
        test_app_client: httpx.AsyncClient,
 | 
						|
        user: UserDB,
 | 
						|
        superuser: UserDB,
 | 
						|
    ):
 | 
						|
        mocker.spy(mock_user_db, "update")
 | 
						|
        current_hashed_passord = user.hashed_password
 | 
						|
 | 
						|
        json = {"password": "merlin"}
 | 
						|
        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
 | 
						|
        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 TestDeleteUser:
 | 
						|
    async def test_missing_token(self, test_app_client: httpx.AsyncClient):
 | 
						|
        response = await test_app_client.delete("/d35d213e-f3d8-4f08-954a-7e0d1bea286f")
 | 
						|
        assert response.status_code == status.HTTP_401_UNAUTHORIZED
 | 
						|
 | 
						|
    async def test_regular_user(self, test_app_client: httpx.AsyncClient, user: UserDB):
 | 
						|
        response = await test_app_client.delete(
 | 
						|
            "/d35d213e-f3d8-4f08-954a-7e0d1bea286f",
 | 
						|
            headers={"Authorization": f"Bearer {user.id}"},
 | 
						|
        )
 | 
						|
        assert response.status_code == status.HTTP_403_FORBIDDEN
 | 
						|
 | 
						|
    async def test_not_existing_user(
 | 
						|
        self, test_app_client: httpx.AsyncClient, superuser: UserDB
 | 
						|
    ):
 | 
						|
        response = await test_app_client.delete(
 | 
						|
            "/d35d213e-f3d8-4f08-954a-7e0d1bea286f",
 | 
						|
            headers={"Authorization": f"Bearer {superuser.id}"},
 | 
						|
        )
 | 
						|
        assert response.status_code == status.HTTP_404_NOT_FOUND
 | 
						|
 | 
						|
    async def test_superuser(
 | 
						|
        self,
 | 
						|
        mocker,
 | 
						|
        mock_user_db,
 | 
						|
        test_app_client: httpx.AsyncClient,
 | 
						|
        user: UserDB,
 | 
						|
        superuser: UserDB,
 | 
						|
    ):
 | 
						|
        mocker.spy(mock_user_db, "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
 | 
						|
        assert response.json() is None
 | 
						|
        assert mock_user_db.delete.called is True
 | 
						|
 | 
						|
        deleted_user = mock_user_db.delete.call_args[0][0]
 | 
						|
        assert deleted_user.id == user.id
 |