Skip to content

Commit 992371f

Browse files
author
Ran Isenberg
committed
Feature request: Time based feature flags actions
1 parent f04884d commit 992371f

File tree

2 files changed

+268
-0
lines changed

2 files changed

+268
-0
lines changed

aws_lambda_powertools/utilities/feature_flags/schema.py

+20
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,26 @@ class RuleAction(str, Enum):
3131
KEY_NOT_IN_VALUE = "KEY_NOT_IN_VALUE"
3232
VALUE_IN_KEY = "VALUE_IN_KEY"
3333
VALUE_NOT_IN_KEY = "VALUE_NOT_IN_KEY"
34+
TIME_RANGE = "TIME_RANGE" # 24 hours clock UTC time
35+
TIME_SELECTED_DAYS = "TIME_SELECTED_DAYS"
36+
37+
38+
class TimeKeys(str, Enum):
39+
CURRENT_HOUR_UTC = "CURRENT_HOUR_UTC"
40+
CURRENT_DAY_UTC = "CURRENT_DAY_UTC"
41+
CURRENT_TIME_UTC = "CURRENT_TIME_UTC"
42+
43+
44+
class TimeValues(str, Enum):
45+
START_TIME = "START_TIME"
46+
END_TIME = "END_TIME"
47+
SUNDAY = "SUNDAY"
48+
MONDAY = "MONDAY"
49+
TUESDAY = "TUESDAY"
50+
WEDNESDAY = "WEDNESDAY"
51+
THURSDAY = "THURSDAY"
52+
FRIDAY = "FRIDAY"
53+
SATURDAY = "SATURDAY"
3454

3555

