@@ -531,6 +531,80 @@ def test_is_running(self, _):
531531
532532 project_config_manager .stop ()
533533
534+ def test_custom_headers (self , _ ):
535+ """ Test that custom headers are included in datafile fetch requests. """
536+ sdk_key = 'some_key'
537+ custom_headers = {
538+ 'X-Custom-Header' : 'custom_value' ,
539+ 'X-Another-Header' : 'another_value'
540+ }
541+
542+ expected_datafile_url = enums .ConfigManager .DATAFILE_URL_TEMPLATE .format (sdk_key = sdk_key )
543+ test_headers = {'Last-Modified' : 'New Time' }
544+ test_datafile = json .dumps (self .config_dict_with_features )
545+ test_response = requests .Response ()
546+ test_response .status_code = 200
547+ test_response .headers = test_headers
548+ test_response ._content = test_datafile
549+
550+ with mock .patch ('requests.Session.get' , return_value = test_response ) as mock_request :
551+ project_config_manager = config_manager .PollingConfigManager (
552+ sdk_key = sdk_key ,
553+ custom_headers = custom_headers
554+ )
555+ project_config_manager .stop ()
556+
557+ # Assert that custom headers were included in the request
558+ mock_request .assert_called_once_with (
559+ expected_datafile_url ,
560+ headers = custom_headers ,
561+ timeout = enums .ConfigManager .REQUEST_TIMEOUT
562+ )
563+ self .assertEqual (test_headers ['Last-Modified' ], project_config_manager .last_modified )
564+ self .assertIsInstance (project_config_manager .get_config (), project_config .ProjectConfig )
565+
566+ def test_custom_headers_override_internal_headers (self , _ ):
567+ """ Test that custom headers override internal SDK headers. """
568+ sdk_key = 'some_key'
569+ custom_last_modified = 'Custom Last Modified Time'
570+ custom_headers = {
571+ 'If-Modified-Since' : custom_last_modified ,
572+ 'X-Custom-Header' : 'custom_value'
573+ }
574+
575+ expected_datafile_url = enums .ConfigManager .DATAFILE_URL_TEMPLATE .format (sdk_key = sdk_key )
576+ test_headers = {'Last-Modified' : 'New Time' }
577+ test_datafile = json .dumps (self .config_dict_with_features )
578+ test_response = requests .Response ()
579+ test_response .status_code = 200
580+ test_response .headers = test_headers
581+ test_response ._content = test_datafile
582+
583+ # First request to set last_modified
584+ with mock .patch ('requests.Session.get' , return_value = test_response ):
585+ project_config_manager = config_manager .PollingConfigManager (
586+ sdk_key = sdk_key ,
587+ custom_headers = custom_headers
588+ )
589+ project_config_manager .stop ()
590+
591+ # Second request should use custom header value instead of internal last_modified
592+ with mock .patch ('requests.Session.get' , return_value = test_response ) as mock_request :
593+ project_config_manager ._initialize_thread ()
594+ project_config_manager .start ()
595+ project_config_manager .stop ()
596+
597+ # Assert that custom If-Modified-Since header overrides the internal one
598+ expected_headers = {
599+ 'If-Modified-Since' : custom_last_modified , # User's value should be used
600+ 'X-Custom-Header' : 'custom_value'
601+ }
602+ mock_request .assert_called_once_with (
603+ expected_datafile_url ,
604+ headers = expected_headers ,
605+ timeout = enums .ConfigManager .REQUEST_TIMEOUT
606+ )
607+
534608
535609@mock .patch ('requests.Session.get' )
536610class AuthDatafilePollingConfigManagerTest (base .BaseTest ):
@@ -637,3 +711,88 @@ def test_fetch_datafile__request_exception_raised(self, _):
637711 )
638712 self .assertEqual (test_headers ['Last-Modified' ], project_config_manager .last_modified )
639713 self .assertIsInstance (project_config_manager .get_config (), project_config .ProjectConfig )
714+
715+ def test_custom_headers (self , _ ):
716+ """ Test that custom headers are included in authenticated datafile fetch requests. """
717+ datafile_access_token = 'some_token'
718+ sdk_key = 'some_key'
719+ custom_headers = {
720+ 'X-Custom-Header' : 'custom_value' ,
721+ 'X-Another-Header' : 'another_value'
722+ }
723+
724+ with mock .patch ('optimizely.config_manager.AuthDatafilePollingConfigManager.fetch_datafile' ), mock .patch (
725+ 'optimizely.config_manager.AuthDatafilePollingConfigManager._run'
726+ ):
727+ project_config_manager = config_manager .AuthDatafilePollingConfigManager (
728+ datafile_access_token = datafile_access_token ,
729+ sdk_key = sdk_key ,
730+ custom_headers = custom_headers
731+ )
732+
733+ expected_datafile_url = enums .ConfigManager .AUTHENTICATED_DATAFILE_URL_TEMPLATE .format (sdk_key = sdk_key )
734+ test_headers = {'Last-Modified' : 'New Time' }
735+ test_datafile = json .dumps (self .config_dict_with_features )
736+ test_response = requests .Response ()
737+ test_response .status_code = 200
738+ test_response .headers = test_headers
739+ test_response ._content = test_datafile
740+
741+ # Call fetch_datafile and assert that request was sent with both authorization and custom headers
742+ with mock .patch ('requests.Session.get' , return_value = test_response ) as mock_request :
743+ project_config_manager .fetch_datafile ()
744+
745+ expected_headers = {
746+ 'Authorization' : f'Bearer { datafile_access_token } ' ,
747+ 'X-Custom-Header' : 'custom_value' ,
748+ 'X-Another-Header' : 'another_value'
749+ }
750+ mock_request .assert_called_once_with (
751+ expected_datafile_url ,
752+ headers = expected_headers ,
753+ timeout = enums .ConfigManager .REQUEST_TIMEOUT ,
754+ )
755+ self .assertIsInstance (project_config_manager .get_config (), project_config .ProjectConfig )
756+
757+ def test_custom_headers_override_authorization (self , _ ):
758+ """ Test that custom Authorization header overrides internal SDK authorization header. """
759+ datafile_access_token = 'some_token'
760+ custom_auth = 'Bearer custom_token'
761+ sdk_key = 'some_key'
762+ custom_headers = {
763+ 'Authorization' : custom_auth ,
764+ 'X-Custom-Header' : 'custom_value'
765+ }
766+
767+ with mock .patch ('optimizely.config_manager.AuthDatafilePollingConfigManager.fetch_datafile' ), mock .patch (
768+ 'optimizely.config_manager.AuthDatafilePollingConfigManager._run'
769+ ):
770+ project_config_manager = config_manager .AuthDatafilePollingConfigManager (
771+ datafile_access_token = datafile_access_token ,
772+ sdk_key = sdk_key ,
773+ custom_headers = custom_headers
774+ )
775+
776+ expected_datafile_url = enums .ConfigManager .AUTHENTICATED_DATAFILE_URL_TEMPLATE .format (sdk_key = sdk_key )
777+ test_headers = {'Last-Modified' : 'New Time' }
778+ test_datafile = json .dumps (self .config_dict_with_features )
779+ test_response = requests .Response ()
780+ test_response .status_code = 200
781+ test_response .headers = test_headers
782+ test_response ._content = test_datafile
783+
784+ # Call fetch_datafile and assert that custom Authorization header is used
785+ with mock .patch ('requests.Session.get' , return_value = test_response ) as mock_request :
786+ project_config_manager .fetch_datafile ()
787+
788+ expected_headers = {
789+ 'Authorization' : custom_auth , # User's custom auth should override
790+ 'X-Custom-Header' : 'custom_value'
791+ }
792+ mock_request .assert_called_once_with (
793+ expected_datafile_url ,
794+ headers = expected_headers ,
795+ timeout = enums .ConfigManager .REQUEST_TIMEOUT ,
796+ )
797+ self .assertIsInstance (project_config_manager .get_config (), project_config .ProjectConfig )
798+
0 commit comments