@@ -434,6 +434,123 @@ async def test_typecheck_flag_value_general_error():
434434 assert err .error_message == "Unknown flag type"
435435
436436
437+ @pytest .mark .parametrize (
438+ "flag_type, flag_value, default_value" ,
439+ [
440+ ("integer" , True , 1 ),
441+ ("integer" , False , 0 ),
442+ ("float" , True , 1.0 ),
443+ ("float" , False , 0.0 ),
444+ ("boolean" , 1 , False ),
445+ ("integer" , "1" , 0 ),
446+ ("float" , 1 , 0.0 ),
447+ ("string" , True , "fallback" ),
448+ ("object" , True , {}),
449+ ],
450+ )
451+ @pytest .mark .parametrize ("is_async" , [False , True ], ids = ["sync" , "async" ])
452+ @pytest .mark .asyncio
453+ async def test_client_returns_default_on_type_mismatch (
454+ flag_type , flag_value , default_value , is_async
455+ ):
456+ provider = InMemoryProvider (
457+ {"flag" : InMemoryFlag ("enabled" , {"enabled" : flag_value })}
458+ )
459+ api .set_provider_and_wait (provider )
460+ client = get_client ()
461+ spy_hook = MagicMock (spec = Hook )
462+ client .add_hooks ([spy_hook ])
463+ suffix = "_async" if is_async else ""
464+
465+ get_details = getattr (client , f"get_{ flag_type } _details{ suffix } " )
466+ details = get_details ("flag" , default_value )
467+ if is_async :
468+ details = await details
469+
470+ assert details .flag_key == "flag"
471+ assert details .value == default_value
472+ # Equality alone cannot distinguish True from 1 (or False from 0).
473+ assert type (details .value ) is type (default_value )
474+ assert details .reason == Reason .ERROR
475+ assert details .error_code == ErrorCode .TYPE_MISMATCH
476+ expected_type = (dict , list ) if flag_type == "object" else type (default_value )
477+ assert (
478+ details .error_message
479+ == f"Expected type { expected_type } but got { type (flag_value )} "
480+ )
481+ spy_hook .error .assert_called_once ()
482+ assert (
483+ spy_hook .error .call_args .kwargs ["exception" ].error_code
484+ == ErrorCode .TYPE_MISMATCH
485+ )
486+ spy_hook .after .assert_not_called ()
487+ spy_hook .finally_after .assert_called_once ()
488+ assert spy_hook .finally_after .call_args .kwargs ["details" ].value == default_value
489+ assert type (spy_hook .finally_after .call_args .kwargs ["details" ].value ) is type (
490+ default_value
491+ )
492+
493+ get_value = getattr (client , f"get_{ flag_type } _value{ suffix } " )
494+ value = get_value ("flag" , default_value )
495+ if is_async :
496+ value = await value
497+ assert value == default_value
498+ assert type (value ) is type (default_value )
499+
500+
501+ @pytest .mark .parametrize (
502+ "flag_type, flag_value, default_value" ,
503+ [
504+ ("boolean" , True , False ),
505+ ("boolean" , False , True ),
506+ ("integer" , 0 , - 1 ),
507+ ("integer" , 1 , - 1 ),
508+ ("integer" , - 1 , 0 ),
509+ ("float" , 0.0 , - 1.0 ),
510+ ("float" , 1.5 , - 1.0 ),
511+ ("string" , "enabled" , "default" ),
512+ ("object" , {"enabled" : True }, {}),
513+ ("object" , [True , 1 , "enabled" ], []),
514+ ],
515+ )
516+ @pytest .mark .parametrize ("is_async" , [False , True ], ids = ["sync" , "async" ])
517+ @pytest .mark .asyncio
518+ async def test_client_preserves_matching_flag_types (
519+ flag_type , flag_value , default_value , is_async
520+ ):
521+ provider = InMemoryProvider (
522+ {"flag" : InMemoryFlag ("enabled" , {"enabled" : flag_value })}
523+ )
524+ api .set_provider_and_wait (provider )
525+ client = get_client ()
526+ suffix = "_async" if is_async else ""
527+
528+ get_details = getattr (client , f"get_{ flag_type } _details{ suffix } " )
529+ details = get_details ("flag" , default_value )
530+ if is_async :
531+ details = await details
532+
533+ assert details .value == flag_value
534+ assert type (details .value ) is type (flag_value )
535+ assert details .variant == "enabled"
536+ assert details .reason == Reason .STATIC
537+ assert details .error_code is None
538+
539+ get_value = getattr (client , f"get_{ flag_type } _value{ suffix } " )
540+ value = get_value ("flag" , default_value )
541+ if is_async :
542+ value = await value
543+ assert value == flag_value
544+ assert type (value ) is type (flag_value )
545+
546+
547+ def test_typecheck_flag_value_accepts_integer_subclasses ():
548+ class IntegerValue (int ):
549+ pass
550+
551+ assert _typecheck_flag_value (IntegerValue (1 ), FlagType .INTEGER ) is None
552+
553+
437554@pytest .mark .asyncio
438555async def test_typecheck_flag_value_type_mismatch_error ():
439556 # Given
0 commit comments