Skip to content

Commit 78ef2a6

Browse files
committed
fixup: make tests run, only 4 tests are missing now
Signed-off-by: Simon Schrottner <[email protected]>
1 parent 7fbc770 commit 78ef2a6

File tree

6 files changed

+258
-38
lines changed

6 files changed

+258
-38
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
[submodule "providers/openfeature-provider-flagd/test-harness"]
55
path = providers/openfeature-provider-flagd/test-harness
66
url = [email protected]:open-feature/flagd-testbed.git
7+
[submodule "providers/openfeature-provider-flagd/spec"]
8+
path = providers/openfeature-provider-flagd/spec
9+
url = https://github.com/open-feature/spec

providers/openfeature-provider-flagd/pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ dependencies = [
3737
"coverage[toml]>=6.5",
3838
"pytest",
3939
"pytest-bdd",
40+
"testcontainers",
41+
"asserts",
42+
"grpcio-health-checking==1.60.0",
4043
]
4144
post-install-commands = [
4245
"./scripts/gen_protos.sh"
Submodule spec added at 3c737a6
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import time
2+
from time import sleep
3+
4+
import grpc
5+
from grpc_health.v1 import health_pb2, health_pb2_grpc
6+
from testcontainers.core.container import DockerContainer
7+
from testcontainers.core.waiting_utils import wait_container_is_ready
8+
9+
HEALTH_CHECK = 8014
10+
11+
12+
class FlagDContainer(DockerContainer):
13+
def __init__(
14+
self,
15+
image: str = "ghcr.io/open-feature/flagd-testbed:v0.5.10",
16+
port: int = 8013,
17+
**kwargs,
18+
) -> None:
19+
super().__init__(image, **kwargs)
20+
self.port = port
21+
self.with_exposed_ports(self.port, HEALTH_CHECK)
22+
23+
def start(self) -> "FlagDContainer":
24+
super().start()
25+
time.sleep(1)
26+
self._checker(self.get_container_host_ip(), self.get_exposed_port(HEALTH_CHECK))
27+
return self
28+
29+
@wait_container_is_ready(ConnectionError)
30+
def _checker(self, host: str, port: str) -> None:
31+
with grpc.insecure_channel(host + ":" + port) as channel:
32+
health_stub = health_pb2_grpc.HealthStub(channel)
33+
34+
def health_check_call(stub: health_pb2_grpc.HealthStub):
35+
request = health_pb2.HealthCheckRequest()
36+
resp = stub.Check(request)
37+
if resp.status == health_pb2.HealthCheckResponse.SERVING:
38+
return True
39+
elif resp.status == health_pb2.HealthCheckResponse.NOT_SERVING:
40+
return False
41+
42+
# Should succeed
43+
# Check health status every 1 second for 30 seconds
44+
ok = False
45+
for _ in range(30):
46+
ok = health_check_call(health_stub)
47+
if ok:
48+
break
49+
sleep(1)
50+
51+
if not ok:
52+
raise ConnectionError("flagD not ready in time")

providers/openfeature-provider-flagd/tests/e2eGherkin/steps.py

Lines changed: 182 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
import typing
33

44
import pytest
5+
from asserts import assert_equal
56
from pytest_bdd import given, parsers, then, when
67
from tests.e2e.parsers import to_bool
78

89
from openfeature import api
910
from openfeature.client import OpenFeatureClient
1011
from openfeature.evaluation_context import EvaluationContext
1112
from openfeature.event import EventDetails, ProviderEvent
13+
from openfeature.flag_evaluation import ErrorCode, FlagEvaluationDetails, Reason
14+
from openfeature.provider import ProviderStatus
1215

1316
JsonPrimitive = typing.Union[str, bool, float, int]
1417

@@ -19,44 +22,44 @@ def evaluation_context() -> EvaluationContext:
1922

2023

2124
@given("a flagd provider is set", target_fixture="client")
25+
@given("a provider is registered", target_fixture="client")
2226
def setup_provider() -> OpenFeatureClient:
23-
return api.get_client()
27+
client = api.get_client()
28+
wait_for(lambda: client.get_provider_status() == ProviderStatus.READY)
29+
return client
2430

2531

2632
@when(
2733
parsers.cfparse(
28-
'a zero-value boolean flag with key "{key}" is evaluated with default value "{default:bool}"',
29-
extra_types={"bool": to_bool},
34+
'a {ignored:s?}boolean flag with key "{key}" is evaluated with {details:s?}default value "{default:bool}"',
35+
extra_types={"bool": to_bool, "s": str},
3036
),
3137
target_fixture="key_and_default",
3238
)
3339
@when(
3440
parsers.cfparse(
35-
'a zero-value string flag with key "{key}" is evaluated with default value "{default}"',
41+
'a {ignored:s?}string flag with key "{key}" is evaluated with {details:s?}default value "{default}"',
42+
extra_types={"s": str},
3643
),
3744
target_fixture="key_and_default",
3845
)
3946
@when(
4047
parsers.cfparse(
41-
'a string flag with key "{key}" is evaluated with default value "{default}"'
48+
'a{ignored:s?} integer flag with key "{key}" is evaluated with {details:s?}default value {default:d}',
49+
extra_types={"s": str},
4250
),
4351
target_fixture="key_and_default",
4452
)
4553
@when(
4654
parsers.cfparse(
47-
'a zero-value integer flag with key "{key}" is evaluated with default value {default:d}',
55+
'a {ignored:s?}float flag with key "{key}" is evaluated with {details:s?}default value {default:f}',
56+
extra_types={"s": str},
4857
),
4958
target_fixture="key_and_default",
5059
)
5160
@when(
5261
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}',
62+
'a string flag with key "{key}" is evaluated as an integer, with details and a default value {default:d}',
6063
),
6164
target_fixture="key_and_default",
6265
)
@@ -111,6 +114,12 @@ def update_context_nested(
111114
evaluation_context.attributes[outer][inner] = value
112115

113116

117+
@then(
118+
parsers.cfparse(
119+
'the resolved boolean value should be "{expected_value:bool}"',
120+
extra_types={"bool": to_bool},
121+
)
122+
)
114123
@then(
115124
parsers.cfparse(
116125
'the resolved boolean zero-value should be "{expected_value:bool}"',
@@ -125,12 +134,34 @@ def assert_boolean_value(
125134
):
126135
key, default = key_and_default
127136
evaluation_result = client.get_boolean_value(key, default, evaluation_context)
128-
assert evaluation_result == expected_value
137+
assert_equal(evaluation_result, expected_value)
129138

130139

131140
@then(
132141
parsers.cfparse(
133-
"the resolved integer zero-value should be {expected_value:d}",
142+
'the resolved boolean details value should be "{expected_value:bool}", the variant should be "{variant}", and the reason should be "{reason}"',
143+
extra_types={"bool": to_bool},
144+
)
145+
)
146+
def assert_boolean_value_with_details(
147+
client: OpenFeatureClient,
148+
key_and_default: tuple,
149+
expected_value: bool,
150+
variant: str,
151+
reason: str,
152+
evaluation_context: EvaluationContext,
153+
):
154+
key, default = key_and_default
155+
evaluation_result = client.get_boolean_details(key, default, evaluation_context)
156+
assert_equal(evaluation_result.value, expected_value)
157+
assert_equal(evaluation_result.reason, reason)
158+
assert_equal(evaluation_result.variant, variant)
159+
160+
161+
@then(
162+
parsers.cfparse(
163+
"the resolved integer {ignored:s?}value should be {expected_value:d}",
164+
extra_types={"s": str},
134165
)
135166
)
136167
@then(parsers.cfparse("the returned value should be {expected_value:d}"))
@@ -139,15 +170,36 @@ def assert_integer_value(
139170
key_and_default: tuple,
140171
expected_value: bool,
141172
evaluation_context: EvaluationContext,
173+
):
174+
key, default = key_and_default
175+
evaluation_result = client.get_integer_value(key, default, evaluation_context)
176+
assert_equal(evaluation_result, expected_value)
177+
178+
179+
@then(
180+
parsers.cfparse(
181+
'the resolved integer details value should be {expected_value:d}, the variant should be "{variant}", and the reason should be "{reason}"',
182+
)
183+
)
184+
def assert_integer_value_with_details(
185+
client: OpenFeatureClient,
186+
key_and_default: tuple,
187+
expected_value: int,
188+
variant: str,
189+
reason: str,
190+
evaluation_context: EvaluationContext,
142191
):
143192
key, default = key_and_default
144193
evaluation_result = client.get_integer_details(key, default, evaluation_context)
145-
assert evaluation_result == expected_value
194+
assert_equal(evaluation_result.value, expected_value)
195+
assert_equal(evaluation_result.reason, reason)
196+
assert_equal(evaluation_result.variant, variant)
146197

147198

148199
@then(
149200
parsers.cfparse(
150-
"the resolved float zero-value should be {expected_value:f}",
201+
"the resolved float {ignored:s?}value should be {expected_value:f}",
202+
extra_types={"s": str},
151203
)
152204
)
153205
def assert_float_value(
@@ -158,7 +210,27 @@ def assert_float_value(
158210
):
159211
key, default = key_and_default
160212
evaluation_result = client.get_float_value(key, default, evaluation_context)
161-
assert evaluation_result == expected_value
213+
assert_equal(evaluation_result, expected_value)
214+
215+
216+
@then(
217+
parsers.cfparse(
218+
'the resolved float details value should be {expected_value:f}, the variant should be "{variant}", and the reason should be "{reason}"',
219+
)
220+
)
221+
def assert_float_value_with_details(
222+
client: OpenFeatureClient,
223+
key_and_default: tuple,
224+
expected_value: float,
225+
variant: str,
226+
reason: str,
227+
evaluation_context: EvaluationContext,
228+
):
229+
key, default = key_and_default
230+
evaluation_result = client.get_float_details(key, default, evaluation_context)
231+
assert_equal(evaluation_result.value, expected_value)
232+
assert_equal(evaluation_result.reason, reason)
233+
assert_equal(evaluation_result.variant, variant)
162234

163235

164236
@then(parsers.cfparse('the returned value should be "{expected_value}"'))
@@ -169,8 +241,8 @@ def assert_string_value(
169241
evaluation_context: EvaluationContext,
170242
):
171243
key, default = key_and_default
172-
evaluation_result = client.get_string_value(key, default, evaluation_context)
173-
assert evaluation_result == expected_value
244+
evaluation_details = client.get_string_details(key, default, evaluation_context)
245+
assert_equal(evaluation_details.value, expected_value)
174246

175247

176248
@then(
@@ -182,10 +254,98 @@ def assert_empty_string(
182254
client: OpenFeatureClient,
183255
key_and_default: tuple,
184256
evaluation_context: EvaluationContext,
257+
):
258+
assert_string(client, key_and_default, evaluation_context, "")
259+
260+
261+
@then(
262+
parsers.cfparse(
263+
'the resolved string value should be "{expected_value}"',
264+
)
265+
)
266+
def assert_string(
267+
client: OpenFeatureClient,
268+
key_and_default: tuple,
269+
evaluation_context: EvaluationContext,
270+
expected_value: str,
185271
):
186272
key, default = key_and_default
187273
evaluation_result = client.get_string_value(key, default, evaluation_context)
188-
assert evaluation_result == ""
274+
assert_equal(evaluation_result, expected_value)
275+
276+
277+
@then(
278+
parsers.cfparse(
279+
"the default string value should be returned",
280+
),
281+
target_fixture="evaluation_details",
282+
)
283+
def assert_default_string(
284+
client: OpenFeatureClient,
285+
key_and_default: tuple,
286+
evaluation_context: EvaluationContext,
287+
) -> FlagEvaluationDetails[str]:
288+
key, default = key_and_default
289+
evaluation_result = client.get_string_details(key, default, evaluation_context)
290+
assert_equal(evaluation_result.value, default)
291+
return evaluation_result
292+
293+
294+
@then(
295+
parsers.cfparse(
296+
"the default integer value should be returned",
297+
),
298+
target_fixture="evaluation_details",
299+
)
300+
def assert_default_integer(
301+
client: OpenFeatureClient,
302+
key_and_default: tuple,
303+
evaluation_context: EvaluationContext,
304+
) -> FlagEvaluationDetails[int]:
305+
key, default = key_and_default
306+
evaluation_result = client.get_integer_details(key, default, evaluation_context)
307+
assert_equal(evaluation_result.value, default)
308+
return evaluation_result
309+
310+
311+
@then(
312+
parsers.cfparse(
313+
'the reason should indicate an error and the error code should indicate a missing flag with "{error}"',
314+
)
315+
)
316+
@then(
317+
parsers.cfparse(
318+
'the reason should indicate an error and the error code should indicate a type mismatch with "{error}"',
319+
)
320+
)
321+
def assert_for_error(
322+
client: OpenFeatureClient,
323+
evaluation_details: FlagEvaluationDetails,
324+
error: str,
325+
):
326+
assert_equal(evaluation_details.error_code, ErrorCode[error])
327+
assert_equal(evaluation_details.reason, Reason.ERROR)
328+
329+
330+
@then(
331+
parsers.cfparse(
332+
'the resolved string details value should be "{expected_value}", the variant should be "{variant}", and the reason should be "{reason}"',
333+
extra_types={"bool": to_bool},
334+
)
335+
)
336+
def assert_string_value_with_details(
337+
client: OpenFeatureClient,
338+
key_and_default: tuple,
339+
expected_value: str,
340+
variant: str,
341+
reason: str,
342+
evaluation_context: EvaluationContext,
343+
):
344+
key, default = key_and_default
345+
evaluation_result = client.get_string_details(key, default, evaluation_context)
346+
assert_equal(evaluation_result.value, expected_value)
347+
assert_equal(evaluation_result.reason, reason)
348+
assert_equal(evaluation_result.variant, variant)
189349

190350

191351
@then(parsers.cfparse('the returned reason should be "{reason}"'))
@@ -198,7 +358,7 @@ def assert_reason(
198358
"""the returned reason should be <reason>."""
199359
key, default = key_and_default
200360
evaluation_result = client.get_string_details(key, default, evaluation_context)
201-
assert evaluation_result.reason.value == reason
361+
assert_equal(evaluation_result.reason, reason)
202362

203363

204364
provider_ready_ran = False

0 commit comments

Comments
 (0)