Skip to content

Commit

Permalink
Feature request: Time based feature flags actions
Browse files Browse the repository at this point in the history
  • Loading branch information
Ran Isenberg committed Oct 5, 2022
1 parent f04884d commit 992371f
Show file tree
Hide file tree
Showing 2 changed files with 268 additions and 0 deletions.
20 changes: 20 additions & 0 deletions aws_lambda_powertools/utilities/feature_flags/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,26 @@ class RuleAction(str, Enum):
KEY_NOT_IN_VALUE = "KEY_NOT_IN_VALUE"
VALUE_IN_KEY = "VALUE_IN_KEY"
VALUE_NOT_IN_KEY = "VALUE_NOT_IN_KEY"
TIME_RANGE = "TIME_RANGE" # 24 hours clock UTC time
TIME_SELECTED_DAYS = "TIME_SELECTED_DAYS"


class TimeKeys(str, Enum):
CURRENT_HOUR_UTC = "CURRENT_HOUR_UTC"
CURRENT_DAY_UTC = "CURRENT_DAY_UTC"
CURRENT_TIME_UTC = "CURRENT_TIME_UTC"


class TimeValues(str, Enum):
START_TIME = "START_TIME"
END_TIME = "END_TIME"
SUNDAY = "SUNDAY"
MONDAY = "MONDAY"
TUESDAY = "TUESDAY"
WEDNESDAY = "WEDNESDAY"
THURSDAY = "THURSDAY"
FRIDAY = "FRIDAY"
SATURDAY = "SATURDAY"


class SchemaValidator(BaseValidator):
Expand Down
248 changes: 248 additions & 0 deletions tests/functional/feature_flags/test_time_based_actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
from typing import Dict, Optional

import pytest
from botocore.config import Config

from aws_lambda_powertools.utilities.feature_flags.appconfig import AppConfigStore
from aws_lambda_powertools.utilities.feature_flags.feature_flags import FeatureFlags
from aws_lambda_powertools.utilities.feature_flags.schema import (
CONDITION_ACTION,
CONDITION_KEY,
CONDITION_VALUE,
CONDITIONS_KEY,
FEATURE_DEFAULT_VAL_KEY,
RULE_MATCH_VALUE,
RULES_KEY,
RuleAction,
TimeKeys,
TimeValues,
)


@pytest.fixture(scope="module")
def config():
return Config(region_name="us-east-1")


def init_feature_flags(
mocker, mock_schema: Dict, config: Config, envelope: str = "", jmespath_options: Optional[Dict] = None
) -> FeatureFlags:
mocked_get_conf = mocker.patch("aws_lambda_powertools.utilities.parameters.AppConfigProvider.get")
mocked_get_conf.return_value = mock_schema

app_conf_fetcher = AppConfigStore(
environment="test_env",
application="test_app",
name="test_conf_name",
max_age=600,
sdk_config=config,
envelope=envelope,
jmespath_options=jmespath_options,
)
feature_flags: FeatureFlags = FeatureFlags(store=app_conf_fetcher)
return feature_flags


def init_fetcher_side_effect(mocker, config: Config, side_effect) -> AppConfigStore:
mocked_get_conf = mocker.patch("aws_lambda_powertools.utilities.parameters.AppConfigProvider.get")
mocked_get_conf.side_effect = side_effect
return AppConfigStore(
environment="env",
application="application",
name="conf",
max_age=1,
sdk_config=config,
)


def test_time_based_utc_in_between_time_range_rule_match(mocker, config):
expected_value = True
mocked_app_config_schema = {
"my_feature": {
FEATURE_DEFAULT_VAL_KEY: False,
RULES_KEY: {
"lambda time is between UTC 11:11-23:59": {
RULE_MATCH_VALUE: True,
CONDITIONS_KEY: [
{
CONDITION_ACTION: RuleAction.TIME_RANGE.value, # this condition matches
CONDITION_KEY: TimeKeys.CURRENT_HOUR_UTC,
CONDITION_VALUE: {TimeValues.START_TIME: "11:11", TimeValues.END_TIME: "23:59"},
},
],
}
},
}
}
# mock time for rule match
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
toggle = feature_flags.evaluate(
name="my_feature",
context={},
default=False,
)
assert toggle == expected_value


