@@ -172,162 +172,207 @@ def test_get_flag_without_pipeline_processor() -> None:
172172 assert flag .enabled is True
173173
174174
175- def _make_lazy_context (
176- * ,
177- extra_features : int = 2 ,
178- identity_trait_value : str = "premium" ,
179- segment_match_value : str = "premium" ,
180- ) -> SDKEvaluationContext :
181- """Build a minimal evaluation context for lazy-Flags tests.
175+ LazyContextFactory = typing .Callable [..., SDKEvaluationContext ]
176+
177+
178+ @pytest .fixture
179+ def lazy_context_factory () -> LazyContextFactory :
180+ """Factory for minimal evaluation contexts used by the lazy-Flags tests.
182181
183- Structure: a "target" feature with a single segment override that
184- matches when ``tier == segment_match_value`` (priority 0), plus a
185- handful of no-override "noise" features whose values should come
186- straight off the base feature context. ``identity_trait_value`` sets
187- the identity's ``tier`` trait so tests can exercise match / no-match.
182+ The returned context has a ``target`` feature with a single segment
183+ override (matches when ``tier == segment_match_value``, priority 0)
184+ plus ``extra_features`` no-override "noise" features whose values
185+ should come straight off the base feature context.
188186 """
189- features : typing .Dict [str , typing .Any ] = {
190- "target" : {
191- "key" : "target" ,
192- "name" : "target" ,
193- "enabled" : False ,
194- "value" : "base-value" ,
195- "metadata" : {"id" : 1 },
196- },
197- }
198- for i in range (extra_features ):
199- features [f"noise_{ i } " ] = {
200- "key" : f"noise_{ i } " ,
201- "name" : f"noise_{ i } " ,
202- "enabled" : True ,
203- "value" : f"noise-value-{ i } " ,
204- "metadata" : {"id" : 100 + i },
187+
188+ def make (
189+ * ,
190+ extra_features : int = 2 ,
191+ identity_trait_value : str = "premium" ,
192+ segment_match_value : str = "premium" ,
193+ ) -> SDKEvaluationContext :
194+ features : typing .Dict [str , typing .Any ] = {
195+ "target" : {
196+ "key" : "target" ,
197+ "name" : "target" ,
198+ "enabled" : False ,
199+ "value" : "base-value" ,
200+ "metadata" : {"id" : 1 },
201+ },
205202 }
206- return {
207- "environment" : {"key" : "env-key" , "name" : "env" },
208- "features" : features ,
209- "segments" : {
210- "premium_segment" : {
211- "key" : "premium_segment" ,
212- "name" : "premium_segment" ,
213- "rules" : [
214- {
215- "type" : "ALL" ,
216- "conditions" : [
217- {
218- "property" : "tier" ,
219- "operator" : "EQUAL" ,
220- "value" : segment_match_value ,
221- },
222- ],
223- }
224- ],
225- "overrides" : [
226- {
227- "key" : "target" ,
228- "name" : "target" ,
229- "enabled" : True ,
230- "value" : "premium-value" ,
231- "priority" : 0.0 ,
232- "metadata" : {"id" : 1 },
233- },
234- ],
203+ for i in range (extra_features ):
204+ features [f"noise_{ i } " ] = {
205+ "key" : f"noise_{ i } " ,
206+ "name" : f"noise_{ i } " ,
207+ "enabled" : True ,
208+ "value" : f"noise-value-{ i } " ,
209+ "metadata" : {"id" : 100 + i },
210+ }
211+ return {
212+ "environment" : {"key" : "env-key" , "name" : "env" },
213+ "features" : features ,
214+ "segments" : {
215+ "premium_segment" : {
216+ "key" : "premium_segment" ,
217+ "name" : "premium_segment" ,
218+ "rules" : [
219+ {
220+ "type" : "ALL" ,
221+ "conditions" : [
222+ {
223+ "property" : "tier" ,
224+ "operator" : "EQUAL" ,
225+ "value" : segment_match_value ,
226+ },
227+ ],
228+ }
229+ ],
230+ "overrides" : [
231+ {
232+ "key" : "target" ,
233+ "name" : "target" ,
234+ "enabled" : True ,
235+ "value" : "premium-value" ,
236+ "priority" : 0.0 ,
237+ "metadata" : {"id" : 1 },
238+ },
239+ ],
240+ },
235241 },
236- },
237- "identity" : {
238- "identifier" : "user-1" ,
239- "key" : "env-key_user-1" ,
240- "traits" : {"tier" : identity_trait_value },
241- },
242- }
242+ "identity" : {
243+ "identifier" : "user-1" ,
244+ "key" : "env-key_user-1" ,
245+ "traits" : {"tier" : identity_trait_value },
246+ },
247+ }
243248
249+ return make
244250
245- def test_lazy_flags__get_flag__applies_matching_segment_override () -> None :
246- ctx = _make_lazy_context ()
247- flags = Flags .from_evaluation_context (
248- context = ctx ,
249- overrides_index = build_segment_overrides_index (ctx ),
251+
252+ @pytest .fixture
253+ def lazy_context (
254+ lazy_context_factory : LazyContextFactory ,
255+ ) -> SDKEvaluationContext :
256+ """Default evaluation context: identity matches the segment override."""
257+ return lazy_context_factory ()
258+
259+
260+ @pytest .fixture
261+ def lazy_flags (lazy_context : SDKEvaluationContext ) -> Flags :
262+ """Lazy ``Flags`` built from the default context, no analytics, no handler."""
263+ return Flags .from_evaluation_context (
264+ context = lazy_context ,
265+ overrides_index = build_segment_overrides_index (lazy_context ),
250266 analytics_processor = None ,
251267 default_flag_handler = None ,
252268 )
253269
254- target = flags .get_flag ("target" )
270+
271+ def test_lazy_flags__get_flag__applies_matching_segment_override (
272+ lazy_flags : Flags ,
273+ ) -> None :
274+ # Given: identity matches the segment rule (default context).
275+ # When: we read the targeted feature.
276+ target = lazy_flags .get_flag ("target" )
277+ # Then: the override wins over the base feature value.
255278 assert target .enabled is True
256279 assert target .value == "premium-value"
257280
258281
259- def test_lazy_flags__get_flag__skips_non_matching_segment_override () -> None :
260- # Segment rule requires tier == "premium"; identity has tier "free" ,
261- # so the override must not win and base-value should come through.
262- ctx = _make_lazy_context ( identity_trait_value = " free")
263-
282+ def test_lazy_flags__get_flag__skips_non_matching_segment_override (
283+ lazy_context_factory : LazyContextFactory ,
284+ ) -> None :
285+ # Given: segment rule requires tier == "premium" but identity has tier " free".
286+ ctx = lazy_context_factory ( identity_trait_value = "free" )
264287 flags = Flags .from_evaluation_context (
265288 context = ctx ,
266289 overrides_index = build_segment_overrides_index (ctx ),
267290 analytics_processor = None ,
268291 default_flag_handler = None ,
269292 )
293+
294+ # When: we read the targeted feature.
270295 target = flags .get_flag ("target" )
296+
297+ # Then: the override doesn't win and base-value comes through.
271298 assert target .enabled is False
272299 assert target .value == "base-value"
273300
274301
275- def test_lazy_flags__get_flag__caches_per_feature () -> None :
276- ctx = _make_lazy_context (extra_features = 5 )
302+ def test_lazy_flags__get_flag__caches_per_feature (
303+ lazy_context_factory : LazyContextFactory ,
304+ ) -> None :
305+ # Given: a context with several no-override features.
306+ ctx = lazy_context_factory (extra_features = 5 )
277307 flags = Flags .from_evaluation_context (
278308 context = ctx ,
279309 overrides_index = build_segment_overrides_index (ctx ),
280310 analytics_processor = None ,
281311 default_flag_handler = None ,
282312 )
283313
314+ # When: we read a single feature once.
284315 flags .get_flag ("noise_0" )
285- # Only the accessed feature is populated.
316+
317+ # Then: only that feature is populated in the cache.
286318 assert set (flags .flags .keys ()) == {"noise_0" }
287319
288- # A repeated read hits the cache rather than rebuilding the Flag .
320+ # And: repeated reads return the same Flag instance, not a rebuild .
289321 first = flags .get_flag ("noise_0" )
290322 second = flags .get_flag ("noise_0" )
291323 assert first is second
292324
293325
294- def test_lazy_flags__all_flags__materialises_every_feature () -> None :
295- ctx = _make_lazy_context (extra_features = 3 )
326+ def test_lazy_flags__all_flags__materialises_every_feature (
327+ lazy_context_factory : LazyContextFactory ,
328+ ) -> None :
329+ # Given: a context with three no-override features plus the target.
330+ ctx = lazy_context_factory (extra_features = 3 )
296331 flags = Flags .from_evaluation_context (
297332 context = ctx ,
298333 overrides_index = build_segment_overrides_index (ctx ),
299334 analytics_processor = None ,
300335 default_flag_handler = None ,
301336 )
302337
338+ # When: the caller asks for the full set.
303339 materialised = flags .all_flags ()
340+
341+ # Then: every feature in the context is present.
304342 names = {flag .feature_name for flag in materialised }
305343 assert names == {"target" , "noise_0" , "noise_1" , "noise_2" }
306- # Second call is a no-op: everything is already resolved.
307- assert flags .all_flags () == materialised
308344
345+ # And: a second call is a no-op — everything is already resolved.
346+ assert flags .all_flags () == materialised
309347
310- def test_lazy_flags__missing_feature__falls_through_to_default_handler () -> None :
311- ctx = _make_lazy_context ()
312348
349+ def test_lazy_flags__missing_feature__falls_through_to_default_handler (
350+ lazy_context : SDKEvaluationContext ,
351+ ) -> None :
352+ # Given: a Flags wired to a default-flag handler.
313353 def default (name : str ) -> DefaultFlag :
314354 return DefaultFlag (enabled = False , value = f"default-for-{ name } " )
315355
316356 flags = Flags .from_evaluation_context (
317- context = ctx ,
318- overrides_index = build_segment_overrides_index (ctx ),
357+ context = lazy_context ,
358+ overrides_index = build_segment_overrides_index (lazy_context ),
319359 analytics_processor = None ,
320360 default_flag_handler = default ,
321361 )
362+
363+ # When: we ask for a feature that isn't in the context.
322364 result = flags .get_flag ("does_not_exist" )
365+
366+ # Then: the handler produces the default flag for that name.
323367 assert result .value == "default-for-does_not_exist"
324368
325369
326- def test_build_segment_overrides_index__indexes_only_overriding_segments () -> None :
327- ctx = _make_lazy_context ()
328- # Add a second segment without overrides — must not appear in the index.
329- assert ctx ["segments" ] is not None
330- ctx ["segments" ]["no_override_segment" ] = {
370+ def test_build_segment_overrides_index__indexes_only_overriding_segments (
371+ lazy_context : SDKEvaluationContext ,
372+ ) -> None :
373+ # Given: a second segment with no overrides on top of the default context.
374+ assert lazy_context ["segments" ] is not None
375+ lazy_context ["segments" ]["no_override_segment" ] = {
331376 "key" : "no_override_segment" ,
332377 "name" : "no_override_segment" ,
333378 "rules" : [
@@ -340,6 +385,9 @@ def test_build_segment_overrides_index__indexes_only_overriding_segments() -> No
340385 ],
341386 }
342387
343- index = build_segment_overrides_index (ctx )
388+ # When: we build the reverse index.
389+ index = build_segment_overrides_index (lazy_context )
390+
391+ # Then: only segments that actually carry an override appear.
344392 assert set (index ) == {"target" }
345393 assert index ["target" ][0 ]["name" ] == "premium_segment"
0 commit comments