fastapi-azure-auth icon indicating copy to clipboard operation
fastapi-azure-auth copied to clipboard

[Docs] Recipe to mock a logged-in user/Active Directory roles for testing

Open martin-greentrax opened this issue 2 years ago • 2 comments

We are using this to keep the unit tests small and simple and I thought it may be helpful for others. Based on the username and the assigned ActiveDirectory role users have different permissions in the system. For a convenient way of testing this we can now mock any logged in user per-request:

Example test:

def test_readonly_user_may_not_write(client):
    with mockuser(user_name="[email protected]", roles=["foo.read"]):
        response = client.post("/somemodel", json={})
    assert response.status_code == 403

The setup:

in the routes:

def prepare_user(request: Request, db: Session = Depends(get_db)):
    """
    On each request the azure authenticated user is used for local user lookup.
    When not found it will be created. The db user object is then stored in `request.state` for easy access.
    """
    if not hasattr(request.state, "user"):
        raise HTTPException(403, detail="No user information found in request object.")
    email: EmailStr = request.state.user.claims["preferred_username"]  # logged in azure user
    request.state.user_obj = UserRepo.get_or_create(db, email)

router = APIRouter(dependencies=[Security(azure_scheme), Depends(prepare_user)])

conftest.py

class mockuser(ContextDecorator):
    """
    Mock any username and role(s) for api requests as if they would come directly from the real
    Azure Auth integration.
    """

    def __init__(self, user_name="[email protected]", roles=["Bar.readonly"]):
        self.user_name = user_name
        self.roles = roles

    def __enter__(self):
        def mock_prepare_user(request: fastapi.Request, db: Session = fastapi.Depends(get_db)):
            request.state.user = User(
                claims={"preferred_username": self.user_name}, roles=self.roles,
                aud="aud", tid="tid", access_token="123"
            )
            return prepare_user(request, db)

        fastapi_app.dependency_overrides[prepare_user] = mock_prepare_user
        fastapi_app_internal.dependency_overrides[prepare_user] = mock_prepare_user

    def __exit__(self, type, value, traceback):
        del fastapi_app.dependency_overrides[prepare_user]
        del fastapi_app_internal.dependency_overrides[prepare_user]


class staffuser(mockuser):
    """Mock a staff user sending an api request."""
    def __init__(self):
        super().__init__(user_name="[email protected] roles=["yourcompany.superuser"])

martin-greentrax avatar May 25 '22 09:05 martin-greentrax

Hi! This is an awesome example - thank you so much. If we skip the DB part of it, it could definitely be added to the docs under Usage and FAQ (with a link to this issue for an extended example). Would you like to contribute with a PR? 😊

JonasKs avatar May 28 '22 08:05 JonasKs

I've also done a variation of this in @Intility/templates. I'll try to publish some docs during the summer if you don't beat me to it 😊

JonasKs avatar Jul 01 '22 12:07 JonasKs