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
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,20 +310,30 @@ Client.watch_snapshot(WatchSnapshotCallback(

## Testing & Development

### Built-in Mocking (Coming Soon)

> 🚧 **Note**: The mocking features are currently under development
### Built-in Mocking

The SDK will include powerful mocking capabilities for testing:

```python
# 🚧 TODO: Mock feature states for testing
# Mock feature states for testing
Client.assume('FEATURE01').true()
assert switcher.is_on('FEATURE01') == True

Client.forget('FEATURE01') # Reset to normal behavior
# Conditional mocking based on input criteria
Client.assume('FEATURE01').true() \
# value can be either 'guest' or 'admin'
.when(StrategiesType.VALUE.value, ['guest', 'admin']) \
.when(StrategiesType.NETWORK.value, '10.0.0.3')

assert switcher \
.check_value('guest') \
.check_network('10.0.0.3') \
.is_on('FEATURE01') == True

# Reset to normal behavior
Client.forget('FEATURE01')

# 🚧 TODO: Mock with metadata
# Mock with metadata
Client.assume('FEATURE01').false().with_metadata({
'message': 'Feature is disabled'
})
Expand Down
2 changes: 2 additions & 0 deletions switcher_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from switcher_client.switcher import Switcher
from switcher_client.lib.globals.global_context import ContextOptions
from switcher_client.lib.snapshot_watcher import WatchSnapshotCallback
from switcher_client.lib.snapshot import StrategiesType

__all__ = [
'Client',
'Switcher',
'ContextOptions',
'WatchSnapshotCallback',
'StrategiesType'
]
49 changes: 44 additions & 5 deletions switcher_client/lib/bypasser/key.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,59 @@
from switcher_client.lib.compat import Self
from switcher_client.lib.snapshot import StrategiesType
from switcher_client.lib.types import ResultDetail

class Key:
""" Key record used to store key response when bypassing criteria execution """

def __init__(self, key: str):
self._key = key
self._result = None
self._result = False
self._reason = None
self._metadata: dict = {}
self._when: dict[str, list[str]] = {}

def true(self):
def true(self) -> Self:
""" Force a switcher value to return true """
self._result = True
self._reason = "Forced to True"
return self

def get_response(self, input_list: list[str]) -> ResultDetail:
return ResultDetail.create(result=True, reason=f"Forced to '{self._result}' - input: {input_list}")
def false(self) -> Self:
""" Force a switcher value to return false """
self._result = False
self._reason = "Forced to False"
return self

def with_metadata(self, metadata: dict) -> Self:
""" Define metadata for the response """
self._metadata = metadata
return self

def when(self, strategy: str, input_strategy: str | list[str]) -> Self:
""" Conditionally set result based on strategy """
if any(s.value == strategy for s in StrategiesType):
self._when[strategy] = input_strategy if isinstance(input_strategy, list) else [input_strategy]
return self

def get_response(self, input_list: list[str] | None) -> ResultDetail:
""" Return key response """
result = self._result
if self._when and input_list is not None:
result = self._get_result_based_on_when(input_list)

return ResultDetail.create(result=result, reason=self._reason, metadata=self._metadata)

@property
def key(self):
""" Get the key of the switcher """
""" Return selected switcher name """
return self._key

def _get_result_based_on_when(self, input_list: list[str]) -> bool:
""" Evaluate the when conditions to determine the result """
for strategy_when, input_when in self._when.items():
entry = [e for e in input_list if e[0] == strategy_when]
if entry and entry[0][1] not in input_when:
self._reason = f"Forced to {not self._result} when: [{', '.join(input_when)}] - input: {entry[0][1]}"
return not self._result

return self._result
5 changes: 5 additions & 0 deletions switcher_client/switcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ def is_on_with_details(self, key: Optional[str] = None) -> ResultDetail:
""" Execute criteria with details """
self._validate_args(key, details=True)

# verify if query from Bypasser
bypass_key = Bypasser.search_key(self._key)
if bypass_key is not None:
return bypass_key.get_response(self._input)

# try get cached result
cached_result = self._try_cached_result()
if cached_result is not None:
Expand Down
81 changes: 80 additions & 1 deletion tests/test_switcher_stub.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from tests.test_switcher_integration import given_context

from switcher_client.lib.snapshot import StrategiesType
from switcher_client.client import Client, ContextOptions

context_options_local = ContextOptions(snapshot_location='tests/snapshots', local=True, logger=True)
Expand All @@ -24,5 +25,83 @@ def test_switcher_stub_result(self):

# test
Client.assume(self.key).true()
assert switcher.is_on()

assert switcher.is_on()
Client.assume(self.key).false()
assert not switcher.is_on()

def test_switcher_stub_result_details(self):
""" Should bypass Switcher evaluation and return the stubbed result with details """

# given
switcher = Client.get_switcher(self.key)

# test
Client.assume(self.key).true()
result_detail = switcher.is_on_with_details(self.key)
assert result_detail.result
assert result_detail.reason == "Forced to True"

def test_switcher_stub_result_with_metadata(self):
""" Should bypass Switcher evaluation and return the stubbed result with details and metadata """

# given
switcher = Client.get_switcher(self.key)

# test
Client.assume(self.key).true().with_metadata({"env": "test", "version": "1.0.0"})

result_detail = switcher.is_on_with_details(self.key)
assert result_detail.result
assert result_detail.reason == "Forced to True"
assert result_detail.metadata == {"env": "test", "version": "1.0.0"}

def test_switcher_stub_with_criteria(self):
""" Should bypass Switcher evaluation based on criteria conditions """

# given
switcher = Client.get_switcher(self.key)

# test
Client.assume(self.key).true() \
.when(StrategiesType.VALUE.value, "Canada") \
.when(StrategiesType.NETWORK.value, "10.0.0.3")

result_detail = switcher \
.check_value("Canada") \
.check_network('10.0.0.3') \
.is_on_with_details()

assert result_detail.result
assert result_detail.reason == "Forced to True"

def test_switcher_stub_with_unrecheable_criteria(self):
""" Should bypass Switcher evaluation based on unrecheable criteria conditions """

# given
switcher = Client.get_switcher(self.key)

# test
Client.assume(self.key).true() \
.when(StrategiesType.VALUE.value, "Canada")

result_detail = switcher \
.check_value("Brazil") \
.is_on_with_details()

assert not result_detail.result
assert result_detail.reason == "Forced to False when: [Canada] - input: Brazil"

def test_switcher_stub_with_multiple_criteria(self):
""" Should bypass Switcher evaluation based on multiple criteria conditions """

# given
switcher = Client.get_switcher(self.key)

# test
Client.assume(self.key).true() \
.when(StrategiesType.VALUE.value, ["Canada", "Brazil"])

assert switcher.check_value("Canada").is_on()
assert switcher.check_value("Brazil").is_on()
assert not switcher.check_value("USA").is_on()