From 991480b73c8e87a413a044bd17d467d391cf7eee Mon Sep 17 00:00:00 2001 From: "Gerald W. Lester" Date: Fri, 1 Oct 2021 09:31:12 -0500 Subject: [PATCH 1/6] Added inqeuality conditions --- .../utilities/feature_flags/feature_flags.py | 5 + .../utilities/feature_flags/schema.py | 5 + docs/utilities/feature_flags.md | 2 +- .../feature_flags/test_feature_flags.py | 335 ++++++++++++++++++ 4 files changed, 346 insertions(+), 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/feature_flags/feature_flags.py b/aws_lambda_powertools/utilities/feature_flags/feature_flags.py index d04e74ff293..690dd488e43 100644 --- a/aws_lambda_powertools/utilities/feature_flags/feature_flags.py +++ b/aws_lambda_powertools/utilities/feature_flags/feature_flags.py @@ -44,6 +44,11 @@ def _match_by_action(action: str, condition_value: Any, context_value: Any) -> b return False mapping_by_action = { schema.RuleAction.EQUALS.value: lambda a, b: a == b, + schema.RuleAction.NOT_EQUALS.value: lambda a, b: a != b, + schema.RuleAction.KEY_GREATER_THAN_VALUE.value: lambda a, b: a > b, + schema.RuleAction.KEY_GREATER_THAN_OR_EQUAL_VALUE.value: lambda a, b: a >= b, + schema.RuleAction.KEY_LESS_THAN_VALUE.value: lambda a, b: a < b, + schema.RuleAction.KEY_LESS_THAN_OR_EQUAL_VALUE.value: lambda a, b: a <= b, schema.RuleAction.STARTSWITH.value: lambda a, b: a.startswith(b), schema.RuleAction.ENDSWITH.value: lambda a, b: a.endswith(b), schema.RuleAction.IN.value: lambda a, b: a in b, diff --git a/aws_lambda_powertools/utilities/feature_flags/schema.py b/aws_lambda_powertools/utilities/feature_flags/schema.py index efce82018db..9a11863d7d7 100644 --- a/aws_lambda_powertools/utilities/feature_flags/schema.py +++ b/aws_lambda_powertools/utilities/feature_flags/schema.py @@ -18,6 +18,11 @@ class RuleAction(str, Enum): EQUALS = "EQUALS" + NOT_EQUALS = "NOT_EQUALS" + KEY_GREATER_THAN_VALUE = "KEY_GREATER_THAN_VALUE" + KEY_GREATER_THAN_OR_EQUAL_VALUE = "KEY_GREATER_THAN_OR_EQUAL_VALUE" + KEY_LESS_THAN_VALUE = "KEY_LESS_THAN_VALUE" + KEY_LESS_THAN_OR_EQUAL_VALUE = "KEY_LESS_THAN_OR_EQUAL_VALUE" STARTSWITH = "STARTSWITH" ENDSWITH = "ENDSWITH" IN = "IN" diff --git a/docs/utilities/feature_flags.md b/docs/utilities/feature_flags.md index d22f9c03296..340e0fd25bb 100644 --- a/docs/utilities/feature_flags.md +++ b/docs/utilities/feature_flags.md @@ -450,7 +450,7 @@ The `conditions` block is a list of conditions that contain `action`, `key`, and } ``` -The `action` configuration can have 5 different values: `EQUALS`, `STARTSWITH`, `ENDSWITH`, `IN`, `NOT_IN`. +The `action` configuration can have ten different values: `EQUALS`, `NOT_EQUALS`, `KEY_LESS_THAN_VALUE`, `KEY_LESS_THAN_OR_EQUAL_VALUE`, `KEY_GREATER_THAN_VALUE`, `KEY_GREATER_THAN_OR_EQUAL_VALUE`, `STARTSWITH`, `ENDSWITH`, `IN`, `NOT_IN`. The `key` and `value` will be compared to the input from the context parameter. diff --git a/tests/functional/feature_flags/test_feature_flags.py b/tests/functional/feature_flags/test_feature_flags.py index 5342105da3d..2802f3ddd0e 100644 --- a/tests/functional/feature_flags/test_feature_flags.py +++ b/tests/functional/feature_flags/test_feature_flags.py @@ -587,3 +587,338 @@ def test_get_feature_toggle_propagates_access_denied_error(mocker, config): # THEN raise StoreClientError error with pytest.raises(StoreClientError, match="AccessDeniedException") as err: feature_flags.evaluate(name="Foo", default=False) + +## +## Inequality test cases +## + +# Test not equals +def test_flags_not_eqaul_no_match(mocker, config): + expected_value = False + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "tenant id not equals 345345435": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.NOT_EQUALS.value, + "key": "tenant_id", + "value": "345345435", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a"}, default=False) + assert toggle == expected_value + +def test_flags_not_eqaul_match(mocker, config): + expected_value = True + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "tenant id not equals 345345435": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.NOT_EQUALS.value, + "key": "tenant_id", + "value": "345345435", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "", "username": "a"}, default=False) + assert toggle == expected_value + + +# Test less than +def test_flags_less_than_no_match_1(mocker, config): + expected_value = False + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "Date less than 2021.10.31": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.KEY_LESS_THAN_VALUE.value, + "key": "current_date", + "value": "2021.10.31", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.12.25"}, default=False) + assert toggle == expected_value + +def test_flags_less_than_no_match_2(mocker, config): + expected_value = False + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "Date less than 2021.10.31": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.KEY_LESS_THAN_VALUE.value, + "key": "current_date", + "value": "2021.10.31", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.10.31"}, default=False) + assert toggle == expected_value + +def test_flags_less_than_match(mocker, config): + expected_value = True + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "Date less than 2021.10.31": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.KEY_LESS_THAN_VALUE.value, + "key": "current_date", + "value": "2021.10.31", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.04.01"}, default=False) + assert toggle == expected_value + +# Test less than or equal to +def test_flags_less_than_or_equal_no_match(mocker, config): + expected_value = False + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "Date less than or equal 2021.10.31": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.KEY_LESS_THAN_OR_EQUAL_VALUE.value, + "key": "current_date", + "value": "2021.10.31", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.12.25"}, default=False) + assert toggle == expected_value + +def test_flags_less_than_or_equal_match_1(mocker, config): + expected_value = True + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "Date less than or equal 2021.10.31": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.KEY_LESS_THAN_OR_EQUAL_VALUE.value, + "key": "current_date", + "value": "2021.10.31", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.04.01"}, default=False) + assert toggle == expected_value + + +def test_flags_less_than_or_equal_match_2(mocker, config): + expected_value = True + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "Date less than or equal 2021.10.31": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.KEY_LESS_THAN_OR_EQUAL_VALUE.value, + "key": "current_date", + "value": "2021.10.31", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.10.31"}, default=False) + assert toggle == expected_value + +# Test greater than +def test_flags_greater_than_no_match_1(mocker, config): + expected_value = False + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "Date greater than 2021.10.31": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.KEY_GREATER_THAN_VALUE.value, + "key": "current_date", + "value": "2021.10.31", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.04.01"}, default=False) + assert toggle == expected_value + +def test_flags_greater_than_no_match_2(mocker, config): + expected_value = False + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "Date greater than 2021.10.31": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.KEY_GREATER_THAN_VALUE.value, + "key": "current_date", + "value": "2021.10.31", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.10.31"}, default=False) + assert toggle == expected_value + +def test_flags_greater_than_match(mocker, config): + expected_value = True + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "Date greater than 2021.10.31": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.KEY_GREATER_THAN_VALUE.value, + "key": "current_date", + "value": "2021.10.31", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.12.25"}, default=False) + assert toggle == expected_value + +# Test greater than or equal to +def test_flags_greater_than_or_equal_no_match(mocker, config): + expected_value = False + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "Date greater than or equal 2021.10.31": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.KEY_GREATER_THAN_OR_EQUAL_VALUE.value, + "key": "current_date", + "value": "2021.10.31", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.12.25"}, default=False) + assert toggle == expected_value + +def test_flags_greater_than_or_equal_match_1(mocker, config): + expected_value = True + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "Date greater than or equal 2021.10.31": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.KEY_GREATER_THAN_OR_EQUAL_VALUE.value, + "key": "current_date", + "value": "2021.10.31", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.04.01"}, default=False) + assert toggle == expected_value + + +def test_flags_greater_than_or_equal_match_2(mocker, config): + expected_value = True + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "Date greater than or equal 2021.10.31": { + "when_match": False, + "conditions": [ + { + "action": RuleAction.KEY_GREATER_THAN_OR_EQUAL_VALUE.value, + "key": "current_date", + "value": "2021.10.31", + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.10.31"}, default=False) + assert toggle == expected_value + From 0052c664be1d46ff25462ef00e58af8b7bd8d382 Mon Sep 17 00:00:00 2001 From: "Gerald W. Lester" Date: Fri, 1 Oct 2021 09:51:09 -0500 Subject: [PATCH 2/6] Resolve conflict --- tests/functional/feature_flags/test_feature_flags.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/functional/feature_flags/test_feature_flags.py b/tests/functional/feature_flags/test_feature_flags.py index 2802f3ddd0e..d62c18c2052 100644 --- a/tests/functional/feature_flags/test_feature_flags.py +++ b/tests/functional/feature_flags/test_feature_flags.py @@ -587,7 +587,18 @@ def test_get_feature_toggle_propagates_access_denied_error(mocker, config): # THEN raise StoreClientError error with pytest.raises(StoreClientError, match="AccessDeniedException") as err: feature_flags.evaluate(name="Foo", default=False) + +def test_get_configuration_with_envelope_and_raw(mocker, config): + expected_value = True + mocked_app_config_schema = {"log_level": "INFO", "features": {"my_feature": {"default": expected_value}}} + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config, envelope="features") + + features_config = feature_flags.get_configuration() + config = feature_flags.store.get_raw_configuration + assert "log_level" in config + assert "log_level" not in features_config + ## ## Inequality test cases ## From 966eac7e040ec6d75981ad1f8e8a3f2cd2b6c4e0 Mon Sep 17 00:00:00 2001 From: "Gerald W. Lester" Date: Fri, 1 Oct 2021 13:42:21 -0500 Subject: [PATCH 3/6] correct when match on tests --- .../feature_flags/test_feature_flags.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/functional/feature_flags/test_feature_flags.py b/tests/functional/feature_flags/test_feature_flags.py index ab257b18910..6502f37ca47 100644 --- a/tests/functional/feature_flags/test_feature_flags.py +++ b/tests/functional/feature_flags/test_feature_flags.py @@ -815,7 +815,7 @@ def test_flags_not_eqaul_no_match(mocker, config): "default": expected_value, "rules": { "tenant id not equals 345345435": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.NOT_EQUALS.value, @@ -838,7 +838,7 @@ def test_flags_not_eqaul_match(mocker, config): "default": expected_value, "rules": { "tenant id not equals 345345435": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.NOT_EQUALS.value, @@ -863,7 +863,7 @@ def test_flags_less_than_no_match_1(mocker, config): "default": expected_value, "rules": { "Date less than 2021.10.31": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.KEY_LESS_THAN_VALUE.value, @@ -886,7 +886,7 @@ def test_flags_less_than_no_match_2(mocker, config): "default": expected_value, "rules": { "Date less than 2021.10.31": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.KEY_LESS_THAN_VALUE.value, @@ -909,7 +909,7 @@ def test_flags_less_than_match(mocker, config): "default": expected_value, "rules": { "Date less than 2021.10.31": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.KEY_LESS_THAN_VALUE.value, @@ -933,7 +933,7 @@ def test_flags_less_than_or_equal_no_match(mocker, config): "default": expected_value, "rules": { "Date less than or equal 2021.10.31": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.KEY_LESS_THAN_OR_EQUAL_VALUE.value, @@ -956,7 +956,7 @@ def test_flags_less_than_or_equal_match_1(mocker, config): "default": expected_value, "rules": { "Date less than or equal 2021.10.31": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.KEY_LESS_THAN_OR_EQUAL_VALUE.value, @@ -980,7 +980,7 @@ def test_flags_less_than_or_equal_match_2(mocker, config): "default": expected_value, "rules": { "Date less than or equal 2021.10.31": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.KEY_LESS_THAN_OR_EQUAL_VALUE.value, @@ -1004,7 +1004,7 @@ def test_flags_greater_than_no_match_1(mocker, config): "default": expected_value, "rules": { "Date greater than 2021.10.31": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.KEY_GREATER_THAN_VALUE.value, @@ -1027,7 +1027,7 @@ def test_flags_greater_than_no_match_2(mocker, config): "default": expected_value, "rules": { "Date greater than 2021.10.31": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.KEY_GREATER_THAN_VALUE.value, @@ -1050,7 +1050,7 @@ def test_flags_greater_than_match(mocker, config): "default": expected_value, "rules": { "Date greater than 2021.10.31": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.KEY_GREATER_THAN_VALUE.value, @@ -1074,7 +1074,7 @@ def test_flags_greater_than_or_equal_no_match(mocker, config): "default": expected_value, "rules": { "Date greater than or equal 2021.10.31": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.KEY_GREATER_THAN_OR_EQUAL_VALUE.value, @@ -1097,7 +1097,7 @@ def test_flags_greater_than_or_equal_match_1(mocker, config): "default": expected_value, "rules": { "Date greater than or equal 2021.10.31": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.KEY_GREATER_THAN_OR_EQUAL_VALUE.value, @@ -1121,7 +1121,7 @@ def test_flags_greater_than_or_equal_match_2(mocker, config): "default": expected_value, "rules": { "Date greater than or equal 2021.10.31": { - "when_match": False, + "when_match": True, "conditions": [ { "action": RuleAction.KEY_GREATER_THAN_OR_EQUAL_VALUE.value, From bcb4012e0f16624d250012546a34d9a08b74998e Mon Sep 17 00:00:00 2001 From: "Gerald W. Lester" Date: Fri, 1 Oct 2021 13:50:06 -0500 Subject: [PATCH 4/6] Correct test data --- tests/functional/feature_flags/test_feature_flags.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/feature_flags/test_feature_flags.py b/tests/functional/feature_flags/test_feature_flags.py index 6502f37ca47..1e7325d5ab8 100644 --- a/tests/functional/feature_flags/test_feature_flags.py +++ b/tests/functional/feature_flags/test_feature_flags.py @@ -1087,7 +1087,7 @@ def test_flags_greater_than_or_equal_no_match(mocker, config): } } feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) - toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.12.25"}, default=False) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.04.01"}, default=False) assert toggle == expected_value def test_flags_greater_than_or_equal_match_1(mocker, config): @@ -1110,7 +1110,7 @@ def test_flags_greater_than_or_equal_match_1(mocker, config): } } feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) - toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.04.01"}, default=False) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a", "current_date": "2021.12.25"}, default=False) assert toggle == expected_value From 0b7095563c01ad877768f58650380ba02163a6c4 Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Sun, 3 Oct 2021 20:31:32 +0200 Subject: [PATCH 5/6] Update tests/functional/feature_flags/test_feature_flags.py --- tests/functional/feature_flags/test_feature_flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/feature_flags/test_feature_flags.py b/tests/functional/feature_flags/test_feature_flags.py index 1e7325d5ab8..62a5b900bc9 100644 --- a/tests/functional/feature_flags/test_feature_flags.py +++ b/tests/functional/feature_flags/test_feature_flags.py @@ -831,7 +831,7 @@ def test_flags_not_eqaul_no_match(mocker, config): toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "345345435", "username": "a"}, default=False) assert toggle == expected_value -def test_flags_not_eqaul_match(mocker, config): +def test_flags_not_equal_match(mocker, config): expected_value = True mocked_app_config_schema = { "my_feature": { From 610b90724508ab4a145ba946fe48b4dfbfc566ad Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Sun, 3 Oct 2021 20:31:42 +0200 Subject: [PATCH 6/6] chore: typo --- tests/functional/feature_flags/test_feature_flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/feature_flags/test_feature_flags.py b/tests/functional/feature_flags/test_feature_flags.py index 62a5b900bc9..c052c4ae0df 100644 --- a/tests/functional/feature_flags/test_feature_flags.py +++ b/tests/functional/feature_flags/test_feature_flags.py @@ -808,7 +808,7 @@ def test_get_configuration_with_envelope_and_raw(mocker, config): ## # Test not equals -def test_flags_not_eqaul_no_match(mocker, config): +def test_flags_not_equal_no_match(mocker, config): expected_value = False mocked_app_config_schema = { "my_feature": {