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
6 changes: 3 additions & 3 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
fetch-depth: 0

- name: Set up Python 3.14
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: "3.14"

Expand All @@ -34,7 +34,7 @@ jobs:
run: pipenv run pytest -v --cov=./switcher_client --cov-report xml --cov-config=.coveragerc

- name: SonarCloud Scan
uses: sonarsource/sonarqube-scan-action@v8.2.0
uses: sonarsource/sonarqube-scan-action@v8.2.1
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
if: env.SONAR_TOKEN != ''
Expand All @@ -53,7 +53,7 @@ jobs:
uses: actions/checkout@v7

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: "3.14"
- name: Install pypa/build
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@v7

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}

Expand All @@ -39,7 +39,7 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: "3.14"
- name: Install pypa/build
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/sonar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
fetch-depth: 0

- name: Set up Python 3.14
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: "3.14"

Expand All @@ -47,7 +47,7 @@ jobs:
run: pipenv run pytest -v --cov=./switcher_client --cov-report xml

- name: SonarCloud Scan
uses: sonarsource/sonarqube-scan-action@v8.2.0
uses: sonarsource/sonarqube-scan-action@v8.2.1
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
if: env.SONAR_TOKEN != ''
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: ${{ github.event.inputs.python }}

Expand Down
323 changes: 323 additions & 0 deletions ARCHITECTURE.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ typing-extensions = {version = "==4.16.0", markers = "python_version <= '3.12'"}
[dev-packages]
exceptiongroup = "*"
tomli = "*"
pylint = "==4.0.6"
pylint = "==4.0.7"
pytest = "==9.1.1"
pytest-cov = "==7.1.0"
pytest-httpx = "==0.36.2"
python-dotenv = "==1.2.2"
python-dotenv = "==1.2.3"
switcher-client = {file = ".", editable = true}
291 changes: 161 additions & 130 deletions Pipfile.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions tests/remote/test_remote_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def fake_auth(_):
assert mock_auth.call_count == 1
mock_schedule.assert_not_called()

def test_stop_auto_renew_ignores_stale_generation():
def test_stop_auto_renew_ignores_stale_generation(monkeypatch):
""" Should not cancel the active renewer when stop is requested with a stale generation """

# given
Expand All @@ -71,7 +71,7 @@ def test_stop_auto_renew_ignores_stale_generation():
))
active_timer = Mock()
current_generation = _current_generation()
RemoteAuth._RemoteAuth__auto_renew_timer = active_timer # type: ignore
monkeypatch.setattr(RemoteAuth, '_RemoteAuth__auto_renew_timer', active_timer)

# test
RemoteAuth._stop_auto_renew(generation=_stale_generation())
Expand Down
6 changes: 3 additions & 3 deletions tests/test_client_check_switchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def test_check_remote_switchers_not_found(httpx_mock):
# test
try:
Client.check_switchers(['MY_SWITCHER', 'ANOTHER_SWITCHER'])
assert False, 'Expected RemoteSwitcherError to be raised'
raise AssertionError('Expected RemoteSwitcherError to be raised')
except RemoteSwitcherError as e:
assert str(e) == 'MY_SWITCHER not found'

Expand All @@ -41,7 +41,7 @@ def test_check_remote_switchers_api_error(httpx_mock):
# test
try:
Client.check_switchers(['MY_SWITCHER', 'ANOTHER_SWITCHER'])
assert False, 'Expected RemoteError to be raised'
raise AssertionError('Expected RemoteError to be raised')
except Exception as e:
assert str(e) == '[check_switchers] failed with status: 500'

Expand All @@ -55,7 +55,7 @@ def test_chek_remote_switchers_auth_error(httpx_mock):
# test
try:
Client.check_switchers(['MY_SWITCHER', 'ANOTHER_SWITCHER'])
assert False, 'Expected RemoteError to be raised'
raise AssertionError('Expected RemoteError to be raised')
except Exception as e:
assert str(e) == 'Invalid API key'

Expand Down
4 changes: 3 additions & 1 deletion tests/test_client_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ def test_context_remote_validation():
domain='My Domain'
)

switcher = Client.get_switcher()

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

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

Expand Down
17 changes: 11 additions & 6 deletions tests/test_switcher_integration.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Please, in order to run these tests, include a .env file in the root of the project with the following content:
# SWITCHER_URL=https://api.switcherapi.com
# SWITCHER_API_KEY=<your_api_key>

import os
from typing import Optional

Expand All @@ -8,13 +12,14 @@
from switcher_client.lib.globals.global_snapshot import LoadSnapshotOptions

load_dotenv()
API_KEY = os.getenv('SWITCHER_API_KEY')
SWITCHER_API_KEY = os.getenv('SWITCHER_API_KEY')
SWITCHER_URL = os.getenv('SWITCHER_URL')
WARN_SKIP = 'API key not found. Please set the SWITCHER_API_KEY environment variable.'

def test_is_on():
""" Should call the remote API with success """

if not API_KEY:
if not SWITCHER_API_KEY:
print(WARN_SKIP)
return