def test_time_based_utc_in_between_full_time_range_rule_match(mocker, config):
expected_value = True
mocked_app_config_schema = {
"my_feature": {
FEATURE_DEFAULT_VAL_KEY: False,
RULES_KEY: {
"lambda time is between UTC october 5th 2022 12:14:32PM to october 10th 2022 12:15:00 PM": {
RULE_MATCH_VALUE: True,
CONDITIONS_KEY: [
{
CONDITION_ACTION: RuleAction.TIME_RANGE.value, # this condition matches
CONDITION_KEY: TimeKeys.CURRENT_TIME_UTC,
CONDITION_VALUE: {
TimeValues.START_TIME: "2022-10-05T12:15:00Z",
TimeValues.END_TIME: "2022-10-10T12:15:00Z",
},
},
],
}
},
}
}
# mock time for rule match
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
toggle = feature_flags.evaluate(
name="my_feature",
context={},
default=False,
)
assert toggle == expected_value


def test_time_based_multiple_conditions_utc_in_between_time_range_rule_match(mocker, config):
expected_value = True
mocked_app_config_schema = {
"my_feature": {
FEATURE_DEFAULT_VAL_KEY: False,
RULES_KEY: {
"lambda time is between UTC 09:00-17:00 and username is ran": {
RULE_MATCH_VALUE: True,
CONDITIONS_KEY: [
{
CONDITION_ACTION: RuleAction.TIME_RANGE.value, # this condition matches
CONDITION_KEY: TimeKeys.CURRENT_HOUR_UTC,
CONDITION_VALUE: {TimeValues.START_TIME: "09:00", TimeValues.END_TIME: "17:00"},
},
{
CONDITION_ACTION: RuleAction.EQUALS.value,
CONDITION_KEY: "tenant",
CONDITION_VALUE: {"username": "ran"},
},
],
}
},
}
}
# mock time for rule match
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
toggle = feature_flags.evaluate(
name="my_feature",
context={"username": "ran"},
default=False,
)
assert toggle == expected_value


def test_time_based_utc_days_range_rule_match(mocker, config):
expected_value = True
mocked_app_config_schema = {
"my_feature": {
FEATURE_DEFAULT_VAL_KEY: False,
RULES_KEY: {
"match only monday through friday": {
RULE_MATCH_VALUE: True,
CONDITIONS_KEY: [
{
CONDITION_ACTION: RuleAction.TIME_SELECTED_DAYS.value, # this condition matches
CONDITION_KEY: TimeKeys.CURRENT_DAY_UTC, # similar to "IN" actions
CONDITION_VALUE: [
TimeValues.MONDAY,
TimeValues.TUESDAY,
TimeValues.WEDNESDAY,
TimeValues.THURSDAY,
TimeValues.FRIDAY,
],
},
],
}
},
}
}
# mock time for rule match
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
toggle = feature_flags.evaluate(
name="my_feature",
context={},
default=False,
)
assert toggle == expected_value


def test_time_based_utc_only_weekend_rule_match(mocker, config):
expected_value = True
mocked_app_config_schema = {
"my_feature": {
FEATURE_DEFAULT_VAL_KEY: False,
RULES_KEY: {
"match only on weekend": {
RULE_MATCH_VALUE: True,
CONDITIONS_KEY: [
{
CONDITION_ACTION: RuleAction.TIME_SELECTED_DAYS.value, # this condition matches
CONDITION_KEY: TimeKeys.CURRENT_DAY_UTC, # similar to "IN" actions
CONDITION_VALUE: [TimeValues.SATURDAY, TimeValues.SUNDAY],
},
],
}
},
}
}
# mock time for rule match
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
toggle = feature_flags.evaluate(
name="my_feature",
context={},
default=False,
)
assert toggle == expected_value


def test_time_based_multiple_conditions_utc_days_range_and_certain_hours_rule_match(mocker, config):
expected_value = True
mocked_app_config_schema = {
"my_feature": {
FEATURE_DEFAULT_VAL_KEY: False,
RULES_KEY: {
"match when lambda time is between UTC 11:00-23:00 and day is either monday or thursday": {
RULE_MATCH_VALUE: True,
CONDITIONS_KEY: [
{
CONDITION_ACTION: RuleAction.TIME_RANGE.value, # this condition matches
CONDITION_KEY: TimeKeys.CURRENT_HOUR_UTC,
CONDITION_VALUE: {TimeValues.START_TIME: "11:00", TimeValues.END_TIME: "23:00"},
},
{
CONDITION_ACTION: RuleAction.TIME_SELECTED_DAYS.value, # this condition matches
CONDITION_KEY: TimeKeys.CURRENT_DAY_UTC,
CONDITION_VALUE: [TimeValues.MONDAY, TimeValues.THURSDAY],
},
],
}
},
}
}
# mock time for rule match
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
toggle = feature_flags.evaluate(
name="my_feature",
context={},
default=False,
)
assert toggle == expected_value

0 comments on commit 992371f

Please sign in to comment.