Skip to content

Commit 86c64df

Browse files
committed
add test coverage, async providers calling sync calls, async only client
Signed-off-by: leohoare <[email protected]>
1 parent 3338f29 commit 86c64df

File tree

3 files changed

+309
-31
lines changed

3 files changed

+309
-31
lines changed

tests/provider/test_in_memory_provider.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,9 @@ async def test_should_resolve_list_flag_from_in_memory():
154154
)
155155
# When
156156
flag_sync = provider.resolve_object_details(flag_key="Key", default_value=[])
157-
flag_async = provider.resolve_object_details(flag_key="Key", default_value=[])
157+
flag_async = await provider.resolve_object_details_async(
158+
flag_key="Key", default_value=[]
159+
)
158160
# Then
159161
assert flag_sync == flag_async
160162
for flag in [flag_sync, flag_async]:
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import asyncio
2+
from typing import Optional, Union
3+
4+
import pytest
5+
6+
from openfeature.api import OpenFeatureClient, get_client, set_provider
7+
from openfeature.evaluation_context import EvaluationContext
8+
from openfeature.flag_evaluation import FlagResolutionDetails
9+
from openfeature.provider import AbstractProvider, Metadata
10+
11+
12+
class SynchronousProvider(AbstractProvider):
13+
def get_metadata(self):
14+
return Metadata(name="SynchronousProvider")
15+
16+
def get_provider_hooks(self):
17+
return []
18+
19+
def resolve_boolean_details(
20+
self,
21+
flag_key: str,
22+
default_value: bool,
23+
evaluation_context: Optional[EvaluationContext] = None,
24+
) -> FlagResolutionDetails[bool]:
25+
return FlagResolutionDetails(value=True)
26+
27+
def resolve_string_details(
28+
self,
29+
flag_key: str,
30+
default_value: str,
31+
evaluation_context: Optional[EvaluationContext] = None,
32+
) -> FlagResolutionDetails[str]:
33+
return FlagResolutionDetails(value="string")
34+
35+
def resolve_integer_details(
36+
self,
37+
flag_key: str,
38+
default_value: int,
39+
evaluation_context: Optional[EvaluationContext] = None,
40+
) -> FlagResolutionDetails[int]:
41+
return FlagResolutionDetails(value=1)
42+
43+
def resolve_float_details(
44+
self,
45+
flag_key: str,
46+
default_value: float,
47+
evaluation_context: Optional[EvaluationContext] = None,
48+
) -> FlagResolutionDetails[float]:
49+
return FlagResolutionDetails(value=10.0)
50+
51+
def resolve_object_details(
52+
self,
53+
flag_key: str,
54+
default_value: Union[dict, list],
55+
evaluation_context: Optional[EvaluationContext] = None,
56+
) -> FlagResolutionDetails[Union[dict, list]]:
57+
return FlagResolutionDetails(value={"key": "value"})
58+
59+
60+
@pytest.mark.parametrize(
61+
"flag_type, default_value, get_method",
62+
(
63+
(bool, True, "get_boolean_value_async"),
64+
(str, "string", "get_string_value_async"),
65+
(int, 1, "get_integer_value_async"),
66+
(float, 10.0, "get_float_value_async"),
67+
(
68+
dict,
69+
{"key": "value"},
70+
"get_object_value_async",
71+
),
72+
),
73+
)
74+
@pytest.mark.asyncio
75+
async def test_sync_provider_can_be_called_async(flag_type, default_value, get_method):
76+
# Given
77+
set_provider(SynchronousProvider(), "SynchronousProvider")
78+
client = get_client("SynchronousProvider")
79+
# When
80+
async_callable = getattr(client, get_method)
81+
flag = await async_callable(flag_key="Key", default_value=default_value)
82+
# Then
83+
assert flag is not None
84+
assert flag == default_value
85+
assert isinstance(flag, flag_type)
86+
87+
88+
@pytest.mark.asyncio
89+
async def test_sync_provider_can_be_extended_async():
90+
# Given
91+
class ExtendedAsyncProvider(SynchronousProvider):
92+
async def resolve_boolean_details_async(
93+
self,
94+
flag_key: str,
95+
default_value: bool,
96+
evaluation_context: Optional[EvaluationContext] = None,
97+
) -> FlagResolutionDetails[bool]:
98+
return FlagResolutionDetails(value=False)
99+
100+
set_provider(ExtendedAsyncProvider(), "ExtendedAsyncProvider")
101+
client = get_client("ExtendedAsyncProvider")
102+
# When
103+
flag = await client.get_boolean_value_async(flag_key="Key", default_value=True)
104+
# Then
105+
assert flag is not None
106+
assert flag is False
107+
108+
109+
# We're not allowing providers to only have async methods
110+
def test_sync_methods_enforced_for_async_providers():
111+
# Given
112+
class AsyncProvider(AbstractProvider):
113+
def get_metadata(self):
114+
return Metadata(name="AsyncProvider")
115+
116+
async def resolve_boolean_details_async(
117+
self,
118+
flag_key: str,
119+
default_value: bool,
120+
evaluation_context: Optional[EvaluationContext] = None,
121+
) -> FlagResolutionDetails[bool]:
122+
return FlagResolutionDetails(value=True)
123+
124+
# When
125+
with pytest.raises(TypeError) as exception:
126+
set_provider(AsyncProvider(), "AsyncProvider")
127+
128+
# Then
129+
# assert
130+
assert str(exception.value).startswith(
131+
"Can't instantiate abstract class AsyncProvider with abstract methods resolve_boolean_details"
132+
)
133+
134+
135+
@pytest.mark.asyncio
136+
async def test_async_provider_not_implemented_exception_workaround():
137+
# Given
138+
class SyncNotImplementedProvider(AbstractProvider):
139+
def get_metadata(self):
140+
return Metadata(name="AsyncProvider")
141+
142+
async def resolve_boolean_details_async(
143+
self,
144+
flag_key: str,
145+
default_value: bool,
146+
evaluation_context: Optional[EvaluationContext] = None,
147+
) -> FlagResolutionDetails[bool]:
148+
return FlagResolutionDetails(value=True)
149+
150+
def resolve_boolean_details(
151+
self,
152+
flag_key: str,
153+
default_value: bool,
154+
evaluation_context: Optional[EvaluationContext] = None,
155+
) -> FlagResolutionDetails[bool]:
156+
raise NotImplementedError("Use the async method")
157+
158+
def resolve_string_details(
159+
self,
160+
flag_key: str,
161+
default_value: str,
162+
evaluation_context: Optional[EvaluationContext] = None,
163+
) -> FlagResolutionDetails[str]:
164+
raise NotImplementedError("Use the async method")
165+
166+
def resolve_integer_details(
167+
self,
168+
flag_key: str,
169+
default_value: int,
170+
evaluation_context: Optional[EvaluationContext] = None,
171+
) -> FlagResolutionDetails[int]:
172+
raise NotImplementedError("Use the async method")
173+
174+
def resolve_float_details(
175+
self,
176+
flag_key: str,
177+
default_value: float,
178+
evaluation_context: Optional[EvaluationContext] = None,
179+
) -> FlagResolutionDetails[float]:
180+
raise NotImplementedError("Use the async method")
181+
182+
def resolve_object_details(
183+
self,
184+
flag_key: str,
185+
default_value: Union[dict, list],
186+
evaluation_context: Optional[EvaluationContext] = None,
187+
) -> FlagResolutionDetails[Union[dict, list]]:
188+
raise NotImplementedError("Use the async method")
189+
190+
# When
191+
set_provider(SyncNotImplementedProvider(), "SyncNotImplementedProvider")
192+
client = get_client("SyncNotImplementedProvider")
193+
flag = await client.get_boolean_value_async(flag_key="Key", default_value=False)
194+
# Then
195+
assert flag is not None
196+
assert flag is True

0 commit comments

Comments
 (0)