1+ import asyncio
2+
3+ import httpx
14import pytest
5+ from kiota_abstractions .authentication import AnonymousAuthenticationProvider
6+ from kiota_abstractions .method import Method
7+ from kiota_abstractions .request_information import RequestInformation
8+ from kiota_http .httpx_request_adapter import HttpxRequestAdapter
29from kiota_http .kiota_client_factory import KiotaClientFactory
10+ from kiota_http .middleware .options import RedirectHandlerOption
311
412from msgraph_core ._enums import FeatureUsageFlag
13+ from msgraph_core .graph_client_factory import GraphClientFactory
514from msgraph_core .middleware import AsyncGraphTransport , GraphRequestContext
15+ from msgraph_core .middleware .async_graph_transport import REQUEST_OPTIONS_KEY
616
717
818def test_set_request_context_and_feature_usage (mock_request , mock_transport ):
@@ -16,3 +26,139 @@ def test_set_request_context_and_feature_usage(mock_request, mock_transport):
1626 assert mock_request .context .feature_usage == hex (
1727 FeatureUsageFlag .RETRY_HANDLER_ENABLED | FeatureUsageFlag .REDIRECT_HANDLER_ENABLED
1828 )
29+
30+
31+ @pytest .mark .parametrize (
32+ 'content_type' , [
33+ 'application/octet-stream' ,
34+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ,
35+ ]
36+ )
37+ def test_binary_download_follows_redirect_with_kiota_request_extensions (content_type ):
38+ calls = []
39+ contexts = []
40+
41+ def handle_request (request ):
42+ calls .append (str (request .url ))
43+ contexts .append (request .context )
44+ if request .url .host == 'graph.example' :
45+ return httpx .Response (302 , headers = {'Location' : 'https://download.example/file' })
46+ return httpx .Response (
47+ 200 , content = b'binary content' , headers = {'Content-Type' : content_type }
48+ )
49+
50+ async def download ():
51+ client = GraphClientFactory .create_with_default_middleware (
52+ client = httpx .AsyncClient (transport = httpx .MockTransport (handle_request ))
53+ )
54+ try :
55+ adapter = HttpxRequestAdapter (AnonymousAuthenticationProvider (), http_client = client )
56+ request_info = RequestInformation ()
57+ request_info .http_method = Method .GET
58+ request_info .url = 'https://graph.example/drive/item/content'
59+ return await adapter .send_primitive_async (request_info , 'bytes' , {})
60+ finally :
61+ await client .aclose ()
62+
63+ assert asyncio .run (download ()) == b'binary content'
64+ assert calls == ['https://graph.example/drive/item/content' , 'https://download.example/file' ]
65+ assert all (isinstance (context , GraphRequestContext ) for context in contexts )
66+
67+
68+ def test_extension_options_take_precedence_over_legacy_attribute (mock_transport ):
69+ middleware = KiotaClientFactory .get_default_middleware (None )
70+ pipeline = KiotaClientFactory .create_middleware_pipeline (middleware , mock_transport )
71+ transport = AsyncGraphTransport (mock_transport , pipeline )
72+ request = httpx .Request ('GET' , 'https://example.org' , extensions = {REQUEST_OPTIONS_KEY : {}})
73+ request .options = {'legacy' : True }
74+
75+ transport .set_request_context_and_feature_usage (request )
76+
77+ assert request .context .middleware_control == {}
78+
79+
80+ def test_request_without_options_bypasses_graph_pipeline ():
81+ calls = []
82+
83+ def handle_request (request ):
84+ calls .append (request )
85+ return httpx .Response (200 , content = b'body' )
86+
87+ async def send ():
88+ underlying_transport = httpx .MockTransport (handle_request )
89+ middleware = KiotaClientFactory .get_default_middleware (None )
90+ pipeline = KiotaClientFactory .create_middleware_pipeline (middleware , underlying_transport )
91+ transport = AsyncGraphTransport (underlying_transport , pipeline )
92+ return await transport .handle_async_request (httpx .Request ('GET' , 'https://example.org' ))
93+
94+ assert asyncio .run (send ()).status_code == 200
95+ assert len (calls ) == 1
96+ assert not hasattr (calls [0 ], 'context' )
97+
98+
99+ def test_extension_only_request_uses_graph_pipeline ():
100+ requests = []
101+
102+ def handle_request (request ):
103+ requests .append (request )
104+ return httpx .Response (200 , content = b'body' )
105+
106+ async def send ():
107+ underlying_transport = httpx .MockTransport (handle_request )
108+ middleware = KiotaClientFactory .get_default_middleware (None )
109+ pipeline = KiotaClientFactory .create_middleware_pipeline (middleware , underlying_transport )
110+ transport = AsyncGraphTransport (underlying_transport , pipeline )
111+ request = httpx .Request ('GET' , 'https://example.org' , extensions = {REQUEST_OPTIONS_KEY : {}})
112+ assert not hasattr (request , 'options' )
113+ return await transport .handle_async_request (request )
114+
115+ assert asyncio .run (send ()).content == b'body'
116+ assert len (requests ) == 1
117+ assert isinstance (requests [0 ].context , GraphRequestContext )
118+ assert requests [0 ].context .middleware_control == {}
119+ assert requests [0 ].context .feature_usage == hex (
120+ FeatureUsageFlag .RETRY_HANDLER_ENABLED | FeatureUsageFlag .REDIRECT_HANDLER_ENABLED
121+ )
122+
123+
124+ @pytest .mark .parametrize (
125+ 'legacy_redirect, extension_options, expected_status, expected_calls' , [
126+ (False , None , 302 , 1 ),
127+ (None , None , 200 , 2 ),
128+ (True , False , 302 , 1 ),
129+ (False , True , 200 , 2 ),
130+ (False , {}, 200 , 2 ),
131+ ]
132+ )
133+ def test_request_redirect_options_are_honored (
134+ legacy_redirect , extension_options , expected_status , expected_calls
135+ ):
136+ requests = []
137+
138+ def handle_request (request ):
139+ requests .append (request )
140+ if request .url .path == '/start' :
141+ return httpx .Response (302 , headers = {'Location' : 'https://example.org/end' })
142+ return httpx .Response (200 , content = b'body' )
143+
144+ async def send ():
145+ underlying_transport = httpx .MockTransport (handle_request )
146+ middleware = KiotaClientFactory .get_default_middleware (None )
147+ pipeline = KiotaClientFactory .create_middleware_pipeline (middleware , underlying_transport )
148+ transport = AsyncGraphTransport (underlying_transport , pipeline )
149+ request = httpx .Request ('GET' , 'https://example.org/start' )
150+ request .options = {}
151+ if legacy_redirect is not None :
152+ option = RedirectHandlerOption (should_redirect = legacy_redirect )
153+ request .options [option .get_key ()] = option
154+ if extension_options is not None :
155+ options = extension_options
156+ if isinstance (options , bool ):
157+ option = RedirectHandlerOption (should_redirect = options )
158+ options = {option .get_key (): option }
159+ request .extensions [REQUEST_OPTIONS_KEY ] = options
160+ return await transport .handle_async_request (request )
161+
162+ assert asyncio .run (send ()).status_code == expected_status
163+ assert len (requests ) == expected_calls
164+ assert isinstance (requests [0 ].context , GraphRequestContext )
0 commit comments