From e85b102653ce99605bb41cae11a70676595293bf Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Sun, 27 Jul 2025 17:32:10 -0700 Subject: [PATCH] Added switcher.prepare() for remote prep/check --- switcher_client/lib/remote_auth.py | 17 +++++++- switcher_client/switcher.py | 24 ++++++++--- tests/test_switcher_remote.py | 69 ++++++++++++++++++++++++++++-- 3 files changed, 100 insertions(+), 10 deletions(-) diff --git a/switcher_client/lib/remote_auth.py b/switcher_client/lib/remote_auth.py index c993543..67da629 100644 --- a/switcher_client/lib/remote_auth.py +++ b/switcher_client/lib/remote_auth.py @@ -1,3 +1,5 @@ +from time import time + from switcher_client.lib.remote import Remote from switcher_client.lib.globals.global_context import Context from switcher_client.lib.globals import GlobalAuth @@ -14,4 +16,17 @@ def init(context: Context): def auth(): token, exp = Remote.auth(RemoteAuth.__context) GlobalAuth.set_token(token) - GlobalAuth.set_exp(exp) \ No newline at end of file + GlobalAuth.set_exp(exp) + + @staticmethod + def check_health(): + if GlobalAuth.get_token() != 'SILENT': + return + + @staticmethod + def is_token_expired() -> bool: + exp = GlobalAuth.get_exp() + if exp is None: + return True + + return float(exp) < time() \ No newline at end of file diff --git a/switcher_client/switcher.py b/switcher_client/switcher.py index ea3d9f2..98eb34e 100644 --- a/switcher_client/switcher.py +++ b/switcher_client/switcher.py @@ -12,22 +12,25 @@ def __init__(self, context: Context, key: Optional[str] = None): super().__init__(key) self._context = context + def prepare(self, key: Optional[str] = None): + """ Checks API credentials and connectivity """ + self.__validate_args(key) + RemoteAuth.auth() + def is_on(self, key: Optional[str] = None) -> bool: """ Execute criteria """ self._show_details = False - self.__validate_args(key) + self.__validate_args(key, details=False) return self.__submit().result def is_on_with_details(self, key: Optional[str] = None) -> ResultDetail: """ Execute criteria with details """ - self._show_details = True - self.__validate_args(key) + self.__validate_args(key, details=True) return self.__submit() def __submit(self) -> ResultDetail: """ Submit criteria for execution (local or remote) """ self.__validate() - self.__execute_api_checks() response = self.__execute_remote_criteria() return response @@ -39,18 +42,27 @@ def __validate(self) -> 'Switcher': if not self._key: errors.append('Missing key field') + self.__execute_api_checks() + if not GlobalAuth.get_token(): + errors.append('Missing token field') + if errors: raise ValueError(f"Something went wrong: {', '.join(errors)}") return self - def __validate_args(self, key: Optional[str] = None): + def __validate_args(self, key: Optional[str] = None, details: Optional[bool] = None): if key is not None: self._key = key + if details is not None: + self._show_details = details + def __execute_api_checks(self): """ Assure API is available and token is valid """ - RemoteAuth.auth() + RemoteAuth.check_health() + if RemoteAuth.is_token_expired(): + self.prepare(self._key) def __execute_remote_criteria(self): """ Execute remote criteria """ diff --git a/tests/test_switcher_remote.py b/tests/test_switcher_remote.py index 9031fd0..e5da839 100644 --- a/tests/test_switcher_remote.py +++ b/tests/test_switcher_remote.py @@ -1,9 +1,12 @@ +from typing import Optional + import pytest import responses import time from switcher_client.errors import RemoteAuthError -from switcher_client import Client, ContextOptions +from switcher_client import Client +from switcher_client.lib.globals.global_auth import GlobalAuth @responses.activate def test_remote(): @@ -41,6 +44,28 @@ def test_remote_with_input(): assert switcher \ .check_value('user_id') \ .is_on('MY_SWITCHER') + +@responses.activate +def test_remote_with_prepare(): + """ Should prepare call the remote API with success """ + + # given + given_auth() + given_check_criteria(response={'result': True}, match=[ + responses.matchers.json_params_matcher({ + 'entry': [{ + 'strategy': 'VALUE_VALIDATION', + 'input': 'user_id' + }] + }) + ]) + given_context() + + switcher = Client.get_switcher() + + # test + switcher.check_value('user_id').prepare('MY_SWITCHER') + assert switcher.is_on() @responses.activate def test_remote_with_details(): @@ -66,18 +91,56 @@ def test_remote_with_details(): assert response.result is True assert response.metadata == {'key': 'value'} +@responses.activate +def test_remote_renew_token(): + """ Should renew the token when it is expired """ + + # given + given_auth(status=200, token='[expired_token]', exp=int(round(time.time())) - 3600) + given_auth(status=200, token='[new_token]', exp=int(round(time.time())) + 3600) + given_check_criteria(response={'result': True}) + given_context() + + switcher = Client.get_switcher() + + # test + switcher.is_on('MY_SWITCHER') + assert GlobalAuth.get_token() == '[expired_token]' + switcher.is_on('MY_SWITCHER') + assert GlobalAuth.get_token() == '[new_token]' + +@responses.activate def test_remote_err_no_key(): """ Should raise an exception when no key is provided """ # given + given_auth() given_context() switcher = Client.get_switcher() # test - with pytest.raises(ValueError): + with pytest.raises(ValueError) as excinfo: switcher.is_on() + assert 'Missing key field' in str(excinfo.value) + +@responses.activate +def test_remote_err_no_token(): + """ Should raise an exception when no token is provided """ + + # given + given_auth(status=200, token=None) + given_context() + + switcher = Client.get_switcher() + + # test + with pytest.raises(ValueError) as excinfo: + switcher.is_on('MY_SWITCHER') + + assert 'Missing token field' in str(excinfo.value) + @responses.activate def test_remote_err_invalid_api_key(): """ Should raise an exception when the API key is invalid """ @@ -121,7 +184,7 @@ def given_context(url='https://api.switcherapi.com', api_key='[API_KEY]'): component='switcher-playground' ) -def given_auth(status=200, token='[token]', exp=int(round(time.time() * 1000))): +def given_auth(status=200, token: Optional[str]='[token]', exp=int(round(time.time() * 1000))): responses.add( responses.POST, 'https://api.switcherapi.com/criteria/auth',