Search code examples
pythonpytestfastapifixtures

How can I skip authentication in a single test with a fixture in Fast API together with pytest?


I have built authentication similar to what is described in the documentation. So I have this dependency copied from there:

async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_data = TokenData(username=username)
    except JWTError:
        raise credentials_exception
    user = get_user(fake_users_db, username=token_data.username)
    if user is None:
        raise credentials_exception
    return user

Which I use in a set of endpoints, for example, in the other example of the documentation, for User I have GET, POST, PUT, DELETE and GET ALL.

The only method which does not require authentication is the POST method to create a new user.

I want to be able to define unit tests that verify the method can not be accessed without authentication and also I want to skip the authentication completely when I'm focusing on the content of the method.

Therefore I used the override functionality in a fixture. For example for this test:

test_user.py

def test_create_user(test_db, create_user, user, skip_authentication):
    """
    Verify a user can be created and retrieved
    """
    response = client.post(
        "/api/v1/users/",
        json=create_user,
    )

    # Assert creation
    assert response.status_code == 200, response.text
    data = response.json()
    assert "id" in data
    user_id = data["id"]
    del data["id"]
    assert data == user

    # Assert get user
    response = client.get(f"/api/v1/users/{user_id}")
    assert response.status_code == 200, response.text
    data = response.json()
    assert user_id == data["id"]
    del data["id"]
    assert data == user

conftest.py

@pytest.fixture
def skip_authentication() -> None:

    def get_current_user():
        pass
    app.dependency_overrides[get_current_active_user] = get_current_user

And this seems to work to remove the authentication, but it removes it in all tests, not just in the ones with the fixture skip_authentication.

How can I limit it to only the tests I want?


Solution

  • Following the comment of @MatsLindh I was able to make it work. I'm not sure if is the ideal solution but works for me.

    I created two fixtures one for authenticated user and the other one for the other tests:

    conftest.py

    @pytest.fixture
    def client():
        """
        Return an API Client
        """
        app.dependency_overrides = {}
        return TestClient(app)
    
    @pytest.fixture
    def client_authenticated():
        """
        Returns an API client which skips the authentication
        """
        def skip_auth():
            pass
        app.dependency_overrides[get_current_active_user] = skip_auth
        return TestClient(app)
    

    Then I was able to test my normal tests and also verify the authentication with:

    def test_premissions_user(client, test_db, create_user):
        """
        Verify that not logged in users can not access the user functions excluding create
        """
        # Create user
        response = client.post(
            "/api/v1/users/",
            json=create_user
        )
        assert response.status_code == 200, response.text
    
        # Get all users
        response = client.get(
            "/api/v1/users/",
        )
        assert response.status_code == 401, response.text
        
        # Get user 1
        response = client.get(
            "/api/v1/users/1",
        )
        assert response.status_code == 401, response.text        
        
        # Delete user 1
        response = client.get(
            "/api/v1/users/1",
        )
        assert response.status_code == 401, response.text    
        
        # Modify user 1
        response = client.delete(
            "/api/v1/users/1",
        )
        assert response.status_code == 401, response.text
    
    def test_premissions_user_authenticated(client_authenticated, test_db, create_user):
        """
        Verify that not logged in users can not access the user functions excluding create
        """
        # Create user
        response = client_authenticated.post(
            "/api/v1/users/",
            json=create_user
        )
        assert response.status_code == 200, response.text
    
        # Get all users
        response = client_authenticated.get(
            "/api/v1/users/",
        )
        assert response.status_code == 200, response.text
        
        # Get user 1
        response = client_authenticated.get(
            "/api/v1/users/1",
        )
        assert response.status_code == 200, response.text        
        
        # Delete user 1
        response = client_authenticated.get(
            "/api/v1/users/1",
        )
        assert response.status_code == 200, response.text    
        
        # Modify user 1
        response = client_authenticated.delete(
            "/api/v1/users/1",
        )
        assert response.status_code == 204, response.text