Expand All @@ -29,7 +34,7 @@ def test_is_on():
def test_load_snapshot():
""" Should load the snapshot from the remote API with success """

if not API_KEY:
if not SWITCHER_API_KEY:
print(WARN_SKIP)
return

Expand All @@ -47,7 +52,7 @@ def test_load_snapshot():
def test_check_switcher_availability():
""" Should check the switcher availability with success """

if not API_KEY:
if not SWITCHER_API_KEY:
print(WARN_SKIP)
return

Expand All @@ -64,8 +69,8 @@ def test_check_switcher_availability():

def given_context(options: Optional[ContextOptions] = None):
Client.build_context(
url='https://api.switcherapi.com',
api_key=API_KEY,
url=SWITCHER_URL,
api_key=SWITCHER_API_KEY,
domain='Switcher API',
component='switcher-client-python',
options=options
Expand Down
32 changes: 17 additions & 15 deletions tests/test_switcher_remote.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
import sys
import time
import threading
import httpx
Expand Down Expand Up @@ -206,12 +207,12 @@ def test_remote_with_remote_required_request(httpx_mock):
# test
assert switcher.remote().is_on(key)

def test_remote_with_custom_cert(httpx_mock):
def test_remote_with_custom_cert(httpx_mock, monkeypatch):
""" Should call the remote API with success using a custom certificate """

# Reset Remote client to ensure fresh SSL context creation
from switcher_client.lib.remote import Remote
Remote._client = None
monkeypatch.setattr(Remote, '_client', None)

# given
given_auth(httpx_mock)
Expand Down Expand Up @@ -240,7 +241,7 @@ def test_remote_err_with_remote_reqquired_request_no_local():

# test
with pytest.raises(ValueError) as excinfo:
switcher.remote().is_on('MY_SWITCHER')
switcher.remote()

assert 'Local mode is not enabled' in str(excinfo.value)

Expand Down Expand Up @@ -306,16 +307,17 @@ def test_remote_err_check_criteria(httpx_mock):

assert '[check_criteria] failed with status: 500' in str(excinfo.value)

def test_remote_err_check_criteria_default_result(httpx_mock):
def test_remote_err_check_criteria_default_result(httpx_mock, monkeypatch):
""" Should return the default result when the check criteria fails """

# given
given_auth(httpx_mock)
given_check_criteria(httpx_mock, show_details=True, status=500)
given_context()

globals().update(async_error=None)
Client.subscribe_notify_error(lambda error: globals().update(async_error=str(error)))
this_module = sys.modules[__name__]
monkeypatch.setattr(this_module, 'async_error', None)
Client.subscribe_notify_error(lambda error: monkeypatch.setattr(this_module, 'async_error', str(error)))
switcher = Client.get_switcher()

# test
Expand Down Expand Up @@ -350,12 +352,12 @@ def test_remote_health_check_unavailable(httpx_mock):
# test
assert not Remote.check_api_health(Client._context)

def test_remote_client_rebuilds_when_timeout_changes(httpx_mock):
def test_remote_client_rebuilds_when_timeout_changes(httpx_mock, monkeypatch):
""" Should rebuild the shared remote client when timeout options change """

# given
Remote._client = None
Remote._client_config = None
monkeypatch.setattr(Remote, '_client', None)
monkeypatch.setattr(Remote, '_client_config', None)
given_auth(httpx_mock)
given_check_criteria(httpx_mock, response={'result': True}, match={'entry': []},
match_extensions={
Expand Down Expand Up @@ -401,12 +403,12 @@ def test_remote_client_rebuilds_when_timeout_changes(httpx_mock):
# test
assert Client.get_switcher().is_on('MY_SWITCHER')

def test_remote_client_uses_default_connection_limits():
def test_remote_client_uses_default_connection_limits(monkeypatch):
""" Should build the shared remote client using the default connection limit options """

# given
Remote._client = None
Remote._client_config = None
monkeypatch.setattr(Remote, '_client', None)
monkeypatch.setattr(Remote, '_client_config', None)
given_context()

# test
Expand All @@ -416,12 +418,12 @@ def test_remote_client_uses_default_connection_limits():
assert pool._max_connections == 100
assert pool._keepalive_expiry == 30.0

def test_remote_client_rebuilds_when_connection_limits_change():
def test_remote_client_rebuilds_when_connection_limits_change(monkeypatch):
""" Should rebuild the shared remote client when connection limit options change """

# given
Remote._client = None
Remote._client_config = None
monkeypatch.setattr(Remote, '_client', None)
monkeypatch.setattr(Remote, '_client_config', None)
given_context(options=ContextOptions(
remote=RemoteOptions(
max_keepalive_connections=5,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_switcher_stub_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def flaky_assume(key: str):
)
def decorated_test():
""" This test should never run because setup fails before execution. """
assert False
pytest.fail('The decorated test should not run when setup fails.')

with pytest.raises(RuntimeError, match='setup failed'):
decorated_test()
Expand Down