|
| 1 | +import time |
| 2 | +import typing |
| 3 | + |
| 4 | +import pytest |
| 5 | +from pytest_bdd import given, parsers, then, when |
| 6 | +from tests.e2e.parsers import to_bool |
| 7 | + |
| 8 | +from openfeature import api |
| 9 | +from openfeature.client import OpenFeatureClient |
| 10 | +from openfeature.evaluation_context import EvaluationContext |
| 11 | +from openfeature.event import EventDetails, ProviderEvent |
| 12 | + |
| 13 | +JsonPrimitive = typing.Union[str, bool, float, int] |
| 14 | + |
| 15 | + |
| 16 | +@pytest.fixture |
| 17 | +def evaluation_context() -> EvaluationContext: |
| 18 | + return EvaluationContext() |
| 19 | + |
| 20 | + |
| 21 | +@given("a flagd provider is set", target_fixture="client") |
| 22 | +def setup_provider() -> OpenFeatureClient: |
| 23 | + return api.get_client() |
| 24 | + |
| 25 | + |
| 26 | +@when( |
| 27 | + parsers.cfparse( |
| 28 | + 'a zero-value boolean flag with key "{key}" is evaluated with default value "{default:bool}"', |
| 29 | + extra_types={"bool": to_bool}, |
| 30 | + ), |
| 31 | + target_fixture="key_and_default", |
| 32 | +) |
| 33 | +@when( |
| 34 | + parsers.cfparse( |
| 35 | + 'a zero-value string flag with key "{key}" is evaluated with default value "{default}"', |
| 36 | + ), |
| 37 | + target_fixture="key_and_default", |
| 38 | +) |
| 39 | +@when( |
| 40 | + parsers.cfparse( |
| 41 | + 'a string flag with key "{key}" is evaluated with default value "{default}"' |
| 42 | + ), |
| 43 | + target_fixture="key_and_default", |
| 44 | +) |
| 45 | +@when( |
| 46 | + parsers.cfparse( |
| 47 | + 'a zero-value integer flag with key "{key}" is evaluated with default value {default:d}', |
| 48 | + ), |
| 49 | + target_fixture="key_and_default", |
| 50 | +) |
| 51 | +@when( |
| 52 | + parsers.cfparse( |
| 53 | + 'an integer flag with key "{key}" is evaluated with default value {default:d}', |
| 54 | + ), |
| 55 | + target_fixture="key_and_default", |
| 56 | +) |
| 57 | +@when( |
| 58 | + parsers.cfparse( |
| 59 | + 'a zero-value float flag with key "{key}" is evaluated with default value {default:f}', |
| 60 | + ), |
| 61 | + target_fixture="key_and_default", |
| 62 | +) |
| 63 | +def setup_key_and_default( |
| 64 | + key: str, default: JsonPrimitive |
| 65 | +) -> typing.Tuple[str, JsonPrimitive]: |
| 66 | + return (key, default) |
| 67 | + |
| 68 | + |
| 69 | +@when( |
| 70 | + parsers.cfparse( |
| 71 | + 'a context containing a targeting key with value "{targeting_key}"' |
| 72 | + ), |
| 73 | +) |
| 74 | +def assign_targeting_context(evaluation_context: EvaluationContext, targeting_key: str): |
| 75 | + """a context containing a targeting key with value <targeting key>.""" |
| 76 | + evaluation_context.targeting_key = targeting_key |
| 77 | + |
| 78 | + |
| 79 | +@when( |
| 80 | + parsers.cfparse('a context containing a key "{key}", with value "{value}"'), |
| 81 | +) |
| 82 | +@when( |
| 83 | + parsers.cfparse('a context containing a key "{key}", with value {value:d}'), |
| 84 | +) |
| 85 | +def update_context( |
| 86 | + evaluation_context: EvaluationContext, key: str, value: JsonPrimitive |
| 87 | +): |
| 88 | + """a context containing a key and value.""" |
| 89 | + evaluation_context.attributes[key] = value |
| 90 | + |
| 91 | + |
| 92 | +@when( |
| 93 | + parsers.cfparse( |
| 94 | + 'a context containing a nested property with outer key "{outer}" and inner key "{inner}", with value "{value}"' |
| 95 | + ), |
| 96 | +) |
| 97 | +@when( |
| 98 | + parsers.cfparse( |
| 99 | + 'a context containing a nested property with outer key "{outer}" and inner key "{inner}", with value {value:d}' |
| 100 | + ), |
| 101 | +) |
| 102 | +def update_context_nested( |
| 103 | + evaluation_context: EvaluationContext, |
| 104 | + outer: str, |
| 105 | + inner: str, |
| 106 | + value: typing.Union[str, int], |
| 107 | +): |
| 108 | + """a context containing a nested property with outer key, and inner key, and value.""" |
| 109 | + if outer not in evaluation_context.attributes: |
| 110 | + evaluation_context.attributes[outer] = {} |
| 111 | + evaluation_context.attributes[outer][inner] = value |
| 112 | + |
| 113 | + |
| 114 | +@then( |
| 115 | + parsers.cfparse( |
| 116 | + 'the resolved boolean zero-value should be "{expected_value:bool}"', |
| 117 | + extra_types={"bool": to_bool}, |
| 118 | + ) |
| 119 | +) |
| 120 | +def assert_boolean_value( |
| 121 | + client: OpenFeatureClient, |
| 122 | + key_and_default: tuple, |
| 123 | + expected_value: bool, |
| 124 | + evaluation_context: EvaluationContext, |
| 125 | +): |
| 126 | + key, default = key_and_default |
| 127 | + evaluation_result = client.get_boolean_value(key, default, evaluation_context) |
| 128 | + assert evaluation_result == expected_value |
| 129 | + |
| 130 | + |
| 131 | +@then( |
| 132 | + parsers.cfparse( |
| 133 | + "the resolved integer zero-value should be {expected_value:d}", |
| 134 | + ) |
| 135 | +) |
| 136 | +@then(parsers.cfparse("the returned value should be {expected_value:d}")) |
| 137 | +def assert_integer_value( |
| 138 | + client: OpenFeatureClient, |
| 139 | + key_and_default: tuple, |
| 140 | + expected_value: bool, |
| 141 | + evaluation_context: EvaluationContext, |
| 142 | +): |
| 143 | + key, default = key_and_default |
| 144 | + evaluation_result = client.get_integer_details(key, default, evaluation_context) |
| 145 | + assert evaluation_result == expected_value |
| 146 | + |
| 147 | + |
| 148 | +@then( |
| 149 | + parsers.cfparse( |
| 150 | + "the resolved float zero-value should be {expected_value:f}", |
| 151 | + ) |
| 152 | +) |
| 153 | +def assert_float_value( |
| 154 | + client: OpenFeatureClient, |
| 155 | + key_and_default: tuple, |
| 156 | + expected_value: bool, |
| 157 | + evaluation_context: EvaluationContext, |
| 158 | +): |
| 159 | + key, default = key_and_default |
| 160 | + evaluation_result = client.get_float_value(key, default, evaluation_context) |
| 161 | + assert evaluation_result == expected_value |
| 162 | + |
| 163 | + |
| 164 | +@then(parsers.cfparse('the returned value should be "{expected_value}"')) |
| 165 | +def assert_string_value( |
| 166 | + client: OpenFeatureClient, |
| 167 | + key_and_default: tuple, |
| 168 | + expected_value: bool, |
| 169 | + evaluation_context: EvaluationContext, |
| 170 | +): |
| 171 | + key, default = key_and_default |
| 172 | + evaluation_result = client.get_string_value(key, default, evaluation_context) |
| 173 | + assert evaluation_result == expected_value |
| 174 | + |
| 175 | + |
| 176 | +@then( |
| 177 | + parsers.cfparse( |
| 178 | + 'the resolved string zero-value should be ""', |
| 179 | + ) |
| 180 | +) |
| 181 | +def assert_empty_string( |
| 182 | + client: OpenFeatureClient, |
| 183 | + key_and_default: tuple, |
| 184 | + evaluation_context: EvaluationContext, |
| 185 | +): |
| 186 | + key, default = key_and_default |
| 187 | + evaluation_result = client.get_string_value(key, default, evaluation_context) |
| 188 | + assert evaluation_result == "" |
| 189 | + |
| 190 | + |
| 191 | +@then(parsers.cfparse('the returned reason should be "{reason}"')) |
| 192 | +def assert_reason( |
| 193 | + client: OpenFeatureClient, |
| 194 | + key_and_default: tuple, |
| 195 | + evaluation_context: EvaluationContext, |
| 196 | + reason: str, |
| 197 | +): |
| 198 | + """the returned reason should be <reason>.""" |
| 199 | + key, default = key_and_default |
| 200 | + evaluation_result = client.get_string_details(key, default, evaluation_context) |
| 201 | + assert evaluation_result.reason.value == reason |
| 202 | + |
| 203 | + |
| 204 | +provider_ready_ran = False |
| 205 | + |
| 206 | + |
| 207 | +@when(parsers.cfparse("a PROVIDER_READY handler is added")) |
| 208 | +def provider_ready_add(client: OpenFeatureClient): |
| 209 | + client.add_handler(ProviderEvent.PROVIDER_READY, provider_ready_handler) |
| 210 | + |
| 211 | + |
| 212 | +def provider_ready_handler(event_details: EventDetails): |
| 213 | + global provider_ready_ran |
| 214 | + provider_ready_ran = True |
| 215 | + |
| 216 | + |
| 217 | +@then(parsers.cfparse("the PROVIDER_READY handler must run")) |
| 218 | +def provider_ready_was_executed(client: OpenFeatureClient): |
| 219 | + assert provider_ready_ran |
| 220 | + |
| 221 | + |
| 222 | +provider_changed_ran = False |
| 223 | + |
| 224 | + |
| 225 | +@when(parsers.cfparse("a PROVIDER_CONFIGURATION_CHANGED handler is added")) |
| 226 | +def provider_changed_add(client: OpenFeatureClient): |
| 227 | + client.add_handler( |
| 228 | + ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, provider_changed_handler |
| 229 | + ) |
| 230 | + |
| 231 | + |
| 232 | +def provider_changed_handler(event_details: EventDetails): |
| 233 | + global provider_changed_ran |
| 234 | + provider_changed_ran = True |
| 235 | + |
| 236 | + |
| 237 | +@pytest.fixture(scope="function") |
| 238 | +def context(): |
| 239 | + return {} |
| 240 | + |
| 241 | + |
| 242 | +@when(parsers.cfparse('a flag with key "{flag_key}" is modified')) |
| 243 | +def assert_reason2( |
| 244 | + client: OpenFeatureClient, |
| 245 | + context, |
| 246 | + flag_key: str, |
| 247 | +): |
| 248 | + context["flag_key"] = flag_key |
| 249 | + |
| 250 | + |
| 251 | +@then(parsers.cfparse("the PROVIDER_CONFIGURATION_CHANGED handler must run")) |
| 252 | +def provider_changed_was_executed(client: OpenFeatureClient): |
| 253 | + wait_for(lambda: provider_changed_ran) |
| 254 | + assert provider_changed_ran |
| 255 | + |
| 256 | + |
| 257 | +def wait_for(pred, poll_sec=2, timeout_sec=10): |
| 258 | + start = time.time() |
| 259 | + while not (ok := pred()) and (time.time() - start < timeout_sec): |
| 260 | + time.sleep(poll_sec) |
| 261 | + assert pred() |
| 262 | + return ok |
0 commit comments