Skip to content

rs_server_common/utils/pytest/pytest_utils.md

<< Back to index

Utility functions used by the pytest unit tests.

mock_oauth2(mocker, client, endpoint, user_id, username, iam_roles, enabled=True, assert_success=True) async

Only for unit tests: mock the OAuth2 authorization code flow process.

Parameters:

Name Type Description Default
mocker

pytest mocker

required
client TestClient

pytest client

required
endpoint str

endpoint to test

required
user_id str

user id in keycloak

required
username str

username in keycloak

required
iam_roles list[str]

user iam roles in keycloak

required
enabled bool

is the user enabled in keycloak ?

True
assert_success bool

is the login process expected to success ?

True
Source code in docs/rs-server/services/common/rs_server_common/utils/pytest/pytest_utils.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
async def mock_oauth2(  # pylint: disable=too-many-arguments
    mocker,
    client: TestClient,
    endpoint: str,
    user_id: str,
    username: str,
    iam_roles: list[str],
    enabled: bool = True,
    assert_success: bool = True,
) -> httpx.Response:
    """
    Only for unit tests: mock the OAuth2 authorization code flow process.

    Args:
        mocker: pytest mocker
        client: pytest client
        endpoint: endpoint to test
        user_id: user id in keycloak
        username: username in keycloak
        iam_roles: user iam roles in keycloak
        enabled: is the user enabled in keycloak ?
        assert_success: is the login process expected to success ?
    """

    # Clear the cookies, except for the logout endpoint which does it itself
    logout = endpoint.endswith("/logout")
    if logout:
        assert "session" in dict(client.cookies)  # nosec
    else:
        client.cookies.clear()

    # If we are not loging from the console, we simulate the fact that our request comes from a browser
    login_from_console = endpoint.endswith(oauth2.LOGIN_FROM_CONSOLE)
    headers = {"user-agent": "Mozilla/"}

    # The 1st step of the oauth2 authorization code flow returns a redirection to the keycloak login page.
    # After login, it returns a redirection to the original calling endpoint, but this time
    # with a 'code' and 'state' params.
    # Here we do not test the keycloak login page, we only mock the last redirection.
    mocker.patch.object(
        StarletteOAuth2App,
        "authorize_redirect",
        return_value=RedirectResponse(f"{endpoint}?code=my_code&state=my_state", status_code=302),
    )

    # The 2nd step checks the 'code' and 'state' params then returns a dict which contains the user information
    mocker.patch.object(
        StarletteOAuth2App,
        "authorize_access_token",
        return_value={"userinfo": {"sub": user_id, "preferred_username": username}},
    )

    # Then the service will ask for user information in KeyCloak
    mocker.patch.object(KeycloakAdmin, "get_user", return_value={"enabled": enabled})
    mocker.patch.object(
        KeycloakAdmin,
        "get_composite_realm_roles_of_user",
        return_value=[{"name": role} for role in iam_roles],
    )

    # We need the client to follow redirections.
    # Note: even with this, the "login from browser" fails with a 400, I don't know why.
    # Use the "login from console instead".
    old_follow_redirects = client.follow_redirects
    try:
        client.follow_redirects = True

        # Call the endpoint that will run the oauth2 authentication process
        response = client.get(endpoint, headers=headers)

        # From the console, the redirection after the 1st step must be done manually
        if login_from_console:
            assert response.is_success  # nosec
            response = client.get(response.json())

    # Restore the redirections
    finally:
        client.follow_redirects = old_follow_redirects

    if assert_success:
        assert response.is_success, f"{endpoint} => {response}"  # nosec

    # After this, if successful, we should have a cookie with the authentication information.
    # Except for the logout endpoint which should have removed the cookie.
    has_cookie = response.is_success and not logout
    if client.cookies:
        assert ("session" in dict(client.cookies)) == has_cookie  # nosec

    return response