Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ httpx = {extras = ["http2"], version = "==0.28.1"}
[dev-packages]
pylint = "==4.0.5"
pytest = "==9.0.2"
pytest-cov = "==7.0.0"
pytest-cov = "==7.1.0"
pytest-httpx = "==0.36.0"
typing-extensions = "4.15.0"
switcher-client = {file = ".", editable = true}
270 changes: 147 additions & 123 deletions Pipfile.lock

Large diffs are not rendered by default.

76 changes: 76 additions & 0 deletions tests/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import time

from typing import Optional
from pytest_httpx import HTTPXMock

from switcher_client.client import Client
from switcher_client.lib.globals.global_context import DEFAULT_ENVIRONMENT, ContextOptions

def given_context(*,
url='https://api.switcherapi.com',
api_key='[API_KEY]',
domain='Switcher API',
component='switcher-client-python',
environment=DEFAULT_ENVIRONMENT,
options=ContextOptions()):
Client.build_context(
url=url,
api_key=api_key,
domain=domain,
component=component,
environment=environment,
options=options
)

def given_auth(httpx_mock: HTTPXMock, status=200, token: Optional[str]='[token]', exp=int(round(time.time() * 1000)), is_reusable=False):
httpx_mock.add_response(
url='https://api.switcherapi.com/criteria/auth',
method='POST',
status_code=status,
json={'token': token, 'exp': exp},
is_reusable=is_reusable
)

def given_check_criteria(httpx_mock: HTTPXMock, status=200, key='MY_SWITCHER', response={}, show_details=False, match=None):
httpx_mock.add_response(
is_reusable=True,
url=f'https://api.switcherapi.com/criteria?showReason={str(show_details).lower()}&key={key}',
method='POST',
status_code=status,
json=response,
match_json=match
)

def given_check_switchers(httpx_mock: HTTPXMock, status=200, not_found: Optional[list[str]]=None):
httpx_mock.add_response(
url='https://api.switcherapi.com/criteria/switchers_check',
method='POST',
status_code=status,
json={'not_found': not_found or []}
)

def given_check_health(httpx_mock: HTTPXMock, status=200):
httpx_mock.add_response(
is_reusable=False,
url='https://api.switcherapi.com/check',
method='GET',
status_code=status,
)

def given_check_snapshot_version(httpx_mock: HTTPXMock, status_code=200, version=0, status=False, is_reusable=False):
httpx_mock.add_response(
url=f'https://api.switcherapi.com/criteria/snapshot_check/{version}',
method='GET',
status_code=status_code,
json={'status': status},
is_reusable=is_reusable
)

def given_resolve_snapshot(httpx_mock: HTTPXMock, status_code=200, data=[], is_reusable=False):
httpx_mock.add_response(
url='https://api.switcherapi.com/graphql',
method='POST',
status_code=status_code,
json={'data': { 'domain': data}},
is_reusable=is_reusable
)
32 changes: 1 addition & 31 deletions tests/test_client_check_switchers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import time

from typing import Optional
from pytest_httpx import HTTPXMock
from tests.helpers import given_context, given_auth, given_check_switchers

from switcher_client.client import Client
from switcher_client.errors import LocalSwitcherError, RemoteSwitcherError
Expand Down Expand Up @@ -100,30 +97,3 @@ def test_check_local_switchers_no_snapshot():
assert False, 'Expected LocalSwitcherError to be raised'
except LocalSwitcherError as e:
assert str(e) == 'FF2FOR2020, NON_EXISTENT_SWITCHER not found'

# Helpers

def given_context(url='https://api.switcherapi.com', api_key='[API_KEY]', options = ContextOptions()):
Client.build_context(
url=url,
api_key=api_key,
domain='Playground',
component='switcher-playground',
options=options
)

def given_auth(httpx_mock: HTTPXMock, status=200, token: Optional[str]='[token]', exp=int(round(time.time() * 1000))):
httpx_mock.add_response(
url='https://api.switcherapi.com/criteria/auth',
method='POST',
status_code=status,
json={'token': token, 'exp': exp}
)

def given_check_switchers(httpx_mock: HTTPXMock, status=200, not_found: Optional[list[str]]=None):
httpx_mock.add_response(
url='https://api.switcherapi.com/criteria/switchers_check',
method='POST',
status_code=status,
json={'not_found': not_found or []}
)
18 changes: 2 additions & 16 deletions tests/test_client_context.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import pytest
import time

