import time from unittest.mock import AsyncMock, Mock import pytest from src.backend.environment_vars import AUTH0_SETTINGS from src.backend.security.auth0 import client @pytest.fixture(autouse=True) def mute_logger(mocker): mocker.patch.object(client, "logger", autospec=True) @pytest.fixture def mocker_httpx(mocker): def _(method: str): return mocker.patch.object( client.httpx.AsyncClient, method, return_value=AsyncMock(spec_set=client.httpx.Response), autospec=True, ) return _ @pytest.fixture(autouse=True) def mocker_httpx_get(mocker_httpx): yield mocker_httpx("get") @pytest.fixture(autouse=True) def mocker_httpx_post(mocker_httpx): yield mocker_httpx("post") @pytest.fixture def mocker_is_cached_token_valid(mocker, instance): def _(returns: bool): return mocker.patch.object( instance, "_is_cached_token_valid", return_value=returns ) return _ class TestAuth0: _class = client.Auth0 @pytest.fixture def instance(self): yield self._class(AUTH0_SETTINGS) class TestInstanceArgs: def test_instance_settings_as_dict(self): assert client.Auth0(AUTH0_SETTINGS) def test_instance_settings_as_dataclass(self): assert client.Auth0(client.Auth0Settings(**AUTH0_SETTINGS)) def test_instance_settings_as_invalid(self): # Test that an invalid settings type raises a ValueError with pytest.raises(ValueError): client.Auth0({"invalid": "settings"}) def test_settings(self, instance): assert isinstance(instance.settings, client.Auth0Settings) @pytest.mark.asyncio async def test_token(self, instance): value = Mock() instance._token = value assert instance.token is value @pytest.mark.asyncio async def test_expires_at(self, instance): value = Mock() instance._expires_at = value assert instance.expires_at is value test_time = int(time.time()) @pytest.mark.asyncio @pytest.mark.parametrize("is_cached_token_valid", [True, False]) async def test_get_token( self, instance, is_cached_token_valid, mocker_is_cached_token_valid ): cached_token = Mock(name="mock_cached_token") instance._token = cached_token mocker_is_cached_token_valid(is_cached_token_valid) returned_token = await instance.get_token() if is_cached_token_valid: assert returned_token is cached_token else: assert returned_token assert returned_token is not cached_token @pytest.mark.asyncio async def test_verify_bad_token_format(self, instance): with pytest.raises(client.HTTPException): instance.verify("badtoken.format.123") @pytest.mark.asyncio @pytest.mark.parametrize("exception_raised", [True, False]) async def test_verify_payload_decode(self, mocker, instance, exception_raised): mocker.patch.object(instance, "_jwks_client", autospec=True) if exception_raised: mocker.patch.object(client.jwt, "decode", side_effect=Exception) else: mocker.patch.object(client.jwt, "decode", return_value={"sub": "mock_sub"}) func = instance.verify # Prevent the cache from being used, otherwise there will be # interferences when running other tests in the same session. func.cache_clear() token = await instance.get_token() if exception_raised: with pytest.raises(client.HTTPException): instance.verify(token) else: assert instance.verify(token) func.cache_clear() @pytest.mark.asyncio async def test_api_dispatch(self, instance, mocker_httpx_get): result = await instance._api_dispatch("mock_endpoint") assert result == mocker_httpx_get.return_value.json.return_value @pytest.mark.asyncio @pytest.mark.parametrize("is_cached_token_valid", [True, False]) async def test_api_dispatch_get_fresh_token_if_cached_invalid( self, mocker, instance, is_cached_token_valid, mocker_is_cached_token_valid ): mocker_is_cached_token_valid = mocker_is_cached_token_valid( is_cached_token_valid ) mocker_get_token = mocker.patch.object(instance, "get_token", autospec=True) await instance._api_dispatch("mock_endpoint") assert mocker_is_cached_token_valid.called is True assert mocker_get_token.called is not is_cached_token_valid @pytest.mark.parametrize("token", [None, "", "123"]) @pytest.mark.parametrize( "expires_at", [None, test_time - 120, test_time, test_time + 60] ) def test_is_cached_token_valid(self, monkeypatch, instance, token, expires_at): monkeypatch.setattr(instance, "_token", token) monkeypatch.setattr(instance, "_expires_at", expires_at) result = instance.is_cached_token_valid assert result == bool( token and (expires_at or 0) > (self.test_time + instance._token_invalid_if_secs_left) )