1212
1313from datetime import datetime , timedelta
1414from unittest import IsolatedAsyncioTestCase
15-
16- import unittest
17- import pytest
18- from unittest .mock import patch , MagicMock
19- from openfga_sdk .credentials import CredentialConfiguration
20- from openfga_sdk .oauth2 import OAuth2Client
21-
15+ from unittest .mock import patch
2216
2317import urllib3
2418
@@ -501,126 +495,116 @@ async def test_get_authentication_add_scheme_and_path(self, mock_request):
501495 )
502496 await rest_client .close ()
503497
504- class TestOAuth2ClientScopeHandling (unittest .TestCase ):
505- """Test OAuth2Client scope serialization in synchronous context"""
498+ @patch .object (rest .RESTClientObject , "request" )
499+ async def test_get_authentication_obtain_client_credentials_with_scopes_list (self , mock_request ):
500+ """
501+ Test getting authentication header when method is client credentials with scopes as list
502+ """
503+ response_body = """
504+ {
505+ "expires_in": 120,
506+ "access_token": "AABBCCDD"
507+ }
508+ """
509+ mock_request .return_value = mock_response (response_body , 200 )
506510
507- def setUp (self ):
508- self .config = CredentialConfiguration (
509- client_id = "test_client_id" ,
510- client_secret = "test_client_secret" ,
511- api_issuer = "https://example.com" ,
512- api_audience = "test_audience"
513- )
514- self .oauth_client = OAuth2Client (self .config )
515-
516- @patch ('requests.post' )
517- def test_list_scopes_serialization (self , mock_post ):
518- """Test that list scopes are serialized as space-delimited string"""
519- # Setup mock response
520- mock_response = MagicMock ()
521- mock_response .json .return_value = {"access_token" : "test_token" , "expires_in" : 3600 }
522- mock_response .status_code = 200
523- mock_post .return_value = mock_response
524-
525- # Set list scopes
526- self .config .scopes = ["read" , "write" , "admin" ]
527-
528- # Make token request
529- self .oauth_client .request_token ()
530-
531- # Verify scope parameter was correctly formatted
532- args , kwargs = mock_post .call_args
533- self .assertIn ('data' , kwargs )
534- self .assertIn ('scope' , kwargs ['data' ])
535- self .assertEqual (kwargs ['data' ]['scope' ], "read write admin" )
536-
537- @patch ('requests.post' )
538- def test_string_scope_serialization (self , mock_post ):
539- """Test that string scope is used unchanged"""
540- # Setup mock response
541- mock_response = MagicMock ()
542- mock_response .json .return_value = {"access_token" : "test_token" , "expires_in" : 3600 }
543- mock_response .status_code = 200
544- mock_post .return_value = mock_response
545-
546- # Set string scope
547- self .config .scopes = "read write admin"
548-
549- # Make token request
550- self .oauth_client .request_token ()
551-
552- # Verify scope parameter was passed unchanged
553- args , kwargs = mock_post .call_args
554- self .assertIn ('data' , kwargs )
555- self .assertIn ('scope' , kwargs ['data' ])
556- self .assertEqual (kwargs ['data' ]['scope' ], "read write admin" )
557-
558-
559- class TestOAuth2ClientScopeHandlingAsync :
560- """Test OAuth2Client scope serialization in asynchronous context"""
561-
562- @pytest .fixture
563- def oauth_client (self ):
564- config = CredentialConfiguration (
565- client_id = "test_client_id" ,
566- client_secret = "test_client_secret" ,
567- api_issuer = "https://example.com" ,
568- api_audience = "test_audience"
569- )
570- return OAuth2Client (config )
571-
572- @pytest .mark .asyncio
573- async def test_list_scopes_serialization_async (self , oauth_client , monkeypatch ):
574- """Test that list scopes are serialized as space-delimited string in async context"""
575- # Mock the async HTTP response
576- mock_response = MagicMock ()
577- mock_response .json = MagicMock (return_value = {"access_token" : "test_token" , "expires_in" : 3600 })
578- mock_response .status_code = 200
579-
580- # Set up mock for the async HTTP client
581- mock_client_session = MagicMock ()
582- mock_client_session .post = MagicMock (return_value = mock_response )
583- mock_client_session .__aenter__ = MagicMock (return_value = mock_client_session )
584- mock_client_session .__aexit__ = MagicMock (return_value = None )
585-
586- monkeypatch .setattr ('aiohttp.ClientSession' , MagicMock (return_value = mock_client_session ))
587-
588- # Set list scopes
589- oauth_client .configuration .scopes = ["read" , "write" , "admin" ]
590-
591- # Make async token request
592- await oauth_client .request_token_async ()
593-
594- # Verify scope parameter was correctly formatted
595- args , kwargs = mock_client_session .post .call_args
596- self .assertIn ('data' , kwargs )
597- self .assertIn ('scope' , kwargs ['data' ])
598- self .assertEqual (kwargs ['data' ]['scope' ], "read write admin" )
599-
600- @pytest .mark .asyncio
601- async def test_string_scope_serialization_async (self , oauth_client , monkeypatch ):
602- """Test that string scope is used unchanged in async context"""
603- # Mock the async HTTP response
604- mock_response = MagicMock ()
605- mock_response .json = MagicMock (return_value = {"access_token" : "test_token" , "expires_in" : 3600 })
606- mock_response .status_code = 200
607-
608- # Set up mock for the async HTTP client
609- mock_client_session = MagicMock ()
610- mock_client_session .post = MagicMock (return_value = mock_response )
611- mock_client_session .__aenter__ = MagicMock (return_value = mock_client_session )
612- mock_client_session .__aexit__ = MagicMock (return_value = None )
613-
614- monkeypatch .setattr ('aiohttp.ClientSession' , MagicMock (return_value = mock_client_session ))
615-
616- # Set string scope
617- oauth_client .configuration .scopes = "read write admin"
618-
619- # Make async token request
620- await oauth_client .request_token_async ()
621-
622- # Verify scope parameter was passed unchanged
623- args , kwargs = mock_client_session .post .call_args
624- self .assertIn ('data' , kwargs )
625- self .assertIn ('scope' , kwargs ['data' ])
626- self .assertEqual (kwargs ['data' ]['scope' ], "read write admin" )
511+ credentials = Credentials (
512+ method = "client_credentials" ,
513+ configuration = CredentialConfiguration (
514+ client_id = "myclientid" ,
515+ client_secret = "mysecret" ,
516+ api_issuer = "issuer.fga.example" ,
517+ api_audience = "myaudience" ,
518+ scopes = ["read" , "write" , "admin" ],
519+ ),
520+ )
521+ rest_client = rest .RESTClientObject (Configuration ())
522+ current_time = datetime .now ()
523+ client = OAuth2Client (credentials )
524+ auth_header = await client .get_authentication_header (rest_client )
525+ self .assertEqual (auth_header , {"Authorization" : "Bearer AABBCCDD" })
526+ self .assertEqual (client ._access_token , "AABBCCDD" )
527+ self .assertGreaterEqual (
528+ client ._access_expiry_time , current_time + timedelta (seconds = 120 )
529+ )
530+ expected_header = urllib3 .response .HTTPHeaderDict (
531+ {
532+ "Accept" : "application/json" ,
533+ "Content-Type" : "application/x-www-form-urlencoded" ,
534+ "User-Agent" : "openfga-sdk (python) 0.9.5" ,
535+ }
536+ )
537+ mock_request .assert_called_once_with (
538+ method = "POST" ,
539+ url = "https://issuer.fga.example/oauth/token" ,
540+ headers = expected_header ,
541+ query_params = None ,
542+ body = None ,
543+ _preload_content = True ,
544+ _request_timeout = None ,
545+ post_params = {
546+ "client_id" : "myclientid" ,
547+ "client_secret" : "mysecret" ,
548+ "audience" : "myaudience" ,
549+ "grant_type" : "client_credentials" ,
550+ "scope" : "read write admin" ,
551+ },
552+ )
553+ await rest_client .close ()
554+
555+ @patch .object (rest .RESTClientObject , "request" )
556+ async def test_get_authentication_obtain_client_credentials_with_scopes_string (self , mock_request ):
557+ """
558+ Test getting authentication header when method is client credentials with scopes as string
559+ """
560+ response_body = """
561+ {
562+ "expires_in": 120,
563+ "access_token": "AABBCCDD"
564+ }
565+ """
566+ mock_request .return_value = mock_response (response_body , 200 )
567+
568+ credentials = Credentials (
569+ method = "client_credentials" ,
570+ configuration = CredentialConfiguration (
571+ client_id = "myclientid" ,
572+ client_secret = "mysecret" ,
573+ api_issuer = "issuer.fga.example" ,
574+ api_audience = "myaudience" ,
575+ scopes = "read write admin" ,
576+ ),
577+ )
578+ rest_client = rest .RESTClientObject (Configuration ())
579+ current_time = datetime .now ()
580+ client = OAuth2Client (credentials )
581+ auth_header = await client .get_authentication_header (rest_client )
582+ self .assertEqual (auth_header , {"Authorization" : "Bearer AABBCCDD" })
583+ self .assertEqual (client ._access_token , "AABBCCDD" )
584+ self .assertGreaterEqual (
585+ client ._access_expiry_time , current_time + timedelta (seconds = 120 )
586+ )
587+ expected_header = urllib3 .response .HTTPHeaderDict (
588+ {
589+ "Accept" : "application/json" ,
590+ "Content-Type" : "application/x-www-form-urlencoded" ,
591+ "User-Agent" : "openfga-sdk (python) 0.9.5" ,
592+ }
593+ )
594+ mock_request .assert_called_once_with (
595+ method = "POST" ,
596+ url = "https://issuer.fga.example/oauth/token" ,
597+ headers = expected_header ,
598+ query_params = None ,
599+ body = None ,
600+ _preload_content = True ,
601+ _request_timeout = None ,
602+ post_params = {
603+ "client_id" : "myclientid" ,
604+ "client_secret" : "mysecret" ,
605+ "audience" : "myaudience" ,
606+ "grant_type" : "client_credentials" ,
607+ "scope" : "read write admin" ,
608+ },
609+ )
610+ await rest_client .close ()
0 commit comments