from typing import Optional
from pytest_httpx import HTTPXMock

from switcher_client import Client, ContextOptions

Expand All @@ -24,15 +20,15 @@ def test_context_with_optionals():

def test_context_remote_validation():
""" Should raise error when missing required fields for remote """

Client.build_context(
domain='My Domain'
)

# test
with pytest.raises(ValueError) as excinfo:
Client.get_switcher().validate() # used by is_on()

assert 'Missing or empty required fields (url, component, api_key)' in str(excinfo.value)

def test_context_get_switcher_from_cache():
Expand All @@ -48,13 +44,3 @@ def test_context_get_switcher_from_cache():

assert switcher1 is switcher2
assert switcher1 is not switcher3

# Helpers

def given_auth(httpx_mock: HTTPXMock, status=200, token: Optional[str]='[token]', exp=int(round(time.time() * 1000))):
httpx_mock.add_response(
url='https://api.switcherapi.com/criteria/auth',
method='POST',
status_code=status,
json={'token': token, 'exp': exp}
)
78 changes: 18 additions & 60 deletions tests/test_client_load_snapshot_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
import pytest
import time

from typing import Optional
from pytest_httpx import HTTPXMock
from tests.helpers import given_context, given_auth, given_check_snapshot_version, given_resolve_snapshot

from switcher_client import Client
from switcher_client.errors import RemoteError
from switcher_client.lib.globals.global_context import DEFAULT_ENVIRONMENT, ContextOptions
from switcher_client.lib.globals.global_context import ContextOptions
from switcher_client.lib.globals.global_snapshot import LoadSnapshotOptions

context_options_snapshot = ContextOptions(snapshot_location='./tests/snapshots')
context_options_snapshot_temp = ContextOptions(snapshot_location='./tests/snapshots/temp')

def test_load_from_snapshot_in_memory(httpx_mock):
""" Should load in-memory Domain from snapshot remote """

Expand All @@ -34,7 +36,7 @@ def test_load_from_snapshot_no_update(httpx_mock):
# given
given_auth(httpx_mock)
given_check_snapshot_version(httpx_mock, version=1588557288040, status=True)
given_context(snapshot_location='tests/snapshots', environment='default_load_1')
given_context(options=context_options_snapshot, environment='default_load_1')

# test
version = Client.load_snapshot() # load from file
Expand All @@ -50,7 +52,7 @@ def test_check_snapshot_version_error(httpx_mock):
# given
given_auth(httpx_mock)
given_check_snapshot_version(httpx_mock, status_code=500, version=1588557288040)
given_context(snapshot_location='tests/snapshots', environment='default_load_1')
given_context(options=context_options_snapshot, environment='default_load_1')

Client.load_snapshot() # load from file

Expand All @@ -67,7 +69,7 @@ def test_resolve_snapshot_error(httpx_mock):
given_auth(httpx_mock)
given_check_snapshot_version(httpx_mock, version=1588557288040, status=False)
given_resolve_snapshot(httpx_mock, status_code=500)
given_context(snapshot_location='tests/snapshots', environment='default_load_1')
given_context(options=context_options_snapshot, environment='default_load_1')

Client.load_snapshot() # load from file

Expand All @@ -90,16 +92,17 @@ def test_auto_update_snapshot_from_context(httpx_mock):
given_resolve_snapshot(httpx_mock, data=load_snapshot_fixture('tests/snapshots/default_load_2.json'))

# given - context
options = ContextOptions(**vars(context_options_snapshot_temp))
options.snapshot_auto_update_interval = 1
given_context(
snapshot_location='tests/snapshots/temp',
environment='generated-auto-update',
snapshot_auto_update_interval=1
options=options,
environment='generated-auto-update'
)

# test
Client.load_snapshot(LoadSnapshotOptions(fetch_remote=True))
assert Client.snapshot_version() == 1588557288040

time.sleep(1.5) # wait for auto-update to trigger
assert Client.snapshot_version() == 1588557288041

Expand All @@ -121,10 +124,10 @@ def test_auto_update_snapshot(httpx_mock):

# given - context
given_context(
snapshot_location='tests/snapshots/temp',
options=context_options_snapshot_temp,
environment='generated-auto-update'
)

