From aa476e8418bd1e5c9e43d62936b7716b975cbd7b Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Mon, 10 Feb 2025 12:45:04 +0100 Subject: [PATCH 1/4] Implement gherkin hooks tests Signed-off-by: christian.lutnik --- openfeature/exception.py | 3 ++ tests/features/steps/flag_steps.py | 9 +++++ tests/features/steps/hooks_steps.py | 51 +++++++++++++---------------- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/openfeature/exception.py b/openfeature/exception.py index 0576ec17..dedaa1e0 100644 --- a/openfeature/exception.py +++ b/openfeature/exception.py @@ -190,3 +190,6 @@ def to_exception( ) -> OpenFeatureError: exc = cls.__exceptions__.get(error_code.value, GeneralError) return exc(error_message) + + def __str__(self): + return self.value diff --git a/tests/features/steps/flag_steps.py b/tests/features/steps/flag_steps.py index 574d6791..b999c399 100644 --- a/tests/features/steps/flag_steps.py +++ b/tests/features/steps/flag_steps.py @@ -3,6 +3,15 @@ @given('a {flag_type}-flag with key "{flag_key}" and a default value "{default_value}"') def step_impl_flag(context, flag_type: str, flag_key, default_value): + if default_value.lower() == "true" or default_value.lower() == "false": + default_value = bool(default_value) + try: + default_value = int(default_value) + except ValueError: + try: + default_value = float(default_value) + except ValueError: + pass context.flag = (flag_type, flag_key, default_value) diff --git a/tests/features/steps/hooks_steps.py b/tests/features/steps/hooks_steps.py index bc7e156b..47965fbb 100644 --- a/tests/features/steps/hooks_steps.py +++ b/tests/features/steps/hooks_steps.py @@ -1,12 +1,13 @@ from unittest.mock import MagicMock -from behave import then, when +from behave import then, given from openfeature.exception import ErrorCode +from openfeature.flag_evaluation import Reason from openfeature.hook import Hook -@when("a hook is added to the client") +@given("a client with added hook") def step_impl_add_hook(context): hook = MagicMock(spec=Hook) hook.before = MagicMock() @@ -17,18 +18,26 @@ def step_impl_add_hook(context): context.client.add_hooks([hook]) -@then("error hooks should be called") -def step_impl_call_error(context): - assert context.hook.before.called - assert context.hook.error.called - assert context.hook.finally_after.called +@then('the "{hook_name}" hook should have been executed') +def step_impl_should_called(context, hook_name): + hook = get_hook_from_name(context, hook_name) + assert hook.called -@then("non-error hooks should be called") -def step_impl_call_non_error(context): - assert context.hook.before.called - assert context.hook.after.called - assert context.hook.finally_after.called +@then('the "{hook_names}" hooks should be called with evaluation details') +def step_impl_should_have_eval_details(context, hook_names): + for hook_name in hook_names.split(", "): + hook = get_hook_from_name(context, hook_name) + for row in context.table: + flag_type, key, value = row + + value = convert_value_from_flag_type(value, flag_type) + + actual = hook.call_args[1]["details"].__dict__[key] + if isinstance(actual, ErrorCode) or isinstance(actual, Reason): + actual = str(actual) + + assert actual == value def get_hook_from_name(context, hook_name): @@ -45,7 +54,7 @@ def get_hook_from_name(context, hook_name): def convert_value_from_flag_type(value, flag_type): - if value == "None": + if value == "None" or value == "null": return None if flag_type.lower() == "boolean": return bool(value) @@ -54,19 +63,3 @@ def convert_value_from_flag_type(value, flag_type): elif flag_type.lower() == "float": return float(value) return value - - -@then('"{hook_names}" hooks should have evaluation details') -def step_impl_should_have_eval_details(context, hook_names): - for hook_name in hook_names.split(", "): - hook = get_hook_from_name(context, hook_name) - for row in context.table: - flag_type, key, value = row - - value = convert_value_from_flag_type(value, flag_type) - - actual = hook.call_args[1]["details"].__dict__[key] - if isinstance(actual, ErrorCode): - actual = str(actual) - - assert actual == value From 3f8347bbcc0bbc05818d3b1c7c2a8f627b5840fd Mon Sep 17 00:00:00 2001 From: "christian.lutnik" Date: Mon, 10 Feb 2025 16:38:19 +0100 Subject: [PATCH 2/4] fixup! Implement gherkin hooks tests Signed-off-by: christian.lutnik --- tests/features/steps/hooks_steps.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/features/steps/hooks_steps.py b/tests/features/steps/hooks_steps.py index 47965fbb..8036be25 100644 --- a/tests/features/steps/hooks_steps.py +++ b/tests/features/steps/hooks_steps.py @@ -37,6 +37,8 @@ def step_impl_should_have_eval_details(context, hook_names): if isinstance(actual, ErrorCode) or isinstance(actual, Reason): actual = str(actual) + print("expected", value, "actual", actual) + print("expected type", type(value), "actual type", type(actual)) assert actual == value From 88e964dc21352da5c92fa5ab1f64c4058b7f1dae Mon Sep 17 00:00:00 2001 From: gruebel Date: Tue, 11 Feb 2025 21:50:00 +0100 Subject: [PATCH 3/4] fix tests and lint Signed-off-by: gruebel --- openfeature/exception.py | 3 --- tests/features/steps/hooks_steps.py | 15 ++++++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/openfeature/exception.py b/openfeature/exception.py index dedaa1e0..0576ec17 100644 --- a/openfeature/exception.py +++ b/openfeature/exception.py @@ -190,6 +190,3 @@ def to_exception( ) -> OpenFeatureError: exc = cls.__exceptions__.get(error_code.value, GeneralError) return exc(error_message) - - def __str__(self): - return self.value diff --git a/tests/features/steps/hooks_steps.py b/tests/features/steps/hooks_steps.py index 8036be25..20cd2fe5 100644 --- a/tests/features/steps/hooks_steps.py +++ b/tests/features/steps/hooks_steps.py @@ -1,6 +1,6 @@ from unittest.mock import MagicMock -from behave import then, given +from behave import given, then from openfeature.exception import ErrorCode from openfeature.flag_evaluation import Reason @@ -31,11 +31,8 @@ def step_impl_should_have_eval_details(context, hook_names): for row in context.table: flag_type, key, value = row - value = convert_value_from_flag_type(value, flag_type) - + value = convert_value_from_key_and_flag_type(value, key, flag_type) actual = hook.call_args[1]["details"].__dict__[key] - if isinstance(actual, ErrorCode) or isinstance(actual, Reason): - actual = str(actual) print("expected", value, "actual", actual) print("expected type", type(value), "actual type", type(actual)) @@ -55,8 +52,8 @@ def get_hook_from_name(context, hook_name): raise ValueError(str(hook_name) + " is not a valid hook name") -def convert_value_from_flag_type(value, flag_type): - if value == "None" or value == "null": +def convert_value_from_key_and_flag_type(value, key, flag_type): + if value in ("None", "null"): return None if flag_type.lower() == "boolean": return bool(value) @@ -64,4 +61,8 @@ def convert_value_from_flag_type(value, flag_type): return int(value) elif flag_type.lower() == "float": return float(value) + elif key == "reason": + return Reason(value) + elif key == "error_code": + return ErrorCode(value) return value From 1d87260258358577dab90eb4fba00f5c1ad107e2 Mon Sep 17 00:00:00 2001 From: gruebel Date: Tue, 11 Feb 2025 21:52:59 +0100 Subject: [PATCH 4/4] lint Signed-off-by: gruebel --- tests/features/steps/flag_steps.py | 6 +++--- tests/features/steps/hooks_steps.py | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/features/steps/flag_steps.py b/tests/features/steps/flag_steps.py index b999c399..7f6cd153 100644 --- a/tests/features/steps/flag_steps.py +++ b/tests/features/steps/flag_steps.py @@ -1,3 +1,5 @@ +import contextlib + from behave import given, when @@ -8,10 +10,8 @@ def step_impl_flag(context, flag_type: str, flag_key, default_value): try: default_value = int(default_value) except ValueError: - try: + with contextlib.suppress(ValueError): default_value = float(default_value) - except ValueError: - pass context.flag = (flag_type, flag_key, default_value) diff --git a/tests/features/steps/hooks_steps.py b/tests/features/steps/hooks_steps.py index 20cd2fe5..d93ac643 100644 --- a/tests/features/steps/hooks_steps.py +++ b/tests/features/steps/hooks_steps.py @@ -34,8 +34,6 @@ def step_impl_should_have_eval_details(context, hook_names): value = convert_value_from_key_and_flag_type(value, key, flag_type) actual = hook.call_args[1]["details"].__dict__[key] - print("expected", value, "actual", actual) - print("expected type", type(value), "actual type", type(actual)) assert actual == value