@@ -666,3 +666,159 @@ def test_should_noop_if_provider_does_not_support_tracking(monkeypatch):
666666 set_provider (provider )
667667 client = get_client ()
668668 client .track (tracking_event_name = "test" )
669+
670+
671+ def test_client_set_evaluation_context ():
672+ """Test that set_evaluation_context sets the client-level context."""
673+ client = OpenFeatureClient (domain = None , version = None )
674+ ctx = EvaluationContext (
675+ targeting_key = "user-123" , attributes = {"env" : "production" }
676+ )
677+ client .set_evaluation_context (ctx )
678+ assert client .get_evaluation_context () == ctx
679+ assert client .context is ctx
680+
681+
682+ def test_client_get_evaluation_context_default ():
683+ """Test that a new client has an empty evaluation context by default."""
684+ client = OpenFeatureClient (domain = None , version = None )
685+ ctx = client .get_evaluation_context ()
686+ assert ctx is not None
687+ assert ctx .targeting_key is None
688+ assert ctx .attributes == {}
689+
690+
691+ def test_client_set_evaluation_context_replaces_previous ():
692+ """Test that set_evaluation_context replaces any previously set context."""
693+ client = OpenFeatureClient (domain = None , version = None )
694+ ctx1 = EvaluationContext (targeting_key = "first" , attributes = {"a" : "1" })
695+ ctx2 = EvaluationContext (targeting_key = "second" , attributes = {"b" : "2" })
696+ client .set_evaluation_context (ctx1 )
697+ assert client .get_evaluation_context ().targeting_key == "first"
698+ client .set_evaluation_context (ctx2 )
699+ assert client .get_evaluation_context ().targeting_key == "second"
700+ assert client .get_evaluation_context ().attributes == {"b" : "2" }
701+
702+
703+ def test_client_evaluation_context_merging_with_api_and_invocation ():
704+ """Test context merging order: API -> client -> invocation (invocation wins)."""
705+ api .clear_hooks ()
706+ api .set_transaction_context_propagator (ContextVarsTransactionContextPropagator ())
707+
708+ provider = NoOpProvider ()
709+ provider .resolve_boolean_details = MagicMock (wraps = provider .resolve_boolean_details )
710+ api .set_provider (provider )
711+
712+ # API-level context
713+ api .set_evaluation_context (
714+ EvaluationContext (
715+ targeting_key = "api" ,
716+ attributes = {"shared" : "api_value" , "api_only" : "from_api" },
717+ )
718+ )
719+
720+ # Client-level context (set via set_evaluation_context)
721+ client = OpenFeatureClient (domain = None , version = None )
722+ client .set_evaluation_context (
723+ EvaluationContext (
724+ targeting_key = "client" ,
725+ attributes = {"shared" : "client_value" , "client_only" : "from_client" },
726+ )
727+ )
728+
729+ # Invocation-level context
730+ invocation_context = EvaluationContext (
731+ targeting_key = "invocation" ,
732+ attributes = {"shared" : "invocation_value" , "invocation_only" : "from_invocation" },
733+ )
734+
735+ client .get_boolean_details ("flag" , False , invocation_context )
736+
737+ _ , kwargs = provider .resolve_boolean_details .call_args
738+ context = kwargs ["evaluation_context" ]
739+
740+ # Invocation targeting_key wins (last in merge chain)
741+ assert context .targeting_key == "invocation"
742+ # Invocation attribute wins for shared key
743+ assert context .attributes ["shared" ] == "invocation_value"
744+ # All levels contribute their unique attributes
745+ assert context .attributes ["api_only" ] == "from_api"
746+ assert context .attributes ["client_only" ] == "from_client"
747+ assert context .attributes ["invocation_only" ] == "from_invocation"
748+
749+
750+ def test_client_evaluation_context_merging_without_invocation ():
751+ """Test context merging when no invocation context is provided."""
752+ api .clear_hooks ()
753+
754+ provider = NoOpProvider ()
755+ provider .resolve_boolean_details = MagicMock (wraps = provider .resolve_boolean_details )
756+ api .set_provider (provider )
757+
758+ api .set_evaluation_context (
759+ EvaluationContext (
760+ targeting_key = "api" ,
761+ attributes = {"shared" : "api_value" , "api_only" : "from_api" },
762+ )
763+ )
764+
765+ client = OpenFeatureClient (domain = None , version = None )
766+ client .set_evaluation_context (
767+ EvaluationContext (
768+ targeting_key = "client" ,
769+ attributes = {"shared" : "client_value" , "client_only" : "from_client" },
770+ )
771+ )
772+
773+ # No invocation context
774+ client .get_boolean_details ("flag" , False )
775+
776+ _ , kwargs = provider .resolve_boolean_details .call_args
777+ context = kwargs ["evaluation_context" ]
778+
779+ # Client targeting_key wins over API
780+ assert context .targeting_key == "client"
781+ assert context .attributes ["shared" ] == "client_value"
782+ assert context .attributes ["api_only" ] == "from_api"
783+ assert context .attributes ["client_only" ] == "from_client"
784+
785+
786+ @pytest .mark .asyncio
787+ async def test_client_evaluation_context_merging_async ():
788+ """Test context merging works correctly for async evaluation."""
789+ api .clear_hooks ()
790+
791+ provider = NoOpProvider ()
792+ provider .resolve_boolean_details_async = MagicMock (
793+ wraps = provider .resolve_boolean_details_async
794+ )
795+ api .set_provider (provider )
796+
797+ api .set_evaluation_context (
798+ EvaluationContext (
799+ targeting_key = "api" ,
800+ attributes = {"level" : "api" },
801+ )
802+ )
803+
804+ client = OpenFeatureClient (domain = None , version = None )
805+ client .set_evaluation_context (
806+ EvaluationContext (
807+ targeting_key = "client" ,
808+ attributes = {"level" : "client" , "client_attr" : "yes" },
809+ )
810+ )
811+
812+ invocation_context = EvaluationContext (
813+ targeting_key = "invocation" ,
814+ attributes = {"level" : "invocation" },
815+ )
816+
817+ await client .get_boolean_details_async ("flag" , False , invocation_context )
818+
819+ _ , kwargs = provider .resolve_boolean_details_async .call_args
820+ context = kwargs ["evaluation_context" ]
821+
822+ assert context .targeting_key == "invocation"
823+ assert context .attributes ["level" ] == "invocation"
824+ assert context .attributes ["client_attr" ] == "yes"
0 commit comments