Client.load_snapshot(LoadSnapshotOptions(fetch_remote=True))

# test
Expand Down Expand Up @@ -157,10 +160,10 @@ def test_not_auto_update_snapshot_when_error(httpx_mock):

# given - context
given_context(
snapshot_location='tests/snapshots/temp',
options=context_options_snapshot_temp,
environment='generated-auto-update-error'
)

Client.load_snapshot(LoadSnapshotOptions(fetch_remote=True))

# test
Expand All @@ -182,55 +185,10 @@ def test_not_auto_update_snapshot_when_error(httpx_mock):

# Helpers

def given_auth(httpx_mock: HTTPXMock, status=200, token: Optional[str]='[token]', exp=int(round(time.time() * 1000)), is_reusable=False):
httpx_mock.add_response(
url='https://api.switcherapi.com/criteria/auth',
method='POST',
status_code=status,
json={'token': token, 'exp': exp},
is_reusable=is_reusable
)

def given_check_snapshot_version(httpx_mock: HTTPXMock, status_code=200, version=0, status=False, is_reusable=False):
httpx_mock.add_response(
url=f'https://api.switcherapi.com/criteria/snapshot_check/{version}',
method='GET',
status_code=status_code,
json={'status': status},
is_reusable=is_reusable
)

def given_resolve_snapshot(httpx_mock: HTTPXMock, status_code=200, data=[], is_reusable=False):
httpx_mock.add_response(
url='https://api.switcherapi.com/graphql',
method='POST',
status_code=status_code,
json={'data': { 'domain': data}},
is_reusable=is_reusable
)

def given_context(url='https://api.switcherapi.com',
api_key='[API_KEY]',
environment=DEFAULT_ENVIRONMENT,
snapshot_location=None,
snapshot_auto_update_interval=None):
Client.build_context(
url=url,
api_key=api_key,
domain='Switcher API',
component='switcher-client-python',
environment=environment,
options=ContextOptions(
local=True,
snapshot_location=snapshot_location,
snapshot_auto_update_interval=snapshot_auto_update_interval
)
)

def load_snapshot_fixture(file_path: str):
with open(file_path, 'r') as f:
return json.loads(f.read()).get('domain', {})

def delete_snapshot_file(snapshot_location: str, environment: str):
snapshot_file = f"{snapshot_location}/{environment}.json"
if os.path.exists(snapshot_file):
Expand Down
19 changes: 5 additions & 14 deletions tests/test_client_watch_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
import shutil
import time

from typing import Optional
from tests.helpers import given_context

from switcher_client.client import Client, ContextOptions
from switcher_client.lib.globals.global_context import DEFAULT_ENVIRONMENT
from switcher_client.lib.snapshot_watcher import SnapshotWatcher, WatchSnapshotCallback

context_options_local = ContextOptions(local=True, snapshot_location='tests/snapshots/temp')

class TestClientWatchSnapshot:
""" Test suite for Client.watch_snapshot """

Expand All @@ -33,7 +34,7 @@ def test_watch_snapshot(self):

# given
modify_fixture_snapshot(fixture_location, fixture_env, f'tests/snapshots/{fixture_env}.json')
given_context(snapshot_location=fixture_location, environment=fixture_env)
given_context(options=context_options_local, environment=fixture_env)
Client.load_snapshot()

# test
Expand Down Expand Up @@ -79,7 +80,7 @@ def test_watch_snapshot_err_malformed_snapshot(self):

# given
modify_fixture_snapshot(fixture_location, fixture_env, f'tests/snapshots/{fixture_env}.json')
given_context(snapshot_location=fixture_location, environment=fixture_env)
given_context(options=context_options_local, environment=fixture_env)
Client.load_snapshot()

# test
Expand All @@ -99,16 +100,6 @@ def test_watch_snapshot_err_malformed_snapshot(self):

# Helpers

def given_context(environment: str = DEFAULT_ENVIRONMENT, snapshot_location: Optional[str] = None):
Client.build_context(
domain='Playground',
environment=environment,
options=ContextOptions(
local=True,
snapshot_location=snapshot_location
)
)

def modify_fixture_snapshot(location: str, environment: str, fixture_modified_location: str):
with open(fixture_modified_location, 'r') as file:
data = file.read()
Expand Down
Loading
Loading