python-sdk/tests/test_api.py

360 lines
9.4 KiB
Python

from unittest.mock import MagicMock
import pytest
from openfeature.api import (
add_handler,
add_hooks,
clear_hooks,
clear_providers,
get_client,
get_evaluation_context,
get_hooks,
get_provider_metadata,
remove_handler,
set_evaluation_context,
set_provider,
shutdown,
)
from openfeature.evaluation_context import EvaluationContext
from openfeature.event import EventDetails, ProviderEvent, ProviderEventDetails
from openfeature.exception import ErrorCode, GeneralError, ProviderFatalError
from openfeature.hook import Hook
from openfeature.provider import FeatureProvider, Metadata, ProviderStatus
from openfeature.provider.no_op_provider import NoOpProvider
def test_should_not_raise_exception_with_noop_client():
# Given
# No provider has been set
# When
client = get_client()
# Then
assert isinstance(client.provider, NoOpProvider)
def test_should_return_open_feature_client_when_configured_correctly():
# Given
set_provider(NoOpProvider())
# When
client = get_client()
# Then
assert isinstance(client.provider, NoOpProvider)
def test_should_try_set_provider_and_fail_if_none_provided():
# Given
# When
with pytest.raises(GeneralError) as ge:
set_provider(provider=None)
# Then
assert ge.value.error_message == "No provider"
assert ge.value.error_code == ErrorCode.GENERAL
def test_should_invoke_provider_initialize_function_on_newly_registered_provider():
# Given
evaluation_context = EvaluationContext("targeting_key", {"attr1": "val1"})
provider = MagicMock(spec=FeatureProvider)
# When
set_evaluation_context(evaluation_context)
set_provider(provider)
# Then
provider.initialize.assert_called_with(evaluation_context)
def test_should_invoke_provider_shutdown_function_once_provider_is_no_longer_in_use():
# Given
provider_1 = MagicMock(spec=FeatureProvider)
provider_2 = MagicMock(spec=FeatureProvider)
# When
set_provider(provider_1)
set_provider(provider_2)
# Then
assert provider_1.shutdown.called
def test_should_retrieve_metadata_for_configured_provider():
# Given
set_provider(NoOpProvider())
# When
metadata = get_provider_metadata()
# Then
assert isinstance(metadata, Metadata)
assert metadata.name == "No-op Provider"
def test_should_raise_an_exception_if_no_evaluation_context_set():
# Given
with pytest.raises(GeneralError) as ge:
set_evaluation_context(evaluation_context=None)
# Then
assert ge.value
assert ge.value.error_message == "No api level evaluation context"
assert ge.value.error_code == ErrorCode.GENERAL
def test_should_successfully_set_evaluation_context_for_api():
# Given
evaluation_context = EvaluationContext("targeting_key", {"attr1": "val1"})
# When
set_evaluation_context(evaluation_context)
global_evaluation_context = get_evaluation_context()
# Then
assert global_evaluation_context
assert global_evaluation_context.targeting_key == evaluation_context.targeting_key
assert global_evaluation_context.attributes == evaluation_context.attributes
def test_should_add_hooks_to_api_hooks():
# Given
hook_1 = MagicMock(spec=Hook)
hook_2 = MagicMock(spec=Hook)
clear_hooks()
# When
add_hooks([hook_1])
add_hooks([hook_2])
# Then
assert get_hooks() == [hook_1, hook_2]
def test_should_call_provider_shutdown_on_api_shutdown():
# Given
provider = MagicMock(spec=FeatureProvider)
set_provider(provider)
# When
shutdown()
# Then
assert provider.shutdown.called
def test_should_provide_a_function_to_bind_provider_through_domain():
# Given
provider = MagicMock(spec=FeatureProvider)
test_client = get_client("test")
default_client = get_client()
# When
set_provider(provider, domain="test")
# Then
assert default_client.provider != provider
assert default_client.domain is None
assert test_client.provider == provider
assert test_client.domain == "test"
def test_should_not_initialize_provider_already_bound_to_another_domain():
# Given
provider = MagicMock(spec=FeatureProvider)
set_provider(provider, "foo")
# When
set_provider(provider, "bar")
# Then
provider.initialize.assert_called_once()
def test_should_shutdown_unbound_provider():
# Given
provider = MagicMock(spec=FeatureProvider)
set_provider(provider, "foo")
# When
other_provider = MagicMock(spec=FeatureProvider)
set_provider(other_provider, "foo")
provider.shutdown.assert_called_once()
def test_should_not_shutdown_provider_bound_to_another_domain():
# Given
provider = MagicMock(spec=FeatureProvider)
set_provider(provider, "foo")
set_provider(provider, "bar")
# When
other_provider = MagicMock(spec=FeatureProvider)
set_provider(other_provider, "foo")
provider.shutdown.assert_not_called()
def test_shutdown_should_shutdown_every_registered_provider_once():
# Given
provider_1 = MagicMock(spec=FeatureProvider)
provider_2 = MagicMock(spec=FeatureProvider)
set_provider(provider_1)
set_provider(provider_1, "foo")
set_provider(provider_2, "bar")
set_provider(provider_2, "baz")
# When
shutdown()
# Then
provider_1.shutdown.assert_called_once()
provider_2.shutdown.assert_called_once()
def test_clear_providers_shutdowns_every_provider_and_resets_default_provider():
# Given
provider_1 = MagicMock(spec=FeatureProvider)
provider_2 = MagicMock(spec=FeatureProvider)
set_provider(provider_1)
set_provider(provider_2, "foo")
set_provider(provider_2, "bar")
# When
clear_providers()
# Then
provider_1.shutdown.assert_called_once()
provider_2.shutdown.assert_called_once()
assert isinstance(get_client().provider, NoOpProvider)
def test_provider_events():
# Given
spy = MagicMock()
provider = NoOpProvider()
set_provider(provider)
add_handler(ProviderEvent.PROVIDER_READY, spy.provider_ready)
add_handler(
ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, spy.provider_configuration_changed
)
add_handler(ProviderEvent.PROVIDER_ERROR, spy.provider_error)
add_handler(ProviderEvent.PROVIDER_STALE, spy.provider_stale)
provider_details = ProviderEventDetails(message="message")
details = EventDetails.from_provider_event_details(
provider.get_metadata().name, provider_details
)
# When
provider.emit_provider_configuration_changed(provider_details)
provider.emit_provider_error(provider_details)
provider.emit_provider_stale(provider_details)
# Then
# NOTE: provider_ready is called immediately after adding the handler
spy.provider_ready.assert_called_once()
spy.provider_configuration_changed.assert_called_once_with(details)
spy.provider_error.assert_called_once_with(details)
spy.provider_stale.assert_called_once_with(details)
def test_add_remove_event_handler():
# Given
provider = NoOpProvider()
set_provider(provider)
spy = MagicMock()
add_handler(
ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, spy.provider_configuration_changed
)
remove_handler(
ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, spy.provider_configuration_changed
)
provider_details = ProviderEventDetails(message="message")
# When
provider.emit_provider_configuration_changed(provider_details)
# Then
spy.provider_configuration_changed.assert_not_called()
# Requirement 5.3.3
def test_handlers_attached_to_provider_already_in_associated_state_should_run_immediately():
# Given
provider = NoOpProvider()
set_provider(provider)
spy = MagicMock()
# When
add_handler(ProviderEvent.PROVIDER_READY, spy.provider_ready)
# Then
spy.provider_ready.assert_called_once()
def test_provider_ready_handlers_run_if_provider_initialize_function_terminates_normally():
# Given
provider = NoOpProvider()
spy = MagicMock()
add_handler(ProviderEvent.PROVIDER_READY, spy.provider_ready)
spy.reset_mock() # reset the mock to avoid counting the immediate call on subscribe
# When
set_provider(provider)
# Then
spy.provider_ready.assert_called_once()
def test_provider_error_handlers_run_if_provider_initialize_function_terminates_abnormally():
# Given
provider = MagicMock(spec=FeatureProvider)
provider.initialize.side_effect = ProviderFatalError()
spy = MagicMock()
add_handler(ProviderEvent.PROVIDER_ERROR, spy.provider_error)
# When
set_provider(provider)
# Then
spy.provider_error.assert_called_once()
def test_provider_status_is_updated_after_provider_emits_event():
# Given
provider = NoOpProvider()
set_provider(provider)
client = get_client()
# When
provider.emit_provider_error(ProviderEventDetails(error_code=ErrorCode.GENERAL))
# Then
assert client.get_provider_status() == ProviderStatus.ERROR
# When
provider.emit_provider_error(
ProviderEventDetails(error_code=ErrorCode.PROVIDER_FATAL)
)
# Then
assert client.get_provider_status() == ProviderStatus.FATAL
# When
provider.emit_provider_stale(ProviderEventDetails())
# Then
assert client.get_provider_status() == ProviderStatus.STALE
# When
provider.emit_provider_ready(ProviderEventDetails())
# Then
assert client.get_provider_status() == ProviderStatus.READY