3656
class SchemaValidator(BaseValidator):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
from typing import Dict, Optional
2+
3+
import pytest
4+
from botocore.config import Config
5+
6+
from aws_lambda_powertools.utilities.feature_flags.appconfig import AppConfigStore
7+
from aws_lambda_powertools.utilities.feature_flags.feature_flags import FeatureFlags
8+
from aws_lambda_powertools.utilities.feature_flags.schema import (
9+
CONDITION_ACTION,
10+
CONDITION_KEY,
11+
CONDITION_VALUE,
12+
CONDITIONS_KEY,
13+
FEATURE_DEFAULT_VAL_KEY,
14+
RULE_MATCH_VALUE,
15+
RULES_KEY,
16+
RuleAction,
17+
TimeKeys,
18+
TimeValues,
19+
)
20+
21+
22+
@pytest.fixture(scope="module")
23+
def config():
24+
return Config(region_name="us-east-1")
25+
26+
27+
def init_feature_flags(
28+
mocker, mock_schema: Dict, config: Config, envelope: str = "", jmespath_options: Optional[Dict] = None
29+
) -> FeatureFlags:
30+
mocked_get_conf = mocker.patch("aws_lambda_powertools.utilities.parameters.AppConfigProvider.get")
31+
mocked_get_conf.return_value = mock_schema
32+
33+
app_conf_fetcher = AppConfigStore(
34+
environment="test_env",
35+
application="test_app",
36+
name="test_conf_name",
37+
max_age=600,
38+
sdk_config=config,
39+
envelope=envelope,
40+
jmespath_options=jmespath_options,
41+
)
42+
feature_flags: FeatureFlags = FeatureFlags(store=app_conf_fetcher)
43+
return feature_flags
44+
45+
46+
def init_fetcher_side_effect(mocker, config: Config, side_effect) -> AppConfigStore:
47+
mocked_get_conf = mocker.patch("aws_lambda_powertools.utilities.parameters.AppConfigProvider.get")
48+
mocked_get_conf.side_effect = side_effect
49+
return AppConfigStore(
50+
environment="env",
51+
application="application",
52+
name="conf",
53+
max_age=1,
54+
sdk_config=config,
55+
)
56+
57+
58+
def test_time_based_utc_in_between_time_range_rule_match(mocker, config):
59+
expected_value = True
60+
mocked_app_config_schema = {
61+
"my_feature": {
62+
FEATURE_DEFAULT_VAL_KEY: False,
63+
RULES_KEY: {
64+
"lambda time is between UTC 11:11-23:59": {
65+
RULE_MATCH_VALUE: True,
66+
CONDITIONS_KEY: [
67+
{
68+
CONDITION_ACTION: RuleAction.TIME_RANGE.value, # this condition matches
69+
CONDITION_KEY: TimeKeys.CURRENT_HOUR_UTC,
70+
CONDITION_VALUE: {TimeValues.START_TIME: "11:11", TimeValues.END_TIME: "23:59"},
71+
},
72+
],
73+
}
74+
},
75+
}
76+
}
77+
# mock time for rule match
78+
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
79+
toggle = feature_flags.evaluate(
80+
name="my_feature",
81+
context={},
82+
default=False,
83+
)
84+
assert toggle == expected_value
85+
86+
87+
def test_time_based_utc_in_between_full_time_range_rule_match(mocker, config):
88+
expected_value = True
89+
mocked_app_config_schema = {
90+
"my_feature": {
91+
FEATURE_DEFAULT_VAL_KEY: False,
92+
RULES_KEY: {
93+
"lambda time is between UTC october 5th 2022 12:14:32PM to october 10th 2022 12:15:00 PM": {
94+
RULE_MATCH_VALUE: True,
95+
CONDITIONS_KEY: [
96+
{
97+
CONDITION_ACTION: RuleAction.TIME_RANGE.value, # this condition matches
98+
CONDITION_KEY: TimeKeys.CURRENT_TIME_UTC,
99+
CONDITION_VALUE: {
100+
TimeValues.START_TIME: "2022-10-05T12:15:00Z",
101+
TimeValues.END_TIME: "2022-10-10T12:15:00Z",
102+
},
103+
},
104+
],
105+
}
106+
},
107+
}
108+
}
109+
# mock time for rule match
110+
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
111+
toggle = feature_flags.evaluate(
112+
name="my_feature",
113+
context={},
114+
default=False,
115+
)
116+
assert toggle == expected_value
117+
118+
119+
def test_time_based_multiple_conditions_utc_in_between_time_range_rule_match(mocker, config):
120+
expected_value = True
121+
mocked_app_config_schema = {
122+
"my_feature": {
123+
FEATURE_DEFAULT_VAL_KEY: False,
124+
RULES_KEY: {
125+
"lambda time is between UTC 09:00-17:00 and username is ran": {
126+
RULE_MATCH_VALUE: True,
127+
CONDITIONS_KEY: [
128+
{
129+
CONDITION_ACTION: RuleAction.TIME_RANGE.value, # this condition matches
130+
CONDITION_KEY: TimeKeys.CURRENT_HOUR_UTC,
131+
CONDITION_VALUE: {TimeValues.START_TIME: "09:00", TimeValues.END_TIME: "17:00"},
132+
},
133+
{
134+
CONDITION_ACTION: RuleAction.EQUALS.value,
135+
CONDITION_KEY: "tenant",
136+
CONDITION_VALUE: {"username": "ran"},
137+
},
138+
],
139+
}
140+
},
141+
}
142+
}
143+
# mock time for rule match
144+
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
145+
toggle = feature_flags.evaluate(
146+
name="my_feature",
147+
context={"username": "ran"},
148+
default=False,
149+
)
150+
assert toggle == expected_value
151+
152+
153+
def test_time_based_utc_days_range_rule_match(mocker, config):
154+
expected_value = True
155+
mocked_app_config_schema = {
156+
"my_feature": {
157+
FEATURE_DEFAULT_VAL_KEY: False,
158+
RULES_KEY: {
159+
"match only monday through friday": {
160+
RULE_MATCH_VALUE: True,
161+
CONDITIONS_KEY: [
162+
{
163+
CONDITION_ACTION: RuleAction.TIME_SELECTED_DAYS.value, # this condition matches
164+
CONDITION_KEY: TimeKeys.CURRENT_DAY_UTC, # similar to "IN" actions
165+
CONDITION_VALUE: [
166+
TimeValues.MONDAY,
167+
TimeValues.TUESDAY,
168+
TimeValues.WEDNESDAY,
169+
TimeValues.THURSDAY,
170+
TimeValues.FRIDAY,
171+
],
172+
},
173+
],
174+
}
175+
},
176+
}
177+
}
178+
# mock time for rule match
179+
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
180+
toggle = feature_flags.evaluate(
181+
name="my_feature",
182+
context={},
183+
default=False,
184+
)
185+
assert toggle == expected_value
186+
187+
188+
def test_time_based_utc_only_weekend_rule_match(mocker, config):
189+
expected_value = True
190+
mocked_app_config_schema = {
191+
"my_feature": {
192+
FEATURE_DEFAULT_VAL_KEY: False,
193+
RULES_KEY: {
194+
"match only on weekend": {
195+
RULE_MATCH_VALUE: True,
196+
CONDITIONS_KEY: [
197+
{
198+
CONDITION_ACTION: RuleAction.TIME_SELECTED_DAYS.value, # this condition matches
199+
CONDITION_KEY: TimeKeys.CURRENT_DAY_UTC, # similar to "IN" actions
200+
CONDITION_VALUE: [TimeValues.SATURDAY, TimeValues.SUNDAY],
201+
},
202+
],
203+
}
204+
},
205+
}
206+
}
207+
# mock time for rule match
208+
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
209+
toggle = feature_flags.evaluate(
210+
name="my_feature",
211+
context={},
212+
default=False,
213+
)
214+
assert toggle == expected_value
215+
216+
217+
def test_time_based_multiple_conditions_utc_days_range_and_certain_hours_rule_match(mocker, config):
218+
expected_value = True
219+
mocked_app_config_schema = {
220+
"my_feature": {
221+
FEATURE_DEFAULT_VAL_KEY: False,
222+
RULES_KEY: {
223+
"match when lambda time is between UTC 11:00-23:00 and day is either monday or thursday": {
224+
RULE_MATCH_VALUE: True,
225+
CONDITIONS_KEY: [
226+
{
227+
CONDITION_ACTION: RuleAction.TIME_RANGE.value, # this condition matches
228+
CONDITION_KEY: TimeKeys.CURRENT_HOUR_UTC,
229+
CONDITION_VALUE: {TimeValues.START_TIME: "11:00", TimeValues.END_TIME: "23:00"},
230+
},
231+
{
232+
CONDITION_ACTION: RuleAction.TIME_SELECTED_DAYS.value, # this condition matches
233+
CONDITION_KEY: TimeKeys.CURRENT_DAY_UTC,
234+
CONDITION_VALUE: [TimeValues.MONDAY, TimeValues.THURSDAY],
235+
},
236+
],
237+
}
238+
},
239+
}
240+
}
241+
# mock time for rule match
242+
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config)
243+
toggle = feature_flags.evaluate(
244+
name="my_feature",
245+
context={},
246+
default=False,
247+
)
248+
assert toggle == expected_value

0 commit comments

Comments
